在CTF比赛中搭配使用R2和Z3解一道逆向题目
2020-08-11 11:04:40 Author: www.4hou.com(查看原文) 阅读量:265 收藏

0x01 逆向分析

题目是一个64位的elf二进制文件,使用radare2对其进行分析:

$ r2 reee.bin
[0x00400430]> aaa
[0x00400430]> i
fd       3
file     reee.bin
size     0x19d8
humansz  6.5K
mode     r-x
format   elf64
iorw     false
blksz    0x0
block    0x100
type     EXEC (Executable file)
arch     x86
baddr    0x400000
binsz    4753
bintype  elf
bits     64
canary   false
class    ELF64
compiler GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609
crypto   false
endian   little
havecode true
intrp    /lib64/ld-linux-x86-64.so.2
laddr    0x0
lang     c
linenum  false
lsyms    false
machine  AMD x86-64 architecture
maxopsz  16
minopsz  1
nx       true
os       linux
pcalign  0
pic      false
relocs   false
relro    partial
rpath    NONE
sanitiz  false
static   false
stripped true
subsys   linux
va       true
[0x00400430]> afl
0x00400430    1 41           entry0
0x00400410    1 6            sym.imp.__libc_start_main
0x00400400    1 6            sym.imp.puts
0x0040064e   14 747  -> 195  main
0x00400500    8 134  -> 90   entry.init0
0x004004e0    3 28           entry.fini0
0x00400460    4 50   -> 41   fcn.00400460
0x00400526    1 296          fcn.00400526
0x004006e5    6 176  -> 60   fcn.004006e5
0x004003c8    3 26           fcn.004003c8

它是二进制文件,不是PIE可执行文件,这是它的主要逻辑图:

img

反编译结果如下:

void main(char *argc, char **argv)
{
    char *placeholder_2;
    code cVar1;
    int64_t iVar2;
    uint32_t in_ECX;
    char **arg2;
    char **var_40h;
    undefined8 var_34h;
    int32_t second_index;
    int32_t index;
    int64_t var_20h;
    uint32_t var_18h;
    
    arg2 = argv;
    if ((int32_t)argc < 2) {
        argc = "need a flag!";
        sym.imp.puts();
    }
    placeholder_2 = argv[1];
    index = 0;
    while (index < 0x7a69) {
        second_index = 0;
        while (second_index < 0x228) {
            argc = (char *)(uint32_t)fcn.004006e5[second_index];
            cVar1 = (code)fcn.00400526((int64_t)argc);
            fcn.004006e5[second_index] = cVar1;
            second_index = second_index + 1;
        }
        index = index + 1;
    }
    iVar2 = fcn.004006e5(argc, (int64_t)arg2, placeholder_2, in_ECX);
    if (iVar2 == 0) {
        sym.imp.puts("Wrong!");
    } else {
        sym.imp.puts("Correct!");
    }
    return;
}

从反编译版本中,我们可以推断出三件事:

1. 程序接受输入参数(flag)。

2. 将参数传递给函数fcn.004006e5。

3. 使用函数fcn.00400526在嵌套中修改了函数fcn.004006e5。

实际上,检查fcn.004006e5的asm 是否是混乱的:

┌ 60: fcn.004006e5 (int64_t arg2, uint32_t arg4, int64_t arg_58ae7f6ah);
│     ╎╎╎   ; arg int64_t arg_58ae7f6ah @ rbp+0x58ae7f6a
│     ╎╎╎   ; arg int64_t arg2 @ rsi
│     ╎╎╎   ; arg uint32_t arg4 @ rcx
│     ╎╎╎   0x004006e5      stc
│     ╎╎╎   0x004006e6      xchg eax, ebx
│    ┌────< 0x004006e7      jne 0x400716
│    │╎╎╎   0x004006e9      fcmovnb st(0), st(6)
│    │╎╎╎   0x004006eb      stosd dword [rdi], eax
│    │╎╎└─< 0x004006ec      loopne 0x400690
│    │╎╎    0x004006ee      cmp ecx, dword [rcx - 0x63]                ; arg4
│    │╎╎    0x004006f1      mov word [rsi + rax*4 - 0x23], ds          ; arg2
     │╎╎    0x004006f5      invalid
     │╎└──< 0x004006f6      jae 0x4006c5                               ; main+0x77
     │╎     0x004006f8      stc
     │╎     0x004006f9      invalid
     │╎     0x004006fa      imul ebx, dword [rax], 0x2f592932
     │╎     0x00400700      invalid
     │╎     0x00400701      cmpsd dword [rsi], dword ptr [rdi]
     │╎     0x00400702      invalid
     │╎     ; CODE XREF from fcn.004006e5 @ +0x90
     │╎ ┌─< 0x00400703      jns 0x40070e
     │╎ │   0x00400705      adc byte [rdi + 0x5bcbfed6], cl
     │╎ │   0x0040070b      ret
     │└───< 0x0040070c      jo 0x4006d1                                ; main+0x83
     │  │   ; CODE XREF from fcn.004006e5 @ +0x1e
     │  └─> 0x0040070e      invalid
     │      0x0040070f      invalid
     │      0x00400710      lodsb al, byte [rsi]
     │      0x00400711      xchg dword [rdx - 0x61], eax
     │  ┌─< 0x00400714      jns 0x400760
│    │  │   ; CODE XREF from fcn.004006e5 @ 0x4006e7
│    └────> 0x00400716      xor eax, 0xe0bb9da8
│       │   0x0040071c      sar dword [arg_58ae7f6ah], 0xeb
│       │   0x00400723      pop rsp
│       │   0x00400724      add dword [rcx + 0x1dad7797], esi          ; arg4
│      ╎│   0x0040072a      adc esp, ebp
│      ╎│   0x0040072c      adc byte [0x6e340251], bh
│    ┌────< 0x00400732      loop 0x40079b
│    │╎╎│   0x00400734      mov cl, 0x79                               ; 'y' ; 121
│    │╎╎│   0x00400736      sahf
│    │╎╎│   0x00400737      pop rsi
│    │╎╎│   0x00400738      cmp al, 0x14                               ; 20
│    │╎╎│   0x0040073a      xor cl, cl
│    │╎╎│   0x0040073c      cld
│    │╎╎│   0x0040073d      mov bh, bh
│    │╎╎│   0x0040073f      int3
     │╎╎│   0x00400740      call 0xffffffff8987d4da
     │╎╎│   0x00400745      fnstenv [rcx + 1]
     │╎╎│   0x00400748      mov bh, 0x25                               ; '%' ; 37
     │╎╎│   0x0040074a      and al, 0x6a                               ; 106
     │╎╎│   0x0040074c      invalid
     │╎╎│   0x0040074d      mov dh, 0xe2                               ; 226
     │╎╎│   0x0040074f      int1
     │╎╎│   0x00400750      sbb al, 0x5c
     │╎╎│   0x00400752      fidiv dword [rax + rdi*4 + 0x2b]
     │╎╎│   0x00400756      invalid
     │╎╎│   0x00400757      pop rbp
     │╎╎│   0x00400759      and bl, byte [rax + 0x60299194]
     │╎└──< 0x0040075f      jns 0x400729                               ; fcn.004006e5+0x44
     │└───< 0x00400761      jge 0x400730                               ; fcn.004006e5+0x4b
     │      0x00400763      sub eax, 0x8e88118d
     │      0x00400768      rol byte [rdx + 0x66], cl
     │      0x0040076b      sti
     │      0x0040076c      xlatb
     │      0x0040076d      hlt
     │      0x0040076e      mov al, byte [rdx]

有很多无效的指令,我们可以尝试检查fcn.00400526:

int32_t fcn.00400526(int64_t arg1)
{
    int64_t var_4h;
    
    *(uint8_t *)0x601161 = *(char *)0x601161 + 1;
    *(uint8_t *)0x601162 = *(char *)0x601162 + *(char *)((int64_t)*(uint8_t *)0x601161 + 0x601060);
    *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060) =
         *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060) ^
         *(uint8_t *)((int64_t)*(uint8_t *)0x601162 + 0x601060);
    *(uint8_t *)((int64_t)*(uint8_t *)0x601162 + 0x601060) =
         *(uint8_t *)((int64_t)*(uint8_t *)0x601162 + 0x601060) ^
         *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060);
    *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060) =
         *(uint8_t *)((int64_t)*(uint8_t *)0x601161 + 0x601060) ^
         *(uint8_t *)((int64_t)*(uint8_t *)0x601162 + 0x601060);
    return arg1 +
           *(uint8_t *)
            ((int64_t)
            (*(char *)((int64_t)*(uint8_t *)(0x601162 + 0x601060) +
            *(char *)((int64_t)*(uint8_t *)(0x601161 + 0x601060) + (0x601060);
}

这个函数不是不可能理解的,但是我们可以采取另一种方法。我们可以在以下位置设置断点:

iVar2 = fcn.004006e5(argc, (int64_t)arg2, placeholder_2, in_ECX);

调试二进制文件

[0x0040064e]> db 0x004006db
[0x0040064e]> ood PCTF{fake_flag}
Process with PID 6619 started...
[0x7f2400442100]> dc
hit breakpoint at: 4006db
[0x004006db]> v!

img

现在函数更加清晰了,我们还可以反编译该函数:

[0x004006e5]> pdg
undefined fcn.004006e5(uint32_t arg4)
{
    undefined uVar1;
    undefined4 in_RDI;
    
    uVar1 = func_0x00400702(CONCAT44(in_RDI, arg4));
    return uVar1;
}

函数0x00400702的asm代码很长,但是我们可以很容易地反编译它。由于未在radare2上定义此函数,因此我们需要对其进行定义。在可视模式下,查找0x00400702并按df,然后pdg:

bool fcn.00400702(int64_t arg1)
{
    char cVar1;
    char *in_RAX;
    int32_t iVar2;
    int64_t iVar3;
    char *pcVar4;
    uint8_t uVar5;
    int32_t iVar6;
    int32_t iVar7;
    bool bVar8;
    
    iVar3 = -1;
    // in_RAX = our argument
    pcVar4 = in_RAX;
    do {
        if (iVar3 == 0) break;
        iVar3 = iVar3 + -1;
        cVar1 = *pcVar4;
        pcVar4 = pcVar4 + 1;
    } while (cVar1 != '\0');
    // compute the length of the argument
    iVar2 = ~(uint32_t)iVar3 - 1; 
    uVar5 = 0x50;
    iVar6 = 0;
    // apply this function to our flag
    // (xor every character with the precedent one)
    while (iVar6 < 0x539) {
        iVar7 = 0;
        while (iVar7 < iVar2) {
            in_RAX[iVar7] = in_RAX[iVar7] ^ uVar5;
            uVar5 = uVar5 ^ in_RAX[iVar7];
            iVar7 = iVar7 + 1;
        }
        iVar6 = iVar6 + 1;
    }
    bVar8 = true;
    iVar6 = 0;
    while (iVar6 < iVar2) {
        if (bVar8 == false) {
            bVar8 = false;
        } else {
            bVar8 = in_RAX[iVar6] == *(char *)((int64_t)iVar6 + 0x4008eb);
        }
        iVar6 = iVar6 + 1;
    }
    return bVar8;
}

这段代码现在非常清楚。fcn.00400702在我们的输入中应用了argv[1]一个函数:将每个字符与前一个字符进行异或运算,并重复操作0x539次。

最后,该函数检查结果是否等于0x4008eb中的数组。

我们可以转储0x4008eb的内容:

[0x0040071a]> pxj 100@0x4008eb
[
 72,95,54,53,53,37,20,44,29,1,3,45,12,111,53,97,126,52,10,68,36,44,74,70,25,89,
 91,14,120,116,41,19,44,0,72,137,194,72,137,85,232,72,131,125,232,0,116,12,191,
 209,9,64,0,232,219,250,255,255,235,10,191,218,9,64,0,232,207,250,255,255,144,
 72,131,196,56,91,93,195,15,31,128,0,0,0,0,65,87,65,86,65,137,255,65,85,65,84,
 76,141,37,190
]

现在我们有一个问题,我们不知道flag的长度。

我们可以:

1. 暴力破解。

2. 猜解。

3. 数学运算。

我们使用bruteforce做到了这一点,但是我们可以猜测,直到\x00的输出中的第一个空终止符。

现在是时候计算flag了,我个人选择使用z3。

0x02 漏洞利用

from z3 import *

data = [
        0x48, 0x5f, 0x36, 0x35, 0x35, 0x25, 0x14, 0x2c, 0x1d, 0x01, 0x03, 0x2d,
        0x0c, 0x6f, 0x35, 0x61, 0x7e, 0x34, 0x0a, 0x44, 0x24, 0x2c, 0x4a, 0x46,
        0x19, 0x59, 0x5b, 0x0e, 0x78, 0x74, 0x29, 0x13, 0x2c
        ]

flag = [BitVec(f'{i:2}', 8) for i in range(len(data))]
s = Solver()

# flag mut be a printable character
for f in flag:
    s.add(f > 0x20, f <= 0x7f)

# just copy the code decompiled and rename some variables
xored = 0x50
indx = 0
while (indx < 0x539):
    index = 0
    while (index < len(flag)):
        flag[index] = flag[index] ^ xored
        xored = xored ^ flag[index]
        index = index + 1
    indx += 1

for f, d in zip(flag, data):
    s.add(f == d)

print(s.check())
m = s.model()
model = sorted([(d, m[d]) for d in m], key = lambda x: str(x[0]))
for m in model:
    print(chr(m[1].as_long()), end='')

0x03 flag

pctf{ok_nothing_too_fancy_there!}

img

https://twitter.com/PatchFriday/status/1043136025781055489

本文翻译自:https://meowmeowxw.gitlab.io/ctf/plaid-2020-reee/如若转载,请注明原文地址:


文章来源: https://www.4hou.com/posts/8O7g
如有侵权请联系:admin#unsafe.sh