-
-
[原创]看雪 CTF2018.12 第二题 回马枪&牛刀
-
2018-12-5 11:37 4087
-
提纲:
一、
回马枪校验机制,擒贼先擒王, 拼手速。二、番外牛刀小试(符号计算应用)
一、
回马枪校验机制,擒贼先擒王, 拼手速。
(1)根据输入提示'Please Input:',在IDA中String窗口定位该字符串位置及其引用位置;
引用位置即为主函数位置。
我们可能会遇到主函数堆栈平衡分析错误,如下图的控制流实际是存在分析错误的情形,从而引起堆栈平衡冲突:
实际是因为 Hi_exit_or_terminate_process_54A7B0_w1 函数并不返回造成。而IDA无法识别而有不确定其不返回。
我们做一下修改,右键编辑函数"Edit Function",选择不返回。
这时的graph控制流才是正确的,堆栈不平衡问题也自然地解决。
主函数代码相对简单,伪码如图,
读入用户输入key到Hi_key_byte_5F3068
长度要求在[10,30)之间,然后拷贝到动态分配的内存Hi_innerkey_byte_5F3088中,并要求key[7]='A'
最后由主函数进入Hi_set_ch23h_and_xor_1F_49DBD0_w1,
而
Hi_set_ch23h_and_xor_1F的业务逻辑也很简单,就是将key[7]='A'强制更改为'#'字符后,与0x1F异或加密。
到此,主函数的完了,其中看不到任何校验逻辑,似乎有点费解。
快捷键X查阅Hi_innerkey_byte_5F3088的交叉引用,如图
第一条是动态内存分配,后面三条是主函数的操作,都无关紧要。
我们将注意力放在第二条。
Hi_IsKeyOK_49DC80 的算法,如下,是很明显的校验逻辑
其输入参数refKey=“invalid argument"
refKey 经过与0x1C异或加密后在与之前已经通过设置'#'和异或0x1F加密的
Hi_innerkey_byte_5F3088 比较。
匹配即输出‘ok’,以下是python表演时间
整个校验算法如下: key[7]='A' key[7]='#' key = key^0x1F refkey = 'invalid argument' refkey = refkey ^0x1C key==refkey python逆运算可以得到key a = "invalid argument" b = ''.join(chr(ord(ch)^0x1C) for ch in a) #b = 'urj}pux<}n{iqyrh' c = ''.join(chr(ord(ch)^0x1F) for ch in b) #c='jmubojg#bqdvnfmw' key = jmubojgAbqdvnfmw
于是我们得到 key = jmubojgAbqdvnfmw
问题:为何主函数看不到对这个校验逻辑的引用?它是如何被调用的?
我们不妨继续追溯Hi_IsKeyOK_49DC80的调用层次
Hi_check_key_when_exit_5AFCB0 -> Hi_printOK_if_key_match_49CEB0_w1 -> Hi_printOK_if_key_match_49CEB0 -> Hi_IsKeyOK_49DC80_w1 -> Hi_IsKeyOK_49DC80
至此即一目了然,
Hi_check_key_when_exit_5AFCB0实际是在C\C++运行时初始化时,
由初始化函数组中的函数Hi_initarray_func_4957B0注册的退出时调用的函数。
类似我们通常说的析构函数。即其实际校验是下退出时杀个回马枪,
不过抓住
Hi_innerkey_byte_5F3088这个主要矛盾,并不一定需要了解其运作机制。
二、番外牛刀小试
这个样例是静态编译样例。许多函数都无法签名识别,且开启了调式特性,导致大量的封装函数。
如样例中虽然由一万多个函数名,实际由于调试封装,可以折半。
这里,我们编写自动标识封装函数的处理IDAPython脚本。
另外,对于其中有趣的内部动态加载的类似IAT的处理,我们也尝试处理一下(尝试符号计算的应用)。
(1)封装函数识别重命名
如图,这类是编译器开启调式选项形成的。
另外一类如只是简单地三部曲 enter,call,leave。
这里我们只识别这两类,实际,一个函数中,若只对一个函数调用,我们都可以认为是封装型函数。
def sark_name_func_wx(): wd = {} #wrap:{func_w:func} for func in sark.functions(): wl = sark_get_func_wrapline(func) if wl is not None: wd[func.name] = wl.insn.operands[0].text # nf = [] #name-func which are not wrapper for others for k,v in wd.viewitems():# if v not in wd.keys(): nf.append(v) #mark func #return wd,nf for k,v in wd.viewitems(): if k in nf: continue d,n = sark_get_call_deep(k,wd,nf) if n.startswith('Hi_'): new_name= "{}_w{}".format(n,d) else: new_name= "Hi_{}_w{}".format(n,d) print sark.Function(name=k).name,new_name sark.Function(name=k).name = new_name
调用sark_name_func_wx()后,就会自动重命名封装函数,在原函数基础上加上后缀”_wX“,其中X为封装深度,或说第X层封装。
一般我们完成了底层函数的意义名称逆向后,在执行
sark_name_func_wx(),封装函数就会被自动更新名称。
实际应用一般会重复调用多次,比较随着逆向解析的深入,更多的函数会被我们重新命名。
这函数可以帮我们省略再次对封装函数的命名操作。
(2)符号计算的引用,这里我们以Hi_inneriat_init_4A9D60为例。
Hi_inneriat_init_4A9D60函数主要完成了内置函数表Hi_inneriat_5F32C8的初始化
Hi_inneriat_5F32C8 用于实现涉及的函数的间接调用,也间接让IDA摸不着头脑。
红线标识是我们标定后识别命名的函数,重命名后再执行下(1)的函数是个不错的主意。
基本思路
(1)找到函数表中对应位置的指针代表的函数,即主要为了得到下面字典。
inneriat = { 0x00:'FlsAlloc', 0x04:'FlsFree', 0x08:'FlsGetValue', 0x0C:'FlsSetValue', 0x10:'InitializeCriticalSectionEx', 0x14:'InitOnceExecuteOnce', 0x18:'CreateEventExW', 0x1C:'CreateSemaphoreW', 0x20:'CreateSemaphoreExW', 0x24:'CreateThreadpoolTimer', 0x28:'SetThreadpoolTimer', 0x2C:'WaitForThreadpoolTimerCallbacks', 0x30:'CloseThreadpoolTimer', 0x34:'CreateThreadpoolWait', 0x38:'SetThreadpoolWait', 0x3C:'CloseThreadpoolWait', 0x40:'FlushProcessWriteBuffers', 0x44:'FreeLibraryWhenCallbackReturns', 0x48:'GetCurrentProcessorNumber', 0x4C:'CreateSymbolicLinkW', 0x50:'GetCurrentPackageId', 0x54:'GetTickCount64', 0x58:'GetFileInformationByHandleEx', 0x5C:'SetFileInformationByHandle', 0x60:'GetSystemTimePreciseAsFileTime', 0x64:'InitializeConditionVariable', 0x68:'WakeConditionVariable', 0x6C:'WakeAllConditionVariable', 0x70:'SleepConditionVariableCS', 0x74:'InitializeSRWLock', 0x78:'AcquireSRWLockExclusive', 0x7C:'TryAcquireSRWLockExclusive', 0x80:'ReleaseSRWLockExclusive', 0x84:'SleepConditionVariableSRW', 0x88:'CreateThreadpoolWork', 0x8C:'SubmitThreadpoolWork', 0x90:'CloseThreadpoolWork', 0x94:'CompareStringEx', 0x98:'GetLocaleInfoEx', 0x9C:'LCMapStringEx'}
我们用IDAPython脚本自动找出Hi_inneriat_init_4A9D60函数中每个GetProcAddress引用位置;
然后取该位置函数调用的参数2获取函数名;
而在函数调用的后面,其是对函数表偏移量指针赋值,我们通过符号计算得到偏移;
(当然,不借助符号运算,我们也可以针对每种指令集合情形,采用硬编码针对性获取偏移量,
但,这里不会只是mov ecx, imul edx,ecx.3的情形,如前面的mov ecx,4,shl ecx,0,
还有不是以edx为偏移的情形等等,我们初衷还是希望能找到一种灵活应对各种情况的解决方案,
而这里,符号计算是个不错选择,虽然有些杀鸡用牛刀,这里只是做符号计算的简单演示,
其在逆向中的威力取决于我们的想象力)
下图是我们的符号计算引擎,用于计算 mov Hi_inneriat_5F32C8[ecx], eax引用处前两条指令时产生的IR表示,
并获取偏移寄存器的计算值,比如
mov ecx,4
imul edx,ecx,3
的IR表示如图
from miasm2.analysis.machine import Machine from miasm2.core.bin_stream import bin_stream_str from miasm2.ir.symbexec import SymbolicExecutionEngine from miasm2.expression.expression import ExprId machine = Machine('x86_32') dis_engine,ira = machine.dis_engine,machine.ira def miasm2_get_symbol_run_result(l,reg='edx'): global machine,dis_engine,ira shellcode=l.bytes+l.next.bytes bs = bin_stream_str(shellcode) bs_dis_engine = dis_engine(bs) bs_dis_engine.dont_dis = [shellcode.__len__()] block = bs_dis_engine.dis_block(0) ir_arch = ira(bs_dis_engine.loc_db) ircfg = ir_arch.new_ircfg() ir_block = ir_arch.add_asmblock_to_ircfg(block,ircfg) sb = SymbolicExecutionEngine(ir_arch) sb.run_block_at(ircfg,0) ret = sb.symbols[ExprId('{}'.format(reg.upper()),32)] return ret.arg.arg
以下用于获取函数内所有对GetProcAddress的引用位置,
然后向前定位参数2,获取函数名,向后定位Hi_inneriat_5F32C8引用,利用符号计算来获取偏移
hlafindpX,hla_find_next_instr_x在以前的文章中已经提供过,可以参考最后
GetProcAddress_ea = 0x005F5044 Hi_inneriat_init_4A9D60_ea = 0x4A9D60 def get_crefs_to_func1_wihtin_func2(func1,func2): within_fun_sea = sark.Function(ea = func2).startEA call_crefs_to = [] for cr in sark.Line(ea = func1).crefs_to: if sark.Function(ea = cr).startEA == within_fun_sea: call_crefs_to.append(cr) return call_crefs_to crs = get_crefs_to_func1_wihtin_func2(GetProcAddress_ea,Hi_inneriat_init_4A9D60_ea) for cr in crs: push_func_name = hlafindpX(cr,1) func_name = sark.get_string(sark.Line(ea = push_func_name).insn.operands[0].imm) opl = sark.Line(ea = (hla_find_next_instr_x(cr,['Hi_inneriat_'],0,SEARCH_DOWN))) reg_name = list(opl.insn.operands[0].regs)[0] tbloff = miasm2_get_symbol_run_result(opl.prev.prev,reg_name) print " 0x{:02X}:'{}',".format(tbloff,func_name)
有了偏移和函数名,我们就可以进行标定命名。
get_proxy_crefs_to_tgt用于找到所有对函数表Hi_inneriat_5F32C8_ea的调用
如图,通过检测
Hi_inneriat_5F32C8_ea的引用后是否用security_cookie解密函数指针来判定
代码块是对函数表函数的封装调用。
也可以看到,其函数表的偏移由引用处的前两条指令计算出来,
这里我们还是用前面的符号计算引擎计算获取。
Hi_inneriat_5F32C8_ea = 0x005F32C8 def get_proxy_crefs_to_tgt(tgt): proxy_crefs_to = [] for cr in sark.Line(ea = tgt).drefs_to: proxy_rl = sark.Line(ea = cr) if 'security_cookie' in proxy_rl.next.disasm: proxy_crefs_to.append(cr) return proxy_crefs_to pcrs = get_proxy_crefs_to_tgt(Hi_inneriat_5F32C8_ea) for pcr in pcrs: pcrl = sark.Line(ea = pcr) reg_name = list(pcrl.insn.operands[1].regs)[0] tbloff = miasm2_get_symbol_run_result(pcrl.prev.prev,reg_name) func_name = inneriat[tbloff] enter_el = sark.Line(ea = (hla_find_next_instr_x(pcr,['mov ebp, esp'],0,SEARCH_UP))) if enter_el.prev.disasm == 'push ebp': sark.function.add_func(enter_el.prev.ea) f = sark.Function(enter_el.prev.ea) new_name = "Hi_{}_{:X}".format(func_name,f.startEA) f.name = new_name else: print "{:X} maybe need your manual check.".format(enter_el.ea),'-'*80 print "{:X} {}".format(enter_el.prev.ea,new_name)
最终,我们标定创建 以下函数,并给以函数命名
4AA460 Hi_AcquireSRWLockExclusive_4AA460 4AA4B0 Hi_CloseThreadpoolTimer_4AA4B0 4AA500 Hi_CloseThreadpoolWait_4AA500 4AA550 Hi_CloseThreadpoolWork_4AA550 4AA5A0 Hi_CreateEventExW_4AA5A0 4AA620 Hi_CreateSemaphoreExW_4AA620 4AA620 Hi_CreateSemaphoreW_4AA620 4AA6E0 Hi_CreateSymbolicLinkW_4AA6E0 4AA740 Hi_CreateThreadpoolTimer_4AA740 4AA7A0 Hi_CreateThreadpoolWait_4AA7A0 4AA800 Hi_CreateThreadpoolWork_4AA800 4AA850 Hi_FlsAlloc_4AA850 4AA8B0 Hi_FlsFree_4AA8B0 4AA910 Hi_FlsGetValue_4AA910 4AA970 Hi_FlsSetValue_4AA970 4AA9D0 Hi_FlushProcessWriteBuffers_4AA9D0 4AAA20 Hi_FreeLibraryWhenCallbackReturns_4AAA20 4AAA70 Hi_GetCurrentProcessorNumber_4AAA70 4AAAC0 Hi_GetFileInformationByHandleEx_4AAAC0 4AAB30 Hi_GetSystemTimePreciseAsFileTime_4AAB30 4AAB90 Hi_GetTickCount64_4AAB90 4AABE0 Hi_InitOnceExecuteOnce_4AABE0 4AAD40 Hi_InitializeConditionVariable_4AAD40 4AAD90 Hi_InitializeCriticalSectionEx_4AAD90 4AAE00 Hi_InitializeSRWLock_4AAE00 4AAEA0 Hi_ReleaseSRWLockExclusive_4AAEA0 4AAEF0 Hi_SetFileInformationByHandle_4AAEF0 4AAF60 Hi_SetThreadpoolTimer_4AAF60 4AAFC0 Hi_SetThreadpoolWait_4AAFC0 4AB020 Hi_SleepConditionVariableCS_4AB020 4AB070 Hi_SleepConditionVariableSRW_4AB070 4AB0D0 Hi_SubmitThreadpoolWork_4AB0D0 4AB120 Hi_TryAcquireSRWLockExclusive_4AB120 4AB170 Hi_WaitForThreadpoolTimerCallbacks_4AB170 4AB1C0 Hi_WakeAllConditionVariable_4AB1C0 4AB210 Hi_WakeConditionVariable_4AB210 4AB260 Hi_GetCurrentPackageId_4AB260 4B55F0 Hi_CompareStringEx_4B55F0 4B57B0 Hi_GetLocaleInfoEx_4B57B0 4B5830 Hi_LCMapStringEx_4B5830
hlafindpX,hla_find_next_instr_x代码
def hlafindpX(calladdr = 0x0, paramX = 0x0): searchflag = SEARCH_NEXT | SEARCH_CASE & ~SEARCH_DOWN #paramX is the (paramX+1)th parameter, so-called --index parameters pX = 0 codeaddr = calladdr while pX <= paramX: codeaddr = FindCode(codeaddr,searchflag) if IshlaPush(codeaddr): pX = pX + 1 return codeaddr def IshlaPush(codeaddr=0x0): retbool = 0 hlacodebyte = Byte(codeaddr) for pushflag in xrange(0x50,0x58): if hlacodebyte == pushflag: retbool = 2 return retbool if hlacodebyte == 0x6A: retbool = 2 return retbool if hlacodebyte == 0x68: retbool = 3 return retbool if hlacodebyte == 0x0E or hlacodebyte == 0x16: retbool = 4 return retbool if hlacodebyte == 0x1E or hlacodebyte == 0x06: retbool = 4 return retbool if Word(codeaddr) == 0xA00F or Word(codeaddr) == 0xA80F: retbool = 4 return retbool if Word(codeaddr) in [0x77FF,0x73FF,0x75FF,0x76FF,0x70FF,0x71FF,0x72FF,0x74FF,0x76FF]: #70 eax,ecx,edx,ebx,esp,ebp,esi,edi retbool = 5 return retbool def hla_find_next_instr_x(ea,instr = ['push'], index = 0,up_down = SEARCH_DOWN): # note: this is a weak func if (ea < 0) or (index < 0): Message('ea_cur can not set to zero.\n') return cnt = 0 cur_code_addr = ea if up_down == SEARCH_DOWN: while 1: #Message('{:08X} {}\n'.format(cur_code_addr,GetDisasm(cur_code_addr))) hla_curasm = GetDisasm(cur_code_addr) #Message('{}\n'.format(hla_curasm)) for instr_ in instr: if instr_ in hla_curasm: cnt = cnt + 1 #Message('cnt-1:{} index{}\n'.format(cnt-1,index)) if (cnt - 1) == index: return cur_code_addr cur_code_addr = FindCode(cur_code_addr,SEARCH_NEXT|SEARCH_DOWN) else: while 1: #Message('{:08X} {}\n'.format(cur_code_addr,GetDisasm(cur_code_addr))) hla_curasm = GetDisasm(cur_code_addr) for instr_ in instr: if instr_ in hla_curasm: cnt = cnt + 1 if (cnt - 1) == index: return cur_code_addr cur_code_addr = FindCode(cur_code_addr,SEARCH_NEXT|SEARCH_UP)
[培训]二进制漏洞攻防(第3期);满10人开班;模糊测试与工具使用二次开发;网络协议漏洞挖掘;Linux内核漏洞挖掘与利用;AOSP漏洞挖掘与利用;代码审计。
最后于 2018-12-5 15:16
被HHHso编辑
,原因: 修正
赞赏
他的文章
鸿蒙通识
23310
看原图