首页
社区
课程
招聘
X‘NUCA 2020 unravelmfc
发表于: 2020-11-12 19:56 1850

X‘NUCA 2020 unravelmfc

2020-11-12 19:56
1850

目录
]

RE

unravelmfc

题目提示:烦人的windows底层api

解题思路:

  1. 打开程序,发现确定按钮不能点击
    用Resources Hacker改变空间属性以后重新打开,发现确定按钮仍然无法打开
    查找关键api,EnableWindow
    1
    2
    3
    // hWnd:被允许/禁止的窗口句柄
    // bEnable:1 允许,0 禁止
    BOOL EnableWindow(HWND hWnd,BOOL bEnable)
    发现程序中多处调用EnableWindow,且有多处禁止了这个按钮,对程序进行patch
    再次,打开程序,发现按钮可以点击了,但是点击之后无法查看对应的消息处理函数
  2. 乱找了一通api,找到了比较关键的API:GetDlgItem,才定位到消息处理函数
    1
    2
    3
    4
    5
    6
    //返回窗口中,指定参数ID的子元素的句柄
    //需要明确的概念,标识符!=句柄,该函数的返回值是子元素的句柄
    HWND GetDlgItem(
    HWNDhDlg, // handle to dialog box
    intnIDDlgItem// control identifier
    );
    后来发现自己蠢了,这里至少还有两种更方便的方法去找消息处理函数
    1
    2
    1. IDA找不到汉字的提示消息,但是OD查找字符串可以找到"恭喜"两个字。 
    2. 寻找对应的消息处理函数      
  3. 后面逐步分析,发现这个程序还是加了壳并且做了控制流混淆的。
    如何对抗花指令?

    1
    2
    1. 笨方法:动态调试观察程序大体意思    
    2. 清洗指令(==未尝试==)    

    找到对应的消息处理函数就容易了,加密的步骤共有三步:

    1. 输入长度应该是66位,分两步处理输入
    2. 前半段输入,先按如下步骤处理:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      ```
      v6 = 0;
      for ( i = 0; i < halflen; ++i )
      {
      v7 = (v7 + 1) % 256;
      v6 = (v6 + (unsigned __int8)byte_875088[v7]) % 256;
      v3 = byte_875088[v7];
      byte_875088[v7] = byte_875088[v6];
      byte_875088[v6] = v3;
      v2 = i + input_flag;
      *(_BYTE *)(i + input_flag) ^= byte_875088[((unsigned __int8)byte_875088[v6] + (unsigned __int8)byte_875088[v7])
                                              % 256];
      }
      ```

      需要注意的是,数组byte_875088的数据会在程序运行过程中改变,需要从内存中dump。

    3. 对上一步的结果,作类似于base64的变换,每3个字符替换成4个字符,替换规则如下:
      111111 112222 222233 333333
      每一块再加上23,最后与字符串<code>"$HM%/NEX,79PBN\C/BQLVSW,*/'8T#UMC4%EG@@@,.%5"</code>比较。(==需要注意的是ida的静态分析结果中也有类似的字符串,我们仍然需要从内存中dump该字符串==)

    4. 后半段输入,先是改变了默认delta的TEA加密,共分4段,key在数组dword_875048中,直接用静态分析的就可以。(因为delta改变了,findcyrpt插件居然没有查出来,不过没关系,可以看出来)
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      v7 = 32;
      v18 = v14;
      v4 = v15;
      v15 = 0;
      v20 = v4;
      v9 = 0;
      do
      {
        v9 += 0x2433B95A;
        v6 += (v9 + v8) ^ (v21 + 16 * v8) ^ (v14 + (v8 >> 5));
        v10 = (v9 + v6) ^ (v19 + 16 * v6) ^ ((unsigned int)v20 + (v6 >> 5));
        v14 = v18;
        v15 = (int *)(v10 + v8);
        --v7;
        v8 = (unsigned int)v15;
      }
      while ( v7 );
    5. 最后还有一个超级简单的方程组,用于判断

      1
      2
      3
      4
      5
      6
      7
      8
      9
      if ( v12 != 0x2F9970FF )
      goto LABEL_25;
      index = (char *)8;
      if ( v13 != 0xDF3634AE )
      goto LABEL_25;
       
      v14 - v11 != 0x3F66B755B4490579i64
      || (true_input = 0, index = (char *)(v11 + v15), v11 + v15 != 0x162F924623D2CAE0i64)
      || (index = (char *)16, true_input = v15 - v14, v15 - v14 != 0x7C3C71F1B295D77Fi64) )

      其中v12、v13是第段输入加密的结果,v11是第一段加密的结果,v14、v15是第3、4段的加密结果

总结与反思

对程序的认知:

  1. mfc程序,禁用按钮
    利用资源修改器修改按钮属性,仍然不行则需要取程序中寻找关键api
  2. 寻找消息处理函数(这里我花费了很长时间,一个主要原因是flag验证失败没有回显,但归根结底还是因为我不熟悉windows的消息处理机制)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    //MFC消息映射表
    struct AFX_MSGMAP{
    AFX_MSGMAP * pBaseMessageMap;  //指向GetMessageMap的函数指针
    AFX_MSGMAP_ENTRY * lpEntries;   //指向AFX_MSGMAP_ENTRY,往往指向下一个紧邻数据结构
    }
    struct AFX_MSGMAP_ENTRY{
        UINT nMessage;    //Windows Message
        UINT nCode        //Control code or WM_NOTIFY code
        UINT nID;         //control ID (or 0 for windows messages)
        UINT nLastID;     //used for entries specifying a range of control id's
        UINT nSig;        //signature type(action) or pointer to message
        AFX_PMSG pfn;     //routine to call (or specical value)
    }
    共有三种方法找到对应控件的消息处理函数:
    1. 利用idc脚本寻找AFX_MESSAGEMAP,这种方法存在局限性,只能找到如下内存分布的消息映射表
      图片描述
      但是实际逆向过程中,内存分布不一定是这样,所以使用idc脚本不一定能找到AFX_MSGMAP
    2. 通过函数(GetMessageMap)调用查找,需要动态调试,函数路径如下:
      CWnd::WindowProc -> CWnd::OnWndMsg(IDA可能会识别该函数)->GetMessageMap
      在这个题中,这个方法似乎也不是很好用,GetMessageMap函数不是很好找。
    3. 获取控件ID,例如本题输入框的ID是1000即0x3e8,确定按钮的ID是1,1太小了,我们搜索0x3E8,利用IDA search查找所有.rdata段中的对应序列,可以在如下位置找到对应的消息映射列表
      1
      2
      3
      4
      5
      6
      7
      8
      .rdata:00A98CD8 12 01 00 00 00 00 00 00 00 00+stru_A98CD8     AFX_MSGMAP_ENTRY <112h, 0, 0, 0, 1Fh, offset sub_792113>
      .rdata:00A98CD8 00 00 00 00 00 00 1F 00 00 00+                                        ; DATA XREF: .rdata:00A98D9C↓o
      .rdata:00A98CD8 13 21 79 00 10 00 00 00 00 00+                AFX_MSGMAP_ENTRY <10h, 0, 0, 0, 13h, offset sub_7902FF>
      .rdata:00A98CD8 00 00 00 00 00 00 00 00 00 00+                AFX_MSGMAP_ENTRY <0Fh, 0, 0, 0, 13h, offset sub_790EBC>
      .rdata:00A98CD8 13 00 00 00 FF 02 79 00 0F 00+                AFX_MSGMAP_ENTRY <37h, 0, 0, 0, 29h, offset sub_79C424>
      .rdata:00A98CD8 00 00 00 00 00 00 00 00 00 00+                AFX_MSGMAP_ENTRY <111h, 0, 1, 1, 3Ah, offset sub_790AC5>
      .rdata:00A98CD8 00 00 00 00 13 00 00 00 BC 0E+                AFX_MSGMAP_ENTRY <111h, 300h, 3E8h, 3E8h, 3Ah, offset sub_78F4C2>
      .rdata:00A98CD8 79 00 37 00 00 00 00 00 00 00+                AFX_MSGMAP_ENTRY <111h, 0, 2, 2, 3Ah, offset sub_78F206>
      到此为止,就找到了我们需要的消息处理函数。
  3. 程序中存在自解压(密),很多数据都是动态变化的,并且该程序存在控制流混淆。这里我采用了最笨的方法,动态分析读汇编指令。
  4. 太菜了,还是没搞明白这个程序的消息是怎么传递的,爬...

附录

解密脚本:

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
from Crypto.Util.number import *
 
cipher = "$HM%/NEX,79PBN\C/BQLVSW,*/'8T#UMC4%EG@@@,.%5"
arr = [83, 191, 130, 73, 58, 8, 224, 253, 138, 187, 68, 199, 196, 153, 6, 60, 194, 128, 104, 207, 81, 91, 220, 140, 118, 180, 190, 98, 15, 50, 234, 122, 252, 131, 217, 99, 219, 201, 247, 198, 212, 108, 203, 175, 233, 179, 139, 126, 245, 161, 48, 176, 186, 146, 218, 159, 67, 137, 26, 158, 89, 111, 231, 216, 116, 56, 66, 18, 90, 160, 173, 4, 63, 79, 237, 183, 77, 149, 52, 29, 12, 181, 96, 11, 109, 92, 229, 162, 5, 249, 44, 200, 235, 211, 129, 46, 2, 115, 240, 243, 148, 236, 45, 246, 151, 177, 123, 193, 165, 47, 106, 71, 7, 133, 184, 189, 24, 202, 94, 150, 142, 242, 232, 64, 113, 255, 93, 205, 227, 55, 225, 75, 254, 209, 117, 3, 69, 135, 102, 82, 49, 141, 76, 178, 155, 57, 22, 1, 171, 84, 144, 152, 43, 244, 167, 10, 53, 39, 13, 97, 164, 168, 107, 72, 65, 134, 35, 14, 86, 88, 70, 34, 221, 80, 121, 182, 157, 215, 147, 41, 119, 136, 172, 238, 59, 38, 40, 62, 36, 132, 103, 188, 54, 120, 78, 33, 27, 30, 51, 204, 25, 95, 37, 112, 85, 9, 32, 185, 169, 228, 143, 124, 101, 20, 208, 250, 127, 110, 105, 16, 87, 223, 170, 174, 163, 23, 31, 100, 28, 114, 251, 222, 195, 61, 17, 42, 21, 192, 241, 0, 239, 206, 197, 145, 166, 156, 19, 210, 213, 226, 125, 214, 248, 74, 154, 230]
# print(len(cipher))
 
def decode2(cipher):
    tmp = [ord(char) - 0x23 for char in cipher]
    res = []
    for i in range(11):
        old = tmp[i * 4:(i + 1) * 4]
        new = [0,0,0]
        new[0] = (old[0] << 2) | (old[1] >> 4)
        new[1] = ((old[1] & 0xf) << 4) | ((old[2] >> 2) & 0xf)
        new[2] = ((old[2] & 0x3) << 6) | old[3]
        res.extend(new)
    print([hex(i) for i in res])
    return res
    # print([hex(ord(i)-0x23) for i in cipher])
 
def decode1(res1):
    print([hex(i) for i in res1])
    v6 = 0
    v7 = 0
    for i in range(33):
        v7 = (v7 + 1) & 0xff
        v6 = (v6 + arr[v7]) & 0xff
        v3 = arr[v7]
        arr[v7] = arr[v6]
        arr[v6] = v3
        res1[i] ^= arr[(arr[v6] + arr[v7]) & 0xff]
    # print([hex(i) for i in res1])
    # print(''.join([chr(i) for i in res1]))
    return res1
 
res = b''
 
class TEA:
    """
    TEA加密与解密
    """
    def __init__(self, key, delta=0x9E3779B9):
        self.delta = delta
        self.key = key
 
    def encrypt(self,plain_text):
        """
        encrypt - Encode plain_text and return cipher_text
        :param plain_text: tuple(int32,int32)
        :return: tuple(int32,int32)
        """
        left,right = plain_text[0],plain_text[1]
        n,d = 32,0
        while n > 0:
            d += self.delta
            d = d & 0xffffffff
            left += ((right << 4) + self.key[0]) ^ (right + d) ^ ((right >> 5) + self.key[1])
            left = left & 0xffffffff
            right += ((left << 4) + self.key[2]) ^ (left + d) ^ ((left >> 5) + self.key[3])
            right = right & 0xffffffff
            n -= 1
        return left,right,d
 
    def decrypt(self,cipher_text):
        """
        decrypt - Decode cipher_text and return plain_text
        :param cipher_text:
        :return:
        """
        n,x = 32,self.get_final_delta()
        left,right = cipher_text[0], cipher_text[1]
        for i in range(32):
            # print(hex(right))
            right =(right | 0x100000000) - \
                   ((((left << 4) + self.key[2]) ^ (left + x) ^ ((left >> 5) + self.key[3])) & 0xffffffff)
            right = right & 0xffffffff
            left -= ((right << 4) + self.key[0]) ^ (right + x) ^ ((right >> 5) + self.key[1])
            left = left & 0xffffffff
            x -= self.delta
            x = x & 0xffffffff
        global res
        res +=(long_to_bytes(right)+long_to_bytes(left))[::-1]
        return left,right
 
    def get_final_delta(self):
        d = 0
        for i in range(32):
            d += self.delta
            d = d & 0xffffffff
        return d
 
 
a = 0x3F66B755B4490579
b = 0x1162F924623D2CAE0
c = 0x7C3C71F1B295D77F
 
v15 = (a+b+c) // 2
v14 = v15 - c
v11 = v14 - a
print(hex(v15))
print(hex(v14))
print(hex(v11))
keyarr = [3647016194, 716023165, 2742368241, 3265149203, 3583257832, 1619840614, 1834562594, 568710898, 3980038709,
          2645385924,945185819, 1912036253, 3705592552, 3939684768, 3133470052, 3662115500]
# print(len(key))
ciphers = [v11 & 0xffffffff,v11 >> 32, 0x2F9970FF, 0xDF3634AE,v14 & 0xffffffff, v14 >> 32,v15 & 0xffffffff, v15 >> 32]
# print([hex(i) for i in ciphers])
 
for rounds in range(4):
    delta = 0x2433B95A
    key = keyarr[rounds*4:(rounds+1)*4]
    cipher_text = ciphers[rounds*2:(rounds+1)*2][::-1]
    t = TEA(key,delta=delta)
    t.decrypt(cipher_text)
 
print("flag:",''.join([chr(i) for i in decode1(decode2(cipher))]).encode()+res)
 
# Fr4nk1y_MfC_l5_t0O_ComPIeX_4nd_dlff1cUlt_foR_THe_r0Ok1E_t0_REver5e

参考文章

https://www.cnblogs.com/h2zZhou/p/10593168.html
https://www.freebuf.com/articles/system/242305.html


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

最后于 2020-11-12 20:01 被n0rthwater编辑 ,原因: 更新表格
收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回
//