首页
社区
课程
招聘
[原创] 看雪 2025 KCTF 第六题 秘辛揭露
发表于: 2025-8-27 03:39 4187

[原创] 看雪 2025 KCTF 第六题 秘辛揭露

2025-8-27 03:39
4187

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"))    # second argument of function sub_12352547
 
 
sbox = list(bytes.fromhex("""\
29 23 BE 84 E1 6C D6 AE 52 90 49 F1 F1 BB E9 EB B3 A6 DB 3C 87 0C 3E 99 24 5E 0D 1C 06 B7 47 DE B3 12 4D C8 43 BB 8B A6 1F 03 5A 7D 09 38 25 1F 5D D4 CB FC 96 F5 45 3B 13 0D 89 0A 1C DB AE 32 20 9A 50 EE 40 78 36 FD 12 49 32 F6 9E 7D 49 DC AD 4F 14 F2 44 40 66 D0 6B C4 30 B7 32 3B A1 22 F6 22 91 9D E1 8B 1F DA B0 CA 99 02 B9 72 9D 49 2C 80 7E C5 99 D5 E9 80 B2 EA C9 CC 53 BF 67 D6 BF 14 D6 7E 2D DC 8E 66 83 EF 57 49 61 FF 69 8F 61 CD D1 1E 9D 9C 16 72 72 E6 1D F0 84 4F 4A 77 02 D7 E8 39 2C 53 CB C9 12 1E 33 74 9E 0C F4 D5 D4 9F D4 A4 59 7E 35 CF 32 22 F4 CC CF D3 90 2D 48 D3 8F 75 E6 D9 1D 2A E5 C0 F7 2B 78 81 87 44 0E 5F 50 00 D4 61 8D BE 7B 05 15 07 3B 33 82 1F 18 70 92 DA 64 54 CE B1 85 3E 69 15 F8 46 6A 04 96 73 0E D9 16 2F 67 68 D4 F7 4A 4A D0 57 68 76 5E 7C 6D FF AE E2 A7 39 5F 99 CE 6E F1 95 19 80 87 B1 96 58 CD 54 FC 4C 6C 9C 1E 1A 40 42 0E 65 BE 13 8D 4D 85 66 C3 BC 11 DE FE A2 2C DA C5 C8 D3 B7 B4 48 5A 45 EA 18 89 E5 E0 F9 52 35 EC 1B 47 AF DB A0 1F 12 E9 B3 3F C7 24 33 E6 BD 46 2A 88 53 76 7A 00 6F D8 3C 0D 81 59 D0 AA DD 01 75 D1 26 AC 77 4A 09 05 8B 0F F3 02 17 ED 57 F2 5D 70 08 B2 3E EF C1 A9 2F F5 A8 06 60 51 9F 1C CC 72 8C 31 98 29 EB AD 64 9E F8 B0 30 78 E4 9A 62 E1 9B 1D 63 10 84 74 C0 DC 15 49 7D 4B CB FB 16 0B 56 2D A1 E7 34 27 86 EE 0A C2 CF 50 C6 55 9D D4 61 8F 41 C9 D5 94 BB 20 79 8E 92 BA 68 E8 A3 25 3A 7F D9 BF A5 5B 14 DF FD 37 44 23 D6 83 32 A6 D7 7E 5C 6A 3D B8 F0 E3 7B 28 0C D2 CA B6 C4 43 22 82 03 F6 07 FA 97 21 90 6B 4E A4 AB 93 67 F4 71 38 F7 3B 69 2E 4F B5 B9 2B 8A 36 91 73 04 5E 4C EC 03 4C 73 E6 05 B4 31 0E AA AD CF D5 B0 CA 27 FF D8 9D 14 4D F4 79 27 59 42 7C 9C C1 F8 CD 8C 87 20 23 64 B8 A6 87 95 4C B0 5A 8D 4E 2D 99 E7 3D B1 60 DE B1 80 AD 08 41 E9 67 41 A5 D5 9F E4 18 9F 15 42 00 26 FE 4C D1 21 04 93 2F B3 8F 73 53 40 43 8A AF 7E CA 6F D5 CF D3 A1 95 CE 5A BE 65 27 2A F6 07 AD A1 BE 65 A6 B4 C9 C0 69 32 34 09 2C 4D 01 8F 17 56 C6 DB 9D C8 A6 D8 0B 88 81 38 61 6B 68 12 62 F9 54 D0 E7 71 17 48 78 0D 92 29 1D 86 29 99 72 DB 74 1C FA 4F 37 B8 B5 B0 95 57 F5 DF 80 6C 6D 8D 74 D9 8B 43 65 11 08 A5 F6 79 BD F7 EB 15 B8 E0 E1 60 8F 6E 3C 7B F4 5B 62 8A 8A 8F 27 5C F7 E5 87 4A 3B 32 9B 61 40 84 C6 C3 B1 A7 30 4A 10 EE 75 6F 03 2F 9E 6A EF 10 50 9B C8 81 43 29 28 8A F6 E9 9E 47 A1 81 48 31 6C CD A4 9E DE 81 A3 8C 98 10 FF 9A 43 CD CF 57 C7 50 59 BF BD 1C 27 03 28 7F 5D 89 5F B9 49 34 4E 60 3C E5 DE 02 98 42 B2 0D 2B B6 14 EC BB B8 2F 73 E2 51 7E 7D 1D D8 84 D3 1F 01 BE 50 6B 16 D6 43 21 83 19 15 18 98 2B 2C 2E 8B F9 0E DC BC F0 CA 0E 3D 6D 94 31 92 74 AF 8D B5 A4 90 D5 5E 6A 40 FC 80 76 02 4B 17 6B 36 B1 21 DB 7D 5A EA 72 1E 82 8D 71 A8 8C B8 5E D9 4E AF FA BF B0 94 74 1D 75 E5 DC 10 58 46 DA F2 5B 81 A0 7F 5C CB 1D 36 E9 49 74 02 55 D2 AC 1A 0B F7 A9 26 23 40 5B A3 33 B9 35 88 68 AD E1 2A D5 B2 32 5D 0A E5 5A DC E9 77 5D EB B5 69 C5 3A 6C 93 98 0D 57 EB 87 9A DF 04 68 B2 A2 D5 E6 A4 C6 BC 77 5F 8D C3 8F D6 2A 21 14 A9 D4 04 11 01 18 8D AE BB 73 1C 60 CA 20 CF 5D D6 2F 45 53 29 D7 A8 59 CC 0D EA 26 ED 55 4E 80 84 D9 2B F8 37 B8 ED D5 7A A0 5C 4E FA 9F 21 FC 3C 36 85 8E 81 B0 7D BF EE B1
"""))    # dumped at 0x123536B2
 
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):    # sub_12352547
    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
            # print(hex(v150))
 
        v45 = k
        c = global_a2[v45]
        v150 ^= c
        # print(hex(v150))
 
        for n in range(4):
            buffer[k*4 + n] = (v150 >> 8*n) & 0xff
    # print_buffer(buffer)
 
    for j in range(114514):
        for i in range(32):
            if i % 2 == 1 and i > 0:
                index = 0x100 + buffer[i]
                # print(hex(index))
                buffer[i] = sbox[index]
            elif i % 2 == 0 and i > 0:
                buffer[i] ^= buffer[i-1]
    # print_buffer(buffer)
    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")    # notice the encoding
    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")    # dumped at 0x12351A0E
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"))    # second argument of function sub_12352547
 
 
sbox = list(bytes.fromhex("""\
29 23 BE 84 E1 6C D6 AE 52 90 49 F1 F1 BB E9 EB B3 A6 DB 3C 87 0C 3E 99 24 5E 0D 1C 06 B7 47 DE B3 12 4D C8 43 BB 8B A6 1F 03 5A 7D 09 38 25 1F 5D D4 CB FC 96 F5 45 3B 13 0D 89 0A 1C DB AE 32 20 9A 50 EE 40 78 36 FD 12 49 32 F6 9E 7D 49 DC AD 4F 14 F2 44 40 66 D0 6B C4 30 B7 32 3B A1 22 F6 22 91 9D E1 8B 1F DA B0 CA 99 02 B9 72 9D 49 2C 80 7E C5 99 D5 E9 80 B2 EA C9 CC 53 BF 67 D6 BF 14 D6 7E 2D DC 8E 66 83 EF 57 49 61 FF 69 8F 61 CD D1 1E 9D 9C 16 72 72 E6 1D F0 84 4F 4A 77 02 D7 E8 39 2C 53 CB C9 12 1E 33 74 9E 0C F4 D5 D4 9F D4 A4 59 7E 35 CF 32 22 F4 CC CF D3 90 2D 48 D3 8F 75 E6 D9 1D 2A E5 C0 F7 2B 78 81 87 44 0E 5F 50 00 D4 61 8D BE 7B 05 15 07 3B 33 82 1F 18 70 92 DA 64 54 CE B1 85 3E 69 15 F8 46 6A 04 96 73 0E D9 16 2F 67 68 D4 F7 4A 4A D0 57 68 76 5E 7C 6D FF AE E2 A7 39 5F 99 CE 6E F1 95 19 80 87 B1 96 58 CD 54 FC 4C 6C 9C 1E 1A 40 42 0E 65 BE 13 8D 4D 85 66 C3 BC 11 DE FE A2 2C DA C5 C8 D3 B7 B4 48 5A 45 EA 18 89 E5 E0 F9 52 35 EC 1B 47 AF DB A0 1F 12 E9 B3 3F C7 24 33 E6 BD 46 2A 88 53 76 7A 00 6F D8 3C 0D 81 59 D0 AA DD 01 75 D1 26 AC 77 4A 09 05 8B 0F F3 02 17 ED 57 F2 5D 70 08 B2 3E EF C1 A9 2F F5 A8 06 60 51 9F 1C CC 72 8C 31 98 29 EB AD 64 9E F8 B0 30 78 E4 9A 62 E1 9B 1D 63 10 84 74 C0 DC 15 49 7D 4B CB FB 16 0B 56 2D A1 E7 34 27 86 EE 0A C2 CF 50 C6 55 9D D4 61 8F 41 C9 D5 94 BB 20 79 8E 92 BA 68 E8 A3 25 3A 7F D9 BF A5 5B 14 DF FD 37 44 23 D6 83 32 A6 D7 7E 5C 6A 3D B8 F0 E3 7B 28 0C D2 CA B6 C4 43 22 82 03 F6 07 FA 97 21 90 6B 4E A4 AB 93 67 F4 71 38 F7 3B 69 2E 4F B5 B9 2B 8A 36 91 73 04 5E 4C EC 03 4C 73 E6 05 B4 31 0E AA AD CF D5 B0 CA 27 FF D8 9D 14 4D F4 79 27 59 42 7C 9C C1 F8 CD 8C 87 20 23 64 B8 A6 87 95 4C B0 5A 8D 4E 2D 99 E7 3D B1 60 DE B1 80 AD 08 41 E9 67 41 A5 D5 9F E4 18 9F 15 42 00 26 FE 4C D1 21 04 93 2F B3 8F 73 53 40 43 8A AF 7E CA 6F D5 CF D3 A1 95 CE 5A BE 65 27 2A F6 07 AD A1 BE 65 A6 B4 C9 C0 69 32 34 09 2C 4D 01 8F 17 56 C6 DB 9D C8 A6 D8 0B 88 81 38 61 6B 68 12 62 F9 54 D0 E7 71 17 48 78 0D 92 29 1D 86 29 99 72 DB 74 1C FA 4F 37 B8 B5 B0 95 57 F5 DF 80 6C 6D 8D 74 D9 8B 43 65 11 08 A5 F6 79 BD F7 EB 15 B8 E0 E1 60 8F 6E 3C 7B F4 5B 62 8A 8A 8F 27 5C F7 E5 87 4A 3B 32 9B 61 40 84 C6 C3 B1 A7 30 4A 10 EE 75 6F 03 2F 9E 6A EF 10 50 9B C8 81 43 29 28 8A F6 E9 9E 47 A1 81 48 31 6C CD A4 9E DE 81 A3 8C 98 10 FF 9A 43 CD CF 57 C7 50 59 BF BD 1C 27 03 28 7F 5D 89 5F B9 49 34 4E 60 3C E5 DE 02 98 42 B2 0D 2B B6 14 EC BB B8 2F 73 E2 51 7E 7D 1D D8 84 D3 1F 01 BE 50 6B 16 D6 43 21 83 19 15 18 98 2B 2C 2E 8B F9 0E DC BC F0 CA 0E 3D 6D 94 31 92 74 AF 8D B5 A4 90 D5 5E 6A 40 FC 80 76 02 4B 17 6B 36 B1 21 DB 7D 5A EA 72 1E 82 8D 71 A8 8C B8 5E D9 4E AF FA BF B0 94 74 1D 75 E5 DC 10 58 46 DA F2 5B 81 A0 7F 5C CB 1D 36 E9 49 74 02 55 D2 AC 1A 0B F7 A9 26 23 40 5B A3 33 B9 35 88 68 AD E1 2A D5 B2 32 5D 0A E5 5A DC E9 77 5D EB B5 69 C5 3A 6C 93 98 0D 57 EB 87 9A DF 04 68 B2 A2 D5 E6 A4 C6 BC 77 5F 8D C3 8F D6 2A 21 14 A9 D4 04 11 01 18 8D AE BB 73 1C 60 CA 20 CF 5D D6 2F 45 53 29 D7 A8 59 CC 0D EA 26 ED 55 4E 80 84 D9 2B F8 37 B8 ED D5 7A A0 5C 4E FA 9F 21 FC 3C 36 85 8E 81 B0 7D BF EE B1
"""))    # dumped at 0x123536B2
 
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):    # sub_12352547
    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
            # print(hex(v150))
 
        v45 = k
        c = global_a2[v45]
        v150 ^= c
        # print(hex(v150))
 
        for n in range(4):
            buffer[k*4 + n] = (v150 >> 8*n) & 0xff

传播安全知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 0
支持
分享
最新回复 (3)
雪    币: 29051
活跃值: (15028)
能力值: ( LV15,RANK:2060 )
在线值:
发帖
回帖
粉丝
2

补充一下,我用的IDA 9.2.250718版本,全程没有遇到任何反编译报错,看来高版本已经修复了bextr指令的问题

最后于 2025-8-27 13:45 被mb_mgodlfyn编辑 ,原因:
2025-8-27 13:43
0
雪    币: 1508
活跃值: (2041)
能力值: ( LV12,RANK:229 )
在线值:
发帖
回帖
粉丝
3

sub_12351368:一个奇怪的数组累乘,但对于上面的字符串结构由于第一个4字节总是1,似乎等价于取第二个4字节出来,也即获取字符串的长度 

易语言对于核心库相关的操作设计了一份独立的调用约定,简单来说,IDA的反编译结果存在错误。

sub_12351368的实际功能是取数组的指针和大小,下面这段汇编代码等价于crypt_key { 75, 12, 84, 15, 50, 0, 2, 53 }

12351584

B8 74623D12

mov eax,天龙八部之crackme_2——1.123D6274


12351589

50

push eax


1235158A

50

push eax


1235158B

8BD8

mov ebx,eax


1235158D

E8 D6FDFFFF

call <天龙八部之crackme_2——1.取数组成员内容>


12351592

58

pop eax


12351593

2BD8

sub ebx,eax


12351595

03CB

add ecx,ebx


12351597

51

push ecx


12351598

E8 FA230000

call <天龙八部之crackme_2——1.分配内存>


1235159D

59

pop ecx


1235159E

5E

pop esi


1235159F

8BF8

mov edi,eax


123515A1

F3:A4

rep movsb


123515A3

50

push eax


123515A4

8B1D 0C874012

mov ebx,dword ptr ds:[1240870C]

1240870C:"P5m"

123515AA

53

push ebx


123515AB

E8 E1230000

call <天龙八部之crackme_2——1.释放内存>


123515B0

83C4 04

add esp,4


123515B3

58

pop eax


123515B4

A3 0C874012

mov dword ptr ds:[1240870C],eax

1240870C:"P5m"

sub_12354220:意义不明,但调试看下来没做什么事 

%模运算,由于易语言调用约定的问题,IDA反编译结果可能会发生变化:

五个参数:sub_12353BC0(2, (double)v207, -2147482111, 1024.0 - (double)dword_12408704 * 2.0, -2147482111)

四个参数:sub_12353BC0(1, v16[v18], 0, -2147482879)

2025-8-27 14:27
0
雪    币: 0
活跃值: (502)
能力值: ( LV2,RANK:15 )
在线值:
发帖
回帖
粉丝
4
mb_mgodlfyn 补充一下,我用的IDA&nbsp;9.2.250718版本,全程没有遇到任何反编译报错,看来高版本已经修复了bextr指令的问题
似乎9.0以上都没报错
2025-8-27 16:31
0
游客
登录 | 注册 方可回帖
返回