-
-
[原创]Flare-On 8th Challenge 9复现
-
2022-1-21 11:05 9637
-
这道题目可以好好看下官方WriteUp,其前半部分是Core Architecture Concepts——将题目中所用到的各种核心技术都进行了讲解,后半部分Challenge Walkthrough则是以题目作为前半部分技术的例子,对细节进行了阐述。这篇文章将侧重技术而并非解题。
0x01 CRT Initialization Functions
笔者使用的VS版本如下:
进入main
函数之前的函数调用栈如下:
上图所示的栈回溯并不完整,完整调用关系如下:
重点在于_initterm_e
与_initterm
两个函数(摘自How to explicitly call CRT startup functions):
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 | typedef void (__cdecl * _PVFV)(); void _initterm(const _PVFV * ppfn, const _PVFV * end) { do { if (_PVFV pfn = * + + ppfn) { pfn(); } } while (ppfn < end); } typedef int (__cdecl * _PIFV)(); int _initterm_e(const _PIFV * ppfn, const _PIFV * end) { do { if (_PIFV pfn = * + + ppfn) { if ( int err = pfn()) return err; } } while (ppfn < end); return 0 ; } |
__xi_a
,__xi_z
,__xc_a
与__xc_z
含义如下(摘自How can I schedule some code to run after all '_atexit()' functions are completed):
1 2 3 4 5 6 7 8 | extern _CRTALLOC( ".CRT$XIA" ) _PIFV __xi_a[]; extern _CRTALLOC( ".CRT$XIZ" ) _PIFV __xi_z[]; / * C initializers * / extern _CRTALLOC( ".CRT$XCA" ) _PVFV __xc_a[]; extern _CRTALLOC( ".CRT$XCZ" ) _PVFV __xc_z[]; / * C + + initializers * / extern _CRTALLOC( ".CRT$XPA" ) _PVFV __xp_a[]; extern _CRTALLOC( ".CRT$XPZ" ) _PVFV __xp_z[]; / * C pre - terminators * / extern _CRTALLOC( ".CRT$XTA" ) _PVFV __xt_a[]; extern _CRTALLOC( ".CRT$XTZ" ) _PVFV __xt_z[]; / * C terminators * / |
微软在其文档中给出这样一段代码:
1 2 3 4 5 6 7 8 9 10 11 | int func(void) { return 3 ; } int gi = func(); int main() { return gi; } |
笔者编译时配置属性如下:
禁用优化:
可以看到生成之后在__xc_a
与__xc_z
中间有一_dynamic_initializer_for__gi__
指针:
题目中使用该技术进行VEH Hook,具体细节笔者会放到VEH Hooking一节中描述。关于该技术的利用,趋势科技在其博客中列出一表格如下:
文末会给出具体链接,感兴趣的读者可以进一步阅读。
0x02 VEH Hooking
VEH——Vectored Exception Handling,向量化异常处理。关于VEH与SEH知识讲解可以看VEH和SEH,这里不再赘述。
Vectored Exception Handling, Hooking Via Forced Exception这篇文章通过页的PAGE_GUARD属性触发VEH:
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 | bool LeoHook::Hook(uintptr_t original_fun, uintptr_t hooked_fun) { LeoHook::og_fun = original_fun; LeoHook::hk_fun = hooked_fun; / / We cannot hook two functions in the same page, because we will cause an infinite callback if (AreInSamePage((const uint8_t * )og_fun, (const uint8_t * )hk_fun)) return false; / / Register the Custom Exception Handler VEH_Handle = AddVectoredExceptionHandler(true, (PVECTORED_EXCEPTION_HANDLER)LeoHandler); / / Toggle PAGE_GUARD flag on the page if (VEH_Handle && VirtualProtect((LPVOID)og_fun, 1 , PAGE_EXECUTE_READ | PAGE_GUARD, &oldProtection)) return true; return false; } LONG WINAPI LeoHook::LeoHandler(EXCEPTION_POINTERS * pExceptionInfo) { if (pExceptionInfo - >ExceptionRecord - >ExceptionCode = = STATUS_GUARD_PAGE_VIOLATION) / / We will catch PAGE_GUARD Violation { if (pExceptionInfo - >ContextRecord - >XIP = = (uintptr_t)og_fun) / / Make sure we are at the address we want within the page { pExceptionInfo - >ContextRecord - >XIP = (uintptr_t)hk_fun; / / Modify EIP / RIP to where we want to jump to instead of the original function } pExceptionInfo - >ContextRecord - >EFlags | = 0x100 ; / / Will trigger an STATUS_SINGLE_STEP exception right after the next instruction get executed. In short, we come right back into this exception handler 1 instruction later return EXCEPTION_CONTINUE_EXECUTION; / / Continue to next instruction } if (pExceptionInfo - >ExceptionRecord - >ExceptionCode = = STATUS_SINGLE_STEP) / / We will also catch STATUS_SINGLE_STEP, meaning we just had a PAGE_GUARD violation { DWORD dwOld; VirtualProtect((LPVOID)og_fun, 1 , PAGE_EXECUTE_READ | PAGE_GUARD, &dwOld); / / Reapply the PAGE_GUARD flag because everytime it is triggered, it get removes return EXCEPTION_CONTINUE_EXECUTION; / / Continue the next instruction } return EXCEPTION_CONTINUE_SEARCH; / / Keep going down the exception handling list to find the right handler IF it is not PAGE_GUARD nor SINGLE_STEP } |
由于设置了pExceptionInfo->ContextRecord->EFlags |= 0x100
,每执行一条指令就会重新回到LeoHandler
。题目中__scrt_common_main_seh
函数在调用_initterm
时,会依次执行__xc_a
与__xc_z
之间每个函数:
我们先来看sub_402130
与sub_402150
两个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | sub_402130 proc near mov edx, 542F881Eh mov ecx, 246132h call GetProcAddr mov dword_6D16E4, eax retn sub_402130 endp sub_402150 proc near push offset sub_406AD0 push 1 call dword_6D16E4 retn sub_402150 endp |
sub_4054B0
根据传递参数获取指定API地址,其具体过程暂不作展开。sub_402130
通过给该函数传递值获取AddVectoredExceptionHandler
地址,之后由sub_402150
进行调用。跟进sub_406AD0
查看其功能:
进入异常处理的一种情况:
sub_406AD0
先是传递ContextRecord中ECX与EDX值给sub_4054B0
获取API地址,之后将该地址写入ContextRecord—>EAX中,最后更改ContextRecord->Eip + 3处指令以及EIP值:
另一种触发异常类似于下面形式:
0x03 API Hashes
上文提到sub_4054B0
函数会根据传递参数获取指定API地址,其接受两个参数——参数1用来指定DLL,参数2用来指定API。其获取DLL名称是通过搜索由sub_401100
函数建立起来的映射,该函数首先会解密DLL名称:
字符串第一个字符解密算法不同于其他字符,之后会为每个DLL名称赋予一个Key值。最终建立起来的映射如下:
1 2 3 4 5 6 7 8 | 0x176684 ntdll.dll 0x246132 kernel32.dll 0x052325 ws2_32.dll 0x234324 user32.dll 0x523422 advapi32.dll 0x43493856 gdi32.dll 0x4258672 ole32.dll 0x7468951 oleaut32.dll |
其计算导出函数名称Hash值算法如下:
官方WriteUp在这里给了一个计算Hash值的脚本:
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 | import pefile import sys import os M32 = 0xffffffff def main(): if len (sys.argv) ! = 3 : print ( "usage: generate_hashes <in.dll> <out.hashes>" ) sys.exit( 0 ) d = [pefile.DIRECTORY_ENTRY[ "IMAGE_DIRECTORY_ENTRY_EXPORT" ]] pe = pefile.PE(sys.argv[ 1 ], fast_load = True ) pe.parse_data_directories(directories = d) names = [ e.name for e in pe.DIRECTORY_ENTRY_EXPORT.symbols] names = [i for i in names if i] with open (sys.argv[ 2 ], 'wb' ) as f: f.write(b "enum %s_hash\n" % os.path.split(sys.argv[ 1 ])[ - 1 ].split( '.' )[ 0 ].encode()) f.write(b "{\n" ) for name in sorted (names): h = 0x40 for x in name: h = x - 0x45523f21 * h & M32 f.write((b " %s = 0x%x,\n" % (name, h))) f.write(b "};" ) if __name__ = = '__main__' : main() |
通过题目中算法计算输入DLL中每个导出函数名称的Hash值并保存至C Header文件,之后于File—>Load File—>Parse C Header File选择C Header文件,便可在Local Types窗口看到对应DLL的枚举类型:
分析时遇到Hash值直接选择对应enum即可:
wmsuper师傅在Flare-ON 8th 之第九题evil一文中采用的方法是获取需要修改的地址,对应DLL的Key及Hash值,之后再去对应DLL导出表中寻找匹配函数进行输出,最后通过得到的输出结果修复原题目对应地址处指令。
笔者在处理时采用了一个折衷的方案,之前在复现Challenge 7时使用了一个IDA Plugin——Shellcode Hashes,虽然其覆盖算法很多,但对于题目中这种特定算法没有在脚本当中实现,笔者于make_sc_hash_db.py
中添加了该算法:
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 | def imul45523f21hSub(inString,fName): if inString is None : return 0 val = 0x40 for i in inString: val = val * 0x45523f21 val = i - val val = val & 0xFFFFFFFF val = val & 0xFFFFFFFF return val pseudocode_imul45523f21hSub = '''acc := 0x40; for c in input_string { acc := acc * 45523f21h: acc := c -acc; } ''' ...... # The list of tuples of (supported hash name, hash size, pseudo_code) HASH_TYPES = [ ( 'ror7AddHash32' , 32 , pseudocode_ror7AddHash32), ( 'ror9AddHash32' , 32 , pseudocode_ror9AddHash32), ( 'ror11AddHash32' , 32 , pseudocode_ror11AddHash32), ( 'ror13AddHash32' , 32 , pseudocode_ror13AddHash32), ( 'ror13AddWithNullHash32' , 32 , pseudocode_ror13AddWithNullHash32), ( 'ror13AddHash32AddDll' , 32 , pseudocode_ror13AddHash32AddDll), ( 'ror13AddHash32DllSimple' , 32 , pseudocode_ror13AddHash32DllSimple), ( 'ror13AddHash32Sub20h' , 32 , pseudocode_ror13AddHash32Sub20h), ( 'ror13AddHash32Sub1' , 32 , pseudocode_ror13AddHash32), ( 'addRor4WithNullHash32' , 32 , pseudocode_addRor4WithNullHash32), ( 'addRor13Hash32' , 32 , pseudocode_addRor13Hash32), ( 'addRor13HashOncemore32' , 32 , pseudocode_addRor13HashOncemore32), ( 'rol3XorEax' , 32 , pseudocode_rol3XorEax), ( 'rol3XorHash32' , 32 , pseudocode_rol3XorHash32), ( 'rol5AddHash32' , 32 , pseudocode_rol5AddHash32), ( 'addRol5HashOncemore32' , 32 , pseudocode_addRol5HashOncemore32), ( 'rol7AddHash32' , 32 , pseudocode_rol7AddHash32), ( 'rol7AddXor2Hash32' , 32 , pseudocode_rol7AddXor2Hash32), ( 'rol7XorHash32' , 32 , pseudocode_rol7XorHash32), ( 'rol5XorHash32' , 32 , pseudocode_rol5XorHash32), ( 'rol8Xor0xB0D4D06Hash32' , 32 , pseudocode_rol8Xor0xB0D4D06Hash32), ( 'chAddRol8Hash32' , 32 , pseudocode_chAddRol8Hash32), ( 'rol9AddHash32' , 32 , pseudocode_rol9AddHash32), ( 'rol9XorHash32' , 32 , pseudocode_rol9XorHash32), ( 'xorRol9Hash32' , 32 , pseudocode_xorRol9Hash32), ( 'shl7Shr19XorHash32' , 32 , pseudocode_shl7Shr19XorHash32), ( 'shl7Shr19AddHash32' , 32 , pseudocode_shl7Shr19AddHash32), ( 'shl7SubHash32DoublePulser' , 32 , pseudocode_shl7SubHash32DoublePulser), ( 'sll1AddHash32' , 32 , pseudocode_sll1AddHash32), ( 'shr2Shl5XorHash32' , 32 , pseudocode_shr2Shl5XorHash32), ( 'xorShr8Hash32' , 32 , pseudocode_xorShr8Hash32), ( 'imul83hAdd' , 32 , pseudocode_imul83hAdd), ( 'imul21hAddHash32' , 32 , pseudocode_imul21hAddHash32), ( 'or21hXorRor11Hash32' , 32 , pseudocode_or21hXorRor11Hash32), ( 'or23hXorRor17Hash32' , 32 , pseudocode_or23hXorRor17Hash32), ( 'playWith0xe8677835Hash' , 32 , pseudocode_playWith0xe8677835Hash), ( 'poisonIvyHash' , 32 , pseudocode_poisonIvyHash), ( 'crc32' , 32 , 'Standard crc32' ), ( 'crc32Xor0xca9d4d4e' , 32 , 'crc32 ^ 0xCA9D4D4E' ), ( 'crc32bzip2lower' , 32 , 'crc32 bzip2 and str lower' ), ( 'mult21AddHash32' , 32 , pseudocode_hashMult21), ( 'add1505Shl5Hash32' , 32 , pseudocode_add1505Shl5Hash32), ( 'dualaccModFFF1Hash' , 32 , pseudocode_dualaccModFFF1Hash), ( 'hash_Carbanak' , 32 , pseudocode_hash_Carbanak), ( 'hash_ror13AddUpperDllnameHash32' , 32 , pseudocode_hash_ror13AddUpperDllnameHash32), ( 'fnv1Xor67f' , 32 , pseudocode_fnv1Xor67f), ( 'adler32_666' , 32 , 'Adler32 with starting value 666' ), ( 'shift0x82F63B78' , 32 , 'like crc32c' ), ( 'contiApiHashing' , 32 , pseudocode_contiApiHashing), ( 'fnv1' , 32 , pseudocode_fnv1), ( 'imul45523f21hSub' , 32 , pseudocode_imul45523f21hSub) ] |
之后生成数据库文件,使用插件时选择新生成数据库再应用对应算法即可:
关于该工具更多信息可阅读Using Precalculated String Hashes when Reverse Engineering Shellcode,make_sc_hash_db.py
脚本对DLL目录中每个DLL的所有导出函数应用脚本内所有Hash算法计算Hash值写入到数据库文件中:
官方WriteUp上给出了一个Nop题目中Anti-Disassembly指令的脚本:
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 | import sys callfuncs = [ b '\x33\xC0\xF7\xF0\xEB\x00\xEb' , b '\x33\xC0\x8B\x00\xEB\xFF\xE8' , b '\x33\xF6\xF7\xF6\xE8\xFF\xD2' , b '\x33\xC0\x8B\x00\x74\x03\x75' , b '\x33\xFF\xF4\xF4\x33\xC0\x74' , b '\x33\xFF\xF7\xF7\x33\xC0\x74' , b '\x33\xC0\xF7\xF0\x33\xC0\x74' , b '\x33\xC0\xF7\xF0\xE8\xFF\xD2' , b '\x33\xC0\xF7\xF0\x5B\x5D\xC3' ] nops = b '\x90\x90\x90\x90\x90\x90\x90' def main(): if len (sys.argv) ! = 3 : print ( "usage: nop_anti_dis.py <in out>\n" ) sys.exit( 0 ) with open (sys.argv[ 1 ], 'rb' ) as f: d = f.read() for cf in callfuncs: d = d.replace(cf, nops) with open (sys.argv[ 2 ], 'wb' ) as f: f.write(d) if __name__ = = "__main__" : main() |
再配合上nop-hidder脚本:
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 | from idautils import * from idc import * mnemonics = dict () hides = [] in_nop_sled = 0 curr_pos = 0 sled_len = 0 for seg_ea in Segments(): for head in Heads(seg_ea, SegEnd(seg_ea)): if isCode(GetFlags(head)): mnem = GetMnem(head) if mnem = = 'nop' : sled_len + = 1 if in_nop_sled = = 0 : curr_pos = head in_nop_sled = 1 else : if in_nop_sled = = 1 : in_nop_sled = 0 hides.append([curr_pos,sled_len]) curr_pos = 0 sled_len = 0 for h in hides: if h[ 1 ] > 1 : HideArea(h[ 0 ],h[ 0 ] + h[ 1 ],' ',' ',' ', 0 ) print 'Done hidding...' |
如此一来,使用IDA进行静态分析便会轻松很多。
0x04 Anti-Debug & Anti-Virtualization
关于Windows调试原理可以参阅:
Anti-Debug:
- Anti-Debug: Process Memory(下面两处Patch代码均出自该文)
- Anti Debugging Protection Techniques with Examples
sub_4023D0
函数首先会PatchDbgBreakPoint
与DbgUiRemoteBreakin
两个函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | void Patch_DbgBreakPoint() { HMODULE hNtdll = GetModuleHandleA( "ntdll.dll" ); if (!hNtdll) return ; FARPROC pDbgBreakPoint = GetProcAddress(hNtdll, "DbgBreakPoint" ); if (!pDbgBreakPoint) return ; DWORD dwOldProtect; if (!VirtualProtect(pDbgBreakPoint, 1 , PAGE_EXECUTE_READWRITE, &dwOldProtect)) return ; * (PBYTE)pDbgBreakPoint = (BYTE) 0xC3 ; / / ret if (!VirtualProtect(pDbgBreakPoint, 1 , PAGE_EXECUTE_READ, &dwOldProtect)) return ; } |
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 | #pragma pack(push, 1) struct DbgUiRemoteBreakinPatch { WORD push_0; BYTE push; DWORD CurrentPorcessHandle; BYTE mov_eax; DWORD TerminateProcess; WORD call_eax; }; #pragma pack(pop) void Patch_DbgUiRemoteBreakin() { HMODULE hNtdll = GetModuleHandleA( "ntdll.dll" ); if (!hNtdll) return ; FARPROC pDbgUiRemoteBreakin = GetProcAddress(hNtdll, "DbgUiRemoteBreakin" ); if (!pDbgUiRemoteBreakin) return ; HMODULE hKernel32 = GetModuleHandleA( "kernel32.dll" ); if (!hKernel32) return ; FARPROC pTerminateProcess = GetProcAddress(hKernel32, "TerminateProcess" ); if (!pTerminateProcess) return ; DbgUiRemoteBreakinPatch patch = { 0 }; patch.push_0 = '\x6A\x00' ; patch.push = '\x68' ; patch.CurrentPorcessHandle = 0xFFFFFFFF ; patch.mov_eax = '\xB8' ; patch.TerminateProcess = (DWORD)pTerminateProcess; patch.call_eax = '\xFF\xD0' ; DWORD dwOldProtect; if (!VirtualProtect(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), PAGE_READWRITE, &dwOldProtect)) return ; ::memcpy_s(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), &patch, sizeof(DbgUiRemoteBreakinPatch)); VirtualProtect(pDbgUiRemoteBreakin, sizeof(DbgUiRemoteBreakinPatch), dwOldProtect, &dwOldProtect); } |
通过Vmware使用的I/O端口0x5658进行检测:
如果是运行在Vmware中则直接退出:
之后的检测方法是来自Another VMWare Detection一文:
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 | / / vmgpftest.cpp / / Derek Soeder - eEye Digital Security - 09 / 05 / 2006 #include <windows.h> #include <stdio.h> typedef LONG (NTAPI * NTSETLDTENTRIES)(DWORD,DWORD,DWORD,DWORD,DWORD,DWORD); #define EndUserModeAddress (*(UINT_PTR*)0x7FFE02B4) int detect(LPEXCEPTION_POINTERS lpep) { if ((UINT_PTR)(lpep - >ExceptionRecord - >ExceptionAddress) > EndUserModeAddress) printf( "VMware emulation mode detected!\n" ); return EXCEPTION_EXECUTE_HANDLER; } void __declspec(naked) switchcs() { __asm { pop eax push 0x000F push eax retf } } int main( int argc, char * argv[]) { NTSETLDTENTRIES ZwSetLdtEntries; LDT_ENTRY csdesc; ZwSetLdtEntries = (NTSETLDTENTRIES)GetProcAddress(GetModuleHandle( "ntdll.dll" ), "ZwSetLdtEntries" ); memset(&csdesc, 0 , sizeof(csdesc)); csdesc.LimitLow = (WORD)(EndUserModeAddress >> 12 ); csdesc.HighWord.Bytes.Flags1 = 0xFA ; / / P = 1 , DPL = 3 , Code32, RX csdesc.HighWord.Bytes.Flags2 = 0xC0 | ((EndUserModeAddress >> 28 ) & 0x0F ); / / G = 1 , B = 1 , Limit[ 19. . 16 ] ZwSetLdtEntries( 0x000F , ((DWORD * )&csdesc)[ 0 ], ((DWORD * )&csdesc)[ 1 ], 0 , 0 , 0 ); __try { switchcs(); __asm { or eax, - 1 jmp eax } } __except(detect(GetExceptionInformation())) { } return 0 ; } |
执行SELECT * FROM Win32_PnPEntity
以检测是否位于VirtualBox中(下面代码出自al-khaser):
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 | / * Check vbox devices using WMI * / BOOL vbox_pnpentity_pcideviceid_wmi() { IWbemServices * pSvc = NULL; IWbemLocator * pLoc = NULL; IEnumWbemClassObject * pEnumerator = NULL; BOOL bStatus = FALSE; HRESULT hRes; BOOL bFound = FALSE; / / Init WMI bStatus = InitWMI(&pSvc, &pLoc, _T( "ROOT\\CIMV2" )); if (bStatus) { / / If success, execute the desired query bStatus = ExecWMIQuery(&pSvc, &pLoc, &pEnumerator, _T( "SELECT * FROM Win32_PnPEntity" )); if (bStatus) { / / Get the data from the query IWbemClassObject * pclsObj = NULL; ULONG uReturn = 0 ; VARIANT vtProp; while (pEnumerator) { hRes = pEnumerator - > Next (WBEM_INFINITE, 1 , &pclsObj, &uReturn); if ( 0 = = uReturn) break ; / / Get the value of the Name property hRes = pclsObj - >Get(_T( "DeviceId" ), 0 , &vtProp, 0 , 0 ); if (SUCCEEDED(hRes)) { if (vtProp.vt = = VT_BSTR) { / / Do our comparaison if (_tcsstr(vtProp.bstrVal, _T( "PCI\\VEN_80EE&DEV_CAFE" )) ! = 0 ) { bFound = TRUE; } } VariantClear(&vtProp); } / / release the current result object pclsObj - >Release(); if (bFound) break ; } / / Cleanup pSvc - >Release(); pLoc - >Release(); pEnumerator - >Release(); CoUninitialize(); } } return bFound; } |
IsDebuggerPresent
:
1 2 3 4 5 | 0 : 000 < u kernelbase!IsDebuggerPresent L3 KERNELBASE!IsDebuggerPresent: 751ca8d0 64a130000000 mov eax,dword ptr fs:[ 00000030h ] 751ca8d6 0fb64002 movzx eax,byte ptr [eax + 2 ] 751ca8da c3 ret |
之后依次是CheckRemoteDebuggerPresent,NtGlobalFlag,SeDebugPrivilege,HardwareBreakpoints及GetTickCount相关Anti-Debug,具体实现可以参阅al-khaser及Anti-Debug:Timing,这里不再一一赘述。可以通过ScyllaHide进行Anti-Anti-Debug。
0x05 Flag
题目会校验传递参数个数:
之后sub_403A70
会调用sub_410EE0
函数(根据官方WriteUp,这里应该是使用了PolyHook2)HookCryptImportKey
:
解密字符串:
一共解密出4个字符串:"L0ve", "s3cret", "5Ex", "g0d"。笔者在这里没能动调,而是根据IDA静态分析结果计算而来,由于卡在此处故查到Flare-On 8 – Task 9一文,阅读过后发现其使用方法和工具都极高效,便不再重写这些步骤,感兴趣的读者可以跟着这篇文章学习一下。
0x06 参阅链接
- 《C++反汇编与逆向分析技术揭秘(第2版)》3.2节
- CRT Initialization—Microsoft Docs
- How can I schedule some code to run after all '_atexit()' functions are completed—Stackoverflow
- How to explicitly call CRT startup functions—Stackoverflow
- C/C++ Runtime Library Code Tampering in Supply Chain
- Vectored Exception Handling, Hooking Via Forced Exception
- How Windows Debuggers Work——User-Mode Debugging
- 以Debugger原理拆解LINE的Anti-Debug Attach
- Anti-Debug: Process Memory
- Anti Debugging Protection Techniques with Examples
- How does malware know the difference between the virtual world and the real world?
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课