eeeeerte

查壳

Alt text

可以发现无壳,易语言逆向题

运行

程序运行就是一只向你挑衅的大”鲨”鱼。并没有任何按钮或者其他反应。

Alt text

ida 易语言

E-Decompiler:E-Decompiler是ida识别易语言的插件,可以识别出易语言当中的控件事件和中文函数名称。
效果如下:
Alt text

程序分析

  1. 从函数名称也可以判断出该程序的控件事件就是鼠标点击事件(由于环境除了问题,伪代码中并没有函数的中文名称,但是汇编窗口有,交替着看也没问题)。

    Alt text
    Alt text

  2. 函数的核心逻辑在于下了断点的三次if判断,交叉应引用一下被判断的数组会发现整个鼠标点击事件结束他会自增一次,说明qword_57C620表示图片被鼠标点击了几次。
    Alt text

  3. 得到这个信息之后就直接下断点,开调试,鼠标点击图片,直接修改qword_57C620的值为0x272,,进入sub_402A13函数发现是异或(结合汇编看,有函数中文名),得到提示语句:”Please provide an encryption key:”,之后一个大函数是输出信息框,这时会提示我们输入key。
    Alt text

  4. 之后sub_402BAB会将我们的输入拷贝到另一块内存区域(加上长度信息),然后再次调用sub_402A13函数将另一块区域异或出来——结果就是题目中提到的假的flag,然后将fake_flag也计算长度拷贝到另一块内存区域:
    Alt text

  5. 将假的flag和我们的输入一起调用sub_402D04,这是核心的加密逻辑,整个函数先做了一些初始化逻辑,然后在for循环中:首先取input的前十个字符,然后将假的flag分为八个一组,然后调用sub_40325C进行核心的加密逻辑。

  6. 经过反复调试,算法的逻辑如下:

    • 假设输入的前十个字符是:1234567890

    • 假的flag为:flag{Misdirection?MysteriousJack?FAAAKE},所以第一组为”flag{Mis”

    • 初始化逻辑将”flag{Mis”字符串做了一点点变化,假设变化后的数组名称为flag_array,长度为4:
      Alt text

    • for循环是32轮。然后if和else是通过除法取余的方式来判断:八轮交替一次(即0-7轮进入if语句,8-15轮进else语句,16-23轮再进入if语句,以此类推)。

    • if中的逻辑使用python脚本还原如下,每次取flag_array前两个字符做运算,算出来的结果再更新flag_array数组,计算过程中还用到了个常量数组hex_array,如下代码给出:

      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
      # passwd为输入的key:1234567890
      # flag_array:flag{Mis

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

      def insert_inp_b(x, y, i):
      global flag_array
      re = ((flag_array[7] << 8) | flag_array[6]) ^ ((y << 8) | x) ^ i
      first_two_elements = [re & 0xff, re >> 8]
      middle_elements = flag_array[2:6]
      flag_array = first_two_elements + [x, y] + middle_elements

      x = -1
      def inc():
      global x
      x = x + 1
      return x%10

      ll = (passwd[inc()]) ^ flag_array[0]
      tmp1 = hex_array[ll] ^ flag_array[1]
      tmp3 = hex_array[tmp1 ^ (passwd[inc()])] ^ flag_array[0]
      tmp5 = hex_array[tmp3 ^ (passwd[inc()])] ^ tmp1
      tmp7 = hex_array[tmp5 ^ (passwd[inc()])] ^ tmp3
      insert_inp_b(tmp7, tmp5, i+1)

    • else中的逻辑同理,只是先更新再运算:

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

      def insert_inp_b2(i):
      global flag_array
      re = ((flag_array[1] << 8) | flag_array[0]) ^ ((flag_array[3] << 8) | flag_array[2]) ^ i
      first_two_elements = flag_array[6:]
      middle_elements = [re & 0xff, re >> 8]
      flag_array = first_two_elements + flag_array[2:4] + middle_elements + flag_array[4:6]

      def update_inp_b(x, y):
      global flag_array
      flag_array[2] = x
      flag_array[3] = y

      f = flag_array[0]
      l = flag_array[1]
      insert_inp_b2(i+1)
      ll = (passwd[inc()]) ^ f
      tmp1 = hex_array[ll] ^ l
      tmp3 = hex_array[tmp1 ^ (passwd[inc()])] ^ f
      tmp5 = hex_array[tmp3 ^ (passwd[inc()])] ^ tmp1
      tmp7 = hex_array[tmp5 ^ (passwd[inc()])] ^ tmp3
      update_inp_b(tmp7, tmp5)

    • 整个加密逻辑结束后会经过一个base64运算,最后整个算法函数结束。

  7. 之后的函数就是用于将加密之后的结果用信息框展示出来,并释放一些内存:
    Alt text

  8. 至此第一个点击事件判断结束。进入步骤2中的第二次点击次数判断,一样patch一下绕过,调试下来发现这个判断最中会解密一个字符串并且用信息框展示(信息框还没有复制功能,需要手动再内存中找到然后复制):
    Alt text

  9. 很显然上述字符串就是加密结果,然后需要用第六步的算法反推。然后最后看一下步骤2中的第三次点击事件判断,由于有了ida的插件,很容易看出来,整个过程一直在画矩阵。

    简单学习了一个易语言画矩阵的函数可知,图中绿色圈起来的为右下和左上两个顶点的坐标:(30,30),(20,20)

    我也确实不知道为什么这个矩阵最终没有画出来(看了大佬的直播复盘才知道原来是画矩阵的窗体设置的太大了),但是在比赛中,我还是手动在processon里画了一个一样的(设一格坐标为5)。

    最后得知密钥为:NITORI2413

    Alt text
    Alt text

解密脚本

  1. 解密脚本如下(为了调试解密过程,加密的过程也写出来了):
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

import base64
b = 'xmxO93Sn8YjfoWqaFS6poLf6n1q7bWrIyXbLAdcSjygWfs/jAVc4leFrTDZYdFoL'

b_b = base64.b64decode(b)

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

# visible_characters = ''.join([chr(i) for i in range(32, 127)])
p = 'NITORI2413'

passwd = [ord(char) for char in p]

# 以下为加密过程,用于验证密码是否正确
# encrypt
# 题目提示说了NepCTF{}格式
# inp = 'NepCTF{'
# for j in range(0, 0xff+1):
# flattened_hex_values = []
# # inp = inpp + j
# for i in range(0, len(inp), 8):
# global inp_b
# inp_b = [ord(inp[i + 1]), ord(inp[i + 0]), ord(inp[i + 3]), ord(inp[i + 2]), ord(inp[i + 5]), ord(inp[i + 4]), j, ord(inp[i + 6])]
# def insert_inp_b(x, y, i):
# global inp_b
# re = ((inp_b[7] << 8) | inp_b[6]) ^ ((y << 8) | x) ^ i
# first_two_elements = [re & 0xff, re >> 8]
# middle_elements = inp_b[2:6]
# inp_b = first_two_elements + [x, y] + middle_elements

# def insert_inp_b2(i):
# global inp_b
# re = ((inp_b[1] << 8) | inp_b[0]) ^ ((inp_b[3] << 8) | inp_b[2]) ^ i
# first_two_elements = inp_b[6:]
# middle_elements = [re & 0xff, re >> 8]
# inp_b = first_two_elements + inp_b[2:4] + middle_elements + inp_b[4:6]

# def update_inp_b(x, y):
# global inp_b
# inp_b[2] = x
# inp_b[3] = y

# x = -1
# def inc():
# global x
# x = x + 1
# return x%10

# for i in range(32):
# if i//8%2 == 0:
# ll = (passwd[inc()]) ^ inp_b[0]
# tmp1 = hex_array[ll] ^ inp_b[1]
# tmp3 = hex_array[tmp1 ^ (passwd[inc()])] ^ inp_b[0]
# tmp5 = hex_array[tmp3 ^ (passwd[inc()])] ^ tmp1
# tmp7 = hex_array[tmp5 ^ (passwd[inc()])] ^ tmp3
# insert_inp_b(tmp7, tmp5, i+1)
# else:
# f = inp_b[0]
# l = inp_b[1]
# insert_inp_b2(i+1)
# ll = (passwd[inc()]) ^ f
# tmp1 = hex_array[ll] ^ l
# tmp3 = hex_array[tmp1 ^ (passwd[inc()])] ^ f
# tmp5 = hex_array[tmp3 ^ (passwd[inc()])] ^ tmp1
# tmp7 = hex_array[tmp5 ^ (passwd[inc()])] ^ tmp3
# update_inp_b(tmp7, tmp5)

# # print([hex(x) for x in inp_b])
# flattened_hex_values += [inp_b[1], inp_b[0], inp_b[3], inp_b[2], inp_b[5], inp_b[4], inp_b[7], inp_b[6]]

# # print([hex(x) for x in flattened_hex_values])
# inp_b = []
# bytes_value = bytes(flattened_hex_values)
# if(bytes_value==b_b[:len(bytes_value)]):
# print('yes')
# base64_encoded = base64.b64encode(bytes_value)
# print(base64_encoded.decode())

#decrypt
flattened_hex_values = []
for i in range(0, len(b_b), 8):
x = 32*4 - 1 # 32round * 4
global result
result = [int(b_b[i + 1]), int(b_b[i + 0]), int(b_b[i + 3]), int(b_b[i + 2]), int(b_b[i + 5]), int(b_b[i + 4]), int(b_b[i + 7]), int(b_b[i + 6])]
for j in range(31, -1, -1):
def dec():
global x
y = x % 10
x = x - 4
return y

def combine(x, y):
return ((x << 8) | y)

def insert_result(i):
global result
re = combine(result[1], result[0]) ^ combine(result[3], result[2]) ^ i
last = [re & 0xff, re >> 8]
result = result[:2] + result[4:] + last

def update_result(f, l):
global result
result[0] = f
result[1] = l

def insert_result_2(f, l, i):
global result
re = combine(l, f) ^ combine(result[5], result[4]) ^ i
middle_elements = [re & 0xff, re >> 8]
result = [f, l] + middle_elements + result[6:] + result[:2]

dd = dec()
passwd_a = passwd[dd-3]
passwd_b = passwd[dd-2]
passwd_c = passwd[dd-1]
passwd_d = passwd[dd]

if j//8%2 == 0:
tmp7, tmp5 = result[2], result[3]
insert_result(j+1)
tmp3 = hex_array[tmp5 ^ passwd_d] ^ tmp7
tmp1 = hex_array[tmp3 ^ passwd_c] ^ tmp5
f = hex_array[tmp1 ^ passwd_b] ^ tmp3
l = hex_array[f ^ passwd_a] ^ tmp1
update_result(f, l)
else:
tmp7, tmp5 = result[2], result[3]
tmp3 = hex_array[tmp5 ^ passwd_d] ^ tmp7
tmp1 = hex_array[tmp3 ^ passwd_c] ^ tmp5
f = hex_array[tmp1 ^ passwd_b] ^ tmp3
l = hex_array[f ^ passwd_a] ^ tmp1
insert_result_2(f, l, j+1)

flattened_hex_values += [result[1], result[0], result[3], result[2], result[5], result[4], result[7], result[6]]
bytes_value = bytes(flattened_hex_values)
print(bytes_value.decode())


总结

赛题总结

就是对图片点击次数做判断,然后进入三块不同的区域

  • 第一部分告诉了我们加密的流程:需要输入密钥key,并给出了一个假的flag作为示例,将计算的结果base64编码。然后用信息框弹出告诉我们结果是这个。

  • 第二部分弹窗告诉我们最后需要解密的密文。

  • 第三部分用画图的形式告诉我们需要用到的key是什么。

做题总结

第一晚上看了前部分的判断理出了加密过程,然后就陷入了一种思维定势,flag和密钥都是可以通过加密算法倒推出来的,因为有个提示是flag是NepCTF{}格式的,这导致我周六做了一整天,没想明白密码怎么推导,孩子甚至被逼疯去找出题人诉苦去了。

直到最后准备放弃的时候,想了想要不把矩阵画出来看看(最开始我以为这是在画图片中的那只挑衅我的大’鲨’鱼),结果竟然是密钥,太菜了。