虚拟机迷踪、迷雾中的时间胶囊、icrypt、CrackMe(赛后)。排名 34/100。逆向比较常规,但是状况频出。最简单的题目想得太复杂,导致后面没时间做;AES 算法没深入研究过,来不及现搓解密脚本,打的就是一个手忙脚乱,还得加练😎
300 分 vm_challenge.zip
符号表没去、opcode 数量很少,dump 之后搓解析脚本就行。
现场有师傅 7 分钟就解出来了,太神。
我以为会非常难,脚本搓了一个多小时,非常仔细,打算编译之后导入 IDA 分析的。结果调试两下发现只是个简单的异或
opcodes = [0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x24, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0xF4, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2E, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0xE7, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x23, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0xDA, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x25, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0xCD, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x39, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0xC0, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x34, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0xB3, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2F, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0xA6, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x1D, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x99, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x30, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x8C, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x27, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x7F, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x34, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x72, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x27, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x65, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x30, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x58, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x31, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x4B, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2B, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x3E, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x2C, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x31, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x25, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x24, 0x00, 0x00, 0x00, 0x0A, 0x01, 0x42, 0x00, 0x00, 0x00, 0x05, 0x01, 0x3F, 0x00, 0x00, 0x00, 0x04, 0x07, 0x05, 0x00, 0x00, 0x00, 0x06, 0x17, 0x00, 0x00, 0x00, 0x01, 0x43, 0x00, 0x00, 0x00, 0x08, 0x01, 0x6F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x72, 0x00, 0x00, 0x00, 0x08, 0x01, 0x72, 0x00, 0x00, 0x00, 0x08, 0x01, 0x65, 0x00, 0x00, 0x00, 0x08, 0x01, 0x63, 0x00, 0x00, 0x00, 0x08, 0x01, 0x74, 0x00, 0x00, 0x00, 0x08, 0x01, 0x21, 0x00, 0x00, 0x00, 0x08, 0x09, 0x01, 0x57, 0x00, 0x00, 0x00, 0x08, 0x01, 0x72, 0x00, 0x00, 0x00, 0x08, 0x01, 0x6F, 0x00, 0x00, 0x00, 0x08, 0x01, 0x6E, 0x00, 0x00, 0x00, 0x08, 0x01, 0x67, 0x00, 0x00, 0x00, 0x08, 0x01, 0x21, 0x00, 0x00, 0x00, 0x08, 0x09]
gpc = 0
result = []
def readInt32():
global gpc, opcodes
r = 0
for i in range(4):
r |= (opcodes[gpc] << (8*i))
gpc += 1
return r
while gpc < len(opcodes):
pc = gpc
gpc += 1
match (opcodes[pc]):
case 1:
result.append(f"push {readInt32()}")
case 2:
result.append("pop")
case 3:
result.append("add")
case 4:
result.append("sub")
case 5:
result.append("xor")
case 6:
offset = readInt32()
result += [ f"jmp +{offset}" ]
case 7:
offset = readInt32()
result += [ f"jz jmp +{offset}" ]
case 8:
result += [ "puts" ]
case 9:
result += [ "ret" ]
case 10:
result += [ "read from input" ]
case _:
raise Exception("Unknown opcodes")
print("\n".join(result))
然后过滤出来所有的 push ,解密
data = [66 ,36 ,66 ,46 ,66 ,35 ,66 ,37 ,66 ,57 ,66 ,52 ,66 ,47 ,66 ,29 ,66 ,48 ,66 ,39 ,66 ,52 ,66 ,39 ,66 ,48 ,66 ,49 ,66 ,43 ,66 ,44 ,66 ,37 ,66 ,63 ,67 ,111 ,114 ,114 ,101 ,99 ,116 ,33 ,87 ,114 ,111 ,110 ,103 ,33]
for i in range(0, len(data), 2):
print(chr(data[i] ^ data[i+1]), end="")
# flag{vm_reversing},U%F%
500 分 Time_Capsule_bak.zip
upx 加壳了,gdb 断点系统调用然后 dump 出来。
gdb time
catch syscall write
c # 直到 "Debugger detect"
dump memory time.dump 0x8048000 0x8106000
虽然没有符号表,但是加密逻辑还是非常清晰的
简单异或,不过这里的密文解密出来不对,尝试调试获取,方法是 syscall write 打印 “Debugger detected” 之后,在 exit 之前,用
set $rip=<if块外面的地址>
来跳出反调试判断,就能继续往下走了。
data = [0x46,0x49,0x41,0x42,0x5b,0x40,0x4e,0x4f,
0x4f,0x5c,0x7f,0x57,0x13,0x53,0x13,0x57,
0x53,0x40,0x7f,0x17,0x10,0x11,0x19,0x58 ]
key = [32, 37]
result = []
for i in range(len(data)):
result.append((data[i] ^ key[i % 2]))
print(bytes(result))
# b'flag{enjoy_r3v3rse_2049}'
500 分 icrypt.zip
符号表全抠了,导入 sig,但是恢复效果一般,还是得慢慢调试观察,自己恢复符号。
调试分析,有对明文进行修改的只有这两个函数,继续深入 ok_crypt
发现有明显的 AES 特征(有数字 128,而且加密逻辑清晰),猜测是无魔改 AES。a1 是个结构体,包含 key 和 iv。
而通过在进入 ok_random_bytes
前把明文 patch 为 abcdef…
,发现只是将每个块(16个字节)的前 8 个字节反转了顺序。
调换回来的脚本如下
with open("../flag.jpg.icrypt", "rb") as encfile:
data = encfile.read()
out = b""
for i in range(0, len(data), 16):
out += (data[i:i+8][::-1])
out += (data[i+8:i+16])
with open("../flag.jpg.enc", "wb") as outfile:
outfile.write(out)
扔进赛博厨子就能解密
700 分。赛场上没做出来,赛后学习白盒 AES DFA攻击之后做出来了。
题目描述:沙箱机制(seccomp)造成的扰动,找到真正需要逆向的地方
了解了一下,程序用 seccomp 限制了一些系统调用,不过感觉并没有有什么影响。
main 函数的加密逻辑看起来人畜无害,不过既然题目提示了,那就再找找。在 init_array 里找到了三个函数
第一个没什么用。
第二个函数解密真正代码(SMC),设置信号回调函数,用来接受 SIGFPE(计算异常信号),并跳转到真正的代码。
在火车上的时候刚好在写处理信号的程序,也是一种缘分😄
第三个是 seccomp 相关的,最后反调试检测,除以 0 触发异常,然后跳转到刚才解密出来的函数。
下面仔细看一下真正函数,先是解密,简单的异或 0x7F
写 idapython 解密
for ea in range(0x1120, 0x1CE7):
d = ida_bytes.get_byte(ea)
ida_bytes.patch_byte(ea, d ^ 0x7F)
前两个函数初始化局部变量,暂且不管。
深入第三个函数,发现有一大堆莫名奇妙的 rbp - xxxx
的变量,猜测是 IDA 分析函数范围出错了,这里指的应该还是上个函数的局部变量。
所以需要先去汇编,把多余的函数定义全删掉(Edit→Functions→Delete Function),再设置函数范围,Edit→Functions→Edit Function,地址填解密代码的范围,这里我会创建函数失败,所以手动删除函数定义,然后按 E 设置主函数的函数终点。
最终恢复结果如下
需要注意的是,这里的分析依然差一点,&savedregs…-320
事实上就是局部变量 bs,因为地址恰好差了 320,如下图
如果眼尖的话已经一眼丁真白盒 AES 了,但是我之前没见过,在赛场上复现出加密算法之后已经燃尽了🥲
#include <stdio.h>
#include <stdint.h>
#define S_len 0x9000
#define key_len 0x100
#define map_len 0x1000
void printflag(uint8_t *s) {
for (int i = 0; i < 16; i++) {
printf("%02X ", s[i]);
}
printf("\n");
}
void rowshift(uint8_t *s) {
char t0, t1, t2, t3;
t0 = s[1];
s[1] = s[5];
s[5] = s[9];
s[9] = s[13];
s[13] = t0;
t1 = s[2];
s[2] = s[10];
s[10] = t1;
t2 = s[6];
s[6] = s[14];
s[14] = t2;
t3 = s[15];
s[15] = s[11];
s[11] = s[7];
s[7] = s[3];
s[3] = t3;
}
unsigned char data_enc[32] = {
0x24, 0x99, 0xBD, 0xF2, 0xE4, 0x76, 0x8B, 0xCB, 0xE2, 0xA2, 0x94, 0xC9, 0x0B, 0x31, 0x32, 0x80,
0x61, 0x2A, 0x85, 0xDF, 0xB6, 0x72, 0x31, 0x33, 0x8A, 0x08, 0x36, 0x3C, 0x00, 0x3E, 0x3A, 0x2F
};
void enc(uint8_t *input, uint32_t *S, uint8_t *key, uint8_t *map) {
uint32_t x1, y1, x2, y2;
int32_t high0, high1, low0, low1;
for (int i = 0; i <= 8; i++) {
rowshift(input);
for (int j = 0; j <= 3; j++) {
x1 = S[256 * (16LL * i + 4 * j) + input[4 * j]];
y1 = S[256 * (16LL * i + 4 * j + 1) + input[4 * j + 1]];
x2 = S[256 * (16LL * i + 4 * j + 2) + input[4 * j + 2]];
y2 = S[256 * (16LL * i + 4 * j + 3) + input[4 * j + 3]];
high0 = *(key + 16 * (x1 >> 28) + (y1 >> 28));// highest 4 bit
high1 = *(key + 16 * (x2 >> 28) + (y2 >> 28));
low0 = *(key + 16 * ((x1 >> 24) & 0xF) + ((y1 >> 24) & 0xF));// second 4 bit
low1 = *(key + 16 * ((x2 >> 24) & 0xF) + ((y2 >> 24) & 0xF));
input[4 * j] = low1 ^ low0 | (16 * (high0 ^ high1));
high0 = *(key + 16 * ((x1 >> 20) & 0xF) + ((y1 >> 20) & 0xF));
high1 = *(key + 16 * ((x2 >> 20) & 0xF) + ((y2 >> 20) & 0xF));
low0 = *(key + 16 * ((x1 >> 16) & 0xF) + ((y1 >> 16) & 0xF));
low1 = *(key + 16 * ((x2 >> 16) & 0xF) + ((y2 >> 16) & 0xF));
input[4 * j + 1] = low1 ^ low0 | (16 * (high0 ^ high1));
high0 = *(key + 16 * ((x1 >> 12) & 0xF) + ((y1 >> 12) & 0xF));
high1 = *(key + 16 * ((x2 >> 12) & 0xF) + ((y2 >> 12) & 0xF));
low0 = *(key + 16 * ((x1 >> 8) & 0xF) + ((y1 >> 8) & 0xF));
low1 = *(key + 16 * ((x2 >> 8) & 0xF) + ((y2 >> 8) & 0xF));
input[4 * j + 2] = low1 ^ low0 | (16 * (high0 ^ high1));
high0 = *(key + 16 * ((x1 >> 4) & 0xF) + ((y1 >> 4) & 0xF));
high1 = *(key + 16 * ((x2 >> 4) & 0xF) + ((y2 >> 4) & 0xF));
low0 = *(key + 16 * (x1 & 0xF) + (y1 & 0xF));
low1 = *(key + 16 * (x2 & 0xF) + (y2 & 0xF));
input[4 * j + 3] = low1 ^ low0 | (16 * (high0 ^ high1));
}
}
rowshift(input);
printflag(input);
for (int k = 0; k < 16; ++k) {
input[k] = map[256 * k + input[k]];
}
}
int main(int argc, char *argv[])
{
FILE *file_S = fopen("./data_S", "r");
FILE *file_key = fopen("./data_key", "r");
FILE *file_map = fopen("./data_map", "r");
uint32_t S[S_len];
uint8_t key[key_len];
uint8_t map[map_len];
fread(S, 4, 0x9000, file_S);
fread(key, 1, 0x100, file_key);
fread(map, 1, 0x1000, file_map);
fclose(file_S);
fclose(file_key);
fclose(file_map);
uint8_t input[] = "0123456789abcdefghijklmnopqrstuv";
enc(input, S, key, map);
printflag(input);
return 0;
}
这是经典的差分故障分析,需要添加一些扰动,生成故障密文,然后用 phoenixAES 计算出轮密钥,再用 stark 计算出初始密钥。
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#define S_len 0x9000
#define key_len 0x100
#define map_len 0x1000
void printflag(uint8_t *s) {
for (int i = 0; i < 16; i++) {
printf("%02X", s[i]);
}
printf("\n");
}
void rowshift(uint8_t *s) {
char t0, t1, t2, t3;
t0 = s[1];
s[1] = s[5];
s[5] = s[9];
s[9] = s[13];
s[13] = t0;
t1 = s[2];
s[2] = s[10];
s[10] = t1;
t2 = s[6];
s[6] = s[14];
s[14] = t2;
t3 = s[15];
s[15] = s[11];
s[11] = s[7];
s[7] = s[3];
s[3] = t3;
}
unsigned char data_enc[32] = {
0x24, 0x99, 0xBD, 0xF2, 0xE4, 0x76, 0x8B, 0xCB, 0xE2, 0xA2, 0x94, 0xC9, 0x0B, 0x31, 0x32, 0x80,
0x61, 0x2A, 0x85, 0xDF, 0xB6, 0x72, 0x31, 0x33, 0x8A, 0x08, 0x36, 0x3C, 0x00, 0x3E, 0x3A, 0x2F
};
void enc(uint8_t *input, uint32_t *S, uint8_t *key, uint8_t *map, int randomi, uint8_t patchc) {
uint32_t x1, y1, x2, y2;
int32_t high0, high1, low0, low1;
for (int i = 0; i <= 8; i++) {
if (i == 8 && patchc != 0) {
input[randomi] = patchc;
}
rowshift(input);
for (int j = 0; j <= 3; j++) {
x1 = S[256 * (16LL * i + 4 * j) + input[4 * j]];
y1 = S[256 * (16LL * i + 4 * j + 1) + input[4 * j + 1]];
x2 = S[256 * (16LL * i + 4 * j + 2) + input[4 * j + 2]];
y2 = S[256 * (16LL * i + 4 * j + 3) + input[4 * j + 3]];
high0 = *(key + 16 * (x1 >> 28) + (y1 >> 28));// highest 4 bit
high1 = *(key + 16 * (x2 >> 28) + (y2 >> 28));
low0 = *(key + 16 * ((x1 >> 24) & 0xF) + ((y1 >> 24) & 0xF));// second 4 bit
low1 = *(key + 16 * ((x2 >> 24) & 0xF) + ((y2 >> 24) & 0xF));
input[4 * j] = low1 ^ low0 | (16 * (high0 ^ high1));
high0 = *(key + 16 * ((x1 >> 20) & 0xF) + ((y1 >> 20) & 0xF));
high1 = *(key + 16 * ((x2 >> 20) & 0xF) + ((y2 >> 20) & 0xF));
low0 = *(key + 16 * ((x1 >> 16) & 0xF) + ((y1 >> 16) & 0xF));
low1 = *(key + 16 * ((x2 >> 16) & 0xF) + ((y2 >> 16) & 0xF));
input[4 * j + 1] = low1 ^ low0 | (16 * (high0 ^ high1));
high0 = *(key + 16 * ((x1 >> 12) & 0xF) + ((y1 >> 12) & 0xF));
high1 = *(key + 16 * ((x2 >> 12) & 0xF) + ((y2 >> 12) & 0xF));
low0 = *(key + 16 * ((x1 >> 8) & 0xF) + ((y1 >> 8) & 0xF));
low1 = *(key + 16 * ((x2 >> 8) & 0xF) + ((y2 >> 8) & 0xF));
input[4 * j + 2] = low1 ^ low0 | (16 * (high0 ^ high1));
high0 = *(key + 16 * ((x1 >> 4) & 0xF) + ((y1 >> 4) & 0xF));
high1 = *(key + 16 * ((x2 >> 4) & 0xF) + ((y2 >> 4) & 0xF));
low0 = *(key + 16 * (x1 & 0xF) + (y1 & 0xF));
low1 = *(key + 16 * (x2 & 0xF) + (y2 & 0xF));
input[4 * j + 3] = low1 ^ low0 | (16 * (high0 ^ high1));
}
}
rowshift(input);
for (int k = 0; k < 16; ++k) {
input[k] = map[256 * k + input[k]];
}
}
int main(int argc, char *argv[])
{
FILE *file_S = fopen("./data_S", "r");
FILE *file_key = fopen("./data_key", "r");
FILE *file_map = fopen("./data_map", "r");
uint32_t S[S_len];
uint8_t key[key_len];
uint8_t map[map_len];
fread(S, 4, 0x9000, file_S);
fread(key, 1, 0x100, file_key);
fread(map, 1, 0x1000, file_map);
fclose(file_S);
fclose(file_key);
fclose(file_map);
#define TXT "0123456789abcdefghijklmnopqrstuv"
uint8_t input[] = TXT;
enc(input, S, key, map, 0, 0);
printflag(input);
for (int ri = 0; ri < 32; ri++) {
memcpy(input, TXT, 33);
enc(input, S, key, map, ri, 'a');
printflag(input);
}
return 0;
}
第一行是正确密文,剩下的为故障密文。输出结果导出到 tracefile,然后调用 phoenixAES,拿到最后一轮的轮密钥
phoenixAES.crack_file('tracefile', [], True, False, 3)
# Last round key #N found:
# 195DCE39EC1CA57F35584891CB6378DE
传入 stark
> ./aes 195DCE39EC1CA57F35584891CB6378DE 10
K00: 01010101010101010101010101010101
这就是初始密钥,然后正常 AES 解密即可。