之前都是复现,今年第一次参赛,由于题目回归CTF赛制,所以题目难度不是很大,在第一天就成功解出来了,但是强度也挺大的,一直到凌晨三点才写完题解,深夜写完题解,写完的时候已经困傻了….
ACEFirstRound.exe分析
老规矩,先查壳

无壳,ida64位打开,main函数先调用ACEDriverSDK
成员函数来loadDriver
,然后cout,cin等待用户输入

判断输入的长度和头部字符串,要求输入长度大于0x10,头部字符串为“ACE_”

然后发现了一个奇奇怪怪的加密算法,发现和输入无关,动调发现是生成固定的string字符串:sxx


拷贝一份输入,对输入进行base58加密


猜测是常规码表,随意输入ACE_0123456789ABCDEF
,动调得到的是@iCZKJxoTR3SicbazBLej7f
,跟预测不符。

IDA找到码表,交叉引用找到赋值码表处


好吧,全是浮点数寄存器的计算,还是继续动调找到真是码表,abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ123456789
,根据该码表进行base58加密,发现该函数还将加密后的逆序了一下



将sxx
作为异或密钥再进行一层加密

OK,三环加密分析结束,发送消息调用驱动接口进行输入验证

此时输入为:ACE_0123456789ABCDEF
传入驱动的密文:33 11 3B 29 33 32 0B 17 2C 21 4B 2B 1A 1B 1A 12 02 3A 3F 1D 12 44 1E 00
ACEDriver.sys分析
查壳,发现有tvm,丢垃圾桶(
但是只有几十kb,相较于去年的十几mb还算仁慈
IDA打开定位到DriverEntry,果然v掉了

左边函数比较少,符号也有一些,找到PfltMessageNotify
函数,查阅文档发现是用于通信的

刚好三环是通过filterSendMessage
发送消息的,这个应该就是0环通信函数了。windbg对FltCreateCommunicationPort
下断点,从参数定位到MessageNotifyCallback
地址

在该处断点,运行程序,输入ACE_0123456789ABCDEF
,成功断下

观察参数发现确实是预想的传参

接着对MessageNotifyCallback
函数进行分析,尝试找到验证逻辑
发现存在混淆

混淆的方式和ACE-BASE很像,对push和pop之间的nop掉即可,之前刚好写过相似的去花脚本,在此基础上修改了点,直接拿来用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| def getAsmInfo(start,end): dic = {} pointer = start while(pointer <= end): opcode = print_insn_mnem(pointer) operand1 = print_operand(pointer,0) operand2 = print_operand(pointer,1) size = get_item_size(pointer) dic[pointer] = [opcode,operand1,operand2,size] pointer += size return dic
def deObfuscation(start , end, jmp_push_gapMax = 25, jmp_pop_gapMax = 5): asmInfo = getAsmInfo(start,end) obfuscated_blocks = [] for addr in sorted(asmInfo.keys()): processed_jmp = set() processed_pop = set() opcode, op1, _ , _ = asmInfo[addr] if opcode == 'push': reg = op1 for jmp_addr in sorted(filter(lambda x: x > addr, asmInfo.keys())): if jmp_addr - addr > jmp_push_gapMax: break jmp_op, jmp_op1, _ , _ = asmInfo[jmp_addr] if jmp_op == 'jmp' and jmp_op1 == reg and jmp_addr not in processed_jmp: processed_jmp.add(jmp_addr) for pop_addr in sorted(filter(lambda x: x > jmp_addr, asmInfo.keys())): if pop_addr - jmp_addr > jmp_pop_gapMax: break pop_op, pop_op1, _ , size = asmInfo[pop_addr] if pop_op == 'pop' and pop_op1 == reg: processed_pop.add(pop_addr) obfuscated_blocks.append({ 'start': addr, 'end': pop_addr + size, }) break
return obfuscated_blocks
|
还有些没识别出来的,手搓一下,去掉混淆后函数大概长这样

大致能猜出来是sub_140001448
函数处理主要逻辑,但是也被混淆了,继续还原



很明显第三个是一个tea加密,而且存在验证逻辑,提取dword_140004064
和keyACE6
,尝试直接解密,发现结果不对,看来没有那么简单,xref一下tea加密函数,发现还有一处调用

该函数对tea加密存在内存修改行为,怀疑是动态调整加密逻辑,windbg动调看看

发现异常,确实如意料之中,程序加载时对该处内存进行了修改,那dword_140004064
呢?查看内存,发现这个没有修改
OK,逆一下这个修改后的tea加密
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| 原片段: mov ecx, r9d .text:0000000140001059 41 8B C1 mov eax, r9d .text:000000014000105C .text:000000014000105C C1 E9 05 shr ecx, 5 .text:000000014000105F C1 E0 04 shl eax, 4 .text:0000000140001062 .text:0000000140001062 03 CD add ecx, ebp .text:0000000140001064 03 C6 add eax, esi .text:0000000140001066 33 C8 xor ecx, eax .text:0000000140001068 43 8D 04 0B lea eax, [r11+r9] .text:000000014000106C 33 C8 xor ecx, eax .text:000000014000106E 44 03 D1 add r10d, ecx
v9 += result ^ (v5 + v7 << 4) ^ (v6 + (v7 >> 5));
修改后的片段 ffffd90f`52316001 418bc9 mov ecx, r9d ffffd90f`52316004 418bc1 mov eax, r9d ffffd90f`52316007 c1e004 shl eax, 4 v7 << 4 ffffd90f`5231600a c1e905 shr ecx, 5 v7 >> 5 ffffd90f`5231600d 33c8 xor ecx, eax (v7 >> 5) ^ (v7 << 4) ffffd90f`5231600f 418bc3 mov eax, r11d v3 ffffd90f`52316012 48c1e80b shr rax, 0Bh (v3 >> 0x0B) ffffd90f`52316016 4103c9 add ecx, r9d (v7 >> 5) ^ (v7 << 4) + v7 ffffd90f`52316019 83e003 and eax, 3 (v3 >> 0x0B) & 3 ffffd90f`5231601c 418b448500 mov eax, dword ptr [r13+rax*4] [((v3 >> 0x0B) & 3 ) * 4 + key] ffffd90f`52316021 4103c3 add eax, r11d [((v3 >> 0x0B) & 3 ) * 4 + key] + v3 ffffd90f`52316024 33c8 xor ecx, eax ([((v3 >> 0x0B) & 3 ) * 4 + key] + v3 ) ^ ((v7 >> 5) ^ (v7 << 4) + v7) ffffd90f`52316026 4403d1 add r10d, ecx v9 += ([((v3 >> 0x0B) & 3 ) + key] + v3 ) ^ (((v7 >> 5) ^ (v7 << 4) + v7))
|
好了,发现就是对v9的代码行进行了修改,还原后可以进行测试了,先获取ACE_0123456789ABCDEF
加密后的密文,用作加密算法的验证

得到33 11
加密后的密文965f5996 866e20c2
编写tea解密脚本
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| VOID __fastcall DEC(unsigned int* a1, int* a2) { int v2; unsigned int v3; int v4; int v5; int v6; unsigned int v7; __int64 v8; unsigned int v9; __int64 result;
v2 = *a2; v3 = -0x61C88647*32; v4 = a2[1]; v5 = a2[2]; v6 = a2[3]; v7 = *a1; v8 = 32i64; v9 = a1[1]; do { v9 -= (a2[((v3 >> 0x0B) & 3)] + v3) ^ (((v7 >> 5) ^ (v7 << 4)) + v7);
v7 -= (v3 + v9) ^ (v2 + 16 * v9) ^ (v4 + (v9 >> 5)); v3 += 0x61C88647; --v8;
} while (v8); *a1 = v7; a1[1] = v9; }
|
运行结果

跟密文相符,验证成功
解密思路
提取dword_140004064
,注意从dword_140004060
开始提取,然后tea解密,循环异或sxx
,逆序,base58解码,得到flag
