首页
社区
课程
招聘
[原创] KCTF 2023 第十二题 wp - 98k
2023-9-29 19:36 8422

[原创] KCTF 2023 第十二题 wp - 98k

2023-9-29 19:36
8422

程序中的一些关键函数加了混淆,直接把 0x49A0E8 处 patch 为 double 类型的 100.0 ,再声明其类型为 const double 这样 ida F5 就能直接去除无关的逻辑。

题目实现了一个虚拟机,程序启动后会先解密得到虚拟机执行的命令,之后来到函数 0x460D0E 执行:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
char __thiscall global_vtable_2_func_18(C *this)
{
  C *v1; // ebx
  int _pc; // esi
  B *ins_start; // edi MAPDST
  char *v6; // esi
  _DWORD *v7; // eax
  const char *v8; // ecx
  unsigned int v9; // edx
  unsigned int v10; // edi
  _BYTE *v11; // ebx
  char v12; // al
  int v14; // [esp+8h] [ebp-6Ch]
  char v16[88]; // [esp+1Ch] [ebp-58h] BYREF
 
  v1 = this;
  if ( v1->instructions.start == v1->instructions.finish )
    return 0;
  this->pc = 0;
  ins_start = this->instructions.start;
  if ( v1->instructions.finish - ins_start > 0 )
  {
    _pc = 0;
    ins_start = v1->instructions.start;
    do
    {
      if ( _pc == -1 )
      {
        v1->pc = 0;
        _pc = 0;
      }
      if ( !(*((unsigned __int8 (__thiscall **)(C *, B *))v1->vtable + 15))(v1, &ins_start[_pc]) )
      {
        // show error message
      }
      _pc = v1->pc + 1;
      v1->pc = _pc;
      ins_start = v1->instructions.start;
    }
    while ( _pc < v1->instructions.finish - ins_start );
  }
  return 1;
}

v1->vtable + 15 就是虚表中的第 15 项,地址为 0x4525EB 。

相关数据结构:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
struct A
{
  int field_0;
  std::string s0;
  int i1;
  int i2;
  int i3;
  std::string s4;
  std::string s5;
  std::string s6;
};
 
struct std::vector$A$
{
  A *start;
  A *finish;
  A *end_of_storage;
};
 
struct B
{
  int is_label;
  int field_4;
  std::string s1;
  std::string s2;
  std::vector$A$ vector;
};
 
struct std::vector$B$
{
  B *start;
  B *finish;
  B *end_of_storage;
};
 
struct RBTree // std::map<std::string, std::string>
{
  struct RBTree *left;
  struct RBTree *parent;
  struct RBTree *right;
  char color;
  char isnil;
  char padding[2];
  std::string key;
  std::string value;
};
 
struct C
{
  int **vtable;
  RBTree *tree;
  int node_count;
  std::vector$B$ instructions;
  int pc;
  char valid_data;
};

将解密之后的指令所在的内存 dump 出来,写脚本恢复原始指令:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
def vm(ins):
    opcode = int(ins[2])
    operands = ins[3]
    if opcode == 0x21344D4938CE0640:
        print('system({0})'.format(repr(operands[0][0].decode())))
    elif opcode == 0x3820EA1739C3E154:
        print('map[{0}] = hex(vigenere(map[{0}], "恭喜发财".encode("gbk")))'.format(repr(operands[0][4])))
    elif opcode == 0x4B0134D06B40680:
        print('if (len(map[{0}]) {1} {2}) goto {3}'.format(repr(operands[0][4]), operands[1][5].decode(), operands[2][1], operands[3][6].decode()))
    elif opcode == 0x5304FD305CA8C22A:
        print('sleep({})'.format(operands[0][1]))
    elif opcode == 0x5AC009C0F14B76E8:
        print('map[{0}] = input()'.format(repr(operands[0][4])))
    elif opcode == 0x6975C7A3C07CD226:
        print('map[{0}] += {1}'.format(repr(operands[0][4]), repr(operands[1][0])))
    elif opcode == 0x7929CBF0A1496FB0:
        print('map[{0}] = base64encode(map[{0}])'.format(repr(operands[0][4])))
    elif opcode == 0x885F75A1461ECEBB:
        print('if (map[{0}] == map[{1}]) goto {2}'.format(repr(operands[0][4]), repr(operands[1][4]), operands[2][6].decode()))
    elif opcode == 0x8DB9D83D80004137:
        print('print({})'.format(repr(operands[0][0].replace(b'\\n', b'\n').decode('gbk'))))
    elif opcode == 0xA43CBF9D015186F1:
        if len(operands): code = operands[0][1]
        else: code = 0
        print('exit({0})'.format(code))
    elif opcode == 0xAA9C8E70F01F8D61:
        print('map[{0}] = map[{1}]'.format(repr(operands[0][4]), repr(operands[1][4])))
    elif opcode == 0xDDEEFF2200112233:
        print('nop')
    elif opcode == 0xE56D33B21C50A892:
        print('goto {0}'.format(operands[0][6].decode()))
    elif opcode == 0xF4CC06C2E34200F0:
        print('nop 0')
    elif opcode == 0xF4CC06F2E3420459:
        print('nop 1')
    elif opcode == 0xFD1D1DFB19850CA1:
        print('map[{0}] = md5(map[{0}])'.format(repr(operands[0][4])))
    else:
        assert False, hex(opcode)
 
mem_offset = 0xea0000
mem_size = 0x20000
mem_dump = open('MEM_00EA0000_00020000.mem', 'rb').read()
 
ins_start = 0xeb4b00
ins_end = 0xeb62a4
 
def read_dword(addr):
    assert mem_offset <= addr < mem_offset + mem_size
    return int.from_bytes(mem_dump[addr - mem_offset: addr - mem_offset + 4], 'little')
 
def read_data(addr, size):
    assert mem_offset <= addr < mem_offset + mem_size - size
    return mem_dump[addr - mem_offset: addr - mem_offset + size]
 
def read_str(addr):
    size = read_dword(addr + 0x10)
    cap = read_dword(addr + 0x14)
    if cap > 0x10: addr = read_dword(addr)
    return read_data(addr, size)
 
def read_A(addr):
    return (read_str(addr + 4), read_dword(addr + 0x1c), read_dword(addr + 0x20), read_dword(addr + 0x24), read_str(addr + 0x28), read_str(addr + 0x40), read_str(addr + 0x58))
 
def read_B(addr):
    B = (read_dword(addr), read_dword(addr + 4), read_str(addr + 0x20), [])
    A_start = read_dword(addr + 0x38)
    A_end = read_dword(addr + 0x3c)
    for addr in range(A_start, A_end, 0x70):
        B[-1].append(read_A(addr))
    return B
 
ins = []
for addr in range(ins_start, ins_end, 0x44):
    ins.append(read_B(addr))
 
for i in range(len(ins)):
    t = ins[i]
    if t[0]:
        print(t[3][0][6].decode() + ':')
    else:
        # print(i, end=': ')
        print('    ', end='')
        vm(ins[i])

得到输出:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
    sleep(25)
    print('                                           \n')
    sleep(25)
    print('                  _oo0oo_                  \n')
    sleep(25)
    print('                 o8888888o                 \n')
    sleep(25)
    print('                 88" . "88                 \n')
    sleep(25)
    print('                 (| -_- |)                 \n')
    sleep(25)
    print('                 0\\  =  /0                 \n')
    sleep(25)
    print("               ___/`---'\\___               \n")
    sleep(25)
    print("             .'\\.\\|     |/./'.             \n")
    sleep(25)
    print('            / \\.\\||  :  ||/./ \\            \n')
    sleep(25)
    print('           / _||||| -:- |||||- \\           \n')
    sleep(25)
    print('          | | \\.\\.\\  -  /././ | |          \n')
    sleep(25)
    print("          | \\_|  ''\\---/''  |_/ |          \n")
    sleep(25)
    print("          \\  .-\\__  '-'  ___/-. /          \n")
    sleep(25)
    print("        ___'. .'  /--.--\\  `. .'___        \n")
    sleep(25)
    print('     ."" \'<  `.___\\_<|>_/___.\' >\' "".      \n')
    sleep(25)
    print('    | | :  `- \\`.;`\\ _ /`;.`/ - ` : | |    \n')
    sleep(25)
    print('    \\  \\ `_.   \\_ __\\ /__ _/   .-` /  /    \n')
    sleep(25)
    print("=====`-.____`.___ \\_____/___.-`___.-'===== \n")
    sleep(25)
    print("                  `=---='                  \n")
    sleep(25)
    print('                                           \n')
    sleep(25)
    print('             佛祖保佑 永无BUG              \n')
    sleep(25)
    print('            看雪 KCTF2023年度赛            \n')
    sleep(25)
    print('            出题战队:中午吃什么            \n')
    sleep(25)
    print('                                           \n')
    sleep(25)
    print('                                           \n')
    nop
    sleep(25)
    print('请输入用户名:\n--> ')
    map[b'2569430338759937617'] = input()
    nop
    sleep(25)
    print('请输入序列号:\n--> ')
    map[b'1509181994979340817'] = input()
    nop
    nop 0
    nop 1
    if (len(map[b'1509181994979340817']) != 32) goto 10547232137042693405
    nop
    map[b'18097274335226857185'] = map[b'2569430338759937617']
    map[b'18097274335226857185'] += b'KCTF2023'
    map[b'18097274335226857185'] = hex(vigenere(map[b'18097274335226857185'], "恭喜发财".encode("gbk")))
    map[b'18097274335226857185'] = base64encode(map[b'18097274335226857185'])
    map[b'18097274335226857185'] = md5(map[b'18097274335226857185'])
    nop
    if (map[b'18097274335226857185'] == map[b'1509181994979340817']) goto 15317636321340901566
    goto 15315557398280875957
    nop
10547232137042693405:
    sleep(25)
    print('error\n\n\n')
    system('pause')
    exit(0)
    nop
15317636321340901566:
    sleep(25)
    print('Success GoodJob!\n\n\n')
    system('pause')
    exit(0)
    nop
15315557398280875957:
    sleep(25)
    print('error\n\n\n')
    system('pause')
    exit(0)

求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#!/usr/bin/env python3
 
from base64 import b64encode
from hashlib import md5
 
def vigenere(x, y):
    return bytes(x[i] ^ y[i % len(y)] for i in range(len(x)))
 
def solve(username):
    return md5(b64encode(vigenere(username + b'KCTF2023', "恭喜发财".encode("gbk")).hex().upper().encode())).hexdigest()
 
 
'''
User-Name : 4070382B95A4F0ED
Serial-Number : 47a62c6eb8a72031a27b89abc3d976f7
'''
 
assert solve(b'4070382B95A4F0ED') == '47a62c6eb8a72031a27b89abc3d976f7'
print(solve(b'KCTF')) # 35090e1336f1d1e872ba798256db1bfb

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
点赞3
打赏
分享
最新回复 (3)
雪    币: 214
活跃值: (243)
能力值: ( LV5,RANK:77 )
在线值:
发帖
回帖
粉丝
小巫 2023-10-1 15:43
2
0
太强了吧
雪    币: 19410
活跃值: (29069)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
秋狝 2023-10-1 21:15
3
1
感谢分享
雪    币: 1760
活跃值: (2349)
能力值: ( LV12,RANK:234 )
在线值:
发帖
回帖
粉丝
wx_孤城 2023-10-2 10:38
4
0
太强了,脚本原文都还原出来了
游客
登录 | 注册 方可回帖
返回