MFC框架的易语言32位程序。IDA自动识别出了部分mfc的函数,结合AI能更快速的判断库函数的功能。尝试导入mfc140.i386.pdb的结构体发现偏移不太对,可能是版本不对。
但是程序的函数非常多,而且C++虚函数表的间接调用非常讨厌,相当长的时间根本找不到按钮的回调函数以及输入校验的函数所在。
通过AI得知mfc封装了windows api,获取输入框的内容最后还是会调用GetWindowTextA,因此对它下断点,然后向上跟踪调用栈分析控制流,以及对获取的输入下数据断点跟踪分析数据流。
sub_12356850和sub_12357BA0两个函数调用的CWnd::GetWindowTextA获得的是输入框的内容,后者被sub_12357E20调用,如果返回1,会调用三个SendMessageA,但具体检查逻辑不在这里。
从数据断点追踪到一路调用:sub_12362670 <- sub_12367500 <- sub_12366BD0 <- sub_1236E250 <- sub_123539A9 <- sub_12351004。
sub_12351004没有直接调用的交叉引用,只有一个全局变量的引用。里面还引用了大量GBK编码的常量字符串,其中包含了错误输入的弹窗内容(输入测试发现30个字符以内无反应,31个字符及以上会弹窗)。(虽然很早就从字符串交叉引用发现了sub_12351004,但相当长的时间内并没有搞清楚它的作用)
这个函数上半部分可以忽略,下半部分首先调用sub_123512E4获取输入字符串(返回的是一个结构,前4字节意义不明,后4字节是长度,再后面是字符串内容。后续此结构反复出现,在IDA里定义出type然后标记相关变量,增强伪代码的展示效果)。
然后通过sub_123543A0计算长度(被sub_12353BC0包装,此包装函数后续也反复出现,但功能只是封装参数(va_arg等)和返回值,无实际逻辑)。
下面是长度检查,如果>=31,则会调用sub_12351552。
后续分析发现sub_12351552是真正的输入框内容正确性的检查逻辑所在。里面引用调用的一些函数意义和全局变量:
至此才完全确定,sub_12352547是真正对输入内容做具体运算的逻辑所在,而输入校验要求计算后的值与dword_12408710的常量值相等。
sub_12352547接收两个字符串结构作为参数,第一个参数a1来自输入框的内容,第二个参数a2调试发现是固定常量。函数里面有很多浮点数的复杂运算,但都与a1无关,全部跳过。
需要关注的几个位置:
反编译代码不贴了,根据上面的分析以及调试验证和修正,dump出所需的常量数据后,直接整理出相应的计算逻辑,并写出反向逻辑,计算得到正确的输入。
最终正确的输入:
def split_bytes_to_ints(b):
assert len(b) % 4 == 0
count = len(b) // 4
return [int.from_bytes(b[4*i:4*i+4], 'little') for i in range(count)]
global_a2 = list(bytes.fromhex("4B 0C 54 0F 32 00 02 35"))
sbox = list(bytes.fromhex(
))
assert len(sbox) == 0x400
assert len(sbox[0x100:0x200]) == 0x100
assert len(set(sbox[0x100:0x200])) == 0x100
invsbox = [None] * 0x100
for i in range(256):
invsbox[sbox[0x100+i]] = i
def print_buffer(buf):
for n in buf:
print(hex(n), end=" ")
print()
def calc_on_input(input_str):
assert len(input_str) == 32
buffer = [ord(c) for c in input_str]
for k in range(8):
v150 = 0
for m in range(4):
v150 += buffer[k*4 + m] << 8*m
v45 = k
c = global_a2[v45]
v150 ^= c
for n in range(4):
buffer[k*4 + n] = (v150 >> 8*n) & 0xff
for j in range(114514):
for i in range(32):
if i % 2 == 1 and i > 0:
index = 0x100 + buffer[i]
buffer[i] = sbox[index]
elif i % 2 == 0 and i > 0:
buffer[i] ^= buffer[i-1]
result = bytes(buffer)
return result
def reverse_calc_on_result(result_bytes):
assert len(result_bytes) == 32
buffer = list(result_bytes)
for j in range(114514):
for i in range(31, -1, -1):
if i % 2 == 1 and i > 0:
buffer[i] = invsbox[buffer[i]]
elif i % 2 == 0 and i > 0:
buffer[i] ^= buffer[i-1]
for k in range(8):
buffer[4*k] ^= global_a2[k]
input_str = bytes(buffer).decode("gbk")
return input_str
expected_result = bytes.fromhex("00 1D 3B 29 70 12 69 B7 6C 0F 4D 5C 9F 5B 6C 1B B5 47 A2 28 C0 F8 DC E0 7A F8 D6 28 F6 F8 93 B3")
print(reverse_calc_on_result(expected_result))
def split_bytes_to_ints(b):
assert len(b) % 4 == 0
count = len(b) // 4
return [int.from_bytes(b[4*i:4*i+4], 'little') for i in range(count)]
global_a2 = list(bytes.fromhex("4B 0C 54 0F 32 00 02 35"))
sbox = list(bytes.fromhex(
))
assert len(sbox) == 0x400
assert len(sbox[0x100:0x200]) == 0x100
assert len(set(sbox[0x100:0x200])) == 0x100
invsbox = [None] * 0x100
for i in range(256):
invsbox[sbox[0x100+i]] = i
def print_buffer(buf):
for n in buf:
print(hex(n), end=" ")
print()
def calc_on_input(input_str):
assert len(input_str) == 32
buffer = [ord(c) for c in input_str]
for k in range(8):
v150 = 0
for m in range(4):
v150 += buffer[k*4 + m] << 8*m
v45 = k
c = global_a2[v45]
v150 ^= c
for n in range(4):
buffer[k*4 + n] = (v150 >> 8*n) & 0xff
传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!