我是懒狗,懒狗是我,拖到今天。–6.18
0x00 日常查壳
拖进IDA直接闪退,好!
linux下file查一下,可以发知道是RISC-V架构
0x01 Ghidra Time
详见
https://ppppz.net/2022/06/15/Ghidra-learning/
稍加分析可知,符号执行题,那么一个个分析即可
0x02 Opcode
1 2
| F3,00, F4,E1, F4,E2, F2,04,0B, F5, F3,00, F4,E1, F4,E2, F2,04,0B, F5, F1,FF
|
分析可知,这里就是每个FX对应一个操作,这些FX的后面就是所需的参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| void init_opcode(undefined4 *param_1)
{ *param_1 = 0; *(undefined *)(param_1 + 6) = 0xf1; *(undefined **)(param_1 + 8) = &Check; *(undefined *)(param_1 + 10) = 0xf2; *(undefined **)(param_1 + 0xc) = &for_count; *(undefined *)(param_1 + 0xe) = 0xf3; *(undefined **)(param_1 + 0x10) = &Add_Key; *(undefined *)(param_1 + 0x12) = 0xf4; *(undefined **)(param_1 + 0x14) = &ShiftXor; *(undefined *)(param_1 + 0x16) = 0xf5; *(undefined **)(param_1 + 0x18) = &NextRound; return; }
|
逐个分析,分析之前要清楚!
param_1是一个结构体(然而还没找到Ghidra处理结构体比较好的办法)
F1 | Check
这里爆个wrong基本可以猜测另一个奇怪数据是加密后的flag
之后F5函数可以确定DAT_001020c8就是加密后的input
1 2 3 4 5 6 7 8 9 10 11 12 13
| void UndefinedFunction_00100b7e(int *param_1) { int iStack20; for (iStack20 = 0; iStack20 < 4; iStack20 = iStack20 + 1) { if (*(qword *)(&DAT_001020c8 + (longlong)iStack20 * 8) != (&encFlag)[iStack20]) { printf("Wrong!"); exit(0); } } *param_1 = *param_1 + 1; return; }
|
F2 | for_count
- (int)(uint)(byte)(&opcode)[*param_1 + 2] 这里是取了opcode后两个字符
- (int)(uint)(byte)(&opcode)[*param_1 + 1] 同理取后一个
- *param_1 指的是当前opcode
1 2 3 4 5 6 7 8 9 10 11 12
| void UndefinedFunction_00100bfe(int *param_1) { if (count < (int)(uint)(byte)(&opcode)[*param_1 + 2]) { *param_1 = *param_1 - (uint)(byte)(&opcode)[*param_1 + 1]; count = count + 1; } else { count = 0; *param_1 = *param_1 + 3; } return; }
|
那么其实是什么意思呢,整理可知
1 2 3 4 5 6
| for ( count = 0; count < 0xB; count++ ) { f4,e1, f4,e2 函数操作 } count = 0 opcode += 3
|
count小于的是当前F2 opcode后两位也就是0xB
而为了实现循环操作,用了当前opcode - 4的操作,也就是减去的F2 opcode后的第一位0x4
F3 | Add_Key
注意开始我说过要把param_1当成结构体
1 2 3 4 5 6 7 8 9 10 11
| void UndefinedFunction_00100974(int *param_1) { *(longlong *)(param_1 + 2) = *(longlong *)(&input + (longlong)(int)(uint)(byte)(&opcode)[*param_1 + 1] * 8) + _DWORD_00102008; *(longlong *)(param_1 + 4) = *(longlong *)(&input + (longlong)(int)((byte)(&opcode)[*param_1 + 1] + 1) * 8) + _DWORD_00102010; *param_1 = *param_1 + 2; return; }
|
(param_1 + 2) 和 (param_1 + 4)可以当成所使用的临时数 X 和 Y
(&input + (longlong)(int)((byte)(&opcode)[*param_1 + 1] + 1) * 8) 分别根据F3后的第一个操作数,把我们的32位输入划分为64位
(可以想象成我们的32个字节输入是个 64位无符号整形的数组 长度为4)
整理可知
1 2
| X = input[opcode] + key1 Y = input[opcode + 1] + key2
|
F4 | ShfitXor
Ghidra的代码亿点点怪
- 严格区别 [*param_1 + 1] 和 (param_1 + 2)
- -0x1F就是0xE1 符号数区别
- (longlong) - (int)*(undefined8 *)(param_1 + 4) 是 64 - Y(你说怪不怪
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| void UndefinedFunction_00100a10(int *param_1) { if ((&opcode)[*param_1 + 1] == -0x1f) { *(ulonglong *)(param_1 + 2) = _DWORD_00102008 + ((*(ulonglong *)(param_1 + 4) ^ *(ulonglong *)(param_1 + 2)) >> ((longlong)-(int)*(undefined8 *)(param_1 + 4) & 0x3fU) | (*(ulonglong *)(param_1 + 4) ^ *(ulonglong *)(param_1 + 2)) << ((longlong)(int)*(undefined8 *)(param_1 + 4) & 0x3fU)); } if ((&opcode)[*param_1 + 1] == -0x1e) { *(ulonglong *)(param_1 + 4) = _DWORD_00102010 + ((*(ulonglong *)(param_1 + 4) ^ *(ulonglong *)(param_1 + 2)) >> ((longlong)-(int)*(undefined8 *)(param_1 + 2) & 0x3fU) | (*(ulonglong *)(param_1 + 4) ^ *(ulonglong *)(param_1 + 2)) << ((longlong)(int)*(undefined8 *)(param_1 + 2) & 0x3fU)); } *param_1 = *param_1 + 2; return; }
|
整理可得
1 2
| -0x1F: X = ((X ^ Y) >> (64 - Y & 0x3F) | (X ^ Y) << (Y & 0x3F)) + Key1 -0x1E: Y = ((X ^ Y) >> (64 - X & 0x3F) | (X ^ Y) << (X & 0x3F)) + Key2
|
一句话,逻辑左移加key
美其名曰,RC5
F5 | NextRound
世界线收束,注意DAT_001020c8,可以回去看了F1,可以发现这是同一个
我们X和Y赋值了进去,再清除,进行下轮input加密
1 2 3 4 5 6 7 8 9 10
| void UndefinedFunction_00100af0(int *param_1) { *(undefined8 *)(&DAT_001020c8 + (longlong)DAT_0010210c * 8) = *(undefined8 *)(param_1 + 2); *(undefined8 *)(&DAT_001020c8 + (longlong)(DAT_0010210c + 1) * 8) = *(undefined8 *)(param_1 + 4) ; *(undefined8 *)(param_1 + 2) = 0; *(undefined8 *)(param_1 + 4) = 0; DAT_0010210c = DAT_0010210c + 2; *param_1 = *param_1 + 1; return; }
|
0x03 GetFlag
那么整理下流量
- F3,00 (将input变成 X 和 Y,再加上key)
- F4,E1, F4,E2, F2,04,0B (循环操作,RC5加密不过是省去了初始化密钥流的操作)
- F5 (赋值入加密后的input数据区)
- F1 (与加密后的input数据区与encFlag进行比较)
关于RC5详解
好康的维基百科:https://en.wikipedia.org/wiki/RC5
EXP
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
| #include <stdio.h> #include <stdint.h>
#define ROTR(x, y) ((x >> (y & 0x3F)) | (x << (64 - y & 0x3F)))
int main(void) { uint64_t encFlag[] = { 0x4BC21DBB95EF82CA, 0xF57BECAE71B547BE, 0x80A1BDAB15E7F6CD, 0xA3C793D7E1776385 }; uint64_t key[] = { 0x64627421, 0x79796473 }; int i, j, z; for ( i = 0; i < 4; i += 2) { for ( j = 0; j < 12; j++ ) { encFlag[i + 1] = ROTR(encFlag[i + 1] - key[1], encFlag[i]) ^ encFlag[i]; encFlag[i] = ROTR(encFlag[i] - key[0], encFlag[i + 1]) ^ encFlag[i + 1]; } encFlag[i] -= key[0]; encFlag[i + 1] -= key[1];
for ( z = 0; z < 8; z++ ) { printf("%c", encFlag[i] & 0xFF); encFlag[i] >>= 8; } for ( z = 0; z < 8; z++ ) { printf("%c", encFlag[i + 1] & 0xFF); encFlag[i + 1] >>= 8; } } return 0; }
|
GetFlag!