Contents

2022-2023做过的一些有意思的逆向题

前言

这些题目本来是放在XDSEC内部论坛的,供学弟学妹练习的帖子,年末正好搬过来,作为这一年的记录。

第一周

0×00 春秋杯 冬季赛 godeep

用好idapython可以让你做得更优雅

WriteUp

题目逻辑比较清晰了:输入字符串 -> 转成二进制01串 -> 根据每一位的0、1选择不同的分支 -> 直到二叉树的终点。

难点在于节点很多,难以手动走遍整棵二叉树找出正确路径,此时就需要使用idapython做一些自动化的工作。

上面浮小云的做法是从终点往回倒推到起点,这里给出一个从前往后dfs拿到正确路径的做法:

给节点改个名先

1
2
3
4
5
6
7
start = 0x401000
end = 0x583400
index = 0
for i in range(start, end):
    if "godeep_tree." in get_name(i):
        set_name(i, "node"+str(index))
        index += 1

总共5672个节点,获取所有节点和边

 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
from idaapi import *
from idc import *
start = 0x401000
end = 0x583400
out = open("out.txt", "w")

for i in range(start, end):
    parent = get_name(i)
    if parent[0:4] != "node":
        continue
    out.write(parent[4:]+" ")
    j = i
    f = False
    child = GetDisasm(j)
    last = ""
    while child != "retn":
        if last == child:
            j += 1
            child = GetDisasm(j)
            continue
        if child == 'call    node5672':
            if f:
                out.write(child[child.find('e')+1:] +" ")
            else:
                f = True
        elif 'call    node' in child:
            out.write(child[child.find('e')+1:]+" ")
        elif 'wrong' in child:
            out.write("wrong ")
        elif 'right' in child:
            out.write("right ")
        last = child
        j += 1  
        child = GetDisasm(j)
    out.write('\n')

建立二叉树+dfs求路径

 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
from Crypto.Util.number import *
file = open("out.txt", "r")
dic = dict()
class TreeNode:
    def __init__(self, id):
        self.id = id
        self.state = True
        self.flag = False
        self.left = None
        self.right = None

root = TreeNode(9999)
node0 = TreeNode(0)
root.left = node0
dic[0] = node0

for i in file.readlines():
    parent = eval(i.split()[0])
    parent_node = TreeNode(parent)
    if dic.get(parent) != None:
        parent_node = dic.get(parent)
    else:
        dic[parent] = parent_node

    if i.split()[1] == 'wrong':
        parent_node.state = False
        continue
    if i.split()[1] == 'right':
        parent_node.flag = True
        continue
    if len(i.split()) == 2:
        left_node = TreeNode(eval(i.split()[1]))
        dic[eval(i.split()[1])] = left_node
        parent_node.left = left_node
    elif len(i.split()) == 3:
        left_node = TreeNode(eval(i.split()[1]))
        dic[eval(i.split()[1])] = left_node
        parent_node.left = left_node
        right_node = TreeNode(eval(i.split()[2]))
        dic[eval(i.split()[2])] = right_node
        parent_node.right = right_node

path = []
def dfs(parent):
    if parent.flag:
        print(path)
        print(long_to_bytes(eval("0b"+"".join(str(i) for i in path).removeprefix('1'))))
        exit()
    if not parent.state:
        return
    if parent.left:
        path.append(1)
        dfs(parent.left)
        path.pop(-1)
    if parent.right:
        path.append(0)
        dfs(parent.right)
        path.pop(-1)
dfs(root)
print("no answer")
print(path)

0×01 RCTF2022 huowang

这题最后有2个解,不用纠结

writeUp

放这道题主要想法是想让大家对unicron有一个初步的了解,放两篇感觉不错的文章(大家有其他好文章也可以贴一下捏):

整道题有两个check的逻辑,一个很明显的在0×40203D(基址0×400000),一眼地图,但是有很多条路。另一个就是unicron执行shellcode的逻辑。

unicron主要代码逻辑在这块:

1
2
3
4
5
6
7
  uc_mem_write(ucEngine, (__int64)&qword_400080, (__int64)&loc_145E010, 0x7AA8uLL);
  v11 = uc_mem_write(ucEngine, (__int64)&loc_407B34, (__int64)input, input_len);
  v11 = uc_reg_write(ucEngine, 0x1E, (__int64)&v8);// ESP
  v11 = uc_hook_add(ucEngine, v13, 2, (__int64)check2, 0LL, 1LL, 0LL, 0x2BB, v7);
  v11 = uc_emu_start(ucEngine, 0x400119LL, 0x40016ALL, 0LL, 0LL);
  v11 = uc_emu_start(ucEngine, 0x40016ALL, (__int64)&qword_4000D8, 0LL, 0LL);
  uc_close(ucEngine);

流程大概是:

  • 向unicorn内存空间写入此程序loc_145E010这个地方的shellcode
  • 把input作为某一步的一个参数,覆盖了一个无用的函数loc_407B34
  • 修改ID为30的寄存器(ESP)的值为0xFFFFF(开个栈)
  • 添加了一个hook函数,check最后第一个寄存器(应该是RAX)值为1
  • 调用uc_emu_start从0×145E0A9模拟执行到0×145E0FA
  • 接着模拟执行从0×145E0FA到0×145E067

这里需要注意:unicorn自己的虚拟内存空间跟程序空间是无关的,且默认基址为0×400080。可以使用如dd等命令把它保存为一个新的二进制文件更好分析:dd if=./HuoWang of=output skip=17162256 bs=1

分析一下可以发现:

shellcode在不断循环自修改,每次检查输入的一位,实现了地图的第二个check。整个文件很大,就是因为后面附带了很长的shellcode执行所需的数据。

至于解法当时比赛时dx是手动走的,比较费时。一个更简单的做法是根据第一个check枚举出所有可能解,然后输入程序进行爆破,最后只剩下两个解,flag是合理的最短路径。这是这道题出得不好的一点,不用在意。

第二周

0×00 N1CTF Junior checkin-rs

你还害怕rust吗?
附件:checkin-rs

WriteUp

程序运行流程

怎么找到加密逻辑应该是这道题最难的部分,这个问题应该已经在这个帖子里说得很清楚了,这就不多说了。

main函数在re_checkin_rs::main

  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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
volatile signed __int64 *re_checkin_rs::main::h7930adfed10cc1f8()
{
  _OWORD *v0; // rax
  _OWORD *v1; // r15
  char *input_1; // r12
  __int64 input_len; // rbx
  __int64 input_2; // r14
  __int64 input_len_1; // r13
  __int64 v6; // rax
  __int128 v7; // xmm0
  __int128 v8; // xmm1
  __int128 v9; // rdi
  __int128 v10; // rax
  char v11; // al
  volatile signed __int64 *result; // rax
  char v13; // [rsp+Ch] [rbp-BCh]
  __int128 input_stream; // [rsp+10h] [rbp-B8h] BYREF
  __int128 v15; // [rsp+20h] [rbp-A8h]
  __int128 v16; // [rsp+30h] [rbp-98h]
  __int64 v17; // [rsp+40h] [rbp-88h]
  __int64 v18; // [rsp+48h] [rbp-80h]
  volatile signed __int32 *v19[2]; // [rsp+58h] [rbp-70h] BYREF
  char *input; // [rsp+68h] [rbp-60h] BYREF
  __int128 v21; // [rsp+70h] [rbp-58h]
  __int64 v22[9]; // [rsp+80h] [rbp-48h] BYREF

  v0 = (_OWORD *)_rust_alloc(46LL, 1LL);
  if ( !v0 )
    alloc::alloc::handle_alloc_error::h87e3407648c6f1ae(46LL);
  v1 = v0;
  *v0 = xmmword_55CFF2D43AB0;
  v0[1] = xmmword_55CFF2D43AC0;
  qmemcpy(v0 + 2, "R~[PE@]LF\\ZHI^", 14);
  *(_QWORD *)&input_stream = &off_55CFF2D55D90; // input your flag\n
  *((_QWORD *)&input_stream + 1) = 1LL;
  *(_QWORD *)&v15 = 0LL;
  *(_QWORD *)&v16 = "called `Result::unwrap()` on an `Err` valuesrc/main.rsWrong!\n";
  *((_QWORD *)&v16 + 1) = 0LL;
  std::io::stdio::_print::h5cbb3fae9e78870c(&input_stream);
  input = (char *)1;
  v21 = 0LL;
  v19[0] = (volatile signed __int32 *)std::io::stdio::stdin::h7ddc52b357ca7934();
  std::io::stdio::Stdin::read_line::hc035b1783e397e56((__int64)&input_stream, v19, (__int64)&input);
  if ( (_QWORD)input_stream )
  {
    v22[0] = *((_QWORD *)&input_stream + 1);
    core::result::unwrap_failed::hb53671404b9e33c2(
      "failed to input.Congratulation!\ncalled `Result::unwrap()` on an `Err` valuesrc/main.rsWrong!\n",
      16LL,
      v22,
      &off_55CFF2D55D30,
      &off_55CFF2D55DA0);
  }
  input_1 = input;
  input_len = *((_QWORD *)&v21 + 1);
  if ( *((_QWORD *)&v21 + 1) && input[*((_QWORD *)&v21 + 1) - 1] == '\n' )
  {
    input_len = *((_QWORD *)&v21 + 1) - 1LL;
    if ( *((_QWORD *)&v21 + 1) == 1LL )
    {
      input_2 = 1LL;
    }
    else
    {
      if ( input_len < 0 )
        alloc::raw_vec::capacity_overflow::h52630126fb18cfa2();
      input_2 = _rust_alloc(input_len, input_len >= 0);
      if ( !input_2 )
        alloc::alloc::handle_alloc_error::h87e3407648c6f1ae(input_len);
    }
    memcpy((void *)input_2, input_1, input_len);
    v13 = 1;
    input_1 = (char *)input_2;
    input_len_1 = input_len;
  }
  else
  {
    input_len_1 = v21;
    v13 = 0;
  }
  v15 = 0LL;
  LOBYTE(v17) = 2;
  input_stream = xmmword_55CFF2D436E0;
  v6 = _rust_alloc(56LL, 8LL);
  if ( !v6 )
    alloc::alloc::handle_alloc_error::h87e3407648c6f1ae(56LL);
  *(_QWORD *)(v6 + 48) = v17;
  v7 = input_stream;
  v8 = v15;
  *(_OWORD *)(v6 + 32) = v16;
  *(_OWORD *)(v6 + 16) = v8;
  *(_OWORD *)v6 = v7;
  if ( _InterlockedIncrement64((volatile signed __int64 *)v6) <= 0 )
    BUG();
  v19[0] = 0LL;
  v19[1] = (volatile signed __int32 *)v6;
  *(_QWORD *)&input_stream = input_1;
  *((_QWORD *)&input_stream + 1) = input_len_1;
  *(_QWORD *)&v15 = input_len;
  *((_QWORD *)&v15 + 1) = v1;
  v16 = xmmword_55CFF2D43AD0;
  v17 = 0LL;
  v18 = v6;
  *(_QWORD *)&v9 = v22;
  *((_QWORD *)&v9 + 1) = &input_stream;
  std::thread::spawn::h07216e9b542ea8b6(v9);
  *(_QWORD *)&v10 = std::thread::JoinHandle$LT$T$GT$::join::h18c7fa589a3e95f8((__int64)v22);
  if ( (_QWORD)v10 )
  {
    input_stream = v10;
    core::result::unwrap_failed::hb53671404b9e33c2(
      "called `Result::unwrap()` on an `Err` valuesrc/main.rsWrong!\n",
      43LL,
      &input_stream,
      &off_55CFF2D55D50,
      &off_55CFF2D55DB8);
  }
  v11 = std::sync::mpsc::Receiver$LT$T$GT$::recv::h45da6b3200285188((__int64)v19);
  if ( v11 == 2 )
    core::result::unwrap_failed::hb53671404b9e33c2(
      "called `Result::unwrap()` on an `Err` valuesrc/main.rsWrong!\n",
      43LL,
      &input_stream,
      &off_55CFF2D55D70,
      &off_55CFF2D55DD0);
  if ( v11 )
    *(_QWORD *)&input_stream = &off_55CFF2D55DE8;// Congratulations
  else
    *(_QWORD *)&input_stream = &off_55CFF2D55DF8;// wrong
  *((_QWORD *)&input_stream + 1) = 1LL;
  *(_QWORD *)&v15 = 0LL;
  v16 = (unsigned __int64)"called `Result::unwrap()` on an `Err` valuesrc/main.rsWrong!\n";
  std::io::stdio::_print::h5cbb3fae9e78870c(&input_stream);
  result = core::ptr::drop_in_place$LT$std..sync..mpsc..Receiver$LT$bool$GT$$GT$::h6962096ca1e9139b((__int64 *)v19);
  if ( v13 )
  {
    if ( (_QWORD)v21 )
      return (volatile signed __int64 *)_rust_dealloc(input, v21, 1LL);
  }
  return result;
}

前面对输入的处理就不多说了,下面主要写写开启新线程进行加密的过程。

首先使用std::thread::spawn创建了新线程,这里v9就是新线程中执行的闭包,主要是线程内部执行的函数,当然具体是啥在这段伪代码里看不出来。闭包里面也会捕获一些主进程中的变量,包括前面可以看到input相关的赋值操作。

然后使用std:thread::JoinHandle::join阻塞当前线程直到新线程执行完毕,使用std::sync::mpsc::Receiver::recv接收新线程传递来的消息(即v11),v11是1则将输出字符串赋值为congratulations,是0则将输出字符串赋值为wrong。这块对应了加密函数中最后的check部分:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
if ( input_len_1 != input_len.m128i_i64[1] || bcmp((const void *)v21, (const void *)n[1], input_len_1) )
  {
    v37 = std::sync::mpsc::Sender$LT$T$GT$::send::h6efb96cd006889d6(v45.m128i_i64, 0);
    if ( v37 == 2 )
      goto LABEL_38;
    v41 = v37;
    v40 = &off_55CFF2D558A0;
LABEL_52:
    core::result::unwrap_failed::hb53671404b9e33c2(
      "called `Result::unwrap()` on an `Err` valuePoisonErrorsrc/main.rs",
      43LL,
      &v41,
      &off_55CFF2D55868,
      v40);
  }

新线程使用std::sync::mpsc::Sender::send向主线程传递check成功与否的消息。

想要了解rust多线程消息传递实现细节的可以看 线程同步:消息传递 - Rust语言圣经(Rust Course)

加密算法识别

这道题第二个问题在于静态分析难度很大,看不懂在干嘛,但是这题是可以调试的。

调起来可以发现,这个函数的参数a1就存储了输入。在函数的开始就会做一些输入的赋值操作:

1
2
3
4
5
6
  v1 = _mm_loadu_si128(a1 + 1);
  v45 = _mm_loadu_si128(a1 + 3);
  input_len = _mm_loadu_si128(a1 + 2);
  *(__m128i *)n = v1;
  *(__m128i *)input = _mm_loadu_si128(a1);
  input_len_1 = v1.m128i_i64[0];

_mm_loadu_si128类似这种东西是Intel SSE指令集的一些操作,不清楚的直接百度就有。

最后的check是在这:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
if ( input_len_1 != input_len.m128i_i64[1] || bcmp((const void *)v21, (const void *)n[1], input_len_1) )
  {
    v37 = std::sync::mpsc::Sender$LT$T$GT$::send::h6efb96cd006889d6(v45.m128i_i64, 0);
    if ( v37 == 2 )
      goto LABEL_38;
    v41 = v37;
    v40 = &off_55CFF2D558A0;
LABEL_52:
    core::result::unwrap_failed::hb53671404b9e33c2(
      "called `Result::unwrap()` on an `Err` valuePoisonErrorsrc/main.rs",
      43LL,
      &v41,
      &off_55CFF2D55868,
      v40);
  }

结合调试可以看出:v21是被加密的输入,n是比对的密文

输入总共32位,对输入的加密分为了两块

对后16位的加密在这:

1
2
3
4
5
    v35 = _mm_xor_si128(
            _mm_loadu_si128((const __m128i *)(v3 + v28 + 16)),
            _mm_add_epi8(_mm_load_si128((const __m128i *)&xmmword_55CFF2D433F0), si128));
    *(__m128i *)(v21 + v28) = _mm_xor_si128(_mm_loadu_si128((const __m128i *)(v3 + v28)), si128);
    *(__m128i *)(v21 + v28 + 16) = v35;

对前16位的加密在这:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
  LOBYTE(v22) = 0;
  v23 = (_BYTE *)v21;
  v24 = (_BYTE *)v3;
  do
  {
LABEL_34:
    v36 = v22 ^ *v24++;
    *v23++ = v36;
    LOBYTE(v22) = v22 + 1;
  }
  while ( v24 != (_BYTE *)(v3 + input_len_1) );

不要被这些东西吓住,动态看还是能看的,你会发现其实它就是实现了一个简单的input[i] ^= i的加密

0×01 鹏城杯 2022 gocode

没错还是vm
附件:chall.exe

WriteUp

这段时间大家应该做了不少vm了,之所以再放这道,是因为这道并没有实现什么加密算法,也不是什么有规律的加密,必须手动写一个类似反编译器的东西,然后扔到z3里去解方程

善用python可以在进行vm逻辑的同时直接构造表达式丢给z3,省去了来回复制的麻烦事,比如这样:

 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
from z3 import *
opcode = [0x0A, 0x0B, 0xBB, 0x00, 0x00, 0xBB, 0x01, 0x01, 0xBB, 0x02, 0x02, 0xBB, 0x03, 0x03, 0x0C, 0x01, 0x02, 0x02, 0x02, 0x011B, 0xAA, 0x01, 0x02, 0xBB, 0x01, 0x01, 0xBB, 0x02, 0x02, 0x0C, 0x00, 0x01, 0x0E, 0x00, 0x02, 0x0D, 0x00, 0x03, 0x02, 0x02, 0x7901, 0xAA, 0x00, 0x02, 0xBB, 0x02, 0x02, 0x02, 0x00, 0x63, 0x01, 0x02, 0x00, 0xBB, 0x00, 0x00, 0x0E, 0x00, 0x02, 0x0D, 0x00, 0x01, 0x0C, 0x00, 0x03, 0x02, 0x01, 0x1FF6, 0xAA, 0x00, 0x01, 0xBB, 0x01, 0x01, 0xBB, 0x00, 0x00, 0xBB, 0x02, 0x02, 0x0C, 0x01, 0x00, 0x0C, 0x01, 0x02, 0x0C, 0x01, 0x03, 0x02, 0x00, 0x021E, 0xAA, 0x01, 0x00, 0xBB, 0x01, 0x01, 0xBB, 0x00, 0x00, 0x0E, 0x00, 0x03, 0x0D, 0x00, 0x01, 0x0C, 0x00, 0x02, 0x02, 0x03, 0x3331, 0xAA, 0x00, 0x03, 0xBB, 0x00, 0x04, 0xBB, 0x01, 0x05, 0xBB, 0x02, 0x06, 0x02, 0x03, 0x63, 0x0C, 0x00, 0x03, 0x02, 0x03, 0x68, 0x01, 0x00, 0x03, 0x02, 0x03, 0x0152, 0xAA, 0x00, 0x03, 0x02, 0x03, 0x33, 0x0D, 0x01, 0x03, 0x02, 0x03, 0x63, 0x0E, 0x01, 0x03, 0x02, 0x00, 0x0441, 0xAA, 0x00, 0x01, 0x02, 0x03, 0x63, 0x01, 0x02, 0x03, 0x02, 0x03, 0x6B, 0x0C, 0x02, 0x03, 0x02, 0x01, 0x010E, 0xAA, 0x02, 0x01, 0x02, 0x01, 0x9E, 0xBB, 0x00, 0x07, 0xAA, 0x00, 0x01, 0xBB, 0x00, 0x08, 0xBB, 0x01, 0x09, 0xBB, 0x02, 0x0A, 0xBB, 0x03, 0x0B, 0x0E, 0x00, 0x03, 0x02, 0x03, 0x36CE, 0xAA, 0x00, 0x03, 0xBB, 0x00, 0x08, 0xBB, 0x03, 0x0B, 0x0C, 0x00, 0x01, 0x0E, 0x00, 0x02, 0x0D, 0x00, 0x03, 0x02, 0x02, 0x682D, 0xAA, 0x00, 0x02, 0xBB, 0x02, 0x0A, 0x02, 0x00, 0x63, 0x01, 0x02, 0x00, 0xBB, 0x00, 0x08, 0x0E, 0x00, 0x02, 0x0D, 0x00, 0x01, 0x0C, 0x00, 0x03, 0x02, 0x01, 0x15, 0xAA, 0x00, 0x01, 0xBB, 0x01, 0x09, 0xBB, 0x00, 0x08, 0xBB, 0x02, 0x0A, 0x0C, 0x01, 0x00, 0x0C, 0x01, 0x02, 0x0C, 0x01, 0x03, 0x02, 0x00, 0x01AE, 0xAA, 0x01, 0x00, 0xBB, 0x01, 0x09, 0xBB, 0x00, 0x08, 0x0E, 0x00, 0x03, 0x0D, 0x00, 0x01, 0x0C, 0x00, 0x02, 0x02, 0x03, 0x3709, 0xAA, 0x00, 0x03, 0xBB, 0x00, 0x0C, 0xBB, 0x01, 0x0D, 0xBB, 0x02, 0x0E, 0x02, 0x03, 0x63, 0x0C, 0x00, 0x03, 0x02, 0x03, 0x68, 0x01, 0x00, 0x03, 0x02, 0x03, 0xFA, 0xAA, 0x00, 0x03, 0x02, 0x03, 0x1E, 0x0D, 0x01, 0x03, 0x02, 0x03, 0x63, 0x0E, 0x01, 0x03, 0x02, 0x00, 0x018C, 0xAA, 0x00, 0x01, 0x02, 0x03, 0x63, 0x01, 0x02, 0x03, 0x02, 0x03, 0x6B, 0x0C, 0x02, 0x03, 0x02, 0x01, 0x83, 0xAA, 0x02, 0x01, 0x02, 0x01, 0x47, 0xBB, 0x00, 0x0F, 0xAA, 0x00, 0x01]
v40 = [""] * 100000
s = Solver()
input = [BitVec('input[%d]'% i, 8)  for i in range(16)]
i = 2
while i < 374:
#    if opcode[i] == 0xA:
#        print(hex(opcode[i]))
#        i += 1
    if opcode[i] == 0xF:
        v40[opcode[i+1]] = "(" + v40[opcode[i+1]] + "/" + v40[opcode[i+2]] + ")"
        i += 3
    elif opcode[i] == 0xAA:
        s.add(eval(v40[opcode[i+2]] + "==" + v40[opcode[i+1]]))
        i += 3
    elif opcode[i] == 0xBB:
        v40[opcode[i+1]] = "(input[" + str(opcode[i+2]) + "])"
        i += 3
    elif opcode[i] == 0xD:
        v40[opcode[i+1]] ="(" + v40[opcode[i+1]] + "-" + v40[opcode[i+2]] + ")"
        i += 3
    elif opcode[i] == 0xE:
        v40[opcode[i+1]] ="(" + v40[opcode[i+1]] + "*" + v40[opcode[i+2]] + ")"
        i += 3
    elif opcode[i] == 1:
        v40[opcode[i+1]] ="(" + v40[opcode[i+1]] + "^" + v40[opcode[i+2]] + ")"
        i += 3
    elif opcode[i] == 2:
        v40[opcode[i+1]] ="(" + str(opcode[i+2]) + ")"
        i += 3
#    elif opcode[i] == 0xB:
#        print(hex(opcode[i]), end=" ")
#        print()
#        i += 1
    elif opcode[i] == 0xC:
        v40[opcode[i+1]] ="(" + v40[opcode[i+1]] + "+" + v40[opcode[i+2]] + ")"
        i += 3
s.check()
m = s.model()
flag = "".join(hex(eval(str(m[input[i]]))).removeprefix('0x') for i in range(len(m)))
print(flag)

第三周

0×00 CISCN 2023 moveAside

你能用不止一种方法做出来吗?
附件:moveAside.zip

WriteUp

demov

拖进ida发现全是mov,首先应该意识到这是mov混淆,然后尝试demov。一个工具:GitHub - leetonidas/demovfuscator: A work-in-progress deobfuscator for movfuscated binaries

编译有点麻烦,可能会有奇怪的报错,但装上很好用。

这道题使用这个工具不能得到完全正常的程序,但会好看很多,至少可以让你在调试到发疯之前看懂逻辑。

关于demovfuscator工具的安装

首先这三个依赖库一定得装好,这直接按照相应仓库readme makemake install 应该问题不大

下面针对我遇到的报错给出一些解决办法,不一定通用,可以作参考。

  1. 装好依赖库后直接执行make命令进行编译,出现报错:

     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
    
    clang++ -Wall -Wextra -pedantic -std=c++11 -O3 -c demov.cpp
    clang++ -Wall -Wextra -pedantic -std=c++11 -O3 -c ctlhlp.cpp
    In file included from ctlhlp.cpp:1:
    In file included from ./ctlhlp.hpp:8:
    ./ctlelem.hpp:11:2: error: unknown type name 'uint32_t'
           uint32_t name;
           ^
    ./ctlelem.hpp:17:37: error: unknown type name 'uint32_t'
                   ctlelem(ctlflow tp = CTL_INVALID, uint32_t o = 0, uint32_t d = 0);
                                                     ^
    ./ctlelem.hpp:17:53: error: unknown type name 'uint32_t'
                   ctlelem(ctlflow tp = CTL_INVALID, uint32_t o = 0, uint32_t d = 0);
                                                                     ^
    ./ctlelem.hpp:18:11: error: unknown type name 'uint32_t'
                   ctlelem(uint32_t pos, uint32_t name, bool function = false);
                           ^
    ./ctlelem.hpp:18:25: error: unknown type name 'uint32_t'
                   ctlelem(uint32_t pos, uint32_t name, bool function = false);
                                         ^
    ./ctlelem.hpp:21:3: error: unknown type name 'uint32_t'
                   uint32_t pos;
                   ^
    ./ctlelem.hpp:23:4: error: unknown type name 'uint32_t'
                           uint32_t dst;
                           ^
    In file included from ctlhlp.cpp:1:
    In file included from ./ctlhlp.hpp:9:
    ./node.hpp:19:26: error: unknown type name 'uint32_t'
                   node(std::string name, uint32_t pos, uint32_t label = 0);
                                          ^
    ./node.hpp:19:40: error: unknown type name 'uint32_t'
                   node(std::string name, uint32_t pos, uint32_t label = 0);
                                                        ^
    ./node.hpp:25:16: error: unknown type name 'uint32_t'
                   void set_end(uint32_t end);
                                ^
    ./node.hpp:26:3: error: unknown type name 'uint32_t'
                   uint32_t get_pos();
                   ^
    ./node.hpp:27:3: error: unknown type name 'uint32_t'
                   uint32_t get_end();
                   ^
    ./node.hpp:30:18: error: unknown type name 'uint32_t'
                   void set_label(uint32_t l);
                                  ^
    ./node.hpp:34:3: error: unknown type name 'uint32_t'
                   uint32_t pos;
                   ^
    ./node.hpp:35:3: error: unknown type name 'uint32_t'
                   uint32_t label;
                   ^
    ./node.hpp:36:3: error: unknown type name 'uint32_t'
                   uint32_t end;
                   ^
    ./node.hpp:44:34: error: unknown type name 'uint32_t'
    node* get_node(std::string name, uint32_t pos);
                                    ^
    In file included from ctlhlp.cpp:1:
    ./ctlhlp.hpp:16:25: error: use of undeclared identifier 'uint32_t'
                   std::vector<std::pair<uint32_t, uint32_t>> get_blocks();
                                         ^
    ./ctlhlp.hpp:18:12: error: use of undeclared identifier 'uint32_t'
                   std::map<uint32_t, ctlelem> elems;
                            ^
    fatal error: too many errors emitted, stopping now [-ferror-limit=]
    20 errors generated.
    make: *** [Makefile:35:ctlhlp.o] 错误 1
    

    提示uint32_t无法识别,手动编辑./ctlhlp.hpp添加头文件#include <cstdint>

  2. 再次编译,继续报错:

     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
    
    clang++ -Wall -Wextra -pedantic -std=c++11 -O3 -c demov.cpp
    clang++ -Wall -Wextra -pedantic -std=c++11 -O3 -c ctlhlp.cpp
    clang++ -Wall -Wextra -pedantic -std=c++11 -O3 -c ctlelem.cpp
    In file included from ctlelem.cpp:1:
    ./ctlelem.hpp:11:2: error: unknown type name 'uint32_t'
           uint32_t name;
           ^
    ./ctlelem.hpp:17:37: error: unknown type name 'uint32_t'
                   ctlelem(ctlflow tp = CTL_INVALID, uint32_t o = 0, uint32_t d = 0);
                                                     ^
    ./ctlelem.hpp:17:53: error: unknown type name 'uint32_t'
                   ctlelem(ctlflow tp = CTL_INVALID, uint32_t o = 0, uint32_t d = 0);
                                                                     ^
    ./ctlelem.hpp:18:11: error: unknown type name 'uint32_t'
                   ctlelem(uint32_t pos, uint32_t name, bool function = false);
                           ^
    ./ctlelem.hpp:18:25: error: unknown type name 'uint32_t'
                   ctlelem(uint32_t pos, uint32_t name, bool function = false);
                                         ^
    ./ctlelem.hpp:21:3: error: unknown type name 'uint32_t'
                   uint32_t pos;
                   ^
    ./ctlelem.hpp:23:4: error: unknown type name 'uint32_t'
                           uint32_t dst;
                           ^
    ctlelem.cpp:5:30: error: unknown type name 'uint32_t'
    ctlelem::ctlelem(ctlflow tp, uint32_t o, uint32_t d) {
                                ^
    ctlelem.cpp:5:42: error: unknown type name 'uint32_t'
    ctlelem::ctlelem(ctlflow tp, uint32_t o, uint32_t d) {
                                            ^
    ctlelem.cpp:9:3: error: use of undeclared identifier 'lab'; did you mean 'labs'?
                   lab.name = o;
                   ^~~
                   labs
    /usr/include/stdlib.h:862:17: note: 'labs' declared here
    extern long int labs (long int __x) __THROW __attribute__ ((__const__)) __wur;
                   ^
    ctlelem.cpp:9:6: error: member reference base type 'long (long) noexcept(true)' is not a structure or union
                   lab.name = o;
                   ~~~^~~~~
    ctlelem.cpp:10:3: error: use of undeclared identifier 'lab'; did you mean 'labs'?
                   lab.function = d ? true : false;
                   ^~~
                   labs
    /usr/include/stdlib.h:862:17: note: 'labs' declared here
    extern long int labs (long int __x) __THROW __attribute__ ((__const__)) __wur;
                   ^
    ctlelem.cpp:10:6: error: member reference base type 'long (long) noexcept(true)' is not a structure or union
                   lab.function = d ? true : false;
                   ~~~^~~~~~~~~
    ctlelem.cpp:12:3: error: use of undeclared identifier 'dst'
                   dst = d;
                   ^
    ctlelem.cpp:16:18: error: unknown type name 'uint32_t'
    ctlelem::ctlelem(uint32_t pos, uint32_t name, bool function) {
                    ^
    ctlelem.cpp:16:32: error: unknown type name 'uint32_t'
    ctlelem::ctlelem(uint32_t pos, uint32_t name, bool function) {
                                  ^
    ctlelem.cpp:19:2: error: use of undeclared identifier 'lab'; did you mean 'labs'?
           lab.name = name;
           ^~~
           labs
    /usr/include/stdlib.h:862:17: note: 'labs' declared here
    extern long int labs (long int __x) __THROW __attribute__ ((__const__)) __wur;
                   ^
    ctlelem.cpp:19:5: error: member reference base type 'long (long) noexcept(true)' is not a structure or union
           lab.name = name;
           ~~~^~~~~
    ctlelem.cpp:20:2: error: use of undeclared identifier 'lab'; did you mean 'labs'?
           lab.function = function;
           ^~~
           labs
    /usr/include/stdlib.h:862:17: note: 'labs' declared here
    extern long int labs (long int __x) __THROW __attribute__ ((__const__)) __wur;
                   ^
    fatal error: too many errors emitted, stopping now [-ferror-limit=]
    20 errors generated.
    make: *** [Makefile:32ctlelem.o] 错误 1
    

手动编辑./ctlelem.hpp添加头文件#include <cstdint>

同样的还有./node.hpp

  1. 全部添加完成后,执行make即可正常编译出二进制文件(只有一个warning)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    
     make
    clang++ -Wall -Wextra -pedantic -std=c++11 -O3 -c ctlhlp.cpp
    clang++ -Wall -Wextra -pedantic -std=c++11 -O3 -c node.cpp
    In file included from node.cpp:1:
    ./node.hpp:38:8: warning: private field 'del' is not used [-Wunused-private-field]
                   bool del;
                        ^
    1 warning generated.
    clang++ -Wall -Wextra -pedantic -std=c++11 -lcapstone -lkeystone -lz3 -lssl -lcrypto elfhlp.o demov.o test.o memhlp.o dishlp.o utils.o ctlhlp.o ctlelem.o node.o asmhlp.o -o demov
    
  2. 但是运行程序提示找不到一个库

    1
    2
    
     ./demov
    ./demov: error while loading shared libraries: libkeystone.so.0: cannot open shared object file: No such file or directory
    

    搜索发现有这个库文件(在/usr/local/lib/libkeystone.so.0),直接尝试patchelf

    1
    
    patchelf --replace-needed libkeystone.so.0 /usr/local/lib/libkeystone.so.0 ./demov
    

    即可成功运行程序

     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    
    ❯ ./demov -h
    The demovfuscator supports the following parameters:
    
    ./demov [-h] [-i symbols.idc] [-o patched_bin] [-g cfg.dot] obfuscated_input
    
    -h Use for a description  of the options
    -i Derive symbols from the input bin and store them into symbols.idc
    -o Generate a UNIX dot compatible file containing the control flow
       graph (might be easier to read than  IDA's graph view)
       Convert the .dot file to something usable by
    
       cat cfg.dot | dot -Tpng > cfg.png
    

下面说三种解法。

0-神仙解法

[CISCN 2023 初赛]moveAside_FeiJiNcFan的博客-CSDN博客

同样的mov混淆,点名强网拟态2022决赛题目movemove。当时是这么做的:
https://bbs.xdsec.org/assets/files/2023-07-02/1688305023-635664-2023-07-02-17-10-53-image.png

1-调试看逻辑

建议先用上面的工具demov再调,会好看一些,且把原程序通过异常实现的跳转优化成了直接的jmp,舒服很多。

定位输入存储地址

我们调试的目的是观察输入的变化,搞明白程序是如何check的,所以第一步就要锁定输入存储的地址。

这里可以在函数列表找到__isoc99_scanf这个函数,所以直接在这个函数里下断点。

调起来在断点成功断下,由于32位程序使用栈传参,故直接看右下角就能看到scanf的参数,即输入存储地址unk_8600198
https://bbs.xdsec.org/assets/files/2023-07-02/1688305149-38803-2023-07-02-21-03-39-image.png

在输入存储地址下读写断点

与平常在代码段下断点不同,这次我们直接在数据段下断点,按下F2弹出对话框,勾选Read和Write,点击OK即可。每当有代码访问这个地址的数据时,就会触发断点。
https://bbs.xdsec.org/assets/files/2023-07-02/1688305198-757266-2023-07-02-21-07-04-image.png

F9执行直到关键代码

下好断点直接F9,每次断下记得先看右下角堆栈窗口,前两次会断在scanf、puts这些函数里面,直接继续F9即可,比如这个:
https://bbs.xdsec.org/assets/files/2023-07-02/1688305225-321783-2023-07-02-21-16-29-image.png

直到跳回text段执行一些神必代码:
https://bbs.xdsec.org/assets/files/2023-07-02/1688305473-685812-2023-07-02-21-17-42-image.png

这里就是关键加密位置了。

多调两遍识别加密逻辑

到这里如果你看的是demov之后的程序,直接F5是可以看到一个完整的函数的,虽然还有很多很乱的取址操作,但可以跟汇编佐证着看。

  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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
int sub_8051132()
{
  _DWORD *v0; // eax
  _DWORD *v1; // eax
  int v2; // eax
  int v3; // edx
  int v4; // eax
  int v5; // edx
  int v6; // eax
  int v7; // edx
  int v8; // eax
  int v9; // edx
  int v10; // ecx
  _DWORD *v11; // eax
  _DWORD *v12; // eax
  _DWORD *v13; // eax

  dword_8200190 = (int)off_84001B4;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_8200190 = dword_80570C4;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_8200190 = dword_80570C8;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_8200190 = dword_80570CC;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_8200190 = dword_80570D4;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_8200190 = dword_80570D8;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_8200190 = dword_80570E8;
  dword_8200194 = dword_80570EC;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 8;
  dword_84001F4 = (int)off_84001B0;
  v0 = *(&off_84001F0 + 1);
  *v0 = dword_8200190;
  v0[1] = dword_8200194;
  dword_8200190 = dword_80570F0;
  dword_8200194 = dword_80570F4;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 8;
  dword_84001F4 = (int)off_84001B0;
  v1 = *(&off_84001F0 + 1);
  *v1 = dword_8200190;
  v1[1] = dword_8200194;
  dword_84001F4 = (int)&off_84001B4;
  *(_DWORD *)*(&off_84001F0 + 1) = off_84001B0;
  dword_8200190 = (int)off_84001B0 - 64;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 64;
  dword_80570CC = (int)off_84001B4 - 64;
  dword_80570C8 = (int)&unk_8057030;
  dword_84001F4 = (int)off_84001B4 - 64;
  v2 = (int)*(&off_84001F0 + 1);
  dword_84001F4 = (int)&unk_8057030;
  v3 = (int)*(&off_84001F0 + 1);
  *(_DWORD *)v2 = *(_DWORD *)v3;
  *(_DWORD *)(v2 + 4) = *(_DWORD *)(v3 + 4);
  *(_DWORD *)(v2 + 8) = *(_DWORD *)(v3 + 8);
  *(_DWORD *)(v2 + 12) = *(_DWORD *)(v3 + 12);
  *(_DWORD *)(v2 + 16) = *(_DWORD *)(v3 + 16);
  *(_DWORD *)(v2 + 20) = *(_DWORD *)(v3 + 20);
  *(_DWORD *)(v2 + 24) = *(_DWORD *)(v3 + 24);
  *(_DWORD *)(v2 + 28) = *(_DWORD *)(v3 + 28);
  *(_DWORD *)(v2 + 32) = *(_DWORD *)(v3 + 32);
  *(_DWORD *)(v2 + 36) = *(_DWORD *)(v3 + 36);
  *(_BYTE *)(v2 + 40) = *(_BYTE *)(v3 + 40);
  *(_BYTE *)(v2 + 41) = *(_BYTE *)(v3 + 41);
  dword_80570CC = (int)off_84001B4 - 12;
  dword_80570C8 = (int)&unk_805705A;
  dword_84001F4 = (int)off_84001B4 - 12;
  v4 = (int)*(&off_84001F0 + 1);
  dword_84001F4 = (int)&unk_805705A;
  v5 = (int)*(&off_84001F0 + 1);
  *(_DWORD *)v4 = *(_DWORD *)v5;
  *(_BYTE *)(v4 + 4) = *(_BYTE *)(v5 + 4);
  dword_80570CC = (int)off_84001B4 - 20;
  dword_80570C8 = (int)&unk_805705F;
  dword_84001F4 = (int)off_84001B4 - 20;
  v6 = (int)*(&off_84001F0 + 1);
  dword_84001F4 = (int)&unk_805705F;
  v7 = (int)*(&off_84001F0 + 1);
  *(_DWORD *)v6 = *(_DWORD *)v7;
  *(_BYTE *)(v6 + 4) = *(_BYTE *)(v7 + 4);
  dword_80570CC = 0;
  dword_84001F4 = (int)off_84001B4 - 4;
  *(_DWORD *)*(&off_84001F0 + 1) = 0;
  dword_80570CC = 0;
  dword_84001F4 = (int)off_84001B4 - 4;
  *(_DWORD *)*(&off_84001F0 + 1) = 0;
  dword_8200070 = dword_84001E8;
  dword_8200074 = -2012932464;
  v8 = (unsigned __int8)dword_84001E8;
  v9 = *((unsigned __int8 *)*(&off_805A680 + (unsigned __int8)dword_84001E8) + 144);
  dword_8200060 = v9;
  LOBYTE(v8) = BYTE1(dword_84001E8);
  LOBYTE(v9) = 22;
  LOBYTE(v9) = *((_BYTE *)*(&off_805A680 + v8) + v9);
  dword_8200064 = v9;
  LOBYTE(v8) = BYTE2(dword_84001E8);
  LOBYTE(v9) = 5;
  LOBYTE(v9) = *((_BYTE *)*(&off_805A680 + v8) + v9);
  dword_8200068 = v9;
  LOBYTE(v8) = HIBYTE(dword_84001E8);
  LOBYTE(v9) = -120;
  LOBYTE(v9) = *((_BYTE *)*(&off_805A680 + v8) + v9);
  dword_820006C = v9;
  dword_8200060 &= dword_8200064;
  dword_8200060 &= dword_8200068;
  dword_8200060 &= v9;
  v10 = dword_8200060;
  dword_84001F4 = (int)&dword_80570C0;
  *(_DWORD *)*(&off_84001F0 + dword_8200060) = dword_8600210;
  dword_84001F4 = (int)&dword_80570C4;
  *(_DWORD *)*(&off_84001F0 + v10) = dword_8600214;
  dword_84001F4 = (int)&dword_80570C8;
  *(_DWORD *)*(&off_84001F0 + v10) = dword_8600218;
  dword_84001F4 = (int)&dword_80570CC;
  *(_DWORD *)*(&off_84001F0 + v10) = dword_860021C;
  dword_84001F4 = (int)&dword_80570D0;
  *(_DWORD *)*(&off_84001F0 + v10) = dword_8600220;
  dword_84001F4 = (int)&dword_80570D4;
  *(_DWORD *)*(&off_84001F0 + v10) = dword_8600224;
  dword_84001F4 = (int)&dword_80570D8;
  *(_DWORD *)*(&off_84001F0 + v10) = dword_8600228;
  dword_84001F4 = (int)&dword_80570E0;
  v11 = *(&off_84001F0 + v10);
  *v11 = dword_860022C;
  v11[1] = dword_8600230;
  dword_84001F4 = (int)&dword_80570E8;
  v12 = *(&off_84001F0 + v10);
  *v12 = dword_8600234;
  v12[1] = dword_8600238;
  dword_84001F4 = (int)&dword_80570F0;
  v13 = *(&off_84001F0 + v10);
  *v13 = dword_860023C;
  v13[1] = dword_8600240;
  *(_DWORD *)*(&off_84001D0 + dword_8200060) = 1;
  dword_80570CC = (int)off_84001B4 - 4;
  dword_84001F4 = (int)off_84001B4 - 4;
  dword_80570CC = *(_DWORD *)*(&off_84001F0 + 1);
  dword_80570C8 = (int)off_84001B4 + 44;
  dword_84001F4 = (int)off_84001B4 + 44;
  dword_80570C8 = *(_DWORD *)*(&off_84001F0 + 1);
  dword_8200070 = dword_80570CC;
  dword_8200074 = dword_80570C8;
  LOWORD(dword_8200078) = dword_80570CC + dword_80570C8;
  HIWORD(dword_8200078) = HIWORD(dword_80570CC)
                        + HIWORD(dword_80570C8)
                        + (((unsigned __int16)dword_80570CC + (unsigned int)(unsigned __int16)dword_80570C8) >> 16);
  *(int *)((char *)&dword_820007E + 2) = HIWORD(dword_80570CC)
                                       + HIWORD(dword_80570C8)
                                       + (((unsigned __int16)dword_80570CC
                                         + (unsigned int)(unsigned __int16)dword_80570C8) >> 16);
  dword_80570C8 = dword_8200078;
  dword_84001F4 = dword_8200078;
  dword_80570C0 = *(unsigned __int8 *)*(&off_84001F0 + 1);
  dword_80570C8 = dword_81FFBF0[(unsigned __int8)dword_80570C0];
  LOWORD(dword_8200078) = dword_80570C8 + 0x18;
  HIWORD(dword_8200078) = HIWORD(dword_80570C8) + (((unsigned int)(unsigned __int16)dword_80570C8 + 0x18) >> 16);
  *(int *)((char *)&dword_820007E + 2) = HIWORD(dword_80570C8)
                                       + (((unsigned int)(unsigned __int16)dword_80570C8 + 0x18) >> 16);
  dword_80570C4 = 25;
  dword_8200070 = dword_8200078;
  dword_8200074 = 25;
  dword_8200078 ^= 0x19u;
  dword_80570C8 = dword_8200078;
  dword_80570C0 = dword_8200078;
  dword_84001F4 = (int)off_84001B4 - 12;
  *(_BYTE *)*(&off_84001F0 + 1) = dword_8200078;
  dword_80570C8 = (int)off_84001B4 - 64;
  dword_8200070 = dword_80570CC;
  dword_8200074 = (int)off_84001B4 - 64;
  LOWORD(dword_8200078) = dword_80570CC + (_WORD)off_84001B4 - 64;
  HIWORD(dword_8200078) = HIWORD(dword_80570CC)
                        + (((unsigned int)off_84001B4 - 64) >> 16)
                        + (((unsigned __int16)dword_80570CC + (unsigned int)(unsigned __int16)((_WORD)off_84001B4 - 64)) >> 16);
  *(int *)((char *)&dword_820007E + 2) = HIWORD(dword_80570CC)
                                       + (((unsigned int)off_84001B4 - 64) >> 16)
                                       + (((unsigned __int16)dword_80570CC
                                         + (unsigned int)(unsigned __int16)((_WORD)off_84001B4 - 64)) >> 16);
  dword_80570CC = dword_8200078;
  dword_84001F4 = dword_8200078;
  dword_80570C0 = *(unsigned __int8 *)*(&off_84001F0 + 1);
  dword_80570CC = (unsigned __int8)dword_80570C0;
  dword_80570C0 = (unsigned __int8)dword_80570C0;
  dword_84001F4 = (int)off_84001B4 - 20;
  *(_BYTE *)*(&off_84001F0 + 1) = dword_80570C0;
  dword_80570CC = (int)off_84001B4 - 20;
  dword_8200190 = (int)off_84001B4 - 20;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_80570CC = (int)off_84001B4 - 12;
  dword_8200190 = (int)off_84001B4 - 12;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_8200070 = 0x88051F1C;
  dword_8200074 = 0x80000000;
  dword_8200078 = 0x8051F1C;
  *(int *)((char *)&dword_820007E + 2) = 67589;
  dword_8200190 = 0x8051F1C;
  dword_84001F4 = (int)&off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = (char *)off_84001B0 - 4;
  dword_84001F4 = (int)off_84001B0;
  *(_DWORD *)*(&off_84001F0 + 1) = dword_8200190;
  dword_8600254 = (int (*)(void))strcmp;
  return sub_8049090();
}

调着看汇编的话会发现还有很多重复的部分,重点在于每一步抓住输入量参与了什么运算,整个代码已经比较清晰了。

整个逻辑主要有三步:

  1. 通过地址相加实现加法(lea edx, [eax+ecx])

https://bbs.xdsec.org/assets/files/2023-07-02/1688305542-483108-2023-07-02-21-23-13-image.png

  1. xor进行异或

https://bbs.xdsec.org/assets/files/2023-07-02/1688305610-124853-2023-07-02-21-26-07-image.png

  1. strcmp进行比较

这里看右下角栈窗口也能清楚地看到比较的参数。
https://bbs.xdsec.org/assets/files/2023-07-02/1688305646-44376-2023-07-02-21-27-16-image.png

整个的逻辑就是每一位check(input[i] + 0x18) ^ 0x19 == enc[i],逆过来解密即可。

脚本
1
2
3
4
enc = [0x67, 0x9D, 0x60, 0x66, 0x8A, 0x56, 0x49, 0x50, 0x65, 0x65, 0x60, 0x55, 0x64, 0x5C, 0x65, 0x48, 0x50, 0x51, 0x5C, 0x55, 0x67, 0x51, 0x57, 0x5C, 0x49, 0x67, 0x54, 0x63, 0x5C, 0x54, 0x62, 0x52, 0x56, 0x54, 0x54, 0x50, 0x49, 0x53, 0x52, 0x52, 0x56, 0x8C, 0xD4, 0xF7]
for i in enc:
    print(chr((i ^ 0x19) - 0x18), end = '')
# flag{781dda4e-d910-4f06-8f5b-5c3755182337}
2-hook strcmp + 爆破
LD_PRELOAD hook

LD_PRELOAD是linux系统下的一个环境变量,对于动态链接程序,LD_PRELOAD指定的动态链接库中的符号会先于标准库或其他库被使用。

简单来说就是可以自定义函数替代程序中使用的外部库中的函数,达到hook的效果。

这题由于是逐字节加密逐字节比较,故可以hook掉strcmp,加上一些输出,再通过输出判断输入正确的字符数,爆破即可。

脚本

hook.c定义strcmp函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
#include<stdio.h>
int cnt = -1;
int strcmp(char *str1, char *str2) {
    cnt++;
    if (*str1 != *str2) {
        printf("%d", cnt);
        return 1;
    } else {
        return 0;
    }
}

编译成so文件:

1
gcc -fPIC --shared hook.c -o hook.so

爆破脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *
context.log_level = 'error'
flag = b''
for i in range(48):
    for j in range(33, 126):
        test = (flag + chr(j).encode()).ljust(49, b'a')
        p =process('./moveAside', env={'LD_PRELOAD':'./hook.so'})
        p.recvuntil(b'\n')
        p.sendline(test)
        p.recvuntil(b'\n')
        res = p.recv().decode()
        if res == 'yes!\n':
            flag += chr(j).encode()
            print(flag)
            exit(0)
        else:
            l = eval(res)
        p.close()
        if l > i:
            flag += chr(j).encode()
            print(flag)
            break
# flag{781dda4e-d910-4f06-8f5b-5c3755182337}0×00 CISCN 2023 moveAside

0×01 陕西省省赛 2023 WebAssembly

考验你的耐心
附件:wasm.zip

WriteUp

关于wasm的逆向,一般来说有三条路:

  • 使用wabt工具包中的wasm2c将wasm转化成C,再拿gcc编译成二进制,拖进ida分析
  • jeb等直接支持wasm反编译的软件
  • 浏览器调试

也可以结合使用,前两个用来静态分析,看懂大体逻辑。第三个用来动态分析,确定一些特殊变量的值/不清楚的逻辑。

第一个的效果很多时候不如第二个,调试一般比较麻烦。

这道题这里主要使用jeb进行静态分析。拖进jeb,main函数还很清晰。

 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
int __f9() {
    int v0 = __g0 - 176;

    __g0 -= 176;
    *(int*)(v0 + 172) = 0;
    *(char*)(v0 + 168) = a114_514_[&gvar_8];
    *(long long*)(v0 + 160) = *(long long*)&a114_514_[0];
    __f11(65, "91fba5ccfef6e0905eeeb47940d25543c286b10de778fbb268ab7580414c0758", v0 + &gvar_10);
    __f12(0, 0x10101);
    __f12(0, "寻思苦久,小明发现了一个加密后的字符串:91fba5ccfef6e0905eeeb47940d25543c286b10de778fbb268ab7580414c0758\n");
    __f12(0, 66533);
    __f12(0, "你可以找到登录的密码吗,打开那扇神秘的大门!\n\n你的密码是:\n");
    *(int*)v0 = v0 + 96;
    __f13(v0, 0x1005a);
    int v1 = __f8(v0 + &gvar_10, v0 + 96, v0 + 160);
    if(v1 == 0) {
        __f12(0, "看起来密码不对? 再试试看吧 ~ ");
    }
    else {
        __f12(0, "WoW,你的Flag是你的输入!");
    }

    __g0 = v0 + 176;
    return 0;
}

主要加密在__f8函数中:

 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
int __f8(int param0, int param1, int param2) {
    int* ptr0 = __g0 - &gvar_1C;

    __g0 -= &gvar_1C;
    *(ptr0 + 26) = param0;
    *(ptr0 + 25) = param1;
    *(ptr0 + &gvar_18) = param2;
    *(char*)(ptr0 + &gvar_14) = a123456789abcdef[15];
    *(long long*)(ptr0 + 18) = *(long long*)&a123456789abcdef[7];
    *(long long*)(ptr0 + &gvar_10) = *(long long*)"0123456789abcdef";
    int v0 = __f17(*(ptr0 + 26));
    if((unsigned int)v0 < &gvar_8) {
        *(ptr0 + 27) = 0;
    }
    else {
        *(ptr0 + 15) = 0;
        *(ptr0 + 14) = 0;
        while(1) {
            int v1 = *(ptr0 + 14);
            int v2 = __f17(*(ptr0 + 25));
            if(v1 >= (unsigned int)v2) {
                break;
            }
            else {
                *(long long*)(ptr0 + 10) = 0L;
                *(long long*)(ptr0 + &gvar_8) = 0L;
                *(long long*)(ptr0 + 6) = 0L;
                *(long long*)(ptr0 + &gvar_4) = 0L;
                *(ptr0 + 3) = 0;
                while(*(ptr0 + 3) < &gvar_8) {
                    *((int*)(*(ptr0 + 3) * &gvar_4 + (int)ptr0) + &gvar_4) = (unsigned int)(unsigned char)((int)*(char*)(*(ptr0 + 3) + *(ptr0 + 14) + *(ptr0 + 25)) ^ (int)*(char*)(*(ptr0 + 3) + *(ptr0 + 26)));
                    *(ptr0 + 3) = *(ptr0 + 3) + 1;
                }
                *(ptr0 + 2) = 0;
                while(*(ptr0 + 2) < 114) {
                    __f7(3, 2, 1, 0, (int)(ptr0 + &gvar_4));
                    __f7(7, 6, 5, &gvar_4, (int)(ptr0 + &gvar_4));
                    __f7(5, &gvar_4, 1, 0, (int)(ptr0 + &gvar_4));
                    __f7(7, 6, 3, 2, (int)(ptr0 + &gvar_4));
                    *(ptr0 + 2) = *(ptr0 + 2) + 1;
                }
                *(ptr0 + 1) = 0;
                while(*(ptr0 + 1) < &gvar_8) {
                    *(ptr0 + 15) = (!*(ptr0 + 15) ? (unsigned int)((int)*(char*)((int*)(*((int*)(*(ptr0 + 1) * &gvar_4 + (int)ptr0) + &gvar_4) / &gvar_10 + (int)ptr0) + &gvar_10) == (int)*(char*)((*(ptr0 + 1) + *(ptr0 + 14)) * 2 + (int)*(unsigned int*)(ptr0 + &gvar_18))): 1) & 0x1;
                    *(ptr0 + 15) = (!*(ptr0 + 15) ? (unsigned int)((int)*(char*)((char*)((*(ptr0 + 1) + *(ptr0 + 14)) * 2 + (int)*(unsigned int*)(ptr0 + &gvar_18)) + 1) == (int)*(char*)((int*)(*((int*)(*(ptr0 + 1) * &gvar_4 + (int)ptr0) + &gvar_4) % &gvar_10 + (int)ptr0) + &gvar_10)): 1) & 0x1;
                    *(ptr0 + 1) = *(ptr0 + 1) + 1;
                }
                *(ptr0 + 14) = *(ptr0 + 14) + &gvar_8;
            }
        }
        *(ptr0 + 27) = *(ptr0 + 15);
    }

    int result = *(ptr0 + 27);
    __g0 = ptr0 + &gvar_1C;
    return result;
}

有点丑,但是能看。

flag共32位,key114!514!,enc91fba5ccfef6e0905eeeb47940d25543c286b10de778fbb268ab7580414c0758 整个加密逻辑如下:

  • 每次取8位,第i位异或上key[i%8]
1
2
3
4
while(*(ptr0 + 3) < &gvar_8) {
                    *((int*)(*(ptr0 + 3) * &gvar_4 + (int)ptr0) + &gvar_4) = (unsigned int)(unsigned char)((int)*(char*)(*(ptr0 + 3) + *(ptr0 + 14) + *(ptr0 + 25)) ^ (int)*(char*)(*(ptr0 + 3) + *(ptr0 + 26)));
                    *(ptr0 + 3) = *(ptr0 + 3) + 1;
                }
  • 取出的8位进行114轮__f7系列加密
1
2
3
4
5
6
7
while(*(ptr0 + 2) < 114) {
                    __f7(3, 2, 1, 0, (int)(ptr0 + &gvar_4));
                    __f7(7, 6, 5, &gvar_4, (int)(ptr0 + &gvar_4));
                    __f7(5, &gvar_4, 1, 0, (int)(ptr0 + &gvar_4));
                    __f7(7, 6, 3, 2, (int)(ptr0 + &gvar_4));
                    *(ptr0 + 2) = *(ptr0 + 2) + 1;
                }
  • 将结果转换为16进制字符串
1
2
3
4
5
while(*(ptr0 + 1) < &gvar_8) {
                    *(ptr0 + 15) = (!*(ptr0 + 15) ? (unsigned int)((int)*(char*)((int*)(*((int*)(*(ptr0 + 1) * &gvar_4 + (int)ptr0) + &gvar_4) / &gvar_10 + (int)ptr0) + &gvar_10) == (int)*(char*)((*(ptr0 + 1) + *(ptr0 + 14)) * 2 + (int)*(unsigned int*)(ptr0 + &gvar_18))): 1) & 0x1;
                    *(ptr0 + 15) = (!*(ptr0 + 15) ? (unsigned int)((int)*(char*)((char*)((*(ptr0 + 1) + *(ptr0 + 14)) * 2 + (int)*(unsigned int*)(ptr0 + &gvar_18)) + 1) == (int)*(char*)((int*)(*((int*)(*(ptr0 + 1) * &gvar_4 + (int)ptr0) + &gvar_4) % &gvar_10 + (int)ptr0) + &gvar_10)): 1) & 0x1;
                    *(ptr0 + 1) = *(ptr0 + 1) + 1;
                }

最难看的在__f7函数:

 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
void __f7(int param0, int param1, int param2, int param3, int param4) {
    int* ptr0 = __g0 - &gvar_8;

    *(ptr0 + 7) = param0;
    *(ptr0 + 6) = param1;
    *(ptr0 + 5) = param2;
    *(ptr0 + &gvar_4) = param3;
    *(ptr0 + 3) = param4;
    int* ptr1 = (int*)(*(ptr0 + 3) * &gvar_4 + *(ptr0 + 7));
    *ptr1 = (*(int*)(*(ptr0 + &gvar_4) * &gvar_4 + (int)*(unsigned int*)(ptr0 + 7)) + *(int*)(*(ptr0 + 6) * &gvar_4 + (int)*(unsigned int*)(ptr0 + 7))) ^ *ptr1;
    int* ptr2 = (int*)(*(ptr0 + 3) * &gvar_4 + *(ptr0 + 7));
    *ptr2 = (unsigned int)(unsigned char)*ptr2;
    int* ptr3 = (int*)(*(ptr0 + &gvar_4) * &gvar_4 + *(ptr0 + 7));
    *ptr3 = (*(int*)(*(ptr0 + 5) * &gvar_4 + (int)*(unsigned int*)(ptr0 + 7)) + *(int*)(*(ptr0 + 6) * &gvar_4 + (int)*(unsigned int*)(ptr0 + 7))) ^ *ptr3;
    int* ptr4 = (int*)(*(ptr0 + &gvar_4) * &gvar_4 + *(ptr0 + 7));
    *ptr4 = (unsigned int)(unsigned char)*ptr4;
    int* ptr5 = (int*)(*(ptr0 + 5) * &gvar_4 + *(ptr0 + 7));
    *ptr5 = (*(int*)(*(ptr0 + 3) * &gvar_4 + (int)*(unsigned int*)(ptr0 + 7)) + *(int*)(*(ptr0 + &gvar_4) * &gvar_4 + (int)*(unsigned int*)(ptr0 + 7))) ^ *ptr5;
    int* ptr6 = (int*)(*(ptr0 + 5) * &gvar_4 + *(ptr0 + 7));
    *ptr6 = (unsigned int)(unsigned char)*ptr6;
    int* ptr7 = (int*)(*(ptr0 + 6) * &gvar_4 + *(ptr0 + 7));
    *ptr7 = (*(int*)(*(ptr0 + 3) * &gvar_4 + (int)*(unsigned int*)(ptr0 + 7)) + *(int*)(*(ptr0 + 5) * &gvar_4 + (int)*(unsigned int*)(ptr0 + 7))) ^ *ptr7;
    int* ptr8 = (int*)(*(ptr0 + 6) * &gvar_4 + *(ptr0 + 7));
    *ptr8 = (unsigned int)(unsigned char)*ptr8;
}

最好是把这个函数复制出来,在编辑器比如vscode中做一些变量/字符串替换,会比在jeb里硬盯着看舒服一点。

另外一个特征应该注意到整个函数是非常规整的,基本可以确定是具有对称性的算法,于是在不确定的点上就可以大胆猜测。最终长这样:

1
2
3
4
5
6
7
8
9
def cal(a0, a1, a2, a3, a):
    a[a0] ^= a[a3] + a[a1]
    a[a0] &= 0xff
    a[a1] ^= a[a3] + a[a2]
    a[a1] &= 0xff
    a[a2] ^= a[a0] + a[a1]
    a[a2] &= 0xff
    a[a3] ^= a[a0] + a[a2]
    a[a3] &= 0xff

最终脚本:

 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
enc = "91fba5ccfef6e0905eeeb47940d25543c286b10de778fbb268ab7580414c0758"
key = "114!514!"
enc = bytes.fromhex(enc)
aa = list(enc)

def cal(a0, a1, a2, a3, a):
    a[a0] ^= a[a3] + a[a1]
    a[a0] &= 0xff
    a[a1] ^= a[a3] + a[a2]
    a[a1] &= 0xff
    a[a2] ^= a[a0] + a[a1]
    a[a2] &= 0xff
    a[a3] ^= a[a0] + a[a2]
    a[a3] &= 0xff
    return a
def dec(a):
    for i in range(114):
        a = cal(2,3,6,7, a)
        a = cal(0,1,4,5, a)
        a = cal(4,5,6,7, a)
        a = cal(0,1,2,3, a)
    return a

aa[0:8] = dec(aa[0:8])
aa[8:16] = dec(aa[8:16])
aa[16:24] = dec(aa[16:24])
aa[24:32] = dec(aa[24:32])

for i in range(32):
    aa[i] ^= ord(key[i%8])
for i in range(32):
    print(chr(aa[i]), end='')
# flag{Y0u_Kn0w_W45M_n0w!!W0oO0ow}

第四周

0×00 强网拟态2023决赛 movemove

上周的方法你学会了吗?
进阶:程序是如何实现cmp,对比加密明文和密文的?

附件:https://pan.baidu.com/s/1b2U_hawqIoBv8RSCBiHybA?pwd=y9d6

WriteUp

放这题主要是想让大家加深一下对mov混淆的记忆,以及进阶来考验一下大伙的调试能力和耐心。

但由于上周写wp的时候提到了这题,大家都先入为主地认为一个异或有什么好调的,于是都没有细看。

上周那题最后加密输入和密文的比较直接调用了strcmp函数,但这道题实际是进行了巧妙的计算来判断两个值是否相等。

调试前最好还是先demov一下,我们先从后往前理清最终需要什么样的条件才能check通过。

最后一段判断的代码:

 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
.text:08049823 mov     edx, [edx+eax*4]
.text:08049826 mov     edx, [edx]
.text:08049828 mov     byte ptr dword_81F4B58, dl
.text:0804982E mov     eax, dword_81F4B50
.text:08049833 mov     edx, 1
.text:08049838 nop
.text:08049839 mov     eax, eax
.text:0804983B and     eax, edx
.text:0804983D nop
.text:0804983E nop
.text:0804983F nop
.text:08049840 nop
.text:08049841 nop
.text:08049842 nop
.text:08049843 mov     b0, eax
.text:08049848 mov     eax, b0
.text:0804984D mov     eax, sel_target[eax*4]
.text:08049854 mov     edx, branch_temp
.text:0804985A mov     [eax], edx
.text:0804985C mov     ecx, b0
.text:08049862 mov     data_p, offset jmp_r0
.text:0804986C mov     eax, sel_data[ecx*4]
.text:08049873 mov     edx, R0
.text:08049879 mov     [eax], edx
.text:0804987B mov     edx, R1
.text:08049881 mov     [eax+4], edx
.text:08049884 mov     edx, R2
.text:0804988A mov     [eax+8], edx
.text:0804988D mov     edx, R3
.text:08049893 mov     [eax+0Ch], edx
.text:08049896 mov     data_p, offset jmp_f0
.text:080498A0 mov     eax, sel_data[ecx*4]
.text:080498A7 mov     edx, F0
.text:080498AD mov     [eax], edx
.text:080498AF mov     edx, F1
.text:080498B5 mov     [eax+4], edx
.text:080498B8 mov     data_p, offset jmp_d0
.text:080498C2 mov     eax, sel_data[ecx*4]
.text:080498C9 mov     edx, D0
.text:080498CF mov     [eax], edx
.text:080498D1 mov     edx, dword_804BAD4
.text:080498D7 mov     [eax+4], edx
.text:080498DA mov     edx, D1
.text:080498E0 mov     [eax+8], edx
.text:080498E3 mov     edx, dword_804BADC
.text:080498E9 mov     [eax+0Ch], edx
.text:080498EC mov     eax, b0
.text:080498F1 test    eax, eax
.text:080498F3 nop
.text:080498F4 nop
.text:080498F5 nop
.text:080498F6 nop
.text:080498F7 nop
.text:080498F8 jnz     loc_8049D6A
.text:080498F8
.text:080498FE mov     eax, offset aError              ; "error!"

可以锁定eax,至少需要满足eax最低一个比特位为1(其实就是等于1,后面可以确定),才会不往下赋值error,eax的值来自于dword_81F4B50。

追查dword_81F4B50,它是在这块被赋值的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
.text:080497AB mov     eax, 0
.text:080497B0 mov     edx, 0
.text:080497B5 mov     dl, byte ptr alu_s
.text:080497BB mov     al, alu_true[edx+eax]
.text:080497C2 mov     dl, byte ptr alu_s+1
.text:080497C8 mov     al, alu_true[edx+eax]
.text:080497CF mov     dl, byte ptr alu_s+2
.text:080497D5 mov     al, alu_true[edx+eax]
.text:080497DC mov     dl, byte ptr alu_s+3
.text:080497E2 mov     al, alu_true[edx+eax]
.text:080497E9 mov     al, alu_false[eax]
.text:080497EF mov     byte ptr dword_81F4B50, al
.text:080497F4 mov     edx, offset alu_cmp_of
.text:080497F9 mov     al, byte ptr alu_x+3
.text:080497FE mov     eax, eax

dword_81F4B50的值来自于al,al来自于alu_false数组(第一项是1,后面全0),想要dword_81F4B50为1,al就要为1,运行到080497E9时eax就要是0,继续向上推,可推出alu_s必须为0

alu_s的赋值来自于这块:

 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
.text:08049672 lea     edx, [edx+ecx]
.text:08049675 mov     word ptr alu_s+2, dx
.text:0804967C mov     alu_c, edx
.text:08049682 mov     eax, alu_s
.text:08049687 mov     R3, eax
.text:0804968C mov     eax, R3
.text:08049691 mov     edx, 1
.text:08049696 nop
.text:08049697 mov     data_p, eax
.text:0804969C mov     eax, sel_data[edx*4]
.text:080496A3 mov     edx, 0
.text:080496A8 mov     dl, [eax]
.text:080496AA mov     R0, edx
.text:080496B0 mov     edx, 0
.text:080496B5 mov     dl, byte ptr R0
.text:080496BB mov     edx, alu_sex8[edx*4]
.text:080496C2 mov     R3, edx
.text:080496C8 mov     eax, R2
.text:080496CD mov     edx, R3
.text:080496D3 mov     ecx, 88049B90h
.text:080496D8 mov     branch_temp, ecx
.text:080496DE mov     alu_x, eax
.text:080496E3 mov     alu_y, edx
.text:080496E9 mov     alu_t, edx
.text:080496EF mov     eax, 0
.text:080496F4 mov     ecx, 0
.text:080496F9 mov     alu_c, 1
.text:08049703 mov     ax, word ptr alu_x
.text:08049709 mov     cx, word ptr alu_y
.text:08049710 mov     ecx, ecx
.text:08049712 not     cx
.text:08049715 nop
.text:08049716 nop
.text:08049717 nop
.text:08049718 nop
.text:08049719 nop
.text:0804971A nop
.text:0804971B nop
.text:0804971C nop
.text:0804971D nop
.text:0804971E nop
.text:0804971F lea     edx, [eax+ecx]
.text:08049722 mov     edx, alu_add16[edx*4]
.text:08049729 mov     ecx, alu_c
.text:0804972F mov     edx, [edx+ecx*4]
.text:08049732 mov     word ptr alu_s, dx

这其实就是输入与密文计算得到的结果,具体如何计算,我们从头开始,把整个调试过程中的对数据的操作梳理一遍:

  • 输入异或0×37
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
.text:080494B7 mov     data_p, eax
.text:080494BC mov     eax, sel_data[edx*4]
.text:080494C3 mov     edx, 0
.text:080494C8 mov     dl, [eax]
.text:080494CA mov     R0, edx
.text:080494D0 mov     edx, 0
.text:080494D5 mov     dl, byte ptr R0
.text:080494DB mov     edx, alu_sex8[edx*4]
.text:080494E2 mov     R2, edx
.text:080494E8 mov     R1, 37h ; '7'
.text:080494F2 mov     eax, R2
.text:080494F7 mov     edx, R1
.text:080494FD mov     alu_x, eax
.text:08049502 mov     alu_y, edx
.text:08049508 mov     eax, 0
.text:0804950D mov     edx, 0
.text:08049512 mov     al, byte ptr alu_x
.text:08049517 mov     dl, byte ptr alu_y
.text:0804951D mov     eax, eax
.text:0804951F xor     eax, edx
  • 密文取反
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.text:08049697 mov     data_p, eax
.text:0804969C mov     eax, sel_data[edx*4]
.text:080496A3 mov     edx, 0
.text:080496A8 mov     dl, [eax]
.text:080496AA mov     R0, edx
.text:080496B0 mov     edx, 0
.text:080496B5 mov     dl, byte ptr R0
.text:080496BB mov     edx, alu_sex8[edx*4]
.text:080496C2 mov     R3, edx
.text:080496C8 mov     eax, R2
.text:080496CD mov     edx, R3
.text:080496D3 mov     ecx, 88049B90h
.text:080496D8 mov     branch_temp, ecx
.text:080496DE mov     alu_x, eax
.text:080496E3 mov     alu_y, edx
.text:080496E9 mov     alu_t, edx
.text:080496EF mov     eax, 0
.text:080496F4 mov     ecx, 0
.text:080496F9 mov     alu_c, 1
.text:08049703 mov     ax, word ptr alu_x
.text:08049709 mov     cx, word ptr alu_y
.text:08049710 mov     ecx, ecx
.text:08049712 not     cx
  • 前两步结果相加再加1存入alu_s
1
2
3
4
5
.text:0804971F lea     edx, [eax+ecx]
.text:08049722 mov     edx, alu_add16[edx*4]
.text:08049729 mov     ecx, alu_c
.text:0804972F mov     edx, [edx+ecx*4]
.text:08049732 mov     word ptr alu_s, dx

故对于每一位输入,check的完整表达式是:

1
(input[i] ^ 0x37 + (~enc[i] & 0xffff) + 1) & 0xffff == 0

由于正数的补码(符号位不变,其余位取反,再加1)为其本身,此处相当于符号位也取反,则结果为原数的相反数,即相加得0。故上式可化为:

1
input[i] ^ 0x37 == enc[i]

于是就只剩下一个异或了。

0×01 CatCTF 2022 CatFly

猫猫会告诉你答案!
hint:程序没有输入,跑起来经过足够长时间猫猫上面的乱码就会变成flag。
hint2:抄算法跑一分钟内出flag,flag格式:CatCTF{}

附件: https://pan.baidu.com/s/1WC13f_cETvr141NFF6JMaA?pwd=822n

WriteUp

这种题也算是挺新颖的,不过刚看见可能有点懵。

程序跑起来猫猫上面会有一串乱码,时间足够长就会变成flag。做题思路就是找到打印上面这串乱码的逻辑,抄下来自己跑,直到开头符合flag的格式即可。

注意中间不要有输出,要不然会慢很多。

放一个之前写的wp,粘一个最终脚本:

 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
#include <stdio.h>
#include <string.h>

unsigned int key = 0x1106;
unsigned int cal_key() {
    key = (0x41C64E6D * key + 12345) & 0xffffffff;
    return (((int)key >> 10) & 0x7fff);
}

int cal_digit(int num) {
    int digit = 0;
    while(num) {
        num /= 10;
        digit++;
    }
    return digit;
}

int main() {
    int cnt = 0;
    unsigned char flag[50];
    unsigned int enc[] = {
        0x27FB, 0x27A4, 0x464E, 0x0E36, 0x7B70, 0x5E7A, 0x1A4A, 0x45C1,
        0x2BDF, 0x23BD, 0x3A15, 0x5B83, 0x1E15, 0x5367, 0x50B8, 0x20CA,
        0x41F5, 0x57D1, 0x7750, 0x2ADF, 0x11F8, 0x09BB, 0x5724, 0x7374,
        0x3CE6, 0x646E, 0x010C, 0x6E10, 0x64F4, 0x3263, 0x3137, 0x00B8,
        0x229C, 0x7BCD, 0x73BD, 0x480C, 0x14DB, 0x68B9, 0x5C8A, 0x1B61,
        0x6C59, 0x5707, 0x09E6, 0x1FB9, 0x2AD3, 0x76D4, 0x3113, 0x7C7E,
        0x11E0, 0x6C70};
    while(1) {
        for (int i = 0; i < 50; i++) {
            enc[i] ^= cal_key(key);
            if (((enc[i] & 127) > 32) && ((enc[i] & 127) <= 126)) {
                flag[i] = enc[i] & 127;
            } else {
                flag[i] = ' ';
            }
        }
        if (!strncmp(flag, "CatCTF", 6)) {
            puts(flag);
            return 0;
        }
        cnt++;
        key += 41;    // key要加上printf的返回值
        key += cal_digit(cnt);
    }
}

第五周

还热乎的巅峰极客2023

0×00 g0Re

神必的OKXX到底是什么?

附件: https://pan.baidu.com/s/1nXY9lKvoc3pVtHwZXCbe1g?pwd=q5b6

WriteUp

这道题主要点在upx魔改,但是很简单,只要将OKXX改成UPX!即可正常upx -d

剩下的逻辑打开main函数就很纯粹了,正常逆了就行,贴个脚本:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
import base64

enc = [0xE6, 0xCE, 0x89, 0xC8, 0xCF, 0xC5, 0xF5, 0xC9, 0xD2, 0xD9, 0xC0, 0x91, 0xCE, 0x7F, 0xAC, 0xCC, 0xE9, 0xCF, 0xB7, 0xC0, 0x96, 0xD4, 0xEA, 0x92, 0xE2, 0xD7, 0xDF, 0x84, 0xCB, 0xA5, 0xAE, 0x93, 0xA6, 0xCA, 0xBE, 0x97, 0xDF, 0xCE, 0xF0, 0xC9, 0xB7, 0xE1, 0xAE, 0x6B, 0xC4, 0xB1, 0x65, 0xDB, 0xCE, 0xED, 0x92, 0x93, 0xD6, 0x8C, 0xED, 0xC3, 0xA3, 0xDA, 0x94, 0xA5, 0xAA, 0xB2, 0xB5, 0xA7]

key = [ord(i) for i in "wvgitbygwbk2b46d"]
new_table = "456789}#IJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123ABCDEFGH"
stadard_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
table = str.maketrans(new_table, stadard_table)
inp = [0] * 64

for i in range(64):
    inp[i] = (enc[i] - key[i%16]) ^ 0x1a
enc = ""
for i in inp:
    enc += chr(i)
print(enc)
enc = base64.b64decode(enc.translate(table))

for i in enc:
    print(hex(i), end=', ')

0×01 m1_read

dx完全找不到加密在哪https://bbs.xdsec.org/twemoji/assets/svg/1f62d.svg

附件: https://pan.baidu.com/s/12YGN84CaTo9QduaYc_eO1Q?pwd=3ji5

WriteUp

白盒AES识别

这是这道题首要的问题,如果你逆过相关算法肯定能很快地反应过来,如果没见过那就只能根据零星的特征进行猜测了。

定位加密逻辑位置

面对一个无从下手的二进制文件,这都是我们通常的第一步。一般都是通过关键字符串/函数进行交叉引用,或者对于很多库函数的程序,需要能辨别出来哪些是出题人写的哪些是库函数,这就需要经验的积累了。

这道题根据题目得知是写卡程序,直接可以定位SCardTransmit这个函数,再交叉引用结合整个函数进行分析,可以大致猜测加密就在sub_140004BF0函数:

findcrypt查常量

这道题使用findcrypt插件可以直接查出来一堆AES的S盒。AES的S盒是精心设计的,一般来说,不会被魔改,能直接找到S盒,多半也可以确定是和AES有关。(这里要注意有些程序直接把一整个第三方加密库静态编译到程序中,实际并没有使用到,注意甄别)

分析加密函数内容
  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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
void __fastcall sub_140004BF0(__m128i *a1, __m128i *a2)
{
  __int8 *v2; // rdi
  __int64 v3; // r13
  __int64 v4; // r12
  __m128i *v5; // rbx
  __int8 *v6; // rbx
  unsigned int v7; // ebp
  unsigned int v8; // edi
  unsigned int v9; // r14d
  unsigned int v10; // esi
  __int64 v11; // r11
  __int64 v12; // r10
  __int64 v13; // r9
  __int64 v14; // rax
  unsigned int v15; // edi
  __int64 v16; // rdx
  __m128i *v17; // rcx
  __int8 v18; // al
  __int64 v21; // [rsp+70h] [rbp+18h]

  v2 = &a1->m128i_i8[2];
  v3 = 0i64;
  v4 = 0i64;
  v5 = a1;
  do
  {
    sub_140005430(v5);
    v6 = v2;
    v21 = 4i64;
    do
    {
      v7 = dword_1400A6000[v4 + (unsigned __int8)*(v6 - 2)];
      v8 = dword_1400A6400[v4 + (unsigned __int8)*(v6 - 1)];
      v9 = dword_1400A6800[v4 + (unsigned __int8)*v6];
      v10 = dword_1400A6C00[v4 + (unsigned __int8)v6[1]];
      v11 = (unsigned __int8)(byte_1400F2000[16 * v3
                                           + 1280
                                           + 16
                                           * byte_1400F2000[16 * v3 + 512 + 16 * (HIBYTE(v7) & 0xF) + (HIBYTE(v8) & 0xF)]
                                           + byte_1400F2000[16 * v3
                                                          + 768
                                                          + 16 * (HIBYTE(v9) & 0xF)
                                                          + (HIBYTE(v10) & 0xF)]] | (16
                                                                                   * byte_1400F2000[16 * v3 + 1024 + 16 * byte_1400F2000[16 * v3 + 16 * ((unsigned __int64)v7 >> 28) + ((unsigned __int64)v8 >> 28)] + byte_1400F2000[16 * v3 + 256 + 16 * ((unsigned __int64)v9 >> 28) + ((unsigned __int64)v10 >> 28)]]));
      *(v6 - 2) = v11;
      v12 = (unsigned __int8)(byte_1400F2000[16 * v3
                                           + 2816
                                           + 16
                                           * byte_1400F2000[16 * v3
                                                          + 2048
                                                          + 16 * (HIWORD(v7) & 0xF)
                                                          + (HIWORD(v8) & 0xF)]
                                           + byte_1400F2000[16 * v3
                                                          + 2304
                                                          + 16 * (HIWORD(v9) & 0xF)
                                                          + (HIWORD(v10) & 0xF)]] | (16
                                                                                   * byte_1400F2000[16 * v3 + 2560 + 16 * byte_1400F2000[16 * v3 + 1536 + 16 * ((v7 >> 20) & 0xF) + ((v8 >> 20) & 0xF)] + byte_1400F2000[16 * v3 + 1792 + 16 * ((v9 >> 20) & 0xF) + ((v10 >> 20) & 0xF)]]));
      *(v6 - 1) = v12;
      v13 = (unsigned __int8)(byte_1400F2000[16 * v3
                                           + 4352
                                           + 16
                                           * byte_1400F2000[16 * v3 + 3584 + 16 * ((v7 >> 8) & 0xF) + ((v8 >> 8) & 0xF)]
                                           + byte_1400F2000[16 * v3 + 3840 + 16 * ((v9 >> 8) & 0xF) + ((v10 >> 8) & 0xF)]] | (16 * byte_1400F2000[16 * v3 + 4096 + 16 * byte_1400F2000[16 * v3 + 3072 + 16 * ((unsigned __int16)v7 >> 12) + ((unsigned __int16)v8 >> 12)] + byte_1400F2000[16 * v3 + 3328 + 16 * ((unsigned __int16)v9 >> 12) + ((unsigned __int16)v10 >> 12)]]));
      *v6 = v13;
      v14 = (unsigned __int8)(byte_1400F3500[16 * v3
                                           + 512
                                           + 16
                                           * (unsigned __int8)byte_1400F3400[16 * v3 + 16 * (v7 & 0xF) + (v8 & 0xF)]
                                           + byte_1400F3500[16 * v3 + 16 * (v9 & 0xF) + (v10 & 0xF)]] | (16 * byte_1400F3500[16 * v3 + 256 + 16 * byte_1400F2000[16 * v3 + 4608 + 16 * ((unsigned __int8)v7 >> 4) + ((unsigned __int8)v8 >> 4)] + byte_1400F2000[16 * v3 + 4864 + 16 * ((unsigned __int8)v9 >> 4) + ((unsigned __int8)v10 >> 4)]]));
      v6[1] = v14;
      v15 = dword_1400CA000[v4 + v11];
      LODWORD(v11) = dword_1400CA400[v4 + v12];
      LODWORD(v12) = dword_1400CA800[v4 + v13];
      LODWORD(v13) = dword_1400CAC00[v4 + v14];
      *(v6 - 2) = byte_1400F2000[16 * v3
                               + 1280
                               + 16 * byte_1400F2000[16 * v3 + 512 + 16 * (HIBYTE(v15) & 0xF) + (BYTE3(v11) & 0xF)]
                               + byte_1400F2000[16 * v3 + 768 + 16 * (BYTE3(v12) & 0xF) + (BYTE3(v13) & 0xF)]] | (16 * byte_1400F2000[16 * v3 + 1024 + 16 * byte_1400F2000[16 * v3 + 16 * ((unsigned __int64)v15 >> 28) + ((unsigned __int64)(unsigned int)v11 >> 28)] + byte_1400F2000[16 * v3 + 256 + 16 * ((unsigned __int64)(unsigned int)v12 >> 28) + ((unsigned __int64)(unsigned int)v13 >> 28)]]);
      *(v6 - 1) = byte_1400F2000[16 * v3
                               + 2816
                               + 16 * byte_1400F2000[16 * v3 + 2048 + 16 * (HIWORD(v15) & 0xF) + (WORD1(v11) & 0xF)]
                               + byte_1400F2000[16 * v3 + 2304 + 16 * (WORD1(v12) & 0xF) + (WORD1(v13) & 0xF)]] | (16 * byte_1400F2000[16 * v3 + 2560 + 16 * byte_1400F2000[16 * v3 + 1536 + 16 * ((v15 >> 20) & 0xF) + (((unsigned int)v11 >> 20) & 0xF)] + byte_1400F2000[16 * v3 + 1792 + 16 * (((unsigned int)v12 >> 20) & 0xF) + (((unsigned int)v13 >> 20) & 0xF)]]);
      *v6 = byte_1400F2000[16 * v3
                         + 4352
                         + 16
                         * byte_1400F2000[16 * v3 + 3584 + 16 * ((v15 >> 8) & 0xF) + (((unsigned int)v11 >> 8) & 0xF)]
                         + byte_1400F2000[16 * v3
                                        + 3840
                                        + 16 * (((unsigned int)v12 >> 8) & 0xF)
                                        + (((unsigned int)v13 >> 8) & 0xF)]] | (16
                                                                              * byte_1400F2000[16 * v3
                                                                                             + 4096
                                                                                             + 16
                                                                                             * byte_1400F2000[16 * v3 + 3072 + 16 * ((unsigned __int16)v15 >> 12) + ((unsigned __int16)v11 >> 12)]
                                                                                             + byte_1400F2000[16 * v3 + 3328 + 16 * ((unsigned __int16)v12 >> 12) + ((unsigned __int16)v13 >> 12)]]);
      v6[1] = byte_1400F3500[16 * v3
                           + 512
                           + 16 * (unsigned __int8)byte_1400F3400[16 * v3 + 16 * (v15 & 0xF) + (v11 & 0xF)]
                           + byte_1400F3500[16 * v3 + 16 * (v12 & 0xF) + (v13 & 0xF)]] | (16
                                                                                        * byte_1400F3500[16 * v3 + 256 + 16 * byte_1400F2000[16 * v3 + 4608 + 16 * ((unsigned __int8)v15 >> 4) + ((unsigned __int8)v11 >> 4)] + byte_1400F2000[16 * v3 + 4864 + 16 * ((unsigned __int8)v12 >> 4) + ((unsigned __int8)v13 >> 4)]]);
      v4 += 1024i64;
      v6 += 4;
      v3 += 384i64;
      --v21;
    }
    while ( v21 );
    v5 = a1;
    v2 = &a1->m128i_i8[2];
  }
  while ( v4 < 36864 );
  sub_140005430(a1);
  a1->m128i_i8[0] = byte_1400A4000[a1->m128i_u8[0]];
  a1->m128i_i8[1] = byte_1400A4000[a1->m128i_u8[1] + 256];
  a1->m128i_i8[2] = byte_1400A4000[a1->m128i_u8[2] + 512];
  a1->m128i_i8[3] = byte_1400A4000[a1->m128i_u8[3] + 768];
  a1->m128i_i8[4] = byte_1400A4000[a1->m128i_u8[4] + 1024];
  a1->m128i_i8[5] = byte_1400A4000[a1->m128i_u8[5] + 1280];
  a1->m128i_i8[6] = byte_1400A4000[a1->m128i_u8[6] + 1536];
  a1->m128i_i8[7] = byte_1400A4000[a1->m128i_u8[7] + 1792];
  a1->m128i_i8[8] = byte_1400A4000[a1->m128i_u8[8] + 2048];
  a1->m128i_i8[9] = byte_1400A4000[a1->m128i_u8[9] + 2304];
  a1->m128i_i8[10] = byte_1400A4000[a1->m128i_u8[10] + 2560];
  a1->m128i_i8[11] = byte_1400A4000[a1->m128i_u8[11] + 2816];
  a1->m128i_i8[12] = byte_1400A4000[a1->m128i_u8[12] + 3072];
  a1->m128i_i8[13] = byte_1400A4000[a1->m128i_u8[13] + 3328];
  a1->m128i_i8[14] = byte_1400A4000[a1->m128i_u8[14] + 3584];
  a1->m128i_i8[15] = byte_1400A4000[a1->m128i_u8[15] + 3840];
  if ( a2 > (__m128i *)((char *)&a1->m128i_u64[1] + 7) || (__m128i *)((char *)&a2->m128i_u64[1] + 7) < a1 )
  {
    *a2 = _mm_xor_si128(_mm_load_si128((const __m128i *)&xmmword_140008A40), _mm_loadu_si128(a1));
  }
  else
  {
    v16 = 16i64;
    v17 = a2;
    do
    {
      v18 = v17->m128i_i8[(char *)a1 - (char *)a2];
      v17 = (__m128i *)((char *)v17 + 1);
      v17[-1].m128i_i8[15] = v18 ^ 0x66;
      --v16;
    }
    while ( v16 );
  }
}

整个算法复杂度很高,有反复多样的查表替换操作,以及后半部分的异或0×66.

如果这样一个算法让人去逆,那么估计全场零解不在话下。我们第一想法是这玩意应该是某个著名的算法,然后就可以开猜了。

当时直接上传了binary ai,然后它告诉我有百分之七十多的可能是blake哈希,然后我信以为真,以为这是什么数据包的校验哈希之类的,然后就寄了。所以结论就是,别用binary ai,真不行。

赛后测试了一下,你把这块伪代码直接发给gpt3.5,然后告诉他这可能是一种著名的加密算法,它也会猜AES,不得不说,gpt在很多时候还是很有用的。

越是复杂的算法,魔改的可能性就越小,尤其是AES这种,整个算法是精心设计的,稍有更改都可能会导致整个算法解密的不准确性,这道题也不例外。

所以我们的任务就是拿到AES的密钥,即可进行解密。

DFA攻击白盒AES

详细攻击原理有兴趣可以去找点资料看看,这里不涉及密码学原理,只写一下具体流程。

我们要做的无非三步:

  1. 对于给定的明文(控制函数输入参数),在倒数第二轮列混合之前更改密文,构造缺陷数据,重复多次拿到多组错误数据。对于给定的明文,不进行更改拿到正确密文。

  2. 根据正确密文和缺陷密文获取第十轮密钥。

  3. 根据第十轮密钥恢复出初始密钥。

对于第一步,由于这道题无法正常调试,所以需要一些特殊手段执行程序,加密过程中动态修改密文,并获取最终密文。这里有多种方式可以选择,我这里使用qiling、angr、frida均尝试成功,具体脚本可以看这:常用模拟执行、符号执行、插桩hook框架概览与比较

对于第二步,将正确密文和缺陷密文写入tracefile直接使用phoenixAES库即可:

1
2
3
import phoenixAES

phoenixAES.crack_file('tracefile')

输出:

1
2
Last round key #N found:
B4EF5BCB3E92E21123E951CF6F8F188E

对于第三步,还是使用现成工具即可。

最终拿到第一轮密钥,进行解密即可。

第六周

0×00 *CTF 2023 ezcode

PWSH是什么?
附件:https://pan.baidu.com/s/1JvwyLrWtPW3WUCLrEni9FA?pwd=brx6

WriteUp

一眼Powershell,但是加了混淆。其实笔者对这玩意并不了解,在这放一个仅仅能做的方法来抛砖引玉。欢迎了解更多的师傅在下面补充!

思路非常简单,vscode打开直接开调,但是只有一行,不好下断点,故可以在下面加一行echo done或者其他什么东西都行,然后断在这一行,看左边local变量$@*

https://bbs.xdsec.org/assets/files/2023-08-29/1693321296-447994-2023-08-29-22-53-53-image.png

里面一堆[CHar]其实就是ascii码,转换一下就能发现是个python脚本。剩下看着源码逆个算法就不是啥问题了。

0×01 corCTF 2023 crabheartpenguin

KO!!
hint1:一些基础知识推荐阅读 a3的文章
hint2:此题原本有远程环境,现在做这题尝试与内核模块交互拿到test flag即可(搜字符串直接拿flag不算)
附件:https://pan.baidu.com/s/1WF9xuUY6mdJr2ZB9b4BNbg?pwd=4i78

WriteUp

做题之前

内核(通常是内核模块)逆向是有一定门槛的。需要你首先对内核编译、文件系统构建(通常使用busybox)、qemu基本指令有基本的认识,这其实不难,自己多动手就行了。逆向题一般不会涉及多少的内核机制原理,所以难度更大的题还是在二进制本身做文章,比如这道题使用rust编写,本身就已经很难看了,再整点混淆啥的,估计很可能就0解了。

内核题出题也是有门槛的,所以在国内比赛很少见到。随便写个异或移位拿个不知道哪来的编译器一编译丢给选手嗯看不是更爽(x)。但是在国际赛事中还是能经常看到的,最近的例子比如D3CTF 2023, corctf 2023,以及上周末刚打完的project sekai ctf都有出现。

说到这里,其实我觉得你应该更认真地思考一下要不要继续尝试这类题。首先,能接触到这类题说明你的水平已经不低了,你应该发现,逆向这个分类底下还可以分很多个方向,每个方向都足够你研究大学四年。你应该有大致的认知你对哪方面更感兴趣、更想研究下去,想要做内核题你首先就要在环境配置上花费很多时间,有没有必要花这个时间你应该有所权衡。希望你继续尝试下去的理由不仅仅是ctf里会有这种题

接下来开始正题,一些最基本的东西你都可以在a3的blog中学到,遇到问题多问搜索引擎/AI就好,这里不再赘述。

静态分析

第一次看这种题,最简单的办法就是顺着函数列表挨个点一遍,相比于常规逆向题,内核逆向中的函数数量一般少得多。

注意两个名字最正常的两个函数init_modulecleanup_module,这是内核模块的初始化函数和清理函数,所以init_module可以看作是main函数,是整个程序的入口点。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
__int64 init_module()
{
  __int64 rbx; // rbx
  int error[2]; // [rsp+8h] [rbp-18h] BYREF
  __int64 module; // [rsp+10h] [rbp-10h]

  RNvXs_Cs6tAWYO0dm9x_4crabNtB4_7CrabDevNtCs8YfjL7j61RY_6kernel6Module4init(
    (__int64)error,
    (__int64)"crab",
    5LL,
    (__int64)&RNvCs6tAWYO0dm9x_4crab11THIS_MODULE);
  if ( error[0] )
    return RNvMNtCs8YfjL7j61RY_6kernel5errorNtB2_5Error15to_kernel_errno((unsigned int)error[1]);
  rbx = module;
  if ( RNvCs6tAWYO0dm9x_4crab5___MOD_0 )
  {
    RINvNtCsfDBl8rBLEEc_4core3ptr13drop_in_placeINtNtCs8YfjL7j61RY_6kernel6chrdev12RegistrationKj1_EECs6tAWYO0dm9x_4crab(RNvCs6tAWYO0dm9x_4crab5___MOD_0);
    _rust_dealloc(RNvCs6tAWYO0dm9x_4crab5___MOD_0, 64LL, 8LL);
  }
  RNvCs6tAWYO0dm9x_4crab5___MOD_0 = rbx;
  return 0LL;
}

可以看到,首先调用了crabDev_kernelmoduleinit这个函数初始化了crab模块,下面就是一些错误处理和rust的drop_in_place,忽略即可。

  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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
__int64 __fastcall RNvXs_Cs6tAWYO0dm9x_4crabNtB4_7CrabDevNtCs8YfjL7j61RY_6kernel6Module4init(        __int64 a1,        __int64 a2,        __int64 a3,        __int64 a4)
{
  _QWORD *v4; // rax
  __int64 v5; // rbx
  __int64 v6; // rax
  __int64 *v7; // r12
  unsigned int v8; // eax
  int v9; // r15d
  int v10; // eax
  int v11; // edx
  __int64 v13; // rax
  __int64 v14; // rcx
  __int64 v15; // rdx
  __int64 v16; // rax
  int v17; // edx
  __int64 v18; // [rsp+8h] [rbp-68h] BYREF
  __int64 v19; // [rsp+10h] [rbp-60h] BYREF
  __int64 v20; // [rsp+18h] [rbp-58h]
  __int64 v21; // [rsp+20h] [rbp-50h]
  __int64 v22; // [rsp+28h] [rbp-48h]
  __int64 v23; // [rsp+30h] [rbp-40h]
  __int64 v24; // [rsp+38h] [rbp-38h]
  __int64 v25; // [rsp+40h] [rbp-30h]
  __int64 v26[5]; // [rsp+48h] [rbp-28h] BYREF

  v18 = a2;
  v19 = a3;
  v20 = a4;
  v22 = 2LL;
  LOWORD(v25) = 0;
  v4 = (_QWORD *)_rust_alloc(64LL, 8LL);
  if ( v4 )
  {
    v5 = (__int64)v4;
    v4[7] = v25;
    v4[6] = v24;
    v4[5] = v23;
    v4[4] = v22;
    v4[3] = v21;
    v4[2] = v20;
    v6 = v18;
    *(_QWORD *)(v5 + 8) = v19;
    *(_QWORD *)v5 = v6;
    v7 = (__int64 *)(v5 + 24);
    if ( *(_DWORD *)(v5 + 32) == 2 )
    {
      LODWORD(v18) = 0;
      v8 = alloc_chrdev_region(&v18, *(unsigned __int16 *)(v5 + 56), 1LL, *(_QWORD *)v5);
      if ( v8 )
      {
        v9 = RNvMNtCs8YfjL7j61RY_6kernel5errorNtB2_5Error17from_kernel_errno(v8);
        goto LABEL_13;
      }
      v10 = v18;
      *(_QWORD *)(v5 + 32) = 0LL;
      *v7 = 0LL;
      *(_DWORD *)(v5 + 48) = v10;
    }
    else
    {
      v9 = -22;
      if ( *v7 == 1 )
        goto LABEL_13;
    }
    RNvMNtCs8YfjL7j61RY_6kernel6chrdevNtB2_4Cdev5alloc(&v18, &unk_2260, *(_QWORD *)(v5 + 16));
    if ( (_DWORD)v18 )
    {
      v9 = HIDWORD(v18);
    }
    else
    {
      v26[0] = v19;
      if ( !(unsigned int)RNvMNtCs8YfjL7j61RY_6kernel6chrdevNtB2_4Cdev3add(
                            v26,
                            (unsigned int)(*(_DWORD *)(v5 + 24) + *(_DWORD *)(v5 + 48)),
                            1LL) )
      {
        if ( *v7 )
        {
          RNvNtCsfDBl8rBLEEc_4core9panicking18panic_bounds_check(*v7, 1LL, &off_2098);
          BUG();
        }
        v13 = v26[0];
        v15 = *(_QWORD *)(v5 + 40);
        v18 = *(_QWORD *)(v5 + 32);
        v14 = v18;
        v19 = v15;
        *(_QWORD *)(v5 + 32) = 1LL;
        *(_QWORD *)(v5 + 40) = v13;
        if ( v14 )
        {
          RNvXs_NtCs8YfjL7j61RY_6kernel6chrdevNtB4_4CdevNtNtNtCsfDBl8rBLEEc_4core3ops4drop4Drop4drop(&v19);
          v16 = *v7 + 1;
        }
        else
        {
          v16 = 1LL;
        }
        *v7 = v16;
        LOBYTE(v18) = 0;
        if ( !(unsigned int)RNvNtCs8YfjL7j61RY_6kernel6random9getrandom(&v18, 1LL) )
        {
          *(_QWORD *)(a1 + 8) = v5;
          *(_DWORD *)a1 = 0;
          return a1;
        }
        *(_DWORD *)(a1 + 4) = v17;
        goto LABEL_14;
      }
      v9 = v11;
      RNvXs_NtCs8YfjL7j61RY_6kernel6chrdevNtB4_4CdevNtNtNtCsfDBl8rBLEEc_4core3ops4drop4Drop4drop(v26);
    }
LABEL_13:
    *(_DWORD *)(a1 + 4) = v9;
LABEL_14:
    *(_DWORD *)a1 = 1;
    RINvNtCsfDBl8rBLEEc_4core3ptr13drop_in_placeINtNtCs8YfjL7j61RY_6kernel6chrdev12RegistrationKj1_EECs6tAWYO0dm9x_4crab(v5);
    _rust_dealloc(v5, 64LL, 8LL);
    return a1;
  }
  RINvNtCsfDBl8rBLEEc_4core3ptr13drop_in_placeINtNtCs8YfjL7j61RY_6kernel6chrdev12RegistrationKj1_EECs6tAWYO0dm9x_4crab((__int64)&v18);
  *(_DWORD *)(a1 + 4) = RNvXs0_NtCs8YfjL7j61RY_6kernel5errorNtB5_5ErrorINtNtCsfDBl8rBLEEc_4core7convert4FromNtNtBO_5alloc10AllocErrorE4from();
  *(_DWORD *)a1 = 1;
  return a1;
}

这个函数里要关注RNvMNtCs8YfjL7j61RY_6kernel6chrdevNtB2_4Cdev3add创建了一个字符设备。这一点在成功跑起来qemu虚拟机之后就可以看到/dev/crab

另外RNvMNtCs8YfjL7j61RY_6kernel6chrdevNtB2_4Cdev5alloc这里就是对这个字符型设备注册了一系列回调函数,具体点进去qword_2260就可以看到,包括4个设备读写的回调。当我们与这个字符型设备/dev/crab交互时,就会触发这一系列回调函数。

其中read_iter_callbackread_callback同时存在,是为了兼容不同内核,二者同时存在时,内核会选择一个执行,忽略另一个。write_callback同理,同时我们也可以看出带iter和不带iter的函数功能是完全一致的。

可以发现,在read_callback中,会判断*(_QWORD *)(*a1 + 192LL) + 84这个标志位,为1则将flag写入文件(设备),在write_callback中,会进行一些check的逻辑。

到这里我们可以先尝试与/dev/crab进行交互看看效果(就像我们普通题运行看看一样),用简单的open、read、write就行,由于qemu环境中没有库支持,所以我们这选择写静态编译的C程序,然后重新打包进文件系统,就能在qemu中运行了。

我们会发现它生成了许多emoji表情,准确来说是6种,这就是make_prompt函数干的事了,make_prompt函数调用一次,会生成10个随机的emoji,对应的字符串在0×24B0这个位置。

再看伪代码,我们可以发现最终check输入的逻辑在这块:

1
while ( n == qword_24B8[v27] && !bcmp(s1, (&off_24B0)[v27], n) );

这就很明显了,要保证字符串长度和内容都相同,而off_24B0中都是那些emoji对应的单词,可以猜到我们需要识别程序随机生成的emoji并转化成字符串发回来。知道了大致的逻辑,剩下就是一些细节问题了。

往上看,上面两个switch-case:

 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
if ( *v46 > 128048 )
          {
            switch ( v25 )
            {
              case 128049:
                v26 = 4;
                break;
              case 128056:
                v26 = 3;
                break;
              case 129408:
                v26 = 0;
                break;
              default:
                goto LABEL_47;
            }
          }
          else
          {
            switch ( v25 )
            {
              case 127819:
                v26 = 2;
                break;
              case 128009:
                v26 = 5;
                break;
              case 128039:
                v26 = 1;
                break;
              default:
                goto LABEL_47;
            }
          }
        }

这些case的值其实就是那6个emoji的unicode值,用python直接print(chr(128049))就能看到具体是啥。

上面还有一个重要的点在这:

1
RNvMsr_NtNtCsfDBl8rBLEEc_4core3str7patternNtB5_11StrSearcher3new(&v33[2], v16, v17, &byte_2490, 1LL);

byte_2490是-,实际上,在我们的输入中要用-分割每一个单词。

最后一个也是最重要的点在bcmp前一行:

1
v27 = 2LL * (unsigned __int16)(*v24 + v26 - 3 * (((unsigned __int16)(*v24 + v26) / 3u) & 0xFFFE));

其中*v24是代表轮数的循环变量,如果连续11轮正确的话就会成功,这段逻辑在这:

1
2
3
4
if ( *v24 == 10 )
{
    *(_BYTE *)(v4 + 84) = 1;
}

这里的v4同样是*(_QWORD *)(*a1 + 192LL),和上面read_callback中检查的值相同,故只要这里设为1,就能get flag

v26就是emoji的序号,后面这一串暂且把*v24 + v26看作一个整体,类型转换可有可无,这里省略了,即:

1
(num - 3 * ((num / 3) & 0xfffe)

其实就是num % 6 的逻辑,注意这里&0xfffe的作用。前面再乘2只是寻址需要,与逻辑无关。

总结一下:程序会随机生成11组、每组10个emoji,我们需要读取这些emoji,计算(轮数 + emoji下标)% 6对应的字符串,返回给程序,11轮验证通过程序就能get flag

动态调试

如果你能静态分析得到上面的结论,那么你已经可以动手写exp了,但这实际是很困难的,调试能帮我们简化或者验证猜想的逻辑。但是想要调试内核模块,首先得折腾一番环境。一般会选择qemu+gdb+pwndbg/gef这样的组合,搭建过程推荐阅读这篇文章Kernel pwn CTF 入门 - 1-安全客 - 安全资讯平台),跟着来搭建好环境应该问题不大,遇到特殊问题再去解决。下面主要结合这道题举例说明一下,这篇文章涉及到的内容不会再赘述,请仔细阅读并动手尝试。

特别的,这里推荐一个插件decomp2dbg,可以将ida反编译出的伪代码实时显示在gdb中,同时可以使用gdb命令查看ida中定义的临时/全局变量值,使用ida中自定义的变量/函数符号,十分方便。下面有图示。

启动脚本/init脚本修改

这里的启动脚本指run.sh,init脚本指文件系统根目录下的init脚本。

  1. 为了方便调试,我们最好关闭kaslr(内核地址随机化),即-append "nokaslr"

  2. 为了方便调试,可以选择将init脚本前两行insmod crab; rm crab.ko删去,等qemu启动完成后手动加载内核模块。

  3. 将最后一行gid由1000改为0,使用root登录,防止出现权限问题。即setsid /bin/cttyhack setuidgid 0 /bin/sh

与模块交互时触发断点

这种情况发生在insmod crab之后,加载模块后,在gdb中下断点即可,手动/exp读写crab设备时就会触发断点,在gdb中就可以进行调试了。

比如我们可以断在bcmp处查看比较的值:

下面这可以看到同步过来ida的伪代码(decomp2dbg插件)、栈数据、调用栈信息
https://bbs.xdsec.org/assets/files/2023-08-29/1693321591-47549-2023-08-29-21-00-02-image.png

上面有汇编代码和各种寄存器的值,这里由于vmlinux的符号没恢复,所以只能看到call一个地址,但这不影响分析
https://bbs.xdsec.org/assets/files/2023-08-29/1693321664-17837-2023-08-29-20-59-10-image.png

bcmp的三个参数即在rdi、rsi、rdx中,rdi是我们的输入,rsi是比较的内容,rdx为6是长度,可以说非常清晰了。在其他地方下断点还能拿到更多信息,帮助分析程序逻辑。

在模块初始化函数触发断点

由于在模块加载之前,模块地址还不存在,而一旦模块加载完成,又无法下断点重新断回去。所以这种情况我们只能断在模块被加载入内存之后,模块初始化函数开始执行之前。

a3告诉我的方法是拦截sys_init_module,这个syscall的时机恰好就是我们想要的,但是我尝试发现会报错warning: Error inserting catchpoint 1: Your system does not support this type ,不知道是我的操作有问题还是这题的内核有问题。

后来又发现,模块加载之前会调用一个do_init_module函数,所以我们可以先断在这个函数。如果vmlinux成功恢复符号的话直接break do_init_module就行,但是这道题的内核我使用vmlinux-to-elf解压报错退出了,目前原因不明,故需要手动查一下这个函数的地址,具体命令如下:

1
2
~ # grep do_init_module /proc/kallsyms
ffffffff810c0530 t do_init_module

此时在gdb中break 0xffffffff810c0530,然后手动insmod即可断下来。此时模块代码已被加载入内存中,且我们可以使用add-symbol-file直接在gdb中导入符号信息,所以直接break init_module,再continue即可断在init_module函数中:
https://bbs.xdsec.org/assets/files/2023-08-29/1693321714-429997-2023-08-29-21-30-20-image.png

exp

只要能理解题目逻辑,写exp应该不是啥问题,注意静态编译就行。贴一个在这:

 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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <fcntl.h>

int main() {
    char *names[] = {"crab", "penguin", "lemonthink", "msfrog", "af", "dragon"};
    int fd = open("/dev/crab", O_RDWR);
    if (fd < 0) {
        perror("open /dev/crab error");
        exit(1);
    }
    for (int i = 0; i < 11; i++) {
        char res[100];
        memset(res, 0, 100);
        char emoji[1024];
        memset(emoji, 0, 1024);
        int read_len = read(fd, emoji, 1024);
        if (read_len < 0) {
            perror("read emoji error");
            exit(1);
        }
        puts(emoji);
        for (int j = 0; j < 10; j++) {
            // emoji bytes
            // crab       b'\xf0\x9f\xa6\x80'
            // peguin     b'\xf0\x9f\x90\xa7'
            // lemonthink b'\xf0\x9f\x8d\x8b'
            // msfrog     b'\xf0\x9f\x90\xb8'
            // af         b'\xf0\x9f\x90\xb1'
            // dragon     b'\xf0\x9f\x90\x89'
            int index = -1;
            if (emoji[4*j+3] == '\x80') {
                index = 0;
            } else if (emoji[4*j+3] == '\xa7') {
                index = 1;
            } else if (emoji[4*j+3] == '\x8b') {
                index = 2;
            } else if (emoji[4*j+3] == '\xb8') {
                index = 3;
            } else if (emoji[4*j+3] == '\xb1') {
                index = 4;
            } else if (emoji[4*j+3] == '\x89') {
                index = 5;
            }
            index = (i + index) % 6;
            strcat(res, names[index]);
            strcat(res, "-");
        }
        puts(res);
        write(fd, res, strlen(res));
    }
    char flag[100];
    read(fd, flag, 100);
    puts(flag);
    exit(0);
}

运行效果:
https://bbs.xdsec.org/assets/files/2023-08-29/1693321742-829405-2023-08-29-22-59-13-image.png

第七周

0×00 WMCTF 2023 ezAndroid

AES? AES! AES?!?!
附件: https://pan.baidu.com/s/1uPmYm38lZMroKjqRetXWPQ?pwd=cc8a

WriteUp

java层很简单,输入用户名、密码,分别进行check,两个check函数都是native的,直接看lib即可。

lib加了OLLVM,很难看,但是能看,有x86的,可以直接模拟器开调

先看JNI_OnLoad:

 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
__int64 __fastcall JNI_OnLoad(__int64 a1)
{
  int v1; // edx
  __int64 v3; // [rsp+18h] [rbp-88h]
  int v4; // [rsp+24h] [rbp-7Ch]
  __int64 v5; // [rsp+28h] [rbp-78h]
  unsigned int v6; // [rsp+44h] [rbp-5Ch]
  __int64 v7; // [rsp+50h] [rbp-50h] BYREF
  int v8; // [rsp+58h] [rbp-48h]
  int v9; // [rsp+5Ch] [rbp-44h]
  __int64 s[8]; // [rsp+60h] [rbp-40h] BYREF

  s[7] = __readfsqword(0x28u);
  v8 = -1389402607;
  v7 = 0LL;
  v9 = sub_7FF9545D2AF0(a1, &v7, 65542LL);
  v4 = 532552651;
  do
  {
    while ( 1 )
    {
      while ( v4 >= 532552651 )
      {
        if ( v4 < 1799520010 )
        {
          if ( v4 == 532552651 )
          {
            v1 = v8 + 169071759;
            if ( v9 )
              v1 = v8 - 1106044679;
            v4 = v1;
          }
        }
        else if ( v4 == 1799520010 )
        {
          v6 = -1;
          v4 = v8 + 1586923700;
        }
      }
      if ( v4 >= 197521093 )
        break;
      if ( v4 == -1220330848 )
      {
        v3 = v7;
        sub_7FF9545D3EF0((__int64)aComWmctfEzandr, (__int64)(&byte_7FF9545D5960 + 63));
        v5 = sub_7FF9545D27A0(v3, (__int64)aComWmctfEzandr);
        memset(s, 0, 0x30uLL);
        sub_7FF9545D4340(aCheckusername, &byte_7FF9545D5960 + 275);
        s[0] = (__int64)aCheckusername;
        sub_7FF9545D44B0(aLjavaLangStrin_0, &byte_7FF9545D5960 + 335);
        s[1] = (__int64)aLjavaLangStrin_0;
        s[2] = (__int64)check_username;
        sub_7FF9545D4620(aCheck2, &byte_7FF9545D5960 + 407);
        s[3] = (__int64)aCheck2;
        s[4] = (__int64)aLjavaLangStrin_0;
        s[5] = (__int64)check_pass;
        sub_7FF9545D2B30(v7, v5, s, 2LL);
        v6 = 65542;
        v4 = v8 + 1586923700;
      }
    }
  }
  while ( v4 != 197521093 );
  return v6;
}

这块调试起来看得更清楚,CheckusernameCheck2正是java层调用的函数,对应的内容就是check_username和check_pass

先看check_username

首先会有一个长度的检测,需要username长度等于10:

1
2
3
4
5
6
v36 = strlen(s);
...
v43 = v36;
...
if ( v43 != 10 )
    v3 = v42 + 121267380;

这里会生成字符串12345678,重复32次作为后面RC4的key:

1
sub_7FF9545D3D80(&qword_7FF9545D8230, &byte_7FF9545D5960 + 17);

这里实际是一个魔改RC4的逻辑,魔改的点在于将生成的第i个流密钥异或上i:

1
sub_7FF9545D47D0(v12, v11, v10, (__int64)v13, v41);
  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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
unsigned __int64 __fastcall sub_7FF9545D47D0(char *username, char *a2, int len, __int64 a4, int a5)
{
  int v5; // edx
  int v6; // edx
  int v7; // esi
  int i; // [rsp+10h] [rbp-250h]
  int v10; // [rsp+14h] [rbp-24Ch]
  int j; // [rsp+18h] [rbp-248h]
  int i_1; // [rsp+48h] [rbp-218h]
  char key[256]; // [rsp+50h] [rbp-210h]
  unsigned __int8 sbox[256]; // [rsp+150h] [rbp-110h] BYREF
  unsigned __int64 v19; // [rsp+258h] [rbp-8h]

  v19 = __readfsqword(0x28u);
  j = 0;
  v10 = 0;
  i_1 = 0;
  for ( i = -1585902732; ; i = v7 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( i < 283599289 )
        {
          if ( i < -1585902732 )
          {
            if ( i < -2034379086 )
            {
              ++v10;
              i = 622976386;
            }
            else if ( i < -1811513745 )
            {
              ++i_1;
              i = -1585902732;
            }
            else
            {
              sbox[i_1] = i_1;
              key[i_1] = *(_BYTE *)(a4 + i_1 % a5);
              i = -2034379086;
            }
          }
          else if ( i < -338555951 )
          {
            v5 = 1218004640;
            if ( i_1 < 256 )
              v5 = -1811513745;
            i = v5;
          }
          else if ( i < 109075120 )
          {
            v6 = 1759236594;
            if ( i_1 < 256 )
              v6 = 283599289;
            i = v6;
          }
          else
          {
            ++i_1;
            i = -338555951;
          }
        }
        if ( i < 1218004640 )
          break;
        if ( i < 1333836938 )
        {
          i_1 = 0;
          i = -338555951;
        }
        else if ( i < 1759236594 )
        {
          i_1 = (i_1 + 1) % 256;
          j = (sbox[i_1] + j) % 256;
          swap((char *)&sbox[i_1], (char *)&sbox[j]);
          a2[v10] = v10 ^ sbox[(sbox[j] + sbox[i_1]) % 256] ^ username[v10];
          i = -2134342414;
        }
        else
        {
          j = 0;
          i_1 = 0;
          v10 = 0;
          i = 622976386;
        }
      }
      if ( i >= 622976386 )
        break;
      j = ((unsigned __int8)key[i_1] + sbox[i_1] + j) % 256;
      swap((char *)&sbox[i_1], (char *)&sbox[j]);
      i = 109075120;
    }
    if ( i >= 1109868132 )
      break;
    v7 = 1109868132;
    if ( v10 < len )
      v7 = 1333836938;
  }
  return __readfsqword(0x28u);
}魔改的点在于将生成的第i个密钥异或上i

最后和固定字符串比较:

1
v5 = memcmp(s1, s2, 0xAuLL);

由此可以先给username逆出来了:

 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
def rc4_init(s, key, key_len):
    j = 0
    for i in range(256):
        j = (j + s[i] + key[i%key_len])%256
        tmp = s[i]
        s[i] = s[j]
        s[j] = tmp

def rc4_generate_keystream(s, length):
    i = 0
    j = 0
    key_stream = []
    while length:
        i = (i + 1) % 256    # 可以保证每256次循环后s盒中的每个元素至少被交换一次
        j = (j + s[i]) % 256
        tmp = s[i]
        s[i] = s[j]
        s[j] = tmp
        key_stream.append((i-1) ^ s[(s[i] + s[j]) % 256])
        length -= 1
    return key_stream

def main():
    key = [ord(i) for i in "12345678"] * 32      # 准备一些变量
    key_len = len(key)
    enc = [0xE9, 0x97, 0x64, 0xE6, 0x7E, 0xEB, 0xBD, 0xC1, 0xAB, 0x43]
    enc_len = len(enc)
    cipher = [0] * enc_len

    s = [i for i in range(256)]
    rc4_init(s, key, key_len)      # 使用key打乱s盒
    key_stream = rc4_generate_keystream(s[:], enc_len) # 生成密钥流

    for i in range(enc_len):     # 逐字节异或加密
        cipher[i] = enc[i] ^ key_stream[i]
    print("".join(chr(i) for i in cipher))

if __name__ == '__main__':
    main()

得到username是Re_1s_eaSy

再看check_pass,加了ollvm就是一坨,但是能通过一些特征去猜,findcrypt能查到AES的s盒,基本就已经可以确定是aes了

首先会检查password的长度为16,和上面如出一辙:

1
2
3
4
5
6
7
v29 = strlen(s);
...
v31 = v29;
...
if ( v31 != 16 )
    v3 = v30 - 451658405;
    *v8 = v3;

主要逻辑在这部分:

1
2
3
4
5
6
7
8
else if ( v7 == 894391038 )
         {
            aes_encrypt(v14, 16LL, v15);
            v4 = v30 - 385985981;
            if ( _mm_movemask_epi8(_mm_cmpeq_epi8(unk_7FF9545CD160, *v14)) == 0xFFFF )
              v4 = v30 - 385360373;
            *v8 = v4;
          }

进到aes_encrypt函数中,能看到更多aes的细节,比如:

sub_7FF9545D0080这个函数中,有明显的44轮循环

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
  v10 = 0;
  ...
  if ( v10 < 44 )
      v3 = v13 - 498875668;
      v8 = v3;
  ...
  else if ( v8 == 606295280 ) {
      ++v10;
      v8 = v13 + 1173698718;
  }

比较容易确定是AES的密钥扩展

一个大循环中连续调用多个函数,基本能猜到是AES各个步骤:

 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
do
  {
    while ( 1 )
    {
      while ( 1 )
      {
        while ( 1 )
        {
          v7 = *v8;
          if ( v7 < 679415164 )
            break;
          if ( v7 < 1406786683 )
          {
            if ( v7 < 743864786 )
            {
              if ( v7 == 679415164 )
              {
                add_round_key(&v22, (__int64)v26);
                substitute_bytes(&v22, v26);
                shift_rows(&v22, v26, 10LL);
                v22 = v23 + 1781418862;
                mix_columns(&v22, v26, v11 + v15);
                *v8 = v23 + 1605906664;
              }
...

调试断在aes_encrypt这个函数里就可以清晰的看到参数:

1
2
3
a1: password
a2: 16 (length)
a3: Re_1s_eaSy123456 (key)

密文动调或是直接看_init_array中的赋值函数都可以很容易地拿到,但是直接尝试解密会得到乱码

大部分题目中的AES是不会被魔改的,因为一不小心容易玩坏了没法解密,但这道题是个例外,它在_init_array里修改了s盒(打乱了顺序):

 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
unsigned __int64 sub_7FF9545D1690()
{
  int i; // [rsp+2Ch] [rbp-154h]
  char dest[264]; // [rsp+70h] [rbp-110h] BYREF
  unsigned __int64 v3; // [rsp+178h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  memcpy(dest, &byte_7FF9545D57E0, 0x100uLL);
  for ( i = 761845300; ; i = 2015305402 )
  {
    while ( i >= 683804160 )
    {
      if ( i < 2015305402 )
      {
        i = 124905861;
      }
      else
      {
        __memcpy_chk(RijnDael_AES_LONG_7FF9545D8000, dest, 256LL, 256LL);
        i = -1293258920;
      }
    }
    if ( i < -992810655 )
      break;
  }
  return __readfsqword(0x28u);
}

我们需要根据S盒算出逆S盒,才能进行解密:

 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
sbox = [
    0x29, 0x40, 0x57, 0x6E, 0x85, 0x9C, 0xB3, 0xCA, 0xE1, 0xF8,
    0x0F, 0x26, 0x3D, 0x54, 0x6B, 0x82, 0x99, 0xB0, 0xC7, 0xDE,
    0xF5, 0x0C, 0x23, 0x3A, 0x51, 0x68, 0x7F, 0x96, 0xAD, 0xC4,
    0xDB, 0xF2, 0x09, 0x20, 0x37, 0x4E, 0x65, 0x7C, 0x93, 0xAA,
    0xC1, 0xD8, 0xEF, 0x06, 0x1D, 0x34, 0x4B, 0x62, 0x79, 0x90,
    0xA7, 0xBE, 0xD5, 0xEC, 0x03, 0x1A, 0x31, 0x48, 0x5F, 0x76,
    0x8D, 0xA4, 0xBB, 0xD2, 0xE9, 0x00, 0x17, 0x2E, 0x45, 0x5C,
    0x73, 0x8A, 0xA1, 0xB8, 0xCF, 0xE6, 0xFD, 0x14, 0x2B, 0x42,
    0x59, 0x70, 0x87, 0x9E, 0xB5, 0xCC, 0xE3, 0xFA, 0x11, 0x28,
    0x3F, 0x56, 0x6D, 0x84, 0x9B, 0xB2, 0xC9, 0xE0, 0xF7, 0x0E,
    0x25, 0x3C, 0x53, 0x6A, 0x81, 0x98, 0xAF, 0xC6, 0xDD, 0xF4,
    0x0B, 0x22, 0x39, 0x50, 0x67, 0x7E, 0x95, 0xAC, 0xC3, 0xDA,
    0xF1, 0x08, 0x1F, 0x36, 0x4D, 0x64, 0x7B, 0x92, 0xA9, 0xC0,
    0xD7, 0xEE, 0x05, 0x1C, 0x33, 0x4A, 0x61, 0x78, 0x8F, 0xA6,
    0xBD, 0xD4, 0xEB, 0x02, 0x19, 0x30, 0x47, 0x5E, 0x75, 0x8C,
    0xA3, 0xBA, 0xD1, 0xE8, 0xFF, 0x16, 0x2D, 0x44, 0x5B, 0x72,
    0x89, 0xA0, 0xB7, 0xCE, 0xE5, 0xFC, 0x13, 0x2A, 0x41, 0x58,
    0x6F, 0x86, 0x9D, 0xB4, 0xCB, 0xE2, 0xF9, 0x10, 0x27, 0x3E,
    0x55, 0x6C, 0x83, 0x9A, 0xB1, 0xC8, 0xDF, 0xF6, 0x0D, 0x24,
    0x3B, 0x52, 0x69, 0x80, 0x97, 0xAE, 0xC5, 0xDC, 0xF3, 0x0A,
    0x21, 0x38, 0x4F, 0x66, 0x7D, 0x94, 0xAB, 0xC2, 0xD9, 0xF0,
    0x07, 0x1E, 0x35, 0x4C, 0x63, 0x7A, 0x91, 0xA8, 0xBF, 0xD6,
    0xED, 0x04, 0x1B, 0x32, 0x49, 0x60, 0x77, 0x8E, 0xA5, 0xBC,
    0xD3, 0xEA, 0x01, 0x18, 0x2F, 0x46, 0x5D, 0x74, 0x8B, 0xA2,
    0xB9, 0xD0, 0xE7, 0xFE, 0x15, 0x2C, 0x43, 0x5A, 0x71, 0x88,
    0x9F, 0xB6, 0xCD, 0xE4, 0xFB, 0x12]

inv_sbox = [0] * 256
for i in range(256):
    inv_sbox[sbox[i]] = i

print(inv_sbox)

最后随便找个AES的实现,改掉S盒和逆S盒,跑一遍即可,下面贴一个个人看着最舒服的版本:

  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
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
#include <stdio.h>
#include <stdint.h>
#include <memory.h>
/****************************************************************************************************************/
typedef enum {
    AES_CYPHER_128,
    AES_CYPHER_192,
    AES_CYPHER_256,
} AES_CYPHER_T;
/****************************************************************************************************************/
/** Encryption Rounds*/
int g_aes_key_bits[] = {
    /* AES_CYPHER_128 */ 128,
    /* AES_CYPHER_192 */ 192,
    /* AES_CYPHER_256 */ 256,
};
int g_aes_rounds[] = {
    /* AES_CYPHER_128 */  10,
    /* AES_CYPHER_192 */  12,
    /* AES_CYPHER_256 */  14,
};
int g_aes_nk[] = {
    /* AES_CYPHER_128 */  4,
    /* AES_CYPHER_192 */  6,
    /* AES_CYPHER_256 */  8,
};
int g_aes_nb[] = {
    /* AES_CYPHER_128 */  4,
    /* AES_CYPHER_192 */  4,
    /* AES_CYPHER_256 */  4,
};
/****************************************************************************************************************/
/** aes Rcon:** WARNING: Rcon is designed starting from 1 to 15, not 0 to 14.*          FIPS-197 Page 9: "note that i starts at 1, not 0"** i    |   0     1     2     3     4     5     6     7     8     9    10    11    12    13    14* -----+------------------------------------------------------------------------------------------*      | [01]  [02]  [04]  [08]  [10]  [20]  [40]  [80]  [1b]  [36]  [6c]  [d8]  [ab]  [4d]  [9a]* RCON | [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]*      | [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]*      | [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]  [00]*/
static const uint32_t g_aes_rcon[] = {
    0x01000000, 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000,
    0x1b000000, 0x36000000, 0x6c000000, 0xd8000000, 0xab000000, 0xed000000, 0x9a000000
};
/****************************************************************************************************************/
/** aes sbox and invert-sbox*/
static const uint8_t g_aes_sbox[256] = {
    /* 0     1     2     3   u  4     5     6     7     8     9     A     B     C     D     E     F  */
    0x29, 0x40, 0x57, 0x6E, 0x85, 0x9C, 0xB3, 0xCA, 0xE1, 0xF8, 0x0F, 0x26, 0x3D, 0x54, 0x6B, 0x82, 0x99, 0xB0, 0xC7, 0xDE, 0xF5, 0x0C, 0x23, 0x3A, 0x51, 0x68, 0x7F, 0x96, 0xAD, 0xC4, 0xDB, 0xF2, 0x09, 0x20, 0x37, 0x4E, 0x65, 0x7C, 0x93, 0xAA, 0xC1, 0xD8, 0xEF, 0x06, 0x1D, 0x34, 0x4B, 0x62, 0x79, 0x90, 0xA7, 0xBE, 0xD5, 0xEC, 0x03, 0x1A, 0x31, 0x48, 0x5F, 0x76, 0x8D, 0xA4, 0xBB, 0xD2, 0xE9, 0x00, 0x17, 0x2E, 0x45, 0x5C, 0x73, 0x8A, 0xA1, 0xB8, 0xCF, 0xE6, 0xFD, 0x14, 0x2B, 0x42, 0x59, 0x70, 0x87, 0x9E, 0xB5, 0xCC, 0xE3, 0xFA, 0x11, 0x28, 0x3F, 0x56, 0x6D, 0x84, 0x9B, 0xB2, 0xC9, 0xE0, 0xF7, 0x0E, 0x25, 0x3C, 0x53, 0x6A, 0x81, 0x98, 0xAF, 0xC6, 0xDD, 0xF4, 0x0B, 0x22, 0x39, 0x50, 0x67, 0x7E, 0x95, 0xAC, 0xC3, 0xDA, 0xF1, 0x08, 0x1F, 0x36, 0x4D, 0x64, 0x7B, 0x92, 0xA9, 0xC0, 0xD7, 0xEE, 0x05, 0x1C, 0x33, 0x4A, 0x61, 0x78, 0x8F, 0xA6, 0xBD, 0xD4, 0xEB, 0x02, 0x19, 0x30, 0x47, 0x5E, 0x75, 0x8C, 0xA3, 0xBA, 0xD1, 0xE8, 0xFF, 0x16, 0x2D, 0x44, 0x5B, 0x72, 0x89, 0xA0, 0xB7, 0xCE, 0xE5, 0xFC, 0x13, 0x2A, 0x41, 0x58, 0x6F, 0x86, 0x9D, 0xB4, 0xCB, 0xE2, 0xF9, 0x10, 0x27, 0x3E, 0x55, 0x6C, 0x83, 0x9A, 0xB1, 0xC8, 0xDF, 0xF6, 0x0D, 0x24, 0x3B, 0x52, 0x69, 0x80, 0x97, 0xAE, 0xC5, 0xDC, 0xF3, 0x0A, 0x21, 0x38, 0x4F, 0x66, 0x7D, 0x94, 0xAB, 0xC2, 0xD9, 0xF0, 0x07, 0x1E, 0x35, 0x4C, 0x63, 0x7A, 0x91, 0xA8, 0xBF, 0xD6, 0xED, 0x04, 0x1B, 0x32, 0x49, 0x60, 0x77, 0x8E, 0xA5, 0xBC, 0xD3, 0xEA, 0x01, 0x18, 0x2F, 0x46, 0x5D, 0x74, 0x8B, 0xA2, 0xB9, 0xD0, 0xE7, 0xFE, 0x15, 0x2C, 0x43, 0x5A, 0x71, 0x88, 0x9F, 0xB6, 0xCD, 0xE4, 0xFB, 0x12
};
static const uint8_t g_inv_sbox[256] = {
    /* 0     1     2     3     4     5     6     7     8     9     A     B     C     D     E     F  */
    65,232,143,54,221,132,43,210,121,32,199,110,21,188,99,10,177,88,255,166,77,244,155,66,233,144,55,222,133,44,211,122,33,200,111,22,189,100,11,178,89,0,167,78,245,156,67,234,145,56,223,134,45,212,123,34,201,112,23,190,101,12,179,90,1,168,79,246,157,68,235,146,57,224,135,46,213,124,35,202,113,24,191,102,13,180,91,2,169,80,247,158,69,236,147,58,225,136,47,214,125,36,203,114,25,192,103,14,181,92,3,170,81,248,159,70,237,148,59,226,137,48,215,126,37,204,115,26,193,104,15,182,93,4,171,82,249,160,71,238,149,60,227,138,49,216,127,38,205,116,27,194,105,16,183,94,5,172,83,250,161,72,239,150,61,228,139,50,217,128,39,206,117,28,195,106,17,184,95,6,173,84,251,162,73,240,151,62,229,140,51,218,129,40,207,118,29,196,107,18,185,96,7,174,85,252,163,74,241,152,63,230,141,52,219,130,41,208,119,30,197,108,19,186,97,8,175,86,253,164,75,242,153,64,231,142,53,220,131,42,209,120,31,198,109,20,187,98,9,176,87,254,165,76,243,154
};
/****************************************************************************************************************/
uint8_t aes_sub_sbox(uint8_t val)
{
    return g_aes_sbox[val];
}
/****************************************************************************************************************/
uint32_t aes_sub_dword(uint32_t val)
{
    uint32_t tmp = 0;

    tmp |= ((uint32_t)aes_sub_sbox((uint8_t)((val >> 0) & 0xFF))) << 0;
    tmp |= ((uint32_t)aes_sub_sbox((uint8_t)((val >> 8) & 0xFF))) << 8;
    tmp |= ((uint32_t)aes_sub_sbox((uint8_t)((val >> 16) & 0xFF))) << 16;
    tmp |= ((uint32_t)aes_sub_sbox((uint8_t)((val >> 24) & 0xFF))) << 24;

    return tmp;
}
/****************************************************************************************************************/
uint32_t aes_rot_dword(uint32_t val)
{
    uint32_t tmp = val;

    return (val >> 8) | ((tmp & 0xFF) << 24);
}
/****************************************************************************************************************/
uint32_t aes_swap_dword(uint32_t val)
{
    return (((val & 0x000000FF) << 24) |
        ((val & 0x0000FF00) << 8) |
        ((val & 0x00FF0000) >> 8) |
        ((val & 0xFF000000) >> 24));
}
/****************************************************************************************************************/
/** nr: number of rounds* nb: number of columns comprising the state, nb = 4 dwords (16 bytes)* nk: number of 32-bit words comprising cipher key, nk = 4, 6, 8 (KeyLength/(4*8))*/
void aes_key_expansion(AES_CYPHER_T mode, uint8_t *key, uint8_t *round)
{
    uint32_t *w = (uint32_t *)round;
    uint32_t  t;
    int      i = 0;

    do {
        w[i] = *((uint32_t *)&key[i * 4 + 0]);
    } while (++i < g_aes_nk[mode]);

    do {
        if ((i % g_aes_nk[mode]) == 0) {
            t = aes_rot_dword(w[i - 1]);
            t = aes_sub_dword(t);
            t = t ^ aes_swap_dword(g_aes_rcon[i / g_aes_nk[mode] - 1]);
        }
        else if (g_aes_nk[mode] > 6 && (i % g_aes_nk[mode]) == 4) {
            t = aes_sub_dword(w[i - 1]);
        }
        else {
            t = w[i - 1];
        }
        w[i] = w[i - g_aes_nk[mode]] ^ t;
    } while (++i < g_aes_nb[mode] * (g_aes_rounds[mode] + 1));
}
/****************************************************************************************************************/
void aes_add_round_key(AES_CYPHER_T mode, uint8_t *state,    uint8_t *round, int nr)
{
    uint32_t *w = (uint32_t *)round;
    uint32_t *s = (uint32_t *)state;
    int i;

    for (i = 0; i < g_aes_nb[mode]; i++) {
        s[i] ^= w[nr * g_aes_nb[mode] + i];
    }
}
/****************************************************************************************************************/
void aes_sub_bytes(AES_CYPHER_T mode, uint8_t *state)
{
    int i, j;

    for (i = 0; i < g_aes_nb[mode]; i++) {
        for (j = 0; j < 4; j++) {
            state[i * 4 + j] = aes_sub_sbox(state[i * 4 + j]);
        }
    }
}
/****************************************************************************************************************/
void aes_shift_rows(AES_CYPHER_T mode, uint8_t *state)
{
    uint8_t *s = (uint8_t *)state;
    int i, j, r;

    for (i = 1; i < g_aes_nb[mode]; i++) {
        for (j = 0; j < i; j++) {
            uint8_t tmp = s[i];
            for (r = 0; r < g_aes_nb[mode]; r++) {
                s[i + r * 4] = s[i + (r + 1) * 4];
            }
            s[i + (g_aes_nb[mode] - 1) * 4] = tmp;
        }
    }
}
/****************************************************************************************************************/
uint8_t aes_xtime(uint8_t x)
{
    return ((x << 1) ^ (((x >> 7) & 1) * 0x1b));
}
/****************************************************************************************************************/
uint8_t aes_xtimes(uint8_t x, int ts)
{
    while (ts-- > 0) {
        x = aes_xtime(x);
    }

    return x;
}
/****************************************************************************************************************/
uint8_t aes_mul(uint8_t x, uint8_t y)
{
    /*    * encrypt: y has only 2 bits: can be 1, 2 or 3    * decrypt: y could be any value of 9, b, d, or e    */

    return ((((y >> 0) & 1) * aes_xtimes(x, 0)) ^
        (((y >> 1) & 1) * aes_xtimes(x, 1)) ^
        (((y >> 2) & 1) * aes_xtimes(x, 2)) ^
        (((y >> 3) & 1) * aes_xtimes(x, 3)) ^
        (((y >> 4) & 1) * aes_xtimes(x, 4)) ^
        (((y >> 5) & 1) * aes_xtimes(x, 5)) ^
        (((y >> 6) & 1) * aes_xtimes(x, 6)) ^
        (((y >> 7) & 1) * aes_xtimes(x, 7)));
}
/****************************************************************************************************************/
void aes_mix_columns(AES_CYPHER_T mode, uint8_t *state)
{
    uint8_t y[16] = { 2, 3, 1, 1,  1, 2, 3, 1,  1, 1, 2, 3,  3, 1, 1, 2 };
    uint8_t s[4];
    int i, j, r;

    for (i = 0; i < g_aes_nb[mode]; i++) {
        for (r = 0; r < 4; r++) {
            s[r] = 0;
            for (j = 0; j < 4; j++) {
                s[r] = s[r] ^ aes_mul(state[i * 4 + j], y[r * 4 + j]);
            }
        }
        for (r = 0; r < 4; r++) {
            state[i * 4 + r] = s[r];
        }
    }
}
/****************************************************************************************************************/
int aes_encrypt(AES_CYPHER_T mode, uint8_t *data, int len, uint8_t *key)
{
    uint8_t w[4 * 4 * 15] = { 0 }; /* round key */
    uint8_t s[4 * 4] = { 0 }; /* state */

    int nr, i, j;

    /* key expansion */
    aes_key_expansion(mode, key, w);

    /* start data cypher loop over input buffer */
    for (i = 0; i < len; i += 4 * g_aes_nb[mode]) {

        /* init state from user buffer (plaintext) */
        for (j = 0; j < 4 * g_aes_nb[mode]; j++)
            s[j] = data[i + j];

        /* start AES cypher loop over all AES rounds */
        for (nr = 0; nr <= g_aes_rounds[mode]; nr++) {

            if (nr > 0) {

                /* do SubBytes */
                aes_sub_bytes(mode, s);

                /* do ShiftRows */
                aes_shift_rows(mode, s);

                if (nr < g_aes_rounds[mode]) {
                    /* do MixColumns */
                    aes_mix_columns(mode, s);
                }
            }

            /* do AddRoundKey */
            aes_add_round_key(mode, s, w, nr);
        }

        /* save state (cypher) to user buffer */
        for (j = 0; j < 4 * g_aes_nb[mode]; j++)
            data[i + j] = s[j];
    }

    return 0;
}
/****************************************************************************************************************/
int aes_encrypt_ecb(AES_CYPHER_T mode, uint8_t *data, int len, uint8_t *key)
{
    return aes_encrypt(mode, data, len, key);
}
/****************************************************************************************************************/
int aes_encrypt_cbc(AES_CYPHER_T mode, uint8_t *data, int len, uint8_t *key, uint8_t *iv)
{
    uint8_t w[4 * 4 * 15] = { 0 }; /* round key */
    uint8_t s[4 * 4] = { 0 }; /* state */
    uint8_t v[4 * 4] = { 0 }; /* iv */

    int nr, i, j;

    /* key expansion */
    aes_key_expansion(mode, key, w);

    memcpy(v, iv, sizeof(v));

    /* start data cypher loop over input buffer */
    for (i = 0; i < len; i += 4 * g_aes_nb[mode]) {

        /* init state from user buffer (plaintext) */
        for (j = 0; j < 4 * g_aes_nb[mode]; j++)
            s[j] = data[i + j] ^ v[j];

        /* start AES cypher loop over all AES rounds */
        for (nr = 0; nr <= g_aes_rounds[mode]; nr++) {

            if (nr > 0) {

                /* do SubBytes */
                aes_sub_bytes(mode, s);

                /* do ShiftRows */
                aes_shift_rows(mode, s);

                if (nr < g_aes_rounds[mode]) {
                    /* do MixColumns */
                    aes_mix_columns(mode, s);
                }
            }

            /* do AddRoundKey */
            aes_add_round_key(mode, s, w, nr);
        }

        /* save state (cypher) to user buffer */
        for (j = 0; j < 4 * g_aes_nb[mode]; j++)
            data[i + j] = v[j] = s[j];
    }

    return 0;
}
/****************************************************************************************************************/
void inv_shift_rows(AES_CYPHER_T mode, uint8_t *state)
{
    uint8_t *s = (uint8_t *)state;
    int i, j, r;

    for (i = 1; i < g_aes_nb[mode]; i++) {
        for (j = 0; j < g_aes_nb[mode] - i; j++) {
            uint8_t tmp = s[i];
            for (r = 0; r < g_aes_nb[mode]; r++) {
                s[i + r * 4] = s[i + (r + 1) * 4];
            }
            s[i + (g_aes_nb[mode] - 1) * 4] = tmp;
        }
    }
}
/****************************************************************************************************************/
uint8_t inv_sub_sbox(uint8_t val)
{
    return g_inv_sbox[val];
}
/****************************************************************************************************************/
void inv_sub_bytes(AES_CYPHER_T mode, uint8_t *state)
{
    int i, j;

    for (i = 0; i < g_aes_nb[mode]; i++) {
        for (j = 0; j < 4; j++) {
            state[i * 4 + j] = inv_sub_sbox(state[i * 4 + j]);
        }
    }
}
/****************************************************************************************************************/
void inv_mix_columns(AES_CYPHER_T mode, uint8_t *state)
{
    uint8_t y[16] = { 0x0e, 0x0b, 0x0d, 0x09,  0x09, 0x0e, 0x0b, 0x0d,
        0x0d, 0x09, 0x0e, 0x0b,  0x0b, 0x0d, 0x09, 0x0e };
    uint8_t s[4];
    int i, j, r;

    for (i = 0; i < g_aes_nb[mode]; i++) {
        for (r = 0; r < 4; r++) {
            s[r] = 0;
            for (j = 0; j < 4; j++) {
                s[r] = s[r] ^ aes_mul(state[i * 4 + j], y[r * 4 + j]);
            }
        }
        for (r = 0; r < 4; r++) {
            state[i * 4 + r] = s[r];
        }
    }
}
/****************************************************************************************************************/
int aes_decrypt(AES_CYPHER_T mode, uint8_t *data, int len, uint8_t *key)
{
    uint8_t w[4 * 4 * 15] = { 0 }; /* round key */
    uint8_t s[4 * 4] = { 0 }; /* state */

    int nr, i, j;

    /* key expansion */
    aes_key_expansion(mode, key, w);

    /* start data cypher loop over input buffer */
    for (i = 0; i < len; i += 4 * g_aes_nb[mode]) {

        /* init state from user buffer (cyphertext) */
        for (j = 0; j < 4 * g_aes_nb[mode]; j++)
            s[j] = data[i + j];

        /* start AES cypher loop over all AES rounds */
        for (nr = g_aes_rounds[mode]; nr >= 0; nr--) {

            /* do AddRoundKey */
            aes_add_round_key(mode, s, w, nr);

            if (nr > 0) {

                if (nr < g_aes_rounds[mode]) {
                    /* do MixColumns */
                    inv_mix_columns(mode, s);
                }

                /* do ShiftRows */
                inv_shift_rows(mode, s);

                /* do SubBytes */
                inv_sub_bytes(mode, s);
            }
        }

        /* save state (cypher) to user buffer */
        for (j = 0; j < 4 * g_aes_nb[mode]; j++)
            data[i + j] = s[j];
    }

    return 0;
}
/****************************************************************************************************************/
int aes_decrypt_ecb(AES_CYPHER_T mode, uint8_t *data, int len, uint8_t *key)
{
    return aes_decrypt(mode, data, len, key);
}
/****************************************************************************************************************/
int aes_decrypt_cbc(AES_CYPHER_T mode, uint8_t *data, int len, uint8_t *key, uint8_t *iv)
{
    uint8_t w[4 * 4 * 15] = { 0 }; /* round key */
    uint8_t s[4 * 4] = { 0 }; /* state */
    uint8_t v[4 * 4] = { 0 }; /* iv */

    int nr, i, j;

    /* key expansion */
    aes_key_expansion(mode, key, w);

    memcpy(v, iv, sizeof(v));

    /* start data cypher loop over input buffer */
    for (i = 0; i < len; i += 4 * g_aes_nb[mode]) {

        /* init state from user buffer (cyphertext) */
        for (j = 0; j < 4 * g_aes_nb[mode]; j++)
            s[j] = data[i + j];

        /* start AES cypher loop over all AES rounds */
        for (nr = g_aes_rounds[mode]; nr >= 0; nr--) {

            /* do AddRoundKey */
            aes_add_round_key(mode, s, w, nr);

            if (nr > 0) {

                if (nr < g_aes_rounds[mode]) {
                    /* do MixColumns */
                    inv_mix_columns(mode, s);
                }

                /* do ShiftRows */
                inv_shift_rows(mode, s);

                /* do SubBytes */
                inv_sub_bytes(mode, s);
            }
        }

        /* save state (cypher) to user buffer */
        for (j = 0; j < 4 * g_aes_nb[mode]; j++) {
            uint8_t p = s[j] ^ v[j];
            v[j] = data[i + j];
            data[i + j] = p;
        }
    }

    return 0;
}
/****************************************************************************************************************/
void aes_cypher_128_test()
{
#if 1
    uint8_t buf[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
        0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
    uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f };
#else
    uint8_t buf[] = { 0x32, 0x43, 0xf6, 0xa8, 0x88, 0x5a, 0x30, 0x8d,
        0x31, 0x31, 0x98, 0xa2, 0xe0, 0x37, 0x07, 0x34 };
    uint8_t key[] = { 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6,
        0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c };
#endif

    aes_encrypt(AES_CYPHER_128, buf, sizeof(buf), key);

    aes_decrypt(AES_CYPHER_128, buf, sizeof(buf), key);
}
/****************************************************************************************************************/
void aes_cypher_192_test()
{
    uint8_t buf[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
        0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
    uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 };

    aes_encrypt(AES_CYPHER_192, buf, sizeof(buf), key);

    aes_decrypt(AES_CYPHER_192, buf, sizeof(buf), key);
}
/****************************************************************************************************************/
void aes_cypher_256_test()
{
    uint8_t buf[] = { 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,
        0x88, 0x99, 0xaa, 0xbb, 0xcc, 0xdd, 0xee, 0xff };
    uint8_t key[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07,
        0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f,
        0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
        0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f };

    aes_encrypt(AES_CYPHER_256, buf, sizeof(buf), key);

    aes_decrypt(AES_CYPHER_256, buf, sizeof(buf), key);
}
/****************************************************************************************************************/
void main()
{
    //数据
    uint8_t buf[] = { 0x2b,0xc8,0x20,0x8b,0x5c,0x0d,0xa7,0x9b,0x2a,0x51,0x3a,0xd2,0x71,0x71,0xca,0x50 };
    //密钥
    uint8_t key[16] = "Re_1s_eaSy123456";
    //向量
    uint8_t iv[] = {0x31, 0x5F, 0x65, 0x52, 0x61, 0x65, 0x5F, 0x73, 0x32, 0x31, 0x79, 0x53, 0x36, 0x35, 0x34, 0x33};
    switch (sizeof(key))
    {
    //ECB

    case 16:aes_decrypt(AES_CYPHER_128, buf, sizeof(buf), key); break;
    case 24:aes_decrypt(AES_CYPHER_192, buf, sizeof(buf), key); break;
    case 32:aes_decrypt(AES_CYPHER_256, buf, sizeof(buf), key); break;
    //CBC
    /*    case 16:aes_decrypt_cbc(AES_CYPHER_128, buf, sizeof(buf), key, iv); break;    case 24:aes_decrypt_cbc(AES_CYPHER_192, buf, sizeof(buf), key, iv); break;    case 32:aes_decrypt_cbc(AES_CYPHER_256, buf, sizeof(buf), key, iv); break;    */
    }
    for (int i = 0; i < sizeof(buf); i++) {
        printf("%x", buf[i] & 0xFF);
    }
    printf("\n");
    for (int i = 0; i < sizeof(buf); i++) {
        printf("%c", buf[i] & 0xff);
    }
    return;
} // _eZ_Rc4_@nd_AES!

0×01 WMCTF 2023 RightBack

不会有人和我一样当阅读题做吧(
附件:https://pan.baidu.com/s/1LID0fxXp5N72Snuc60D9SQ?pwd=5aee

WriteUp

pyc、python3.9、花指令,buf叠满了。

去花 + 反编译 + 逆源码

这是PZ师傅的预期解,具体可以直接看PZ师傅的解析,同时可以参考源码仓库进行学习

我这里就不再赘述了。

阅读题 + pyc调试

必须得说,这道题在这场比赛中,绝大部分师傅都当阅读题做了,包括我。赛后发现应该只有一位师傅成功去花反编译了,但还是因为这道题里的花比较简单且只有一种,直接全nop掉了。在大多数场景中,写脚本去花风险很高,万一没搞出来就白白浪费了时间,但硬看总是能搞出来的。

但是硬看效率低且易出错,这时就可以尝试调试,去拿到一些关键数据/验证猜测的逻辑。比如这道题最后是个RC5,但是好久未能成功解密,最后调试发现roundKey和用密钥生成的不同,直接取出roundKey就可以秒了。

关于如何调试,推荐一个trepanxpy,能够提供一个类似gdb的调试环境,能够直接对行设置断点,方便查看变量,具体效果如下:

https://bbs.xdsec.org/assets/files/2023-09-07/1694097673-230444-2023-09-07-22-28-16-image.png

运行trepan-xpy时可能会遇到这样的报错:

 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
I don't know about Python version '3.9.18' yet.
Python versions 3.9 and greater are not supported.
I don't know about Python version '3.9.18' yet.
Python versions 3.9 and greater are not supported.
I don't know about Python version '3.9.18' yet.
Python versions 3.9 and greater are not supported.
I don't know about Python version '3.9.18' yet.
Python versions 3.9 and greater are not supported.
I don't know about Python version '3.9.18' yet.
Python versions 3.9 and greater are not supported.
I don't know about Python version '3.9.18' yet.
Python versions 3.9 and greater are not supported.
Traceback (most recent call last):
  File "/home/dx3906/.virtualenvs/pyc39/bin/trepan-xpy", line 5, in <module>
    from trepanxpy.__main__ import main
  File "/home/dx3906/.virtualenvs/pyc39/lib/python3.9/site-packages/trepanxpy/__main__.py", line 7, in <module>
    from trepanxpy.debugger import TrepanXPy
  File "/home/dx3906/.virtualenvs/pyc39/lib/python3.9/site-packages/trepanxpy/debugger.py", line 17, in <module>
    from trepanxpy.processor.cmd import XPyCommandProcessor
  File "/home/dx3906/.virtualenvs/pyc39/lib/python3.9/site-packages/trepanxpy/processor/cmd.py", line 31, in <module>
    import trepan.lib.display as Mdisplay
  File "/home/dx3906/.virtualenvs/pyc39/lib/python3.9/site-packages/trepan/lib/display.py", line 22, in <module>
    import trepan.lib.stack as Mstack
  File "/home/dx3906/.virtualenvs/pyc39/lib/python3.9/site-packages/trepan/lib/stack.py", line 253, in <module>
    opc = get_opcode(PYTHON_VERSION_TRIPLE, IS_PYPY)
  File "/home/dx3906/.virtualenvs/pyc39/lib/python3.9/site-packages/xdis/disasm.py", line 56, in get_opcode
    raise TypeError("%s is not a Python version%s I know about" % (lookup, pypy_str))
TypeError: 3.9.18 is not a Python version I know about

这是xdis这个包的问题,因为python版本在不断更新,而作者并未及时更新新版本的magics,需要手动修改下magics.py添加你的python版本即可,具体参见这个pr