2019百越杯线上赛逆向endless_nightmare题目分析
本帖最后由 baymax0day 于 2019-11-21 22:04 编辑# 2019百越杯线上赛逆向endless_nightmare题目分析
上上周末的线上赛,一开始本来想的是想跟着题目走一下完整的流程的,发现乱七八糟的东西太多,就先放一下解题过程吧~ 主要是想请教一下表哥们前面的东西是如何设计的,主要是想学习一下`混淆机制`和`生成机制`,奈何自己太菜了,分析不出来,只想看解题过程的,移步[解题过程](###解题过程)。。 大白一枚,务必轻喷~
## 题目简述
#### 基础分析
1. 题目运行起来会要求输入username和passwd,除了no以外没有任何提示;
2. ida打开搜索字符串,得出几个可能比较重要的字符串
1. ida查看导入表,几个比较有意思的函数,也是让我最开始分析比较`头冷`的(因为不知道是干嘛用的);a. 此处的互斥体不是让程序互斥的,而是增加了一种加密方式?、b. 原子操作 居然会产生ERROR_FILE_NOT_FOUND错误,这个学到了
4. OD载入程序,最开始因为TLS断下了,说明程序中可能用到了多线程(虽然解题的时候没有用到,但是前面看到了就想分析一下来着),并且程序出错或者结束的时候,上次错误居然是0x2错误,我还以为需要什么文件,但是看了一圈也没发现文件操作,于是就学到了上面的原子会产生0x2错误
#### 反编译查看
1. 直接指出了main函数,并且逻辑相对清晰,输出username和passwd,两个都接收完之后都进行了sub_487300函数进行处理
2. 接下来进入了sub_407960函数,ida反编译之后函数太长,大约1000多行,这也是难住大多数人的地方吧应该,也是我想请教的`第一个地方`, 类似这种的ida混淆的实现方式,有文章推荐么?如果有的话,交流一下。。
3. 于是转入dbg进行分析,到调用sub_407960函数的地方,栈上有输入的username和passwd作为参数,修改函数的返回值eax,程序直接输出flag{我们输入的passwd},即可以简单认为这个函数是我们需要寻找的`校验函数`、
4. 程序中经常调用CreateMutexA,FindAtomA等函数的函数sub_4188A0? 同样sub_418120这个函数会调用上面的函数,并且传入use_fc_key等字符串,我拿类似的字符串度搜了一下,发现别的程序中也有过这个东西,然后再在字符串中发现了多线程的包winpthreads-git20141130-src,所以上面的问题就是我第一次见tdm的多线程,难免大惊小怪,勿怪我~
5. 因为想学习加密和混淆机制来着,最开始尝试过从末尾,即返回值开始回溯(`这里赞一下x64dbg的跟踪机制,太帅了`),寻找校验算法,这种思路纠结了好久但是失败了,路径太多了,所以就先解题吧。。。
## 解题过程
题目比较简单,主要思路就是利用软硬访问断点,对输入的数据进行监控,即可追溯到对输入的数据进行校验的关键代码处
#### 寻找 校验一
1. 接上得知sub_407960是关键的检验函数,并且在栈上有我们的输入;
2. 因此,第一步就是对这两个地址下断,发现第一此访问我们的输入是strlen函数就是求长度,说明程序应该有用到这个长度;
3. 再对这两个长度的地址下断,来到两个校验长度的函数, 在这个地方走了个捷径,在知道这个地方是校验user的长度后,往上翻了一下,发现两个类似,估计是passwd的长度,直接下断分析。
4. 最终得出user的长度得是0x6,passwd的长度得是0x24
#### 寻找username
1. 我不会告诉你,username也是我在向上翻的过程中发现的
2. 就算猜不出来也没关系,因为我们输入的username地址处下了断,因此程序会在以下校验处断下
3. 汇编比较清晰,可以直接看出校验方式为:
u * u == 0x3250
u - u == 0x2f
u ^ u == 0x33
u + u == 0xe6
u % u == 0x33
u | u == u
4. 接下来就是z3一把梭,得出用户名, 代码如下(一个小小的递归),同时我发现使用向量求结果的时候,结果居然是依据定义的向量位数来的??比如u、u定义了8位,但是要求的结果是0x3250,第一次求出来的居然是1和80,原因就是取了0x3250的低8位0x50,
```python
def get_user():
flag = list(string.punctuation + string.ascii_letters + string.digits)
solver = Solver()
def reFind(Res):
list_chr = [(r.name(), chr( int(Res.as_string()))) for r in Res.decls()]
for r in Res.decls():
r_chr = chr( int(Res.as_string()))
if r_chr not in flag:
t = int( Res.as_string() )
solver.add( a!=t ) # 增加条件
if solver.check() == unsat:
return
new_Res = solver.model()
return reFind(new_Res)
tmp_a, tmp_b = int(Res.as_string()), int(Res.as_string())
if(tmp_a * tmp_b != 0x3250):
t = int(Res.as_string())
solver.add(a != t) # 说明此时的值并非正确, 重新计算
if solver.check() == unsat:
return
new_Res = solver.model()
return reFind(new_Res)
return list_chr
a, b, c, d, e, f = BitVecs("a b c d e f", 8)
solver.add(a * b == 0x3250)
solver.add(b - c == 0x2f)
solver.add(c ^ d == 0x33)
solver.add(d + e == 0xe6)
solver.add(e % f == 0x33)
solver.add(f | a == a)
print(solver.check())
Res = solver.model()
print(reFind(Res))
```
#### 寻找passwd
因为没有找到生成算法,因此这个部分就相对简单了。
1. 同样依照之前的,向上翻,当然不往上寻找也是可以的,因为有访问断点,所以会断下的;只不过向上翻可以直接看到校验规则,由于汇编代码很简单可以直接得出计算规则:passwd 在 0x8、0xd、0x12、0x17处得有'-'字符
2. 接着对输入再进行一次比对'-'的操作,在401e50的地方会因访问断点断下,猜测是去除‘-‘字符;单步跟踪会在栈上看到去除'-'的字符串;
3. 对新产生的即去除’-‘的字符串重新下访问断点,最后会在最后的校验函数断下;简单分析发现函数在和输入的passwd进行对比,拿到对比的字符,就是flag
```python
def get_pass():
tmp_p = list(string.ascii_letters[:0x24])
tmp_p = tmp_p = tmp_p = tmp_p = chr(0x2d)
print("".join(tmp_p))
passwd = "89ab3210fedc98ba54761032d8badcfe"
list_p = list(passwd)
list_p.insert(0x8, '-')
list_p.insert(0xD, '-')
list_p.insert(0x12, '-')
list_p.insert(0x17, '-')
print("".join(list_p))
```
题目蓝奏云链接:https://www.lanzouj.com/i7hi9cd 感谢楼主分享!:loveliness: 混淆部分是一堆while1吗 如果是的话 这是OLLVM的控制流平坦化,可以用去平坦化工具deflat.py来去除一些的 very nice 不错,谢谢分享 支持,支持,收藏学习 老哥题目文件能不能发一下 楼主有人牛,感谢分享啦 网上原题,太气人了 感谢分享,学习了