首页
社区
课程
招聘
[原创]看雪 CTF2018.12 第二题 回马枪&牛刀
发表于: 2018-12-5 11:37 4715

[原创]看雪 CTF2018.12 第二题 回马枪&牛刀

HHHso 活跃值
22
2018-12-5 11:37
4715

提纲:
二、番外牛刀小试(符号计算应用)


一、 回马枪校验机制,擒贼先擒王, 拼手速。
(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[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
->
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

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编辑 ,原因: 修正
收藏
免费 3
支持
分享
最新回复 (1)
雪    币: 6369
活跃值: (1688)
能力值: ( LV4,RANK:156 )
在线值:
发帖
回帖
粉丝
2
大佬,小声的说一下,ida7.2是可以正确识别大半的签名的,7.0不支持新VC支持库的签名的,不过还是跟您学到了ida歇菜时候的处理办法
2018-12-5 21:35
0
游客
登录 | 注册 方可回帖
返回
//