Target:样品为VC 6.0 MFC CHello 基础编程样例
0x01 60s内攻破的碰撞成分
0x02 动态分析与静态分析之争
0x03 MFC的启动过程
0x01 60s内攻破的碰撞成分
贞:从拿到样品开始算,如何在60s内得到验证的passKey?
(1)样品的图标折射出MFC的特征;
(2)拖进已经打开的IDA开始自动分析,同时双击启动样品实例;
(3)打开IDA->View->Open subviews->Strings查阅字符信息,如下;
Address Length Type String
.rdata:0040333E 00000006 C (16 bits) @7
.rdata:00403558 00000008 C (&A)...
.rdata:00403560 00000006 C pass!
.rdata:00403580 00000017 C WelcomeToKanXueCtf2017
.rdata:0040359E 00000006 C pass!
.rdata:0040392C 0000000A C MFC42.DLL
.rdata:00403976 0000000B C MSVCRT.dll
.rdata:00403A8A 0000000D C KERNEL32.dll
.rdata:00403B2C 0000000B C USER32.dll
(4)若不怀疑CTF存在简单性的可能,直接复制WelcomeToKanXueCtf2017到提交框就能恰好碰撞成功。
(5)若有所怀疑,且觉得WelcomeToKanXueCtf2017更可能是欢迎标题,敏感点落在pass!字符串身上。
搁置验证尝试,IDA交叉引用pass!(快捷键X)到函数 Hi_msg_pass_success_401770 的如下代码段
.text:0040177B push offset Caption ;
.text:0040177B ; 恭喜!
.text:00401780 push offset Text ; "pass!"
.text:00401785 push 0 ; hWnd
.text:00401787 call ds:MessageBoxA
(6)继续追溯Hi_msg_pass_success_401770的交叉引用到Hi_CDialogPassCheck__Check_sub_4017F0函数体内,如下;
在调用提示通过的函数Hi_msg_pass_success_401770上下文发现了strcmp 和 "WelcomeToKanXueCtf2017",
这时可以足够的信任,"WelcomeToKanXueCtf2017"至少作为passKey最终比对明文的一部分,
再多看一眼局部变量loc_passKeyPtr在上文的来源,其直接就是CEdit的内容,
中间没有任何变换处理,与"WelcomeToKanXueCtf2017"比对后也没有更多的比对操作,
基本可以肯定"WelcomeToKanXueCtf2017"就是该CTF的签到暗语,直接复制提交。
.text:004017F0 Hi_CDialogPassCheck__Check_sub_4017F0 proc near
.text:004017F0 loc_passKeyPtr = dword ptr -8
.text:004017F0 loc_thisptrCDialogPassCheck= dword ptr -4
.text:004017F0 push ebp
.text:004017F1 mov ebp, esp
.text:004017F3 sub esp, 48h
.text:004017F6 push ebx
.text:004017F7 push esi
.text:004017F8 push edi
.text:004017F9 mov [ebp+loc_thisptrCDialogPassCheck], ecx
.text:004017FC mov eax, [ebp+loc_thisptrCDialogPassCheck]
.text:004017FF add eax, 64h
.text:00401802 push eax ; struct CString *
.text:00401803 push 3EAh ; int //CEdit 控件ID
.text:00401808 mov ecx, [ebp+loc_thisptrCDialogPassCheck] ; this
.text:0040180B call CWnd::GetDlgItem(int) //获取CEdit空间对象
.text:00401810 mov ecx, eax ; this
.text:00401812 call CWnd::GetWindowTextA(CString &)
//将CEdit的字符值赋予验证对话框对象位于偏移0x64处的passKey成员
//loc_thisptrCDialogPassCheck.m_64h_CString_passKey = input_pass_key
.text:00401817 mov ecx, [ebp+loc_thisptrCDialogPassCheck]
.text:0040181A add ecx, 64h
.text:0040181D call Hi_CString_GetLength_4018D0 //passKey 长度
.text:00401822 push eax ; int
.text:00401823 mov ecx, [ebp+loc_thisptrCDialogPassCheck]
.text:00401826 add ecx, 64h ; this
.text:00401829 call CString::GetBuffer(int) //passKey Ptr
.text:0040182E mov [ebp+loc_passKeyPtr], eax
.text:00401831 mov ecx, [ebp+loc_passKeyPtr]
.text:00401834 push ecx ; loc_passKeyPtr
.text:00401835 call strlen
.text:0040183A add esp, 4
.text:0040183D test eax, eax
.text:0040183F jnz short loc_401854
.text:00401841 push 0 ; unsigned int
.text:00401843 push 0 ; char *
.text:00401845 push offset byte_403598 ;
.text:00401845 ; 请输入pass!
.text:0040184A mov ecx, [ebp+loc_thisptrCDialogPassCheck] ; this
.text:0040184D call CWnd::MessageBoxA(char const *,char const *,uint)
.text:00401852 jmp short loc_401875
.text:00401854 ; ---------------------------------------------------------------------------
.text:00401854 loc_401854:
.text:00401854 push offset Str2 ; "WelcomeToKanXueCtf2017"
.text:00401859 mov edx, [ebp+loc_passKeyPtr]
.text:0040185C push edx ; Str1
.text:0040185D call strcmp
.text:00401862 add esp, 8
.text:00401865 test eax, eax
.text:00401867 jnz short loc_401870
.text:00401869 call Hi_msg_pass_success_401770 ;
.text:00401869 ; MessageBoxA(0,"pass!","恭喜!")
.text:0040186E jmp short loc_401875
.text:00401870 ; ---------------------------------------------------------------------------
.text:00401870 loc_401870:
.text:00401870 call Hi_msg_ComeOn_fail_4017B0 ;
.text:00401870 ; MessageBoxA(0,"加油!","错了!")
.text:00401875
.text:00401875 loc_401875:
.text:00401875
.text:00401875 pop edi
.text:00401876 pop esi
.text:00401877 pop ebx
.text:00401878 mov esp, ebp
.text:0040187A pop ebp
.text:0040187B retn
.text:0040187B Hi_CDialogPassCheck__Check_sub_4017F0 endp
0x02 动态分析与静态分析之争
贞:在从外表看出来是MFC,体积较小的印象之初,是选择动态调试还是静态分析先?
(1)因为这是小体积的独立样例,核心的逻辑或数据一般都会在里面,
且若无法避免需要动态调试分析时,动态调试分析的针对性需要静态分析辅助,
即先上静态,一窥全貌,再做应对安排。
(2)实际上在应对确定MFC消息响应函数这块,静态分析可能比动态来得更直接快速。
包括直接从MFC类对象的虚表函数中找到其定义的消息映射表,
或如上述通过敏感字符提示信息的交叉引用逆向追溯。
如在403310处为CDialogPassCheck验证对话框的消息响应映射表
[nMsg:00000112,nCode:00000000,nId:00000000,nLID:00000000,nSig:00000012,pfn: sub_4015D0]//WM_SYSCOMMAND
[nMsg:0000000F,nCode:00000000,nId:00000000,nLID:00000000,nSig:0000000C,pfn: sub_401650]//WM_PAINT
[nMsg:00000037,nCode:00000000,nId:00000000,nLID:00000000,nSig:00000023,pfn: sub_401750]//WM_QUERYDRAGICON
[nMsg:00000202,nCode:00000000,nId:00000000,nLID:00000000,nSig:00000031,pfn: sub_401880]//WM_LBUTTONUP
[nMsg:0000001F,nCode:00000000,nId:00000000,nLID:00000000,nSig:0000000C,pfn: sub_4018B0]//WM_CANCELMODE
(3)应当注意到上述WM_LBUTTONUP对应的响应函数是sub_401880,此响应是针对在对话框非控件的区域鼠标左键单击(弹起)响应,
而非点击”验证“按钮的响应,验证按钮的点击响应函数是上述的 Hi_CDialogPassCheck__Check_sub_4017F0
其实际原型是 CDialog::OnOK(),为何能猜定其是OnOK的重载函数,这是由VC 6.0 MFC CDiag的虚函数顺序决定的,如下
类似地,我们还可以知道 OnInitDialog 的对应重载函数为 Hi_CDialogPassCheck_OnInitDialog_sub_401500等等。
.rdata:0040353C dd offset CDialog::DoModal(void)
.rdata:00403540 dd offset Hi_CDialogPassCheck_OnInitDialog_sub_401500
.rdata:00403544 dd offset CDialog::OnSetFont(CFont *)
.rdata:00403548 dd offset Hi_CDialogPassCheck__Check_sub_4017F0
.rdata:0040354C dd offset CDialog::OnCancel(void)
.rdata:00403550 dd offset CDialog::PreInitDialog(void)
在了解MFC机制和类的及其虚表内存布局基础上,可对响应函数的直接静态定位
0x03 MFC的启动过程
贞:如何从程序及MFC机制静态得到关键结构与信息项。
(1)从 00401E9C start 起,做一些基本的初始化工作,包括app_type,fmode,commode,fdiv,cmdln等
(2)其中也包括下述两处的C\C++以及一些全局变量的初始化函数向量数组
.text:00401F24 push offset Hi_x1z_dword_404014
.text:00401F29 push offset Hi_x1a_dword_40401
.text:00401F2E call _initterm
------- -------- -------- -------- -------- -------- --------
.text:00401F57 push offset Hi_x2z_dword_40400C
.text:00401F5C push offset Hi_x2z_dword_404000
.text:00401F61 call _initterm
------- -------- -------- -------- -------- -------- --------
.data:00404000 Hi_x2z_dword_404000 dd 0
.data:00404004 dd offset Hi_InitModuleState_bDll_linkerVersion_w_sub_40206F
.data:00404008 dd offset Hi_g_HelloCWinApp_ctor__registeExitDtor_sub_4010D0
.data:0040400C Hi_x2z_dword_40400C dd 0
.data:00404010 Hi_x1a_dword_40401 dd 0
.data:00404014 Hi_x1z_dword_404014 dd 0
(3)上述Hi_InitModuleState_bDll_linkerVersion_w_sub_40206F 初始化线程模块对象的连接器版本0x600 即 (VC 6.0),非Dll模块(exe)
.text:00402074 Hi_InitModuleState_bDll_linkerVersion_sub_402074 proc near
.text:00402074 ; CODE XREF: Hi_InitModuleState_bDll_linkerVersion_w_sub_40206F↑j
.text:00402074 push 600h ; P2_linkerVersion
.text:00402079 push 0 ; P1_bDll
.text:0040207B call AfxInitialize(int,ulong)
.text:00402080 mov Hi_bInitModuleState_404118, al
.text:00402085 retn
.text:00402085 Hi_InitModuleState_bDll_linkerVersion_sub_402074 endp
(4)Hi_g_HelloCWinApp_ctor__registeExitDtor_sub_4010D0 完成我们MFC开始中的全局CWinApp实例的初始化,并注册退出时需执行的析构函数
.text:004010D9 call Hi_g_HelloCWinApp_ctor_sub_4010F0
.text:004010DE call Hi_registeExitFunc_Hi_g_HelloCWinApp_dtor_sub_401110
(5)在 Hi_g_HelloCWinApp_ctor_sub_4010F0 中,我们可以看到Hi_g_HelloCWinApp_404028静态变量即为我们的CWinApp MFC实例
.text:004010F9 mov ecx, offset Hi_g_HelloCWinApp_404028
.text:004010FE call Hi_HelloCWinApp_ctor_sub_401040
(6)在 Hi_HelloCWinApp_ctor_sub_401040中,
我们可以得到CWinApp MFC实例Hi_g_HelloCWinApp_404028的虚表Hi_HelloCWinApp_vft_403240
进而在虚表中得到初始化函数 :00403298 dd offset Hi_g_HelloCWinApp::Init_401150(void)
.text:0040104E mov ecx, [ebp+loc_thisptr_g_HelloCWinApp] ; this
.text:00401051 call CWinApp::CWinApp(char const *)
.text:00401056 mov eax, [ebp+loc_thisptr_g_HelloCWinApp]
.text:00401059 mov dword ptr [eax], offset Hi_HelloCWinApp_vft_403240
.text:0040105F mov eax, [ebp+loc_thisptr_g_HelloCWinApp]
(7)在CWinApp MFC实例Hi_g_HelloCWinApp_404028的初始化函数 Init_401150 中,
我们可以看到验证对话框是一局部变量以DoModal方式呈现的,而在DoModal将验证对话框呈现给用户之前,
验证对话框CDialogPassCheck的构造函数为 Hi_CDialogPassCheck_ctor_sub_401390
.text:00401188 lea ecx, [ebp+loc_CDialogPassCheck]
.text:0040118B call Hi_CDialogPassCheck_ctor_sub_401390
.text:00401190 ; try {
.text:00401190 mov [ebp+var_4], 0
.text:00401197 mov eax, [ebp+loc_thisptrCHelloCWinApp]
.text:0040119A lea ecx, [ebp+loc_CDialogPassCheck]
.text:0040119D mov [eax+20h], ecx
.text:004011A0 lea ecx, [ebp+loc_CDialogPassCheck] ; this
.text:004011A3 call CDialog::DoModal(v
(8)在 Hi_CDialogPassCheck_ctor_sub_401390 中,
我们可以看到对话框使用的对话框资源ID 66h,拓展分析可以用资源查阅工具得到控件布局与对于的ID
这也能印证我们在0x01中提的".text:00401803 push 3EAh ; int //CEdit 控件ID”
实际上我们还可以得到验证对话框的数据成员变量,如其中的两个字符串成员变量
CDialogPassCheck{
.60hww CString //此成员变量未使用,为何?
//这我们可以猜测很可能是在MFC设计中作者重复添加控件变量所致,
//或预留做增强功能使用,若用于存用户名,干啥用,只有天知道。
.64hww CString //在后续的交互中,我们可以知道其就是 passKey的存储位置
}
text:004013B5 push 66h ; unsigned int
.text:004013B7 mov ecx, [ebp+loc_thisptr_CDialogPassCheck] ; this
.text:004013BA call CDialog::CDialog(uint,CWnd *)
.text:004013BF ; try {
.text:004013BF mov [ebp+var_4], 0
.text:004013C6 mov ecx, [ebp+loc_thisptr_CDialogPassCheck]
.text:004013C9 add ecx, 60h ; this
.text:004013CC call CString::CString(void)
.text:004013CC ; } // starts at 4013BF
.text:004013D1 ; try {
.text:004013D1 mov byte ptr [ebp+var_4], 1
.text:004013D5 mov ecx, [ebp+loc_thisptr_CDialogPassCheck]
.text:004013D8 add ecx, 64h ; this
.text:004013DB call CString::CString(void)
.text:004013DB ; } // starts at 4013D1
.text:004013E0 ; try {
.text:004013E0 mov byte ptr [ebp+var_4], 2
.text:004013E4 mov ecx, [ebp+loc_thisptr_CDialogPassCheck]
.text:004013E7 mov dword ptr [ecx], offset Hi_CDialogPassCheck_vft_40347C
.text:004013ED push offset dword_403478
.text:004013F2 mov ecx, [ebp+loc_thisptr_CDialogPassCheck]
.text:004013F5 add ecx, 64h
.text:004013F8 call CString::operator=(char con*)//passKey 初始化为 ""
by tritium 2017-10-24
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课