提纲:
二、番外牛刀小试(符号计算应用)
一、
回马枪校验机制,擒贼先擒王, 拼手速。
(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表演时间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 整个校验算法如下:
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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 整个校验算法如下:
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的调用层次
1 2 3 4 5 | 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
|
1 2 3 4 5 | 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。
这里我们只识别这两类,实际,一个函数中,若只对一个函数调用,我们都可以认为是封装型函数。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | 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层封装。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课
最后于 2018-12-5 15:16
被HHHso编辑
,原因: 修正