double-joy 和 load
double-joy
找到具体加密函数:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| __int64 __fastcall sub_558A3CFB3D90(__int64 *a1) { __int64 v1 __int64 i __int64 v3
v1 = *a1; for ( i = *((int *)a1 + 4) { v3 = *(unsigned __int8 *)(v1 + i); *((_DWORD *)a1 + 4) = i + 1; if ( (unsigned __int8)v3 <= 0x12u ) break; } return ((__int64 (*)(void))((char *)dword_558A3CFB5004 + dword_558A3CFB5004[v3]))(); }
|
这里是主要是后面的比较关键, 前面的就是取一下指令.
传入的参数我把它改成一个结构体, 具体结构体的定义在Structures里面.
1 2 3 4 5 6
| 00000000 vm struc ; (sizeof=0x18, mappedto_16) 00000000 opcode dq ? 00000008 mem dq ? 00000010 index dd ? 00000014 point dd ? //指向内存的索引, 总是指向最后一个值的下一位置 00000018 vm ends
|
查看流程图窗口, 是有非常多的分支的, 具体跳转位于
1 2 3 4
| text:000055E187261DBD 49 63 04 81 movsxd rax, dword ptr [r9+rax*4] .text:000055E187261DC1 4C 01 C8 add rax, r9 .text:000055E187261DC4 db 3Eh .text:000055E187261DC4 3E FF E0 jmp rax
|
这里我们将每一个rax改为对应的硬编码, 就可以查看微代码了.
这里我大概总结了一下要用到的指令作用(具体细节记录得很丑陋, 就不放出来了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| 0 加法 1 减法 2 乘法 3 除法(带符号) 这里要注意一定要带符号 5 与 7 异或 8 2字节内存赋值 mem[mem[point-2]] = mem[point-1] 9 mem[point-1]作为索引, 再对mem[point-1]赋值 a 非 b 判断是否<0 c 对最后两个值交换 f 跳转指令 10 判断跳转 11 改变索引 12 return
|
然后逐一分析指令集(一共有两套), 可以发现, 两套加密是轮换来的, 并且第一次加密都会有异或,(这也是为什么后面加密其他字节都不改变, 但是头两次加密其他字节都发生变化的原因) 对两个字节的加密, 都需要加密20次才会轮到下两个字节加密. 两套指令分别使用两个结构体和两段内存, 每个内存中都一直存在一个字节, 表示加密次数.
解密脚本: (加密过程写在里面了)
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| #include<iostream>
uint32_t key = 448431333; uint32_t key2 = 3848427296;
int encode1(uint32_t flag[2]) { int key1[4] = {0x494C, 0x6F76, 0x6520, 0x4355}; uint32_t b = (flag[1] * 0x10); uint32_t a = ((signed)flag[1] / 0x20); uint32_t c = ((a ^ b) + flag[1]); uint32_t d = (c ^ (key + key1[key & 3])); flag[0] = d + flag[0]; key += 0x75BCD15; uint32_t e = (flag[0] * 0x10); uint32_t f = ((signed)flag[0] / 0x20); uint32_t g = (e ^ f); uint32_t h = g + flag[0]; uint32_t i = h ^ (key + key1[((signed)key / 0x800) & 3]); flag[1] = i + flag[1]; return 0; } void decode1(uint32_t flag[2]) { int key1[4] = {0x494C, 0x6F76, 0x6520, 0x4355}; uint32_t e = (flag[0] * 0x10); uint32_t f = ((signed)flag[0] / 0x20); uint32_t g = ( e^f ); uint32_t h = g + flag[0]; uint32_t i = h ^ (key + key1[((signed)key / 0x800) & 3]); flag[1] = flag[1] - i; key -= 0x75BCD15; uint32_t b = (flag[1] * 0x10); uint32_t a = ((signed)flag[1] / 0x20); uint32_t c = ((a ^ b) + flag[1]); uint32_t d = (c ^ (key + key1[key & 3])); flag[0] -= d; return; } int decode2(uint32_t flag[2]) { key2 -= 0x154CBF7; uint32_t b1 = (flag[0] * 0x10 + 0x2074); uint32_t c1 = (key2 + flag[0]); uint32_t a1 = ((signed)flag[0] / 0x20 + 0x6561); uint32_t d1 = (c1 ^ a1); uint32_t e1 = d1 ^ b1; flag[1] -= e1; uint32_t b = (flag[1] * 0x10 + 0x5354); uint32_t c = (key2 + flag[1]); uint32_t a = ((signed)flag[1] / 0x20 + 0x4f4d); uint32_t d = (c ^ a); uint32_t e = d ^ b; flag[0] -= e; return 0; } int encode2(uint32_t flag[2]) { key2 += 0x154CBF7; uint32_t b = (flag[1] * 0x10 + 0x5354); uint32_t c = (key2 + flag[1]); uint32_t a = ((signed)flag[1] / 0x20 + 0x4f4d); uint32_t d = (c ^ a); uint32_t e = d ^ b; uint32_t f = e + flag[0]; flag[0] = f; uint32_t b1 = (flag[0] * 0x10 + 0x2074); uint32_t c1 = (key2 + flag[0]); uint32_t a1 = ((signed)flag[0] / 0x20 + 0x6561); uint32_t d1 = (c1 ^ a1); uint32_t e1 = d1 ^ b1; uint32_t f1 = e1 + flag[1]; flag[1] = f1; return 0; } int main() { uint32_t flag[10] = { 0xAEE0FAE8, 0xFC3E4101, 0x167CAD92, 0x51EA6CBE, 0x242A0100, 0x01511A1B, 0x514D6694, 0x2F5FBFEB, 0x46D36398, 0x79EEE3F0};
for (int i = 8; i >= 0; i-=2) { for (int j = 19; j >= 0; j--) { decode2(flag + i); if (i == 0 && j == 0) { flag[0] ^= 0x1010101; flag[1] ^= 0x2020202; } decode1(flag + i); if (i == 0 && j == 0) { flag[0] ^= 0x1010101; flag[1] ^= 0x2020202; } int m = 3; } int n = 3; } for (int i = 0; i < 9; i++) { for (int j = 0; j < 4; j++) printf("%c", *((char *)&flag[i] + j)); } return 0; }
|
load
这里打开了一个文件映射, 并且把输入复制到文件中:
1 2 3
| FileMappingW = CreateFileMappingW(0xFFFFFFFF, 0, 4u, 0, 0x400u, Name); v7 = MapViewOfFile(FileMappingW, 0xF001Fu, 0, 0, 0x400u); strncpy(v7, Source, 0x3FFu);
|
接下来创建了一个进程, 进程对输入进行处理.
这里的进程我没有去dump, 我dump也发生了错误, 所以我直接用ida去附加这个进程, 不管具体子进程如何变化基本上都能分析.在恢复线程执行的函数下好断点, 保证主进程的处理都执行过了. 然后附加好了之后, 选中所有的load.exe段, 让ida分析, 就可以找到关键函数.下好断点, 直接运行, 转过来单步主进程, 就可以在断下来了(注意, 这里会有一个软件断点, 直接pass给进程就可以了)
读取从文件中读取输入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| v0 = kernel32_OpenFileMappingW(983071, 0, aL3hsec); vcruntime140_memset(v26, 0, 1024); if ( v0 ) { v1 = kernel32_MapViewOfFile(v0, 983071, 0, 0, 0); v2 = (char *)v1; do { v3 = *v2++; v26[(_DWORD)v2 - v1 - 1] = v3; } while ( v3 ); kernel32_UnmapViewOfFile(v1); kernel32_CloseHandle(v0); }
|
字符串每两字节转换为16进制,类似于atoi函数.
1 2 3 4 5 6 7 8 9 10 11 12 13
| do { v6 = *((_BYTE *)&v30 + v5); if ( v6 >= '0' && v6 <= '9' ) { v6 -= '0'; *((_BYTE *)&v30 + v5) = v6; } if ( (unsigned __int8)(v6 - 'a') <= 5u ) *((_BYTE *)&v30 + v5) = v6 - 'W'; ++v5; } while ( v5 < v4 );
|
接下来大概就是通过伴随矩阵求逆矩阵
A* / |A| = A-1
这里需要用到一个代数余子式的东西, 具体都是线代里面的内容
a1 a2 a3
a4 a5 a6
a7 a8 a9
tmp = a1 * (a5 * a9 - a8 * a6) - a2 * (a4 * a9 - a7 * a6) + a3 * (a4 * a8 - a7 * a5)
m1 = (a5 * a9 - a8 * a6) / tmp
m2 = -(a4 * a9 - a7 * a6) / tmp (符号分别正负顺序(根据位置决定))
…
1 2 3 4
| sub_401070(3, (int)v34, 0); // v34赋值 sub_401070(2, (int)v25, 9); // v25赋值 sub_401370(v34, 3); // 矩阵运算 sub_401370(v25, 2);
|
解密: 矩阵的逆矩阵只需要再求一次逆矩阵就是本身了.
1 2 3 4 5 6 7 8 9 10 11
| import numpy as np
a = np.array([ [1,0,-9], [0,-1,-6], [-1,-2,-4]]) # 初始化一个非奇异矩阵(数组) # print(np.linalg.inv(a)) # 对应于MATLAB中 inv() 函数
# # 矩阵对象可以通过 .I 更方便的求逆 A = np.matrix(a) print(A.I)
|
flag:
flag{f812f706f306ff02ff0dfde207}
如果您喜欢此博客或发现它对您有用,则欢迎对此发表评论。 也欢迎您共享此博客,以便更多人可以参与。 如果博客中使用的图像侵犯了您的版权,请与作者联系以将其删除。 谢谢 !