#逆向-NCTF-复现|DOSBOX-Frida3DES

🤕5ea1

一个昏昏欲睡的傍晚...为什么没有妹妹来陪我www


Ez_Dos

题目标题也给了,说是DOS下的东西,大致跑一下汇编代码,可以看到是一个RC4的加密,16位编写,拿不到f5,只能初步看到调用了多个函数来魔改,硬读汇编脚本没写出来,只能去dump密钥

有dump那肯定得调试,所以先要搭一个能跑DOS应用的环境下来,这里用的是DOS_BOX,安装其实没什么好讲的,直接跑一遍安装包即可,这点还是很先进的(和某些玩意比起来

打开DOSBOX以后出现一个界面,输什么调试指令都没用,因为DOSBOX相当于一个系统模拟器,模拟的是CPU的指令,需要在系统盘下运行,所以这里要把要调试的目录挂载为系统盘
1
挂载指令:mount c [你自己的路径]
挂载完直接进入盘即可
2
现在需要一个DOS系统下的调试工具:debug.exe,把它直接拖到要调试的目录下,然后打入指令:debug.exe [要调试的应用]
3
具体调试的指令可以看我其他的博客(

来看要找什么,rc4可以简化为明文^密钥流=密文,所以只要拿到其中后面两个即可,密文已知,那么我们有两种方案去拿到密钥流

  1. 类似测信道,预先输入一个明文,观测加密后得到的密文(非实际密文,然后两者异或得到密钥
  2. 直接从异或操作中读取密钥寄存器读取

密文

看汇编,观察seg002:FA段,这句代码是一个xor操作,通过观察分析,此处是加密的最后一步异或
4
继续往下看,加密后的明文预设密文比较段,去几个就能找到下面的汇编
5
此处出现了**[si][di]的比较,往上看,si的预设值是141h,di是168h,蜘蛛感应,不太对,发现si后面又加上了2,di加上了1,所以应该是170h142h**
6
然后你随便调一下就知道170h存的是明文(加密后的明文,那142就是预设密文...
dump下来

unsigned char enc[] =
{
  0x7C, 0x3E, 0x0D, 0x3C, 0x88, 0x54, 0x83, 0x0E, 0x3B, 0xB8, 
  0x99, 0x1B, 0x9B, 0xE5, 0x23, 0x43, 0xC5, 0x80, 0x45, 0x5B, 
  0x9A, 0x29, 0x24, 0x38, 0xA9, 0x5C, 0xCB, 0x7A, 0xE5, 0x93, 
  0x73, 0x0E, 0x70, 0x6D, 0x7C, 0x31, 0x2B, 0x8C
};

1

按照上面的分析,可以知道FA处的xor是明文密钥流异或加密,所以得知执行到这一步的时候的al就是加密的密钥,只要在这一步打下断点,读取寄存器ax后两位即可
7
8
9
只要硬读就行,38次

2

上面也分析到了,170h存放的数据是加密后的明文,所以只要直接步进到程序末尾,然后去读取170h处的数据即可得到加密后明文,再和明文异或就能拿到密钥
10
手动dump,写出如下脚本:

# 定义两个十六进制字符串
hex_str1 = "7C 3E 0D 3C 88 54 83 0E 3B B8 99 1B 9B E5 23 43 C5 80 45 5B 9A 29 24 38 A9 5C CB 7A E5 93 73 0E 70 6D 7C 31 2B 8C"
hex_str2 = "53 1c 38 1b 92 6c d2 1a 05 ed 8a 49 a6 c6 32 62 c2 8c 46 0b 82 17 08 6d bb 49 99 69 db c7 76 5f 73 38 24 67 2f 90"

# 将十六进制字符串转换为字节列表
bytes1 = bytes.fromhex(hex_str1.replace(" ", ""))
bytes2 = bytes.fromhex(hex_str2.replace(" ", ""))

# 执行异或操作
result_bytes = bytes(97^a ^ b for a, b in zip(bytes1, bytes2))

# 将结果转换为十六进制字符串
result_hex = ' '.join(format(byte, '02X') for byte in result_bytes)

# 输出十六进制结果
print(result_hex)

# 将结果转换为 ASCII 字符串
try:
    result_ascii = result_bytes.decode('ascii')
    print(result_ascii)
except UnicodeDecodeError:
    print("结果不能完全转换为 ASCII 字符串。")

运行得到flag:NCTF{Y0u_4r3\Bp@fmb1y_M4st3r_5d0b497e}

x1_login

困死了,速写思路

一开始进去发现字符串混淆,有调用某个函数解密,直接找到对应函数,懒得逆,把so库拿来直接调用一遍,还原混淆的字符串;
还原字符串以后关注check类,里面从assets调用了一个本地库,去掉了前40字节,所以从对应位置找到库,写脚本还原,还原后可以解出用户名
解出用户名以后考虑解密码,密码可以通过hook每轮的密钥来解,因为是动态注册,所以这里要在注册的时候先hook一次,保证加载进来以后再hook本地
hook完直接拿到两个密钥,拿来解一下des即可

字符串混淆

  1. 复写脚本调用
D  准备调用 get 方法,输入: zM1GzM4=
D  DecStr.get 方法的返回结果: check
I  DecStr.get 方法的返回结果: check

D  准备调用 get 方法,输入: Exv3nhr5BNW0axn3aNz/DNv9C3q0wxj/Exe=
D  DecStr.get 方法的返回结果: com.nctf.simplelogin.Check
I  DecStr.get 方法的返回结果: 

D  准备调用 get 方法,输入: ygvUF2vHFgbPiN9J
D  DecStr.get 方法的返回结果: libsimple.so
I  DecStr.get 方法的返回结果: libsimple.so

D  准备调用 get 方法,输入: agDYB3bJ
D  DecStr.get 方法的返回结果: native
I  DecStr.get 方法的返回结果: native

D  准备调用 get 方法,输入: uZPOs29goMu6l38=
D  DecStr.get 方法的返回结果: X1c@dM1n1$t
I  DecStr.get 方法的返回结果: X1c@dM1n1$t
  1. 直接分析算法
    一次换盒base64外加一次异或长度
    11

  2. frida hook

Java.perform(function ()
{
    var DecStr = Java.use('com.nctf.simplelogin.DecStr');
    DecStr.get.implementation = function (str)
    {
        result = this.get(str)
        console.log("传入的参数 str: " + str +"实际值: " + result );
        return this.get(result);
    };
});

root检测

/Secure

  1. smali代码修改

  2. frida hook

Java.perform(function ()
{
    var Secure = Java.use('com.nctf.simplelogin.Secure');
    Secure.checkRoot.implementation = function ()
    {
        return false;
    };
    Secure.checkDebug.implementation = function ()
    {
        return false;
    };
});

本地库加载

12
13
14

  1. 直接调用上面的解密逆
    16
D  准备调用 get 方法,输入: uZPOs29goMu6l38=
D  DecStr.get 方法的返回结果: X1c@dM1n1$t
I  DecStr.get 方法的返回结果: X1c@dM1n1$t
  1. frida hook tocheck
    15
    this.tocheck(str,name);

密码

  1. frida hook密钥
Java.perform(function () {
    function Start_NativeHook(libname) {
        var dlopen = Module.findExportByName(null, "android_dlopen_ext");
        if (dlopen) {
            Interceptor.attach(dlopen, {
                onEnter: function (args) {
                    var filePath = args[0].readCString();
                    if (filePath.indexOf(libname)!== -1) {
                        console.log(`[+] android_dlopen_ext: start hooking ${libname}`);
                        this.isCanHook = true;
                    }
                },
                onLeave: function (retValue) {
                    if (this.isCanHook) {
                        this.isCanHook = false;
                        hook_native();
                    }
                }
            });
        } else {
            console.error('[!] android_dlopen_ext not found');
        }
    }//加载

    function hook_native() {
        var baseAddr = Module.findBaseAddress("libnative.so");
        if (baseAddr) {
            var target_addr = baseAddr.add(0x1F1C);
            Interceptor.attach(target_addr, {
                onEnter: function (args) {
                    var ctx = this.context;
                    var key0 = null;
                    var key1 = null;
                    if (ctx) {
                        key0 = ctx.x22;
                        key1 = ctx.x23;
                    }
                    console.log(`[+] native key = ${key0} ${key1}`);
                },
                onLeave: function (retval) {

                }
            });
        } else {
            console.error('[!] libnative.so not found');
        }
    }//native hook

    Start_NativeHook("libnative");
});
  1. 去混淆

解密

key9784cf3d63dde73和7d2d3436ad3ec537d

已知密文
unsigned char ida_chars[] =
{
0x40, 0x9E, 0xEC, 0x86, 0xB8, 0x84, 0xA5, 0x8B, 0x7E, 0x8A,
0x64, 0xE2, 0x1A, 0xD3, 0xB8, 0xBB, 0xDF, 0x4B, 0xFA, 0x12,
0x46, 0x45, 0x3E, 0x52
};
解密后拿出来拼一下
第一块:~DWPefaS
第二块:+MY?x$y5
第三块:=6mG50U5