-
-
X‘NUCA 2020 unravelmfc
-
发表于: 2020-11-12 19:56 1850
-
]
RE
unravelmfc
题目提示:烦人的windows底层api
解题思路:
- 打开程序,发现确定按钮不能点击
用Resources Hacker改变空间属性以后重新打开,发现确定按钮仍然无法打开
查找关键api,EnableWindow
发现程序中多处调用EnableWindow,且有多处禁止了这个按钮,对程序进行patch123/
/
hWnd:被允许
/
禁止的窗口句柄
/
/
bEnable:
1
允许,
0
禁止
BOOL
EnableWindow(HWND hWnd,
BOOL
bEnable)
再次,打开程序,发现按钮可以点击了,但是点击之后无法查看对应的消息处理函数 - 乱找了一通api,找到了比较关键的API:GetDlgItem,才定位到消息处理函数
后来发现自己蠢了,这里至少还有两种更方便的方法去找消息处理函数123456/
/
返回窗口中,指定参数
ID
的子元素的句柄
/
/
需要明确的概念,标识符!
=
句柄,该函数的返回值是子元素的句柄
HWND GetDlgItem(
HWNDhDlg,
/
/
handle to dialog box
intnIDDlgItem
/
/
control identifier
);
121.
IDA找不到汉字的提示消息,但是OD查找字符串可以找到
"恭喜"
两个字。
2.
寻找对应的消息处理函数
后面逐步分析,发现这个程序还是加了壳并且做了控制流混淆的。
如何对抗花指令?121.
笨方法:动态调试观察程序大体意思
2.
清洗指令(
=
=
未尝试
=
=
)
找到对应的消息处理函数就容易了,加密的步骤共有三步:
- 输入长度应该是66位,分两步处理输入
前半段输入,先按如下步骤处理:
1234567891011121314```
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。
对上一步的结果,作类似于base64的变换,每3个字符替换成4个字符,替换规则如下:
111111 112222 222233 333333
每一块再加上23,最后与字符串<code>"$HM%/NEX,79PBN\C/BQLVSW,*/'8T#UMC4%EG@@@,.%5"</code>比较。(==需要注意的是ida的静态分析结果中也有类似的字符串,我们仍然需要从内存中dump该字符串==)- 后半段输入,先是改变了默认delta的TEA加密,共分4段,key在数组dword_875048中,直接用静态分析的就可以。(因为delta改变了,findcyrpt插件居然没有查出来,不过没关系,可以看出来)1234567891011121314151617
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 );
最后还有一个超级简单的方程组,用于判断
123456789if
( 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段的加密结果
总结与反思
对程序的认知:
- mfc程序,禁用按钮
利用资源修改器修改按钮属性,仍然不行则需要取程序中寻找关键api - 寻找消息处理函数(这里我花费了很长时间,一个主要原因是flag验证失败没有回显,但归根结底还是因为我不熟悉windows的消息处理机制)
共有三种方法找到对应控件的消息处理函数:12345678910111213/
/
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)
}
- 利用idc脚本寻找AFX_MESSAGEMAP,这种方法存在局限性,只能找到如下内存分布的消息映射表
但是实际逆向过程中,内存分布不一定是这样,所以使用idc脚本不一定能找到AFX_MSGMAP - 通过函数(GetMessageMap)调用查找,需要动态调试,函数路径如下:
CWnd::WindowProc -> CWnd::OnWndMsg(IDA可能会识别该函数)->GetMessageMap
在这个题中,这个方法似乎也不是很好用,GetMessageMap函数不是很好找。 - 获取控件ID,例如本题输入框的ID是1000即0x3e8,确定按钮的ID是1,1太小了,我们搜索0x3E8,利用IDA search查找所有.rdata段中的对应序列,可以在如下位置找到对应的消息映射列表
到此为止,就找到了我们需要的消息处理函数。12345678.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>
- 利用idc脚本寻找AFX_MESSAGEMAP,这种方法存在局限性,只能找到如下内存分布的消息映射表
- 程序中存在自解压(密),很多数据都是动态变化的,并且该程序存在控制流混淆。这里我采用了最笨的方法,动态分析读汇编指令。
- 太菜了,还是没搞明白这个程序的消息是怎么传递的,爬...
附录
解密脚本:
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编辑
,原因: 更新表格
赞赏
他的文章
看原图
赞赏
雪币:
留言: