-
-
[原创]看雪 2016 CTF 第十题 Solution
-
发表于: 2016-11-22 11:39 2727
-
周末在外,在群上听大伙说都很容易,反调试效果还历历在目的样子。
三更半夜从外面赶回来,down样本分析,题目作者的技术亮点是展示反调试,
在破解算法也就没有折腾大伙。上IDA静态分析就可以得到。
在没有手动将
.text:004021A4 mov dword ptr [eax], 63637573h
.text:004021AA mov dword ptr [eax+4], 21737365h
转变一下,在IDA的String窗口是得不到succ字符串的,一开始并不是直接找success,因为不知道正确是这个提示。
.text:004021A4 mov dword ptr [eax], 'ccus'
.text:004021AA mov dword ptr [eax+4], '!sse'
一上来是在IDA的String里找password提示字符串,找不到,还是不死心,决得可能是IDA被忽悠了,
于是直接用IDAPython的FindBinary函数命令找。
执行下述IDAPython命令,得到提示信息password的Hex码,供FindBinary用
#------- ------- ------- ------- -------
for i in "password":
print "{:02X}".format(ord(i)),
#------- ------- ------- ------- -------
70 61 73 73 77 6F 72 64
执行下述命令查找,
hex(FindBinary(0x401000,SEARCH_CASE|SEARCH_DOWN|SEARCH_NEXT,"70 61 73 73 77 6F 72 64"))
0xffffffffL //很幸运!找不到(也像上面的success一样属于局部变量分段赋值产生)
换错误信息"error!"再试试,真不幸,找到了(其实这个字符信息IDA的String窗口也有),不过沿袭了上面的FindBinary习惯。
#------- ------- ------- ------- -------
for i in "error!":
print "{:02X}".format(ord(i)),
#------- ------- ------- ------- -------
65 72 72 6F 72 21
hex(FindBinary(0x401000,SEARCH_CASE|SEARCH_DOWN|SEARCH_NEXT,"65 72 72 6F 72 21"))
0x42381c
0x42381c ”error!" 溯源至下述引用地方。
.text:00401F3A loc_401F3A: ; "error!"
.text:00401F3A push offset aError
.text:00401F3F call sub_4063FA
.text:00401F44 add esp, 4
代码位于 Hi_show_error_sub_401E90 函数内,
而 Hi_show_error_sub_401E90 调用与作者设计的主函数 Hi_main_sub_4020A0内,片段如下。
(1)其一开始是调用 Hi_kill_top_window_loc_401F60 干掉系统当前活动窗口(逻辑参考后续简单分析)
(2)紧接着是检测输入的key里是否有"TrustMe"字符串,看到这里时直接提交TrustMe时未成功(看来还得多看一眼)
(其实第一眼看过去,习惯是提交"surTeMt",那是思维习惯,没考虑字符次序的原因)
(3)ntdll.ZwSetInformationThread就是隐藏线程了
(1)和(3)配合得还是很好的(参考后续分析)
.text:004020D3 call Hi_kill_top_window_loc_401F60
.text:004020D8 cmp esi, esp
.text:004020DA call sub_401D90
.text:004020DF mov esi, eax
.text:004020E1 cmp esi, ebp
.text:004020E3 mov dword ptr [ebp+var_24], 'surT'
.text:004020EA mov [ebp+var_20], 'eMt'
.text:004020F1 lea eax, [ebp+var_24]
.text:004020F4 push eax ; char *
.text:004020F5 push esi ; char *
.text:004020F6 call _strstr
.text:004020FB add esp, 8
.text:004020FE test eax, eax
.text:00402100 jnz short loc_402109
.text:00402102 mov ecx, esi
.text:00402104 call Hi_show_error_sub_401E90
.text:00402109 ; ---------------------------------------------------------------------------
.text:00402109
.text:00402109 loc_402109: ; CODE XREF: sub_4020A0+60j
.text:00402109 push offset ProcName ; "ZwSetInformationThread"
.text:0040210E push offset LibFileName ; "ntdll.dll"
.text:00402113 mov edi, ds:LoadLibraryW
.text:00402119 call edi ; LoadLibraryW
.text:0040211B push eax ; hModule
.text:0040211C mov ebx, ds:GetProcAddress
.text:00402122 call ebx ; GetProcAddress
.text:00402124 mov esi, eax
.text:00402126 push 0
.text:00402128 push 0
.text:0040212A push 11h
.text:0040212C call ds:GetCurrentThread
.text:00402132 push eax
.text:00402133 call esi
Hi_kill_top_window_loc_401F60的代码如下,有部分诱导编译器错误反编译的冗余字节,
其主要逻辑是:
a. hwnd_system_top_window = user32.GetForegroundWindow(0) //如果用OD等调试器直接启动,这个窗体就是OD等调试器
b. user32.SendMessageW(hwnd_system_top_window,WM_DESTROY.2) //直接发消息,关闭,相当于点了OD等调试器的关闭按钮
c. user32.EnableWindow(hwnd_system_top_window,False.0) //二重保险,如果关不掉,再退一步让OD等调试器失能
.text:00401F60 Hi_kill_top_window_loc_401F60: ; CODE XREF: Hi_main_sub_4020A0+33
.text:00401F60 push ebp
.text:00401F61 mov ebp, esp
.text:00401F63 push ecx
.text:00401F64 push ebx
.text:00401F65 push esi
.text:00401F66 push edi
.text:00401F67 mov dword ptr [ebp-4], 0
.text:00401F6E call ds:GetCurrentThread
.text:00401F74 call ds:GetCommandLineW
.text:00401F7A test eax, eax
.text:00401F7C jnz short loc_401F87 //永远跳转成功,即后续是冗余代码,永远不会被执行
.text:00401F7E pop ds
.text:00401F7F call near ptr 8B9B7DE3h
.text:00401F84 in eax, 5Dh
.text:00401F86 retn
.text:00401F87 ; ---------------------------------------------------------------------------
.text:00401F87
.text:00401F87 loc_401F87: ; CODE XREF: .text:00401F7C
.text:00401F87 pusha
.text:00401F88 push 0
.text:00401F8A mov eax, Hi_VirtualAddrOf_strlen_off_4275E0
.text:00401F8F jmp short loc_401F93
.text:00401F8F ; ---------------------------------------------------------------------------
.text:00401F91 db 0E8h, 79h //诱导反编译器的冗余字节
.text:00401F93 ; ---------------------------------------------------------------------------
.text:00401F93
.text:00401F93 loc_401F93: ; CODE XREF: .text:00401F8F
.text:00401F93 push 0
.text:00401F95 add eax, Hi_GetForegroundWindow_Addr_OffFrom_strlen_dword_428C60
.text:00401F9B call eax ; user32.GetForegroundWindow
.text:00401F9D mov [ebp-4], eax
.text:00401FA0 xor eax, eax
.text:00401FA2 mov edx, Hi_VirtualAddrOf_strlen_off_4275E0
.text:00401FA8 push WM_DESTROY
.text:00401FAA cmp eax, 0FABEE90h
.text:00401FAF jnz short loc_401FB3
.text:00401FAF ; ---------------------------------------------------------------------------
.text:00401FB1 db 0E8h ; //诱导反编译器的冗余字节
.text:00401FB2 db 79h ; y
.text:00401FB3 ; ---------------------------------------------------------------------------
.text:00401FB3
.text:00401FB3 loc_401FB3: ; CODE XREF: .text:00401FAF
.text:00401FB3 push dword ptr [ebp-4]
.text:00401FB6 add edx, Hi_SendMessageW_Addr_OffFrom_strlen_dword_428C64
.text:00401FBC call edx ; user32.SendMessageW
.text:00401FBE popa
.text:00401FBF push 0
.text:00401FC1 push dword ptr [ebp-4]
.text:00401FC4 call ds:EnableWindow
.text:00401FCA pop edi
.text:00401FCB pop esi
.text:00401FCC pop ebx
.text:00401FCD mov esp, ebp
.text:00401FCF pop ebp
.text:00401FD0 retn
上面的 Hi_kill_top_window_loc_401F60 是让OD等调试器不能直接进入调试的输入环节,还没运行起来就把调试器关掉了;
一般如果还没确定 Hi_kill_top_window_loc_401F60 的存在而采用措施nop掉的话,按套路,
下一步换策略一般会先运行程序再用OD等调试器附加调试,可惜(3)ntdll.ZwSetInformationThread会继续隐藏线程,
让调试器追踪不到,当然,如果提前装载了劫持SSDT服务描述表的NtSetInformationThread函数的驱动进行针对性的反反调试,
附加调试的问题就不存在了,这个论坛上有大侠已经介绍过,参考他们的文章。
更简单的,既然到这里都被你看遍全身了,直接和 Hi_kill_top_window_loc_401F60 都 nop掉就行(如果真要调试的话)
但这题并不需要调试。
经过两个反调试机制(1)(3)中间的(2)检测输入的key里是否有"TrustMe"字符串后,进入 做第二步检测 Hi_CheckStep2_sub_401FE0,
成功与否就看 Hi_CheckStep2_sub_401FE0 的了
.text:00402194 call Hi_CheckStep2_sub_401FE0
.text:00402199 test eax, eax
.text:0040219B jz short loc_4021CF
.text:0040219D push 9
.text:0040219F call sub_40529F
.text:004021A4 mov dword ptr [eax], 'ccus'
.text:004021AA mov dword ptr [eax+4], '!sse' // 提示成功的分支 success!
.text:004021B1 mov byte ptr [eax+8], 0
.text:004021B5 mov edx, eax
.text:004021B7 call sub_403490
.text:004021BC push eax
.text:004021BD call sub_403980
.text:004021C2 push offset aPause ; "pause"
Hi_CheckStep2_sub_401FE0 的检测逻辑也相对简单
a. 其先统计 Hi_keyPtr_dword_428C58 的长度,并通过Hi_ECXbastr_assign_P1Ptr_P2Cnt_sub_4027F0赋值给var_1C_bastr变量
var_1C_bastr为 basic_string<char> 类型
struct _hi_basic_string
{
DWORD lpsz;
DWORD lpsz_04;
DWORD lpsz_08;
DWORD lpsz_0C;
DWORD _10_len;
DWORD _14_cbsize;
};
typedef struct _hi_basic_string Hi_basic_str;
b. 比对 key_bastr 从长度需要为 0xF 个字符
c. 然后截取前7个字符后面的字符并通过strotl转换为整数与0x133A1FA比对
所以到这里key就可以确定为 "TrustMe" + "20161018"(0x133A1FA)
即 "TrustMe20161018"
.text:00402029 push ecx ; size_t
.text:0040202A push edx ; void *
.text:0040202B lea ecx, [esp+28h+var_1C_bastr] ; int
.text:0040202F call Hi_ECXbastr_assign_P1Ptr_P2Cnt_sub_4027F0
.text:00402034 cmp [esp+20h+var_1C_bastr._10_len], 0Fh
.text:00402039 jnz short loc_40205F
.text:0040203B mov eax, Hi_keyPtr_dword_428C58
.text:00402040 add eax, 7
.text:00402043 push eax ; char *
.text:00402044 mov Hi_keyPtr_dword_428C58, eax
.text:00402049 call sub_406193 ;
.text:00402049 ; _strtol(...,0x0A)
.text:0040204E add esp, 4
.text:00402051 cmp eax, 133A1FAh
三更半夜从外面赶回来,down样本分析,题目作者的技术亮点是展示反调试,
在破解算法也就没有折腾大伙。上IDA静态分析就可以得到。
在没有手动将
.text:004021A4 mov dword ptr [eax], 63637573h
.text:004021AA mov dword ptr [eax+4], 21737365h
转变一下,在IDA的String窗口是得不到succ字符串的,一开始并不是直接找success,因为不知道正确是这个提示。
.text:004021A4 mov dword ptr [eax], 'ccus'
.text:004021AA mov dword ptr [eax+4], '!sse'
一上来是在IDA的String里找password提示字符串,找不到,还是不死心,决得可能是IDA被忽悠了,
于是直接用IDAPython的FindBinary函数命令找。
执行下述IDAPython命令,得到提示信息password的Hex码,供FindBinary用
#------- ------- ------- ------- -------
for i in "password":
print "{:02X}".format(ord(i)),
#------- ------- ------- ------- -------
70 61 73 73 77 6F 72 64
执行下述命令查找,
hex(FindBinary(0x401000,SEARCH_CASE|SEARCH_DOWN|SEARCH_NEXT,"70 61 73 73 77 6F 72 64"))
0xffffffffL //很幸运!找不到(也像上面的success一样属于局部变量分段赋值产生)
换错误信息"error!"再试试,真不幸,找到了(其实这个字符信息IDA的String窗口也有),不过沿袭了上面的FindBinary习惯。
#------- ------- ------- ------- -------
for i in "error!":
print "{:02X}".format(ord(i)),
#------- ------- ------- ------- -------
65 72 72 6F 72 21
hex(FindBinary(0x401000,SEARCH_CASE|SEARCH_DOWN|SEARCH_NEXT,"65 72 72 6F 72 21"))
0x42381c
0x42381c ”error!" 溯源至下述引用地方。
.text:00401F3A loc_401F3A: ; "error!"
.text:00401F3A push offset aError
.text:00401F3F call sub_4063FA
.text:00401F44 add esp, 4
代码位于 Hi_show_error_sub_401E90 函数内,
而 Hi_show_error_sub_401E90 调用与作者设计的主函数 Hi_main_sub_4020A0内,片段如下。
(1)其一开始是调用 Hi_kill_top_window_loc_401F60 干掉系统当前活动窗口(逻辑参考后续简单分析)
(2)紧接着是检测输入的key里是否有"TrustMe"字符串,看到这里时直接提交TrustMe时未成功(看来还得多看一眼)
(其实第一眼看过去,习惯是提交"surTeMt",那是思维习惯,没考虑字符次序的原因)
(3)ntdll.ZwSetInformationThread就是隐藏线程了
(1)和(3)配合得还是很好的(参考后续分析)
.text:004020D3 call Hi_kill_top_window_loc_401F60
.text:004020D8 cmp esi, esp
.text:004020DA call sub_401D90
.text:004020DF mov esi, eax
.text:004020E1 cmp esi, ebp
.text:004020E3 mov dword ptr [ebp+var_24], 'surT'
.text:004020EA mov [ebp+var_20], 'eMt'
.text:004020F1 lea eax, [ebp+var_24]
.text:004020F4 push eax ; char *
.text:004020F5 push esi ; char *
.text:004020F6 call _strstr
.text:004020FB add esp, 8
.text:004020FE test eax, eax
.text:00402100 jnz short loc_402109
.text:00402102 mov ecx, esi
.text:00402104 call Hi_show_error_sub_401E90
.text:00402109 ; ---------------------------------------------------------------------------
.text:00402109
.text:00402109 loc_402109: ; CODE XREF: sub_4020A0+60j
.text:00402109 push offset ProcName ; "ZwSetInformationThread"
.text:0040210E push offset LibFileName ; "ntdll.dll"
.text:00402113 mov edi, ds:LoadLibraryW
.text:00402119 call edi ; LoadLibraryW
.text:0040211B push eax ; hModule
.text:0040211C mov ebx, ds:GetProcAddress
.text:00402122 call ebx ; GetProcAddress
.text:00402124 mov esi, eax
.text:00402126 push 0
.text:00402128 push 0
.text:0040212A push 11h
.text:0040212C call ds:GetCurrentThread
.text:00402132 push eax
.text:00402133 call esi
Hi_kill_top_window_loc_401F60的代码如下,有部分诱导编译器错误反编译的冗余字节,
其主要逻辑是:
a. hwnd_system_top_window = user32.GetForegroundWindow(0) //如果用OD等调试器直接启动,这个窗体就是OD等调试器
b. user32.SendMessageW(hwnd_system_top_window,WM_DESTROY.2) //直接发消息,关闭,相当于点了OD等调试器的关闭按钮
c. user32.EnableWindow(hwnd_system_top_window,False.0) //二重保险,如果关不掉,再退一步让OD等调试器失能
.text:00401F60 Hi_kill_top_window_loc_401F60: ; CODE XREF: Hi_main_sub_4020A0+33
.text:00401F60 push ebp
.text:00401F61 mov ebp, esp
.text:00401F63 push ecx
.text:00401F64 push ebx
.text:00401F65 push esi
.text:00401F66 push edi
.text:00401F67 mov dword ptr [ebp-4], 0
.text:00401F6E call ds:GetCurrentThread
.text:00401F74 call ds:GetCommandLineW
.text:00401F7A test eax, eax
.text:00401F7C jnz short loc_401F87 //永远跳转成功,即后续是冗余代码,永远不会被执行
.text:00401F7E pop ds
.text:00401F7F call near ptr 8B9B7DE3h
.text:00401F84 in eax, 5Dh
.text:00401F86 retn
.text:00401F87 ; ---------------------------------------------------------------------------
.text:00401F87
.text:00401F87 loc_401F87: ; CODE XREF: .text:00401F7C
.text:00401F87 pusha
.text:00401F88 push 0
.text:00401F8A mov eax, Hi_VirtualAddrOf_strlen_off_4275E0
.text:00401F8F jmp short loc_401F93
.text:00401F8F ; ---------------------------------------------------------------------------
.text:00401F91 db 0E8h, 79h //诱导反编译器的冗余字节
.text:00401F93 ; ---------------------------------------------------------------------------
.text:00401F93
.text:00401F93 loc_401F93: ; CODE XREF: .text:00401F8F
.text:00401F93 push 0
.text:00401F95 add eax, Hi_GetForegroundWindow_Addr_OffFrom_strlen_dword_428C60
.text:00401F9B call eax ; user32.GetForegroundWindow
.text:00401F9D mov [ebp-4], eax
.text:00401FA0 xor eax, eax
.text:00401FA2 mov edx, Hi_VirtualAddrOf_strlen_off_4275E0
.text:00401FA8 push WM_DESTROY
.text:00401FAA cmp eax, 0FABEE90h
.text:00401FAF jnz short loc_401FB3
.text:00401FAF ; ---------------------------------------------------------------------------
.text:00401FB1 db 0E8h ; //诱导反编译器的冗余字节
.text:00401FB2 db 79h ; y
.text:00401FB3 ; ---------------------------------------------------------------------------
.text:00401FB3
.text:00401FB3 loc_401FB3: ; CODE XREF: .text:00401FAF
.text:00401FB3 push dword ptr [ebp-4]
.text:00401FB6 add edx, Hi_SendMessageW_Addr_OffFrom_strlen_dword_428C64
.text:00401FBC call edx ; user32.SendMessageW
.text:00401FBE popa
.text:00401FBF push 0
.text:00401FC1 push dword ptr [ebp-4]
.text:00401FC4 call ds:EnableWindow
.text:00401FCA pop edi
.text:00401FCB pop esi
.text:00401FCC pop ebx
.text:00401FCD mov esp, ebp
.text:00401FCF pop ebp
.text:00401FD0 retn
上面的 Hi_kill_top_window_loc_401F60 是让OD等调试器不能直接进入调试的输入环节,还没运行起来就把调试器关掉了;
一般如果还没确定 Hi_kill_top_window_loc_401F60 的存在而采用措施nop掉的话,按套路,
下一步换策略一般会先运行程序再用OD等调试器附加调试,可惜(3)ntdll.ZwSetInformationThread会继续隐藏线程,
让调试器追踪不到,当然,如果提前装载了劫持SSDT服务描述表的NtSetInformationThread函数的驱动进行针对性的反反调试,
附加调试的问题就不存在了,这个论坛上有大侠已经介绍过,参考他们的文章。
更简单的,既然到这里都被你看遍全身了,直接和 Hi_kill_top_window_loc_401F60 都 nop掉就行(如果真要调试的话)
但这题并不需要调试。
经过两个反调试机制(1)(3)中间的(2)检测输入的key里是否有"TrustMe"字符串后,进入 做第二步检测 Hi_CheckStep2_sub_401FE0,
成功与否就看 Hi_CheckStep2_sub_401FE0 的了
.text:00402194 call Hi_CheckStep2_sub_401FE0
.text:00402199 test eax, eax
.text:0040219B jz short loc_4021CF
.text:0040219D push 9
.text:0040219F call sub_40529F
.text:004021A4 mov dword ptr [eax], 'ccus'
.text:004021AA mov dword ptr [eax+4], '!sse' // 提示成功的分支 success!
.text:004021B1 mov byte ptr [eax+8], 0
.text:004021B5 mov edx, eax
.text:004021B7 call sub_403490
.text:004021BC push eax
.text:004021BD call sub_403980
.text:004021C2 push offset aPause ; "pause"
Hi_CheckStep2_sub_401FE0 的检测逻辑也相对简单
a. 其先统计 Hi_keyPtr_dword_428C58 的长度,并通过Hi_ECXbastr_assign_P1Ptr_P2Cnt_sub_4027F0赋值给var_1C_bastr变量
var_1C_bastr为 basic_string<char> 类型
struct _hi_basic_string
{
DWORD lpsz;
DWORD lpsz_04;
DWORD lpsz_08;
DWORD lpsz_0C;
DWORD _10_len;
DWORD _14_cbsize;
};
typedef struct _hi_basic_string Hi_basic_str;
b. 比对 key_bastr 从长度需要为 0xF 个字符
c. 然后截取前7个字符后面的字符并通过strotl转换为整数与0x133A1FA比对
所以到这里key就可以确定为 "TrustMe" + "20161018"(0x133A1FA)
即 "TrustMe20161018"
.text:00402029 push ecx ; size_t
.text:0040202A push edx ; void *
.text:0040202B lea ecx, [esp+28h+var_1C_bastr] ; int
.text:0040202F call Hi_ECXbastr_assign_P1Ptr_P2Cnt_sub_4027F0
.text:00402034 cmp [esp+20h+var_1C_bastr._10_len], 0Fh
.text:00402039 jnz short loc_40205F
.text:0040203B mov eax, Hi_keyPtr_dword_428C58
.text:00402040 add eax, 7
.text:00402043 push eax ; char *
.text:00402044 mov Hi_keyPtr_dword_428C58, eax
.text:00402049 call sub_406193 ;
.text:00402049 ; _strtol(...,0x0A)
.text:0040204E add esp, 4
.text:00402051 cmp eax, 133A1FAh
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
赞赏
他的文章
赞赏
雪币:
留言: