强网杯赛题解析 | unicorn_like_a_pro
2021-06-21 19:14:45 Author: mp.weixin.qq.com(查看原文) 阅读量:167 收藏

本文为看雪论坛精华文章
看雪论坛作者ID:无名侠
unicorn framework 是一个基于 qemu 的模拟执行框架。
GitHub链接:https://github.com/unicorn-engine/unicorn
这道题目内部就调用了 unicorn 框架模拟执行一段 x64代码,最开始以为出题人魔改了 unicorn 框架,用 bindiff 分析了一段时间,发现并没有魔改 unicorn 代码。

 符号还原

本题没有符号,逆向时比较困难,用 bindiff 可以直接还原。
在 ubuntu 上编译一份 unicorn 代码,载入 bindiff 插件即可完成大部分符号还原。

IDA 中的 bindiff 插件,载入另外一份 idb 后,按下 Ctrl + 6,点击如下按钮,设置好阀值后即可导入符号。

main 函数分析

2.1 创建虚拟机
uc_open(4u, 8, &v14);                         // UC_ARCH_X86, UC_MODE_64
2.2 复制代码到虚拟机的 0x1000 地址
uc_mem_write(v14, 0x1000LL, &code, 0x1027LL);

2.3 设置回调

Unicorn 支持很多种回调类型,当 Unicorn 运行的代码满足特定的条件时,将触发对应的回调。

在回调中可以对虚拟机中环境上下文操作,类似调试器的调试回调。

本题借助 Unicorn 指令回调,实现指令即时解密,执行后重新加密,打乱控制流。

// 虚拟机输入接口,虚拟机代码中的 in 指令uc_hook_add(v14, &trace, 2, input + 1, 0LL, 1LL, 0LL, 0xDAu);// 2 == UC_HOOK_INSN// 虚拟机输出接口,虚拟机中代码中的 out 指令处理uc_hook_add(v14, &trace, 2, output, 0LL, 1LL, 0LL, 0x1F4u);// 2 = UC_HOOK_INSN// 虚拟机中 syscall 指令处理,设置 rax 寄存器的值为 time(0)uc_hook_add(v14, &trace, 2, syscall_time, 0LL, 1LL, 0LL, 0x2BBu);// 2 = UC_HOOK_INSN// 虚拟机中 fs 内存访问处理,改变 fs:0 的值,关键算法部分很重要uc_hook_add(v14, &trace, 1024, changeKey, 0LL, 0x66660000LL, 0x66661000LL, v4);// UC_HOOK_MEM_READ// 代码解密回调uc_hook_add(v14, &trace, 8, decrypt, &v21, 1LL, 0LL, v5);// UC_HOOK_BLOCK// 代码控制流控制回调1uc_hook_add(v14, &trace, 0x4000,  ControlFlow1, &v21, 1LL, 0LL, v6);// UC_HOOK_INSN_INVALID// 代码控制流控制回调2uc_hook_add(v14, &trace, 4,  ControlFlow2, &v21, 0x10A3LL, 0x10A4LL, v7);// UC_HOOK_CODE
注意控制流有两个控制回调,第二个控制回调只在 0x10A3 地址处有效,就是特殊处理的地址。

 代码解密分析

代码解密的主要逻辑在 decrypt 函数,该函数解密当前即将执行的基本块,加密上一个执行完的基本块。
v9 = miniDec(lastKey, entry_rip);for ( i = 0; i <= 85; ++i ){  if ( v9 == *&size_table[8 * i] )  {    size[0] = *&size_table[8 * i + 4];        // getlen    v12 = malloc(size[0]);    uc_mem_read(v5, addr, v12, size[0]);    for ( j = 0; size[0] > j; ++j )      ;    dec1(v12, size[0], lastKey);    uc_mem_write(v5, addr, v12, size[0]);    for ( k = 0; size[0] > k; ++k )      ;    free(v12);    *(*&size[1] + 4LL) = addr;    *(*&size[1] + 12LL) = **&size[1];    *(*&size[1] + 8LL) = size[0];    *(*&size[1] + 16LL) = addr;    uc_reg_write(v5, 41, &addr);              // UC_X86_REG_RIP  }}
基本块密钥用 miniDec 函数计算,参数为上一个基本块的入口密钥 lastKey 与当前基本块入口 rip,minidec 函数如下:
__int64 __fastcall miniDec(int a1, int a2){  return a2 ^ a1 ^ (a2 * a1) ^ (a1 + a2);}
size_table 是一个数组,该数组保存了基本块密钥与基本块大小的关系,元素结构如下:
dd keydd size
size_table = [0x02F73020, 0x00000015, 0x09D3473A, 0x00000051, 0x0EF87B55, 0x0000000D, 0x147CB028, 0x00000023, 0x15F833AA, 0x00000030, 0x17086780, 0x00000018, 0x1733A9D4, 0x00000014, 0x17D61EE8, 0x00000051, 0x1D52F19E, 0x00000011, 0x1F732DE0, 0x0000000D, 0x1FBECFAD, 0x0000001B, 0x245BD7C8, 0x00000055, 0x25E7ABEE, 0x00000009, 0x2882C190, 0x000000A2, 0x2A2084A0, 0x00000075, 0x326AA6AE, 0x00000036, 0x33074A36, 0x00000024, 0x3440BD69, 0x0000002C, 0x362A1FC3, 0x0000002C, 0x3C0450D0, 0x0000000D, 0x3CB575FD, 0x00000011, 0x41B3B26E, 0x0000004E, 0x46005120, 0x00000011, 0x465A72CF, 0x00000002, 0x492145A0, 0x0000000D, 0x49AA4CE0, 0x0000002D, 0x4BD63647, 0x0000004E, 0x4BF84A87, 0x0000000D, 0x4D102445, 0x00000033, 0x4D4D3C55, 0x0000001B, 0x53723232, 0x0000000A, 0x5809B5CB, 0x000000A2, 0x5B12FFCE, 0x00000015, 0x5B1F3000, 0x00000051, 0x5D9FBD20, 0x00000027, 0x6219EED9, 0x0000008A, 0x65D82D17, 0x0000004C, 0x67F5671A, 0x00000063, 0x6CE2CBC1, 0x00000033, 0x718A739C, 0x0000000B, 0x71A62DD7, 0x00000015, 0x7693A1F6, 0x00000014, 0x7A473FB0, 0x00000047, 0x7AEFEDDC, 0x00000011, 0x7AF2CF90, 0x0000004F, 0x7BE0B8B0, 0x0000001B, 0x80EB3E88, 0x0000000A, 0x8213506A, 0x0000000C, 0x82468114, 0x00000011, 0x86B872A2, 0x0000001C, 0x87FBD296, 0x00000019, 0x88719339, 0x00000016, 0x89E2630A, 0x00000024, 0x8CB6536E, 0x0000004E, 0x92316E00, 0x00000015, 0x9415A51E, 0x0000004F, 0x94D658E0, 0x0000002B, 0x97E8DFCD, 0x00000036, 0x992E3874, 0x0000002A, 0x9B06958D, 0x00000030, 0x9B36B480, 0x0000000D, 0xA03CEFAD, 0x0000005A, 0xA39F47E6, 0x0000004E, 0xA946DEC4, 0x000000B4, 0xAE6173DC, 0x00000051, 0xB044A68D, 0x0000008C, 0xB29E36A8, 0x0000000B, 0xB82781F4, 0x0000000D, 0xC14DFAF8, 0x00000011, 0xC3F42E20, 0x0000001E, 0xC5E0065E, 0x00000067, 0xCAD68B21, 0x00000039, 0xCBF29AC7, 0x00000011, 0xCE8729BC, 0x0000001B, 0xD2A85A94, 0x00000004, 0xD34FA4F3, 0x00000011, 0xD64611B0, 0x00000058, 0xD814FD56, 0x00000018, 0xDD386A80, 0x0000000A, 0xDE82DFAC, 0x00000011, 0xEC68D16F, 0x0000001B, 0xEEDE845B, 0x0000003F, 0xF235F260, 0x0000008D, 0xF9AA1F0B, 0x00000087, 0xFC200887, 0x00000011, 0xFED657A3, 0x0000000C, 0x00000000]

奇数下标数据为key,偶数为基本块字节的长度。

用 python 实现基本块解密函数:

def fuck(prev_key, rip):    return rip ^ prev_key ^ ((rip * prev_key) & 0xffffffff) ^ (prev_key + rip)def deXor(data, key):    key = p32(key)    data = bytearray(data)    for i in range(len(data)):        data[i] ^=  key[i % 4]    return datadef decrypt_block(key, rip):    key2 = fuck(key, rip)    blockSize = get_size(key2)    if blockSize is None:        print("Not found1: rip:%x key:%x" % (rip, key2))        return None, None, None    offset = rip - 0x1000    code_data = code_bin[offset: offset + blockSize]    code_data = deXor(code_data, key)    next_rip = rip + blockSize - 2    key2 = fuck(key, next_rip)    jmps = get_jmps(key2)    return code_data, jmps, next_rip

这里的部分代码还涉及控制流重建,后面会提到。

 控制流分析

ControlFlow1 回调,执行到无效指令会被调用,用于切换程序中的控制流。

该题用 3f 0f作为基本块的结尾(无效指令)触发 ControlFlow1 回调,切换控制流。

ControlFlow1 回调函数根据结尾rip与当前基本块的key计算另外一个 key,用于索引当前基本块的后继基本块的信息。

flowInfo 是一个数组,每一个元素有如下5个字段:
dd keydd zf_0_jmp  当基本块结尾 zf 标志寄存器为 0 的跳转偏移,下面同理dd zf_0_keydd zf_1_jmpdd zf_1_key
根据 zf 标志位跳转:

注意 v9 保存的是上一个基本块的 key,此处做的 += 运算,即上一个基本块的 key 与 下一个基本块的 key 有关联。

由此可见,基本块的 key 与控制流的路径有关!写解密脚本的时候要考虑路径问题。

另外,当虚拟机中程序运行到 0x10A3 时将调整控制流并更改 key:


解密后的基本块,很多都是以读取 fs 寄存器并判断结尾。

r15 的值来源于 fs:xxx ,最后再与 fs:xxx 内存的值比较,很明显最后的 zf = 1。

其实并不是,这道题对 fs 寄存器指向的那段内存做了内存读回调,回调如下:

每次读取 fs 指向的内存,该内存的值都会被改写。所以 mov 与 cmp 对 fs 内存访问出的结果是不同的,自然 zf = 0,走 zf = 0 的分支。

在控制流重建的时候,需要考虑以读取 fs 内存结尾的基本块,将其看作是无条件跳转,而不是 jz/jnz。

 控制流重建

重建思路:

以 bfs 遍历顺序,从入口基本块开始解密,解密后再查询分支信息表获取后继基本块的相对偏移与key,最后将新基本块的信息加入到队列,等待分析。遍历时注意维护路径上的 key 累计值。

所有基本块解密完成后,可以得到每个基本块的后继基本块的相对偏移。

要在基本块的结尾插入跳转指令,这将改变代码布局,使得原始相对偏移不可用,所以我采取重编译来解决这个问题,重编译之前将原始基本块的入口地址作为基本的符号名,基本块结尾用 jmp/jz 等指令连接。

code.bin 文件时 dump 出来的原始 code 数据,输出 1.bin 可以直接在 ida 中反编译。

import ctypesfrom capstone import *from keystone import *from pwn import *context.arch = 'amd64'from pwn import *fuckTable = [0x00412F5E, 0xFFFFFA22, 0x14252652, 0xFFFFF9AC, 0x66CEF8EC, 0x0251D934, 0x0000009F, 0xC56FBF59, 0xFFFFFF61, 0xAA4D5B7C, 0x02745896, 0xFFFFFB7F, 0x34B6D31E, 0xFFFFFBB0, 0x302CC828, 0x02AC5992, 0xFFFFF524, 0x67CC4064, 0xFFFFF483, 0x8A5D9B26, 0x046254D0, 0xFFFFFC37, 0x074AB936, 0xFFFFFC7F, 0xB8EA37F7, 0x0CACD9FE, 0x0000007F, 0x6112F222, 0x00000002, 0x47A72561, 0x0F0FE6EB, 0xFFFFFBAE, 0x0A1411E7, 0xFFFFFC85, 0x3BE88B46, 0x0FC59DC2, 0xFFFFF72F, 0x7D12A5EF, 0xFFFFF691, 0xE67393D6, 0x10B1EBCA, 0x000001CF, 0x473A1295, 0x0000022E, 0x7BC15385, 0x1565D41D, 0xFFFFFDC4, 0x05D337BE, 0xFFFFFE7E, 0xE12982E4, 0x18909E40, 0x000005EB, 0xAE2337AF, 0x000005B1, 0x8E0AB2ED, 0x1AE7593A, 0xFFFFF3BC, 0x23E9058D, 0xFFFFF40B, 0xDFA6CF3E, 0x1B47DA81, 0xFFFFF8C3, 0x349CC616, 0xFFFFF7E9, 0x70C290D0, 0x1D816435, 0x00000002, 0x43F999C9, 0xFFFFFFD8, 0xAB0BCA16, 0x1DACC905, 0xFFFFFF54, 0x5C129962, 0xFFFFFD06, 0xE4515A41, 0x1E03B13C, 0xFFFFFF80, 0x7E763806, 0xFFFFF36A, 0xA25F3D93, 0x22FEFC06, 0xFFFFFCDD, 0xB94E0C2F, 0xFFFFFCB6, 0xF023033D, 0x26B1E690, 0xFFFFFDAB, 0xD0C7ED0C, 0xFFFFFE9B, 0xD49872C6, 0x2A652084, 0x000001EA, 0xDF9B65EE, 0x00000051, 0x5CC5AB90, 0x2FBEBD25, 0x0000048F, 0x60A4E9F2, 0x000009AF, 0x42FE8B0D, 0x34F12D90, 0x000004C0, 0xF6257D94, 0x00000480, 0x5227DE21, 0x35F591D0, 0xFFFFFCA1, 0xDA83E113, 0xFFFFF998, 0x805C7ECB, 0x37EB0B72, 0xFFFFF3EC, 0x7480201A, 0xFFFFF903, 0xAC977E11, 0x389A58A8, 0x00000189, 0xE4005CD7, 0xFFFFFDEC, 0xB043695F, 0x3CB24155, 0x0000084C, 0x8ACB6FF1, 0x00000899, 0xACB471A5, 0x3DCBCDE3, 0x000007A8, 0xA84E3072, 0x00000384, 0xB2624259, 0x3F5290DE, 0xFFFFFE25, 0x8AC11F92, 0xFFFFFD8A, 0x44ACCD78, 0x47FF9B7E, 0x00000A81, 0x9833BF9C, 0x00000B35, 0x9B7199CD, 0x4C7867E6, 0x0000011C, 0x68BB4F80, 0x0000002E, 0x75B675CD, 0x53ADCD80, 0x000004E8, 0x6AA4F705, 0x00000452, 0xBA7C314B, 0x566E1640, 0x00000C8E, 0x203E3737, 0x00000C38, 0xF9367ED9, 0x5EDBB130, 0x000004FF, 0xD4F71A40, 0x000002AA, 0x35DC4141, 0x6C29C83A, 0x00000013, 0xBEAD8A76, 0xFFFFFFB5, 0x7A8A43EF, 0x6E036C9C, 0x00000BD5, 0x225F81E0, 0x00000D89, 0x3C25944D, 0x6FDCCE50, 0x00000605, 0xD3126740, 0x000003D5, 0xA3DA544C, 0x7132D345, 0x0000064E, 0x00915A5A, 0x000006DD, 0x5BCB6B22, 0x720DBD5C, 0x000008C3, 0x64DCFDF6, 0x00000858, 0x190B20BB, 0x7A035AD4, 0x00000424, 0x4DD955FB, 0x000004BF, 0xF65150B5, 0x7CBAED22, 0x00000AA1, 0x62CC154B, 0xFFFFFC58, 0x8DD5CEDB, 0x7EBF8EA8, 0x00000458, 0xCE844A0E, 0xFFFFF734, 0x9079D6BA, 0x804885CD, 0x000007BB, 0x89A8DA66, 0x00000136, 0x7185B813, 0x82190F37, 0xFFFFF58C, 0x013FA7D4, 0xFFFFF4AB, 0x7518093D, 0x83F7826A, 0x00000917, 0x2F33C3DD, 0xFFFFFBF0, 0x02A289B1, 0x8481BFD5, 0xFFFFF927, 0x72EED2D1, 0xFFFFF80A, 0xF46FD351, 0x85A69D6E, 0x000000B4, 0x27A3BB0F, 0x00000181, 0x49235BC0, 0x85F73150, 0x00000259, 0xA300692F, 0x000009BD, 0x5A3E46A9, 0x86E2497A, 0xFFFFFB53, 0xE7614707, 0xFFFFFBB3, 0xFA190B2A, 0x8B261F60, 0xFFFFF323, 0x97B9CC33, 0xFFFFFAB7, 0x2CB73BF0, 0x8B42B00C, 0x00000871, 0xA57A2DE3, 0x00000797, 0xA73082D6, 0x8E4C5C94, 0x000000FE, 0xEE4B594B, 0xFFFFF999, 0xDCE3B74D, 0x913A9FDB, 0xFFFFFE1C, 0x1BFFA329, 0xFFFFFD31, 0x49B21C95, 0x922BFB96, 0xFFFFF61B, 0x4FAFD829, 0xFFFFFBBA, 0x6BD5D317, 0x9F4B8702, 0xFFFFFEC1, 0xB691AD49, 0xFFFFFEF2, 0xCE6C6FE9, 0xA2CEAAA6, 0xFFFFFD89, 0x60E52701, 0xFFFFFCB2, 0x25AD9A9D, 0xAA970D72, 0xFFFFF2BB, 0xC1F58CAC, 0xFFFFF2AB, 0x20B8FE22, 0xABC02B72, 0xFFFFF94B, 0xFF6EA5A6, 0xFFFFFA6A, 0x1CD46647, 0xAE535E9E, 0x000003EC, 0x31246F6B, 0x0000035B, 0x50E2A20A, 0xB7337941, 0xFFFFF856, 0xD1A79AD7, 0xFFFFF955, 0x14673B75, 0xBB8DB95E, 0xFFFFFEEB, 0x6A7F1E5A, 0xFFFFF3B3, 0x1EF2F3AA, 0xBC1EDA22, 0xFFFFFB90, 0xE247955F, 0xFFFFFCE6, 0xA0351A85, 0xBCD91FE8, 0x0000008C, 0x71A348B9, 0x00000030, 0x821754EF, 0xBD38E305, 0xFFFFFF59, 0xE694333F, 0xFFFFFEF9, 0x436B1A45, 0xBE1AA65A, 0xFFFFF93D, 0x8761A810, 0xFFFFFEEB, 0xB2DB19FA, 0xC052453C, 0x000009B5, 0xB05027D7, 0x000009C5, 0xBCA91679, 0xC4A2D780, 0x000008B9, 0xE42FD068, 0x000007C1, 0x9F8B2B83, 0xC6A236BA, 0xFFFFFDBB, 0x20649A12, 0xFFFFFD09, 0x5F73FD94, 0xC6BB5160, 0xFFFFFE90, 0x0ED42674, 0xFFFFFF4B, 0xA76699CC, 0xCB74E940, 0x000003E3, 0x7DA194FF, 0xFFFFFCDA, 0xB23E5B15, 0xD027B387, 0xFFFFF701, 0x880BCC4F, 0xFFFFF785, 0xFEA3D685, 0xD1127D6B, 0xFFFFFAC0, 0xDF3D499A, 0x00000362, 0x84B7777D, 0xD6F5F913, 0xFFFFFCCD, 0xA5D89DB8, 0xFFFFFCAB, 0xF69BAE29, 0xDD04F828, 0xFFFFF705, 0xE18F3BA0, 0xFFFFF64D, 0xEBC799B0, 0xDDF22CB8, 0x0000075D, 0x47F7B857, 0x000001B3, 0x5C1CDEA9, 0xDF34D0A8, 0x0000014D, 0xBFE2CAD5, 0x00000201, 0x1F0C8A89, 0xE146EA40, 0x0000046D, 0x189EB8F9, 0xFFFFF6FB, 0x4CA1090D, 0xE231C560, 0x00000710, 0x2E586529, 0xFFFFFF17, 0x0E9AA776, 0xE2FC6838, 0x00000733, 0xB73DDD7A, 0x00000753, 0x14A1BDE4, 0xE44AE35D, 0x000002C8, 0x46B1F3D1, 0xFFFFFA2D, 0xD2295816, 0xE5AF4AB1, 0x00000DB0, 0x0AFF4FF9, 0x00000D91, 0xB17A4340, 0xE7E3CF21, 0x00000656, 0x9FC50924, 0x00000658, 0x31615022, 0xE8815965, 0x00000BCB, 0x6F51A655, 0x00000C0A, 0x72F5680C, 0xEBDF0F14, 0xFFFFF2B1, 0xD36EC5D4, 0xFFFFF239, 0x3B711343, 0xEC12E59B, 0x00000270, 0x3A38D2E8, 0x0000023D, 0x68D07674, 0xF4013920, 0x00000703, 0xD83CCFAA, 0x000007BA, 0x46891EEB, 0xF6847EC1, 0xFFFFFE62, 0x6D4BAAFC, 0xFFFFFD92, 0x5E6F5A94]size_table = [0x02F73020, 0x00000015, 0x09D3473A, 0x00000051, 0x0EF87B55, 0x0000000D, 0x147CB028, 0x00000023, 0x15F833AA, 0x00000030, 0x17086780, 0x00000018, 0x1733A9D4, 0x00000014, 0x17D61EE8, 0x00000051, 0x1D52F19E, 0x00000011, 0x1F732DE0, 0x0000000D, 0x1FBECFAD, 0x0000001B, 0x245BD7C8, 0x00000055, 0x25E7ABEE, 0x00000009, 0x2882C190, 0x000000A2, 0x2A2084A0, 0x00000075, 0x326AA6AE, 0x00000036, 0x33074A36, 0x00000024, 0x3440BD69, 0x0000002C, 0x362A1FC3, 0x0000002C, 0x3C0450D0, 0x0000000D, 0x3CB575FD, 0x00000011, 0x41B3B26E, 0x0000004E, 0x46005120, 0x00000011, 0x465A72CF, 0x00000002, 0x492145A0, 0x0000000D, 0x49AA4CE0, 0x0000002D, 0x4BD63647, 0x0000004E, 0x4BF84A87, 0x0000000D, 0x4D102445, 0x00000033, 0x4D4D3C55, 0x0000001B, 0x53723232, 0x0000000A, 0x5809B5CB, 0x000000A2, 0x5B12FFCE, 0x00000015, 0x5B1F3000, 0x00000051, 0x5D9FBD20, 0x00000027, 0x6219EED9, 0x0000008A, 0x65D82D17, 0x0000004C, 0x67F5671A, 0x00000063, 0x6CE2CBC1, 0x00000033, 0x718A739C, 0x0000000B, 0x71A62DD7, 0x00000015, 0x7693A1F6, 0x00000014, 0x7A473FB0, 0x00000047, 0x7AEFEDDC, 0x00000011, 0x7AF2CF90, 0x0000004F, 0x7BE0B8B0, 0x0000001B, 0x80EB3E88, 0x0000000A, 0x8213506A, 0x0000000C, 0x82468114, 0x00000011, 0x86B872A2, 0x0000001C, 0x87FBD296, 0x00000019, 0x88719339, 0x00000016, 0x89E2630A, 0x00000024, 0x8CB6536E, 0x0000004E, 0x92316E00, 0x00000015, 0x9415A51E, 0x0000004F, 0x94D658E0, 0x0000002B, 0x97E8DFCD, 0x00000036, 0x992E3874, 0x0000002A, 0x9B06958D, 0x00000030, 0x9B36B480, 0x0000000D, 0xA03CEFAD, 0x0000005A, 0xA39F47E6, 0x0000004E, 0xA946DEC4, 0x000000B4, 0xAE6173DC, 0x00000051, 0xB044A68D, 0x0000008C, 0xB29E36A8, 0x0000000B, 0xB82781F4, 0x0000000D, 0xC14DFAF8, 0x00000011, 0xC3F42E20, 0x0000001E, 0xC5E0065E, 0x00000067, 0xCAD68B21, 0x00000039, 0xCBF29AC7, 0x00000011, 0xCE8729BC, 0x0000001B, 0xD2A85A94, 0x00000004, 0xD34FA4F3, 0x00000011, 0xD64611B0, 0x00000058, 0xD814FD56, 0x00000018, 0xDD386A80, 0x0000000A, 0xDE82DFAC, 0x00000011, 0xEC68D16F, 0x0000001B, 0xEEDE845B, 0x0000003F, 0xF235F260, 0x0000008D, 0xF9AA1F0B, 0x00000087, 0xFC200887, 0x00000011, 0xFED657A3, 0x0000000C, 0x00000000]zf_0_jmp = 0zf_0_key = 1zf_1_jmp = 2zf_1_key = 3code_bin = open("code.bin", "rb").read()print("code size: ", hex(len(code_bin)))def get_size(key):    for i in range(85):        if size_table[i * 2] == key:            return  size_table[i * 2 + 1]    return Nonedef get_jmps(key):    for i in range(85):        base = i * 5        if fuckTable[base] == key:            # zf_0_jmp, zf_0_key, zf_1_jmp, zf_1_key            return fuckTable[base + 1: base + 5]    print("not found2: ", hex(key))    return Nonedef fuck(prev_key, rip):    return rip ^ prev_key ^ ((rip * prev_key) & 0xffffffff) ^ (prev_key + rip)def deXor(data, key):    key = p32(key)    data = bytearray(data)    for i in range(len(data)):        data[i] ^=  key[i % 4]    return datadef decrypt_block(key, rip):    key2 = fuck(key, rip)    blockSize = get_size(key2)    if blockSize is None:        print("Not found1: rip:%x key:%x" % (rip, key2))        return None, None, None    offset = rip - 0x1000    code_data = code_bin[offset: offset + blockSize]    code_data = deXor(code_data, key)    next_rip = rip + blockSize - 2    key2 = fuck(key, next_rip)    jmps = get_jmps(key2)    return code_data, jmps, next_ripclass Node:    def __init__(self, data, rip):        self.code_data = data        self.child1 = 0        self.child2 = 0        self.end_rip = None        self.rip = ripdef disasm(data, baseaddr):    md = Cs(CS_ARCH_X86, CS_MODE_64)    ins = ''    for i in md.disasm(data, baseaddr):        asm_code = "%s\t%s" % (i.mnemonic, i.op_str)        ins += asm_code + "\n"    return insdef buildNode(key_, rip_):    work_queue = [(key_, rip_)]    log_map = {}    while len(work_queue) > 0:        T = work_queue[0]        work_queue.remove(T)        key, rip = T        if rip in log_map:            continue        if rip == 0x10A3:            key -= 0x2B09B990            rip = 0x1EEC        code_data, jmps, next_rip = decrypt_block(key, rip)        if code_data is None:            continue        node_cur = Node(code_data, rip)        node_cur.end_rip = next_rip        log_map[rip] = node_cur        if jmps is None:            continue        asm_text = disasm(node_cur.code_data, rip)        newrip = next_rip + ctypes.c_int32(jmps[zf_0_jmp]).value        node_cur.child1 = newrip        if newrip not in log_map:            work_queue.append(((key + jmps[zf_0_key]) & 0xffffffff, newrip))        if 'qword ptr fs:[' in asm_text.splitlines()[-1]:            continue        newrip = next_rip + ctypes.c_int32(jmps[zf_1_jmp]).value        node_cur.child2 = newrip        if newrip not in log_map:            work_queue.append(((key + jmps[zf_1_key]) & 0xffffffff, newrip))    jmptables = {}    for i in sorted(log_map.keys()):        if log_map[i].child1 is None:            log_map[i].child1 = 0        if log_map[i].child2 is None:            log_map[i].child2 = 0        jmptables[hex(log_map[i].end_rip)] = (hex(log_map[i].rip), hex(log_map[i].child1), hex(log_map[i].child2))    print("len:", len(jmptables))    print(jmptables)    all_asm = ''    for i in sorted(log_map.keys()):        print(hex(i))        node = log_map[i]        if i == 0x1EEC:            all_asm += "_0x10a3:\n"        all_asm += "_" + hex(i) + ":\n"        all_asm += disasm(node.code_data, i)        if node.child1 != 0 and node.child2 != 0:            jmp_code = "jz _" + hex(node.child2) + "\n"            jmp_code += "jmp _" + hex(node.child1) + "\n"        elif node.child1 == 0 and node.child2 != 0:            jmp_code = "jmp _" + hex(node.child2) + "\n"        elif node.child2 == 0 and node.child1 != 0:            jmp_code = "jmp _" + hex(node.child1) + "\n"        else:            jmp_code = '\n'        all_asm += jmp_code    all_asm = all_asm.replace("endbr64", "nop\n" * 4)    code_bin = asm(all_asm)    open('1.bin', 'wb').write(code_bin)    print(all_asm)    print("all nodes: ", len(log_map))buildNode(0x3265B1F5, 0x1000)

 提取出来的代码分析

重建控制流,输出 bin 后,代码比较清晰了。
// rsp = 0x7777F000void __noreturn sub_4(){  unsigned __int8 v0; // al  int v1; // eax  __int64 v2; // rax  __int64 xorKey[4]; // [rsp+0h] [rbp-2C0h]  __int128 v4; // [rsp+21h] [rbp-29Fh] BYREF  char v5[14]; // [rsp+31h] [rbp-28Fh] BYREF  char v6[17]; // [rsp+3Fh] [rbp-281h] BYREF  __int64 compare[5]; // [rsp+50h] [rbp-270h] BYREF  __int64 v8; // [rsp+78h] [rbp-248h]  __int64 FlagInData[8]; // [rsp+80h] [rbp-240h] BYREF  unsigned __int8 v10; // [rsp+C2h] [rbp-1FEh]  unsigned __int8 v11; // [rsp+C3h] [rbp-1FDh]  int kk; // [rsp+C4h] [rbp-1FCh]  __int64 v13; // [rsp+C8h] [rbp-1F8h]  __int128 *v14; // [rsp+D0h] [rbp-1F0h]  unsigned __int8 v15; // [rsp+DAh] [rbp-1E6h]  unsigned __int8 v16; // [rsp+DBh] [rbp-1E5h]  int mm; // [rsp+DCh] [rbp-1E4h]  __int64 v18; // [rsp+E0h] [rbp-1E0h]  __int64 v19; // [rsp+E8h] [rbp-1D8h]  unsigned __int8 v20; // [rsp+F3h] [rbp-1CDh]  int jj; // [rsp+F4h] [rbp-1CCh]  __int64 v22; // [rsp+F8h] [rbp-1C8h]  __int64 *v23; // [rsp+100h] [rbp-1C0h]  __int64 *v24; // [rsp+108h] [rbp-1B8h]  int ii; // [rsp+110h] [rbp-1B0h]  unsigned int v26; // [rsp+114h] [rbp-1ACh]  int v27; // [rsp+118h] [rbp-1A8h]  unsigned int v28; // [rsp+11Ch] [rbp-1A4h]  unsigned int *v29; // [rsp+120h] [rbp-1A0h]  int n; // [rsp+12Ch] [rbp-194h]  unsigned __int64 v31; // [rsp+130h] [rbp-190h]  unsigned __int64 v32; // [rsp+138h] [rbp-188h]  unsigned __int64 v33; // [rsp+140h] [rbp-180h]  unsigned __int64 data2; // [rsp+148h] [rbp-178h]  int m; // [rsp+154h] [rbp-16Ch]  unsigned __int64 v36; // [rsp+158h] [rbp-168h]  unsigned __int64 data1; // [rsp+160h] [rbp-160h]  int i_0; // [rsp+16Ch] [rbp-154h]  __int64 v39; // [rsp+170h] [rbp-150h]  unsigned __int64 from_t0; // [rsp+178h] [rbp-148h]  __int64 const_32; // [rsp+180h] [rbp-140h]  __int64 *flag_ptr; // [rsp+188h] [rbp-138h]  unsigned __int64 v43; // [rsp+190h] [rbp-130h]  __int64 v44; // [rsp+198h] [rbp-128h]  __int64 *v45; // [rsp+1A0h] [rbp-120h]  unsigned __int8 v46; // [rsp+202h] [rbp-BEh]  unsigned __int8 v47; // [rsp+203h] [rbp-BDh]  int nn; // [rsp+204h] [rbp-BCh]  __int64 v49; // [rsp+208h] [rbp-B8h]  char *v50; // [rsp+210h] [rbp-B0h]  __int64 flagLen; // [rsp+218h] [rbp-A8h]  __int64 *v52; // [rsp+220h] [rbp-A0h]  unsigned __int8 v53; // [rsp+22Ah] [rbp-96h]  unsigned __int8 v54; // [rsp+22Bh] [rbp-95h]  int i1; // [rsp+22Ch] [rbp-94h]  __int64 v56; // [rsp+230h] [rbp-90h]  char *v57; // [rsp+238h] [rbp-88h]  __int64 v58; // [rsp+240h] [rbp-80h]  __int64 time0; // [rsp+248h] [rbp-78h]  __int64 v60; // [rsp+250h] [rbp-70h]  __int64 d2; // [rsp+258h] [rbp-68h]  __int64 v62; // [rsp+260h] [rbp-60h]  __int64 d1; // [rsp+268h] [rbp-58h]  int i; // [rsp+274h] [rbp-4Ch]  __int64 data0; // [rsp+278h] [rbp-48h]  unsigned __int8 v66; // [rsp+282h] [rbp-3Eh]  unsigned __int8 v67; // [rsp+283h] [rbp-3Dh]  int j; // [rsp+284h] [rbp-3Ch]  __int64 v69; // [rsp+288h] [rbp-38h]  char *v70; // [rsp+290h] [rbp-30h]  unsigned __int8 v71; // [rsp+29Ah] [rbp-26h]  unsigned __int8 v72; // [rsp+29Bh] [rbp-25h]  int k; // [rsp+29Ch] [rbp-24h]  __int64 v74; // [rsp+2A0h] [rbp-20h]  __int64 *flagIn; // [rsp+2A8h] [rbp-18h]  __int64 v76; // [rsp+2B0h] [rbp-10h]  unsigned __int64 time0_1; // [rsp+2B8h] [rbp-8h]  v58 = 0x7177625F32303231i64;  __writefsqword(0, 0x7177625F32303231ui64);  __asm { syscall; Low latency system call }  time0 = MEMORY[0x1337];                       // time(0)  time0_1 = MEMORY[0x1337];  v2 = MEMORY[0x1337] / 0xE10ui64;  data0 = MEMORY[0x1337] / 0xE10ui64;  for ( i = 0; i != 256; ++i )  {    d1 = 0i64;    d1 = *(_QWORD *)v2;    v62 = d1;    d2 = 0i64;    d2 = *(_QWORD *)(d1 + 1);    v60 = d2;    data0 = __ROL8__((data0 ^ d1) + d2 + 33 * data0 + 1, 13);    if ( (i & 1) != 0 )      data0 = v60 ^ (v62 + data0);    if ( (i & 2) != 0 )      data0 ^= v62 + v60;    if ( (i & 4) != 0 )      data0 ^= v60 ^ v62;    v2 = i & 8;    if ( (i & 8) != 0 )    {      v2 = data0 + v62 + v60;      data0 = v2;    }  }  v76 = data0;  strcpy(v6, "input flag:\n");  v70 = v6;  v69 = 12i64;  for ( j = 0; v69 != j; ++j )  {    v67 = v70[j];    v66 = v67;    __outbyte(1u, v67);                         // putchar  }  memset(FlagInData, 0, sizeof(FlagInData));  flagIn = FlagInData;  v74 = 0x40i64;  for ( k = 0; ; ++k )  {    if ( v74 == k )      goto LABEL_3;    v0 = __inbyte(0);                           // get_char    v72 = v0;    v71 = v0;    if ( v0 == '\n' )      break;    *((_BYTE *)flagIn + k) = v71;  }  *((_BYTE *)flagIn + k) = 0;LABEL_3:  if ( v76 == 0x1C986C3B22EA63E5i64 )  {    v52 = FlagInData;    for ( flagLen = 0i64; *((_BYTE *)v52 + flagLen); ++flagLen )      ;    v8 = flagLen;    if ( flagLen == 32 )    {      v45 = FlagInData;      v44 = 32i64;      v43 = time0_1 / 0xE10;      flag_ptr = FlagInData;      const_32 = 32i64;      from_t0 = time0_1 / 0xE10;      v39 = 0x5249415452455451i64;      __writefsqword(0, 0x5249415452455451ui64);      for ( i_0 = 0; const_32 != i_0; ++i_0 )      {        data1 = __readfsqword(0);               // 0x5249415452455451        v36 = i_0;        for ( m = 0; m != 256; ++m )        {          data2 = __readfsqword(0);          v33 = data2;          v32 = data2;          v31 = data2;          v36 = (v36 ^ data2) + data2 + 0x21 * v36 + 1;          v36 = __ROL8__(v36, 13);          if ( (m & 1) != 0 )            v36 = v31 ^ (v33 + v36);          if ( (m & 2) != 0 )            v36 ^= v33 + v31;          if ( (m & 4) != 0 )            v36 ^= v31 ^ v33;          if ( (m & 8) != 0 )            v36 += v33 + v31;        }        *((_BYTE *)flag_ptr + i_0) ^= (_BYTE)data1 + (_BYTE)v36;      }      for ( n = 0; const_32 != n; n += 4 )      // 4字节一组      {        v29 = (unsigned int *)((char *)flag_ptr + n);        v28 = *v29;        v27 = 0;        v26 = v28;        v28 = from_t0 + _mm_crc32_u32(0, v28);        *v29 = v28;      }      xorKey[0] = 0x178DEC4F232DDB6Ei64;      xorKey[1] = 0xC2AAB7D6D2A167C3ui64;      xorKey[2] = 0xF1AB91F72761A80Fui64;      xorKey[3] = 0x3DCEDC28076C41Ai64;      for ( ii = 0; v44 != ii; ++ii )        *((_BYTE *)v45 + ii) ^= *((_BYTE *)xorKey + ii);      compare[0] = 0x3EC81D9432CEF584i64;      compare[1] = 0xB649A4DCD6BD24FEui64;      compare[2] = 0xC5927F0B767A787Dui64;      compare[3] = 0x1F245B7F751BB52Ei64;      v24 = FlagInData;      v23 = compare;      v22 = v8;      for ( jj = 0; ; ++jj )      {        if ( v22 == jj )        {          v1 = 0;          goto LABEL_47;        }        v20 = *((_BYTE *)v24 + jj) - *((_BYTE *)v23 + jj);        if ( v20 )          break;      }      v1 = v20;LABEL_47:      if ( v1 )      {        strcpy((char *)&v4, "wrong\n");        v14 = &v4;        v13 = 6i64;        for ( kk = 0; v13 != kk; ++kk )        {          v11 = *((_BYTE *)v14 + kk);          v10 = v11;          __outbyte(1u, v11);        }      }      else      {        strcpy((char *)&v4 + 7, "correct\n");        v19 = (__int64)&v4 + 7;        v18 = 8i64;        for ( mm = 0; v18 != mm; ++mm )        {          v16 = *(_BYTE *)(mm + v19);          v15 = v16;          __outbyte(1u, v16);        }      }    }    else    {      strcpy(v5, "wrong\n");      v50 = v5;      v49 = 6i64;      for ( nn = 0; v49 != nn; ++nn )      {        v47 = v50[nn];        v46 = v47;        __outbyte(1u, v47);      }    }  }  else  {    *(_DWORD *)&v5[7] = 'norw';    *(_WORD *)&v5[11] = '\ng';    v5[13] = 0;    v57 = &v5[7];    v56 = 6i64;    for ( i1 = 0; v56 != i1; ++i1 )    {      v54 = v57[i1];      v53 = v54;      __outbyte(1u, v54);    }  }  __halt();}
程序入口调用 syscall,由 unicorn 回调处理获取 time(0) 的值,并根据该值计算一个数 0x1C986C3B22EA63E5。

为了屏蔽IDA的优化,方便分析,我做了一些小 patch:

  • syscall 的 unicorn 回调会修改 rax 寄存器,但是 ida 认为 rax 的值是一个可计算的常数,于是后面与 rax 有关的表达式都被优化。

  • for 循环中读取了两次 fs:0 , 有unicorn回调,每次读取的值肯定不一样,ida 认为两次读取的值一样,于是优化成一次访问。

手动 patch ,即可解决这些问题。

有两种爆破思路:

  • 直接爆破 time(0)

  • 根据 flag 信息反向爆破

可以用 flag 开头来爆破 time(0) 时间常数。

from_t0 = time_0 / 0xE10;

由于 4字节一组,qwb{与QWB{ 与 flag 与 FLAG 都是已知的 4 个字节并且 v28 可以非常轻松的获得。

通过爆破开头也可以反推 time(0)。

爆破出 time(0) 后的做法就是非常简单的计算任务了。

#include <iostream>#include "ida.h"#include <x86intrin.h>static uint64  fs_0;uint64 getFS0() {    // v7[0] = 8461816625668189699LL * v7[0] + 841540768324766462LL;    fs_0 = 0x756E69636F726E03 * fs_0 + 0xBADC0DEC001CAFE;    return fs_0;}void setFSO(int64_t val) {    fs_0 = val;}uint64 test_time(uint64 time) {    uint64  v35;    setFSO(0x7177625F32303231);    uint64 v2 = time ;    uint64  data0 = time ;    uint64 d1, d2,v62, v60;    for (int i = 0; i != 256; ++i )    {        d1 = 0;        d1 = getFS0();        v62 = d1;        d2 = getFS0();        v60 = d2;        data0 = __ROL8__((data0 ^ d1) + d2 + 33 * data0 + 1, 13);        if ( (i & 1) != 0 )            data0 = v60 ^ (v62 + data0);        if ( (i & 2) != 0 )            data0 ^= v62 + v60;        if ( (i & 4) != 0 )            data0 ^= v60 ^ v62;        v2 = i & 8;        if ( (i & 8) != 0 )        {            v2 = data0 + v62 + v60;            data0 = v2;        }    }    printf("%p ==\n", data0);    return data0;}int main() {    std::cout << "Hello, World!" << std::endl;    unsigned __int64 compare[4];    unsigned __int64 xorKey[4];    unsigned int flags[] = {0x67616c66, 0x47414c46, 0x7b627771, 0x7b425751};    unsigned char xorkey0[32] = {0};    uint32  * xorkey0_4 = (uint32  *)xorkey0;    compare[0] = 0x3EC81D9432CEF584; // flag, FLAG, qwb{ ,QWB{    compare[1] = 0xB649A4DCD6BD24FE;    compare[2] = 0xC5927F0B767A787D;    compare[3] = 0x1F245B7F751BB52E;    xorKey[0] = 0x178DEC4F232DDB6E;    xorKey[1] = 0xC2AAB7D6D2A167C3;    xorKey[2] = 0xF1AB91F72761A80F;    xorKey[3] = 0x3DCEDC28076C41A;    unsigned int * compare_4 = (unsigned int *)compare;    unsigned int fs;    uint64 v35, v36, v33,v32, v31, v30;    setFSO(0x5249415452455451);    for (int i_0 = 0; 32 != i_0; ++i_0 )    {        uint64 data0 = getFS0();               // 0x5249415452455451        v35 = i_0;        for (int m = 0; m != 256; ++m )        {            uint64 data1 = getFS0();            uint64 data2 = getFS0();            v35 = (v35 ^ data1) + data2 + 0x21 * v35 + 1;            v35 = __ROL8__(v35, 13);            if ( (m & 1) != 0 )                v35 = data2 ^ (data1 + v35);            if ( (m & 2) != 0 )                v35 ^= data2 + data1;            if ( (m & 4) != 0 )                v35 ^= data2 ^ data1;            if ( (m & 8) != 0 )                v35 += data2 + data1;        }        xorkey0[i_0] =  (_BYTE)data0 + (_BYTE)v35;    }    for(int k = 0; k < 4; k++) {        unsigned char * data = (unsigned  char *)&flags[k];        for (int i = 0 ; i < 4; i++) {            data[i] ^= xorkey0[i];        }       // printf("%x\n", flags[k]);    }    for (int ii = 0; 32 != ii; ++ii )        *((_BYTE *)compare + ii) ^= *((_BYTE *)xorKey + ii);    uint64 targetTT = 0;    for (int i = 0; i < 4; i++)    {        // v27 = from_t0 + _mm_crc32_u32(0, v27);      //  unsigned int from_t0 = compare_4[0] - _mm_crc32_u32(0, flags[i]);        uint64 tt = compare_4[0] - _mm_crc32_u32(0, flags[i]);        uint64 rr = test_time(tt);        if (rr == 0x1c986c3b22ea63e5) {            targetTT = tt;            printf("val:%p\n", tt);        }    }    uint32 flag_arr[8];    for (int i = 0; i < 8; ++i) { // 4 * 8        for (uint32 val = 0; val != 0xffffffff; ++val) {            if (compare_4[i] - targetTT == _mm_crc32_u32(0, val)) {                flag_arr[i] = val;                break;            }        }    }    for (int i = 0; i < 8; ++i) {        flag_arr[i] ^= xorkey0_4[i];    }    printf("%s", (char *)flag_arr);    return 0;}

 

看雪ID:无名侠

https://bbs.pediy.com/user-home-617255.htm

  *本文由看雪论坛 无名侠 原创,转载请注明来自看雪社区

《安卓高级研修班》2021年秋季班火热招生中!

# 往期推荐

公众号ID:ikanxue
官方微博:看雪安全
商务合作:[email protected]

球分享

球点赞

球在看

点击“阅读原文”,了解更多!


文章来源: http://mp.weixin.qq.com/s?__biz=MjM5NTc2MDYxMw==&mid=2458387028&idx=1&sn=871e93d6cc7610a13b7303f50d343934&chksm=b18f32de86f8bbc8ecbb8772bc68b670b9872313cfeeb8aa5666bb9a7805df613818818369a1#rd
如有侵权请联系:admin#unsafe.sh