首页
社区
课程
招聘
[原创]看雪 2016 CTF 第十题 Solution
发表于: 2016-11-22 11:39 2727

[原创]看雪 2016 CTF 第十题 Solution

HHHso 活跃值
22
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

[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课

收藏
免费 0
支持
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回