查看原文
其他

强网杯赛题解析 | unicorn_like_a_pro

无名侠 看雪学苑 2022-07-01
本文为看雪论坛精华文章
看雪论坛作者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
官方微博:看雪安全
商务合作:wsc@kanxue.com



球分享

球点赞

球在看



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

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存