吾爱破解 - LCG - LSG |安卓破解|病毒分析|www.52pojie.cn

 找回密码
 注册[Register]

QQ登录

只需一步,快速开始

查看: 6792|回复: 31
收起左侧

[CTF] 2019百越杯线上赛逆向endless_nightmare题目分析

[复制链接]
baymax0day 发表于 2019-11-21 11:24
本帖最后由 baymax0day 于 2019-11-21 22:04 编辑

[md]# 2019百越杯线上赛逆向endless_nightmare题目分析

上上周末的线上赛,一开始本来想的是想跟着题目走一下完整的流程的,发现乱七八糟的东西太多,就先放一下解题过程吧~ 主要是想请教一下表哥们前面的东西是如何设计的,主要是想学习一下`混淆机制`和`生成机制`,奈何自己太菜了,分析不出来,只想看解题过程的,移步[解题过程](###解题过程)。。 大白一枚,务必轻喷~

## 题目简述

#### 基础分析

1. 题目运行起来会要求输入username和passwd,除了no以外没有任何提示;
2. IDA打开搜索字符串,得出几个可能比较重要的字符串
   

字符串搜索

字符串搜索

字符串搜索

字符串搜索


1. ida查看导入表,几个比较有意思的函数,也是让我最开始分析比较`头冷`的(因为不知道是干嘛用的);a. 此处的互斥体不是让程序互斥的,而是增加了一种加密方式?、b. 原子操作 居然会产生ERROR_FILE_NOT_FOUND错误,这个学到了

导入表

导入表


4. OD载入程序,最开始因为TLS断下了,说明程序中可能用到了多线程(虽然解题的时候没有用到,但是前面看到了就想分析一下来着),并且程序出错或者结束的时候,上次错误居然是0x2错误,我还以为需要什么文件,但是看了一圈也没发现文件操作,于是就学到了上面的原子会产生0x2错误
   

OD

OD


#### 反编译查看

1. 直接指出了main函数,并且逻辑相对清晰,输出username和passwd,两个都接收完之后都进行了sub_487300函数进行处理
   

main函数

main函数


2. 接下来进入了sub_407960函数,ida反编译之后函数太长,大约1000多行,这也是难住大多数人的地方吧应该,也是我想请教的`第一个地方`, 类似这种的ida混淆的实现方式,有文章推荐么?如果有的话,交流一下。。

ida_func.png

3. 于是转入dbg进行分析,到调用sub_407960函数的地方,栈上有输入的username和passwd作为参数,修改函数的返回值eax,程序直接输出flag{我们输入的passwd},即可以简单认为这个函数是我们需要寻找的`校验函数`、

校验函数

校验函数


4. 程序中经常调用CreateMutexA,FindAtomA等函数的函数sub_4188A0? 同样sub_418120这个函数会调用上面的函数,并且传入use_fc_key等字符串,我拿类似的字符串度搜了一下,发现别的程序中也有过这个东西,然后再在字符串中发现了多线程的包winpthreads-git20141130-src,所以上面的问题就是我第一次见tdm的多线程,难免大惊小怪,勿怪我~

atom

atom

多线程

多线程


5. 因为想学习加密和混淆机制来着,最开始尝试过从末尾,即返回值开始回溯(`这里赞一下x64dbg的跟踪机制,太帅了`),寻找校验算法,这种思路纠结了好久但是失败了,路径太多了,所以就先解题吧。。。

## 解题过程

题目比较简单,主要思路就是利用软硬访问断点,对输入的数据进行监控,即可追溯到对输入的数据进行校验的关键代码处

#### 寻找 校验一

   1. 接上得知sub_407960是关键的检验函数,并且在栈上有我们的输入;
   2. 因此,第一步就是对这两个地址下断,发现第一此访问我们的输入是strlen函数就是求长度,说明程序应该有用到这个长度;
   3. 再对这两个长度的地址下断,来到两个校验长度的函数, 在这个地方走了个捷径,在知道这个地方是校验user的长度后,往上翻了一下,发现两个类似,估计是passwd的长度,直接下断分析。
   4. 最终得出user的长度得是0x6,passwd的长度得是0x24

user_len

user_len

pass_len

pass_len


#### 寻找username
   
    1. 我不会告诉你,username也是我在向上翻的过程中发现的
    2. 就算猜不出来也没关系,因为我们输入的username地址处下了断,因此程序会在以下校验处断下

username1.png

user2

user2

   
    3. 汇编比较清晰,可以直接看出校验方式为:
   
    u[0] * u[1] == 0x3250
    u[1] - u[2] == 0x2f
    u[2] ^ u[3] == 0x33
    u[3] + u[4] == 0xe6
    u[4] % u[5] == 0x33
    u[5] | u[0] == u[0]

   4. 接下来就是z3一把梭,得出用户名, 代码如下(一个小小的递归),同时我发现使用向量求结果的时候,结果居然是依据定义的向量位数来的??比如u[0]、u[1]定义了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[r].as_string()))) for r in Res.decls()]
        for r in Res.decls():
            r_chr = chr( int(Res[r].as_string()))
            if r_chr not in flag:
                t = int( Res[a].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[a].as_string()), int(Res.as_string())
        if(tmp_a * tmp_b != 0x3250):
            t = int(Res[a].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处得有'-'字符
   
   

passwd

passwd


   2. 接着对输入再进行一次比对'-'的操作,在401e50的地方会因访问断点断下,猜测是去除‘-‘字符;单步跟踪会在栈上看到去除'-'的字符串;

    del_.png
    trace_del.png

   3. 对新产生的即去除’-‘的字符串重新下访问断点,最后会在最后的校验函数断下;简单分析发现函数在和输入的passwd进行对比,拿到对比的字符,就是flag
   
    end.png

```python
def get_pass():

    tmp_p = list(string.ascii_letters[:0x24])
    tmp_p[0x8] = tmp_p[0xD] = tmp_p[0x12] = tmp_p[0x17] = 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

免费评分

参与人数 11威望 +1 吾爱币 +16 热心值 +10 收起 理由
NanKeYM + 1 + 1 楼主,有线下的wp吗
不挂科夫斯基 + 1 我很赞同!
CLX + 1 + 1 用心讨论,共获提升!
笙若 + 1 + 1 谢谢@Thanks!
gaosld + 1 + 1 用心讨论,共获提升!
ArnoD + 1 + 1 用心讨论,共获提升!
三胖胖胖 + 1 + 1 用心讨论,共获提升!
Hmily + 1 + 7 + 1 感谢发布原创作品,吾爱破解论坛因你更精彩!
刘样andholiday + 1 + 1 用心讨论,共获提升!
smile5 + 1 + 1 热心回复!
乌龙小八戒 + 1 谢谢@Thanks!

查看全部评分

发帖前要善用论坛搜索功能,那里可能会有你要找的答案或者已经有人发布过相同内容了,请勿重复发帖。

liltn 发表于 2019-11-21 15:37
感谢楼主分享!
dongfang155 发表于 2019-11-21 13:00
混淆部分是一堆while1吗 如果是的话 这是OLLVM的控制流平坦化,可以用去平坦化工具deflat.py来去除一些的
AIctiy 发表于 2019-11-21 11:36
txq18363001227 发表于 2019-11-21 12:07
不错,谢谢分享
处女-大龙猫 发表于 2019-11-21 13:08
支持,支持,收藏学习
Ginobili 发表于 2019-11-21 14:52
老哥题目文件能不能发一下
刘样andholiday 发表于 2019-11-21 16:47
楼主有人牛,感谢分享啦
CORLEONEY 发表于 2019-11-21 19:24
网上原题,太气人了
wenyuanzh 发表于 2019-11-21 19:26
感谢分享,学习了
您需要登录后才可以回帖 登录 | 注册[Register]

本版积分规则 警告:本版块禁止灌水或回复与主题无关内容,违者重罚!

快速回复 收藏帖子 返回列表 搜索

RSS订阅|小黑屋|处罚记录|联系我们|吾爱破解 - LCG - LSG ( 京ICP备16042023号 | 京公网安备 11010502030087号 )

GMT+8, 2024-4-26 20:09

Powered by Discuz!

Copyright © 2001-2020, Tencent Cloud.

快速回复 返回顶部 返回列表