【CTF reverse】SCUCTF新生赛2020-re11-fake-TLS反调试+算法逆向黑盒技巧

1,090 阅读10分钟

exe可在这里找到。

PETools查看概况

64位,入口Section: [.text], EP: 0x00000428故无壳。

初步分析

作者:hans774882968以及hans774882968

载入IDA,没有找到main函数。通过Strings window(社工方法)找到如下函数

__int64 sub_140011EE0()
{
  __int64 *v0; // rdi@1
  signed __int64 i; // rcx@1
  __int64 v3; // [sp+0h] [bp-20h]@1

  v0 = &v3;
  for ( i = 58i64; i; --i )
  {
    *(_DWORD *)v0 = -858993460;
    v0 = (__int64 *)((char *)v0 + 4);
  }
  sub_1400110A0(&unk_140022033);
  sub_140011320(&unk_14001AF04, Str1);
  if ( !j_strcmp(Str1, "scuctf{fakeflag}") )
    sub_140011203("%you are right!!");
  return 0i64;
}

sub_1400110A0改成getCurThread

很可惜只是干扰信息。自此,我面对那堆混淆得十分彻底的函数,不知道该咋办了。于是我想动态调试最后挣扎一下算了。

反反调试

打开x64dbg,点“运行到用户代码”,发现底部有“TLS回调函数”字样。TLS即Thread Local Storage(相信背Java八股的都很熟悉它,资料见参考链接1),这玩意可以用来反调试

虽然参考链接1说,可以通过patch掉TLS回调来反反调试,但既然它用到了isDebuggerPresent,我们也可以通过nop填充来让调试能够继续运行。

怎么找到isDebuggerPresent?打开x64dbg,点“运行到用户代码”,此时停在一个jmp指令上面,鼠标放上去看到几行代码,其中就有isDebuggerPresent,于是我们大胆猜想那段代码就是拥有反调试逻辑的TLS回调。点几下步过,nop掉isDebuggerPresent下面那个jne,即可继续调试。如图。

2-TLS.JPG

关键函数

在IDA找到(x64dbg展示的)以上TLS回调所对应的代码段,发现它调用了一个函数,即间接地调用了以下函数。

int sub_1400119C0()
{
  char *v0; // rdi@1
  signed __int64 i; // rcx@1
  char v3; // [sp+0h] [bp-20h]@1
  int Dst; // [sp+24h] [bp+4h]@4
  int (__cdecl *v5)(const char *, const char *); // [sp+48h] [bp+28h]@4

  v0 = &v3;
  for ( i = 74i64; i; --i )
  {
    *(_DWORD *)v0 = -858993460;
    v0 += 4;
  }
  getCurThread((__int64)&unk_140022033);
  v5 = j_strcmp;
  j_memcpy(&Dst, (char *)j_strcmp + 1, 4ui64);
  v5 = (int (__cdecl *)(const char *, const char *))((char *)j_strcmp + Dst + 5);
  j_memcpy(&Dst, (char *)v5 + 2, 4ui64);
  v5 = (int (__cdecl *)(const char *, const char *))((char *)v5 + Dst + 6);
  lpAddress = v5;
  j_memcpy(&unk_14001D178, v5, 8ui64);
  qword_14001D180 = (__int64)sub_14001132A;
  sub_140011046();
  return sub_14001137F((__int64)&v3, (__int64)&unk_14001AEE0);
}

静态查看发现sub_140011046sub_14001137F都是干扰信息。但点击sub_14001132A,再点击一次,即可得到以下函数sub_140015D70。它有异或、移位等运算符,这不太可能是库函数,可以猜测它是有用函数

__int64 __fastcall sub_140015D70(char *a1, char *a2)
{
  char *v2; // rdi@1
  signed __int64 i; // rcx@1
  int v4; // edx@9
  __int64 v5; // kr08_8@12
  unsigned __int8 v6; // dl@12
  int v7; // edx@12
  __int64 v8; // rax@13
  __int64 v9; // rdi@13
  char v11; // [sp+0h] [bp-20h]@1
  char v12[288]; // [sp+30h] [bp+10h]@6
  char v13[276]; // [sp+150h] [bp+130h]@6
  int j; // [sp+264h] [bp+244h]@4
  int v15; // [sp+284h] [bp+264h]@7
  int v16; // [sp+2A4h] [bp+284h]@7
  int v17; // [sp+2C4h] [bp+2A4h]@10
  int v18; // [sp+2E4h] [bp+2C4h]@10
  int v19; // [sp+304h] [bp+2E4h]@10
  int v20; // [sp+324h] [bp+304h]@10
  int v21; // [sp+344h] [bp+324h]@12
  int v22; // [sp+4D4h] [bp+4B4h]@9
  unsigned __int64 v23; // [sp+4D8h] [bp+4B8h]@6
  size_t v24; // [sp+4E0h] [bp+4C0h]@6
  char *v25; // [sp+510h] [bp+4F0h]@1
  char *Str; // [sp+518h] [bp+4F8h]@1

  Str = a2;
  v25 = a1;
  v2 = &v11;
  for ( i = 318i64; i; --i )
  {
    *(_DWORD *)v2 = -858993460;
    v2 += 4;
  }
  getCurThread((__int64)&unk_140022033);
  sub_1400111FE();
  for ( j = 0; j < 256; ++j )
  {
    v12[j] = j;
    v23 = j;
    v24 = j_strlen(Str);
    v13[j] = Str[v23 % v24];
  }
  v15 = 0;
  v16 = 0;
  while ( v15 < 256 )
  {
    v4 = (unsigned __int64)((unsigned __int8)v13[v15] + (unsigned __int8)v12[v15] + v16) >> 32;
    v16 = (unsigned __int8)(v4 + v13[v15] + v12[v15] + v16) - (unsigned __int8)v4;
    v22 = (unsigned __int8)v12[v16] ^ (unsigned __int8)v12[v15];
    v12[v15] = v22;
    LODWORD(v23) = (unsigned __int8)v22 ^ (unsigned __int8)v12[v16];
    v12[v16] = v23;
    v12[v15] ^= v23;
    ++v15;
  }
  v17 = j_strlen(v25);
  v18 = 0;
  v19 = 0;
  v20 = 0;
  while ( v18 < v17 )
  {
    v5 = v19 + 1;
    v19 = (unsigned __int8)(BYTE4(v5) + v19 + 1) - BYTE4(v5);
    v6 = (unsigned __int64)((unsigned __int8)v12[v19] + v20) >> 32;
    v20 = (unsigned __int8)(v6 + v12[v19] + v20) - v6;
    v22 = (unsigned __int8)v12[v20] ^ (unsigned __int8)v12[v19];
    v12[v19] = v22;
    LODWORD(v23) = (unsigned __int8)v22 ^ (unsigned __int8)v12[v20];
    v12[v20] = v23;// v23 = v12[v19] = v12[v18 + 1]
    v12[v19] ^= v23;// v12[v19] = 0
    v7 = (unsigned __int64)((unsigned __int8)v12[v20] + (unsigned __int8)v12[v19]) >> 32;
    v21 = (unsigned __int8)(v7 + v12[v20] + v12[v19]) - (unsigned __int8)v7;
    v25[v18] ^= v12[v21];// v21 = v12[v20]
    ++v18;
  }
  LODWORD(v8) = j_memcmp(v25, &unk_14001D120, v17);
  v9 = v8;
  sub_14001137F((__int64)&v11, (__int64)&unk_14001AE60);
  return v9;
}

我们继续动态调试,发现它要你输入一个字符串。输入后单步几下,也可找到以上函数。

x64dbg动态调试可知,Str"scuctf{fakeflag}"v25即输入串。另外,unk_14001D120是长为44的常量数组。

v13改成Str_loopv15改成i1

算法逆向思路

算法主体是最后一个while循环,它是唯一修改输入串的函数,且修改方式为最简单的逐个字符修改。最后期望等于unk_14001D120常量数组。

v12数组只与Str有关,因此可视为常量。但在修改输入串的过程中,v12也随着v18变化而变化。所以我们只需要用cpp,获取运行过程中的v12数组。因此,我们没有必要尝试看懂最后一个while循环(实际上它也不可逆)。

如何获取v12初值?我们找到第一个while循环后的第一条语句v17 = j_strlen(v25);所对应的汇编代码,并打断点,如图:

1.JPG

在内存窗口转到rsp+30处,选中数据复制即可。

def main():
    raw_dat = '''
27 D7 4E BF 2C 97 81 05 9E 62 43 CA BA A2 9D 29  
AC 21 FD 12 1F 57 B1 03 95 5B AA 00 58 11 0D 36
44 0B 80 52 6D 6A 5F 0C 5C 5A B0 C0 35 B6 3B 4F
83 2E B5 66 DF 7A A5 16 4A F0 AE 34 CE 8B E8 06
B2 9A 50 68 BE CF 8C 08 BB 96 4D 23 4B 54 B3 77
79 F6 5E 00 98 3F 6C 94 A1 2F 10 09 04 F3 AB E4  
3E DD 0F 6B ED 2B AF 99 D1 40 AD 7E 78 84 7D E5  
BC 4C D3 47 EA 55 56 FE 01 C2 3D 45 02 EB 13 8E  
D9 18 A0 C5 F7 DA DB 49 D0 E6 3A 8F EF F1 90 1E  
E0 F9 E9 C6 71 C3 86 F2 60 38 64 FA 63 B4 EE 17  
7C 51 28 1D B8 00 2D 39 5D 42 07 69 9F 82 8D 67  
73 D8 C1 93 FC 15 B9 CC 72 7B D4 E2 32 26 C8 F8  
C7 88 3C D5 CD F4 7F A4 24 DC A9 C9 70 25 6E CB  
6F F5 D6 30 2A 48 46 A6 76 1B 33 9B 74 9C 75 1A  
0A 53 1C A8 FB DE 87 59 22 65 41 91 92 37 A3 20  
E1 61 85 FF BD A7 EC D2 0E E3 B7 8A 31 E7 C4 89  
    '''
    a = []
    for ln in raw_dat.split('\n'):
        ln = ln.strip()
        if not ln: continue
        a.extend([int(v,16) for v in ln.split(' ')])
    print(a)

if __name__ == "__main__":
    main()

cpp代码(defs.h可在IDA安装目录找到)

#include <bits/stdc++.h>
#include "defs.h"
using namespace std;
typedef long long LL;
#define rep(i,a,b) for(int i = (a);i <= (b);++i)
#define re_(i,a,b) for(int i = (a);i < (b);++i)
#define dwn(i,a,b) for(int i = (a);i >= (b);--i)

int v12[256] = {39, 215, 78, 191, 44, 151, 129, 5, 158, 98, 67, 202, 186, 162, 157, 41, 172, 33, 253, 18, 31, 87, 177, 3, 149, 91, 170, 0, 88, 17, 13, 54, 68, 11, 128, 82, 109, 106, 95, 12, 92, 90, 176, 192, 53, 182, 59, 79, 131, 46, 181, 102, 223, 122, 165, 22, 74, 240, 174, 52, 206, 139, 232, 6, 178, 154, 80, 104, 190, 207, 140, 8, 187, 150, 77, 35, 75, 84, 179, 119, 121, 246, 94, 0, 152, 63, 108, 148, 161, 47, 16, 9, 4, 243, 171, 228, 62, 221, 15, 107, 237, 43, 175, 153, 209, 64, 173, 126, 120, 132, 125, 229, 188, 76, 211, 71, 234, 85, 86, 254, 1, 194, 61, 69, 2, 235, 19, 142, 217, 24, 160, 197, 247, 218, 219, 73, 208, 230, 58, 143, 239, 241, 144, 30, 224, 249, 233, 198, 113, 195, 134, 242, 96, 56, 100, 250, 99, 180, 238, 23, 124, 81, 40, 29, 184, 0, 45, 57, 93, 66, 7, 105, 159, 130, 141, 103, 115, 216, 193, 147, 252, 21, 185, 204, 114, 123, 212, 226, 50, 38, 200, 248, 199, 136, 60, 213, 205, 244, 127, 164, 36, 220, 169, 201, 112, 37, 110, 203, 111, 245, 214, 48, 42, 72, 70, 166, 118, 27, 51, 155, 116, 156, 117, 26, 10, 83, 28, 168, 251, 222, 135, 89, 34, 101, 65, 145, 146, 55, 163, 32, 225, 97, 133, 255, 189, 167, 236, 210, 14, 227, 183, 138, 49, 231, 196, 137};
int goal[44] = {
    0x98, 0x11, 0xA1, 0x15, 0x1B, 0xFA, 0x99, 0xAB, 0xAF, 0xEA,
    0x63, 0xCB, 0xB3, 0x98, 0x52, 0x1C, 0x0D, 0xD5, 0xCE, 0xDA,
    0xC4, 0xAF, 0x07, 0xA3, 0x4F, 0xB2, 0x65, 0x99, 0x15, 0x8A,
    0xE1, 0x02, 0x4C, 0x3A, 0x1A, 0x69, 0x86, 0xCD, 0x56, 0x9C,
    0xCA, 0x2C, 0x40, 0x4A
};

void dbg() {
    puts ("");
}
template<typename T, typename... R>void dbg (const T &f, const R &... r) {
    cout << f << " ";
    dbg (r...);
}

int main() {
    int i = 0;
    int v19 = 0;
    int v20 = 0;
    unsigned __int64 v23 = 0;
    string ans;
    while ( i < 44 ) {
        int v5 = v19 + 1;
        v19 = v19 + 1; // (unsigned __int8) (BYTE4 (v5) + v19 + 1) - BYTE4 (v5);
        unsigned __int8 v6 = (unsigned __int64) ( (unsigned __int8) v12[v19] + v20) >> 32;
        v20 = (unsigned __int8) (v6 + v12[v19] + v20) - v6;
        int v22 = (unsigned __int8) v12[v20] ^ (unsigned __int8) v12[v19];
        v12[v19] = v22;
        LODWORD (v23) = (unsigned __int8) v22 ^ (unsigned __int8) v12[v20];
        v12[v20] = v23;
        v12[v19] ^= v23;
        int v7 = (unsigned __int64) ( (unsigned __int8) v12[v20] + (unsigned __int8) v12[v19]) >> 32;
        int v21 = (unsigned __int8) (v7 + v12[v20] + v12[v19]) - (unsigned __int8) v7;
        ans += char (goal[i] ^ v12[v21]);
        ++i;
    }
    dbg (ans); // scuctf{26cb52c0-fe96-11ea-b95c-00d861d79b00}
    return 0;
}

参考链接

  1. TLS介绍:www.52pojie.cn/thread-1490…
  2. 别人的题解:papayawd.github.io/2020/12/16/…