坐牢一天,Dual personality 一眼看出远跳转改了 CS 寄存器把程序变成了 64bit-mod 但是后面的加密逻辑静态看完全看不懂,后面又觉得 babyRE 能做,看出 base8 以后被一个 SHA-1 校验卡了一个小时,最后爆破少了后面的六位没爆出来,大失败属于是😓

babyRE

ida 打开发现 main 函数啥也没有,一看到 TlsCallback 函数就明白了应该是个回调函数 + 异常处理的题,交叉引用找到了三个注册函数

1675677390287

一个一个点进去,发现这三个函数里面有六个子函数,在每个函数开始的地方下断点,一路 F9,就能找到加密函数的执行顺序。

1675677514274

首先可以看出输入为 0 - 9 的数字。

1675677551231

然后是对 408104 进行一个取反,直接调过去就行。

1675677621568

然后 hook 了GetLastError 函数,注册了 base8 加密,没什么用。

1675677855368

然后来到了 base8 加密,表就是之前的 byte_408104, 值得注意的是,这里在加密以后,是从第 16 位开始与密文进行比对的,比较长度为 96 位,前面16位是由 input 的前6位生成的,在这里没有进行比对。

1675677946340

再按一次 F9, 发现是一个 SHA-1 校验,没啥用,当时比赛的时候以为是可以解密的某种加密算法,或者可以用 crypto 来解的算法,最后看出来是 SHA-1 直接裂开。

1675678031204

最后一步是RC4加密,值得注意的是,rc4_key的地址和 input 的地址是连在一起的,且由 init_rc4 的调用可知长度为 6 位

1675678111113

加密对象就是我们之前 base8 编码出来的结果。

我们直接对 rc4_key 进行爆破,然后对解密出来的结果进行验证即可

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
#include <bits/stdc++.h>
using namespace std;

unsigned char base8_cipher[] = "162304651523346214431471150310701503207116032063140334661543446114434066142304661563446615430464";
unsigned char rc4_plain[120];
unsigned char key[6];
unsigned char s_box[256];
unsigned char tmp[120];
unsigned char rc4_cipher[] =
{
0x3F, 0x95, 0xBB, 0xF2, 0x57, 0xF1, 0x7A, 0x5A, 0x22, 0x61,
0x51, 0x43, 0xA2, 0xFA, 0x9B, 0x6F, 0x44, 0x63, 0xC0, 0x08,
0x12, 0x65, 0x5C, 0x8A, 0x8C, 0x4C, 0xED, 0x5E, 0xCA, 0x76,
0xB9, 0x85, 0xAF, 0x05, 0x38, 0xED, 0x42, 0x3E, 0x42, 0xDF,
0x5D, 0xBE, 0x05, 0x8B, 0x35, 0x6D, 0xF3, 0x1C, 0xCF, 0xF8,
0x6A, 0x73, 0x25, 0xE4, 0xB7, 0xB9, 0x36, 0xFB, 0x02, 0x11,
0xA0, 0xF0, 0x57, 0xAB, 0x21, 0xC6, 0xC7, 0x46, 0x99, 0xBD,
0x1E, 0x61, 0x5E, 0xEE, 0x55, 0x18, 0xEE, 0x03, 0x29, 0x84,
0x7F, 0x94, 0x5F, 0xB4, 0x6A, 0x29, 0xD8, 0x6C, 0xE4, 0xC0,
0x9D, 0x6B, 0xCC, 0xD5, 0x94, 0x5C, 0xDD, 0xCC, 0xD5, 0x3D,
0xC0, 0xEF, 0x0C, 0x29, 0xE5, 0xB0, 0x93, 0xF1, 0xB3, 0xDE,
0xB0, 0x70
};

void rc4_init(unsigned char *s,unsigned char *key, unsigned long Len)
{
int i = 0,j = 0;
unsigned char T[256] = {0};
unsigned char tmp = 0;
for(i = 0; i < 256; i++) {
s[i] = i;
T[i] = key[i % Len];
}
for(i = 0; i < 256; i++) {
j = (j + s[i] + T[i]) % 256;
swap(s[i], s[j]);
}
}

void rc4_decrypt(unsigned char *s,unsigned char *Data,unsigned long Len)
{
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for(k = 0; k < Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
swap(s[i], s[j]);
t = (s[i] + s[j]) % 256;
Data[k] ^= s[t];
}
}

bool check()
{
for(int i = 16; i < 112 + 16; i++)
{
if(tmp[i] != base8_cipher[i - 16])return 0;
}
return 1;
}


int main()
{
for(key[0] = '0'; key[0] <= '9'; key[0]++)
for(key[1] = '0'; key[1] <= '9'; key[1]++)
for(key[2] = '0'; key[2] <= '9'; key[2]++)
for(key[3] = '0'; key[3] <= '9'; key[3]++)
for(key[4] = '0'; key[4] <= '9'; key[4]++)
for(key[5] = '0'; key[5] <= '9'; key[5]++)
{

for(int i = 0; i < 112; i++)tmp[i] = rc4_cipher[i];
rc4_init(s_box, key, 6);
rc4_decrypt(s_box, tmp, 112);
if(check())
{
cout << key << endl << tmp << endl;
return 0;
}
}
}

1675678198325

运行以后,把解密的结果在进行一次 base8解码

1675678266036

最后在末尾接上爆破出来的 cr4_key 即可得到 DASCTF{561516915572239428449843076691286116796614807391}

Dual personality

刚看这题的时候以为是一堆反调试和花指令的题,但是patch了半天除了发现 0,0,0,0是花以外就没啥发现了,一动调就发现了不对劲

1675694822001

在经过了函数sub_401120以后,下面的数据被改变了,显然这是一个 SMC ,但是 analyze 以后的代码很奇怪

1675694900750

这是一个远跳转, 远跳转有一个隐形的操作,他会将代码段寄存器CS设置为跳转到的这个段对应的段选择子,我们把 opcode bytes 打开

1675694958866

发现 ida 出现了反汇编错误,真实逻辑是跳到 33:4011D0 这个地址,33 是 CS 寄存器的段选择子,当 CS 为 0x23 时程序为 32bit - mode,当 CS 为 0x33 时 程序为 64bit - mode ,显然程序也是利用了这一点进行了反调试。

但是本题纯静态调试嗯看也是能做的,从远跳转地址 4011D0 开始把 0, 0, 0 ,0给 nop 掉就可以正常 analyze 并创建函数得到源代码,虽然有点抽象但是能看,下面是加密顺序。

1675780437535

1675786027202

sub_4011D0 中有一个反调试, gs:60h 获得PEB结构, mov al, [rax+2]后。al 的值就是 BeingDebugged 的值, 具体可见 BeingDebugged反调试技巧

1675780516967

由上面的 sub_4011D0 的函数可知,此时 dword_407058 的值变成了 0x5DF966AE - 0x21524111,如果触发了反调试的话 dword_407058 的值就是 0xDEADBEEF

1675781195800

也是一个反调试__ROL4__ 可以从网上找到源码

1675778594701

1675779052806

1675779090402

我们先按顺序把加密流程写出来

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
#include <bits/stdc++.h>
#include <stdint.h>
using namespace std;

uint32_t tmp = 0x5DF966AE - 0x21524111;
uint32_t flag[8];
uint32_t key[4] = {0x9D, 0x44, 0x37, 0xB5};
uint8_t cipher[] = {0xAA, 0x4F, 0x0F, 0xE2, 0xE4, 0x41, 0x99, 0x54, 0x2C, 0x2B, 0x84, 0x7E, 0xBC, 0x8F, 0x8B, 0x78, 0xD3, 0x73, 0x88, 0x5E, 0xAE, 0x47, 0x85, 0x70, 0x31, 0xB3, 0x09, 0xCE, 0x13, 0xF5, 0x0D, 0xCA};

template<typename T> T __ROL__(T val, uint32_t count)
{
uint32_t bitcount = sizeof(T) * 8;
count %= bitcount;
return (val << count) | (val >> (bitcount - count));
}

template<typename T> T __ROR__(T val, uint32_t count)
{
uint32_t bitcount = sizeof(T) * 8;
count %= bitcount;
return (val >> count) | (val << (bitcount - count));
}

int main()
{
for(int i = 0; i < 32; i++) cin >> *(uint8_t *)(flag + i);

for(int i = 0; i < 8; i++)
{
flag[i] += tmp;
tmp ^= flag[i];
}

*(uint64_t *)((char *)flag) = __ROL__(*(uint64_t *)((char *)flag), 12);
*(uint64_t *)((char *)flag + 8) = __ROL__(*(uint64_t *)((char *)flag + 8), 34);
*(uint64_t *)((char *)flag + 16) = __ROL__(*(uint64_t *)((char *)flag + 16), 56);
*(uint64_t *)((char *)flag + 24) = __ROL__(*(uint64_t *)((char *)flag + 24), 14);

key[0] &= key[1];
key[1] |= key[2];
key[2] ^= key[3];
key[3] = ~key[3];

for(int i = 0; i < 32; i++) *((uint8_t *)flag + i) ^= key[i % 4];
return 0;
}

然后写出 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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#include <bits/stdc++.h>
#include <stdint.h>
using namespace std;

uint32_t tmp = 0x5DF966AE - 0x21524111;
uint32_t flag[8];
uint32_t key[4] = {0x9D, 0x44, 0x37, 0xB5};
uint8_t cipher[] = {0xAA, 0x4F, 0x0F, 0xE2, 0xE4, 0x41, 0x99, 0x54, 0x2C, 0x2B, 0x84, 0x7E, 0xBC, 0x8F, 0x8B, 0x78, 0xD3, 0x73, 0x88, 0x5E, 0xAE, 0x47, 0x85, 0x70, 0x31, 0xB3, 0x09, 0xCE, 0x13, 0xF5, 0x0D, 0xCA};

template<typename T> T __ROL__(T val, uint32_t count)
{
uint32_t bitcount = sizeof(T) * 8;
count %= bitcount;
return (val << count) | (val >> (bitcount - count));
}

template<typename T> T __ROR__(T val, uint32_t count)
{
uint32_t bitcount = sizeof(T) * 8;
count %= bitcount;
return (val >> count) | (val << (bitcount - count));
}

int main()
{
for(int i = 0; i < 8; i++) *(flag + i) = *(uint32_t *)(cipher + i * 4);

key[0] &= key[1];
key[1] |= key[2];
key[2] ^= key[3];
key[3] = ~key[3];

for(int i = 0; i < 32; i++) *((uint8_t *)flag + i) ^= key[i % 4];

*(uint64_t *)((char *)flag + 24) = __ROR__(*(uint64_t *)((char *)flag + 24), 14);
*(uint64_t *)((char *)flag + 16) = __ROR__(*(uint64_t *)((char *)flag + 16), 56);
*(uint64_t *)((char *)flag + 8) = __ROR__(*(uint64_t *)((char *)flag + 8), 34);
*(uint64_t *)((char *)flag) = __ROR__(*(uint64_t *)((char *)flag), 12);

for(int i = 0; i < 8; i++)
{
uint32_t x = tmp;
tmp ^= flag[i];
flag[i] -= x;
}

cout << (char *)flag;
return 0;
}

运行得到 DASCTF{6cc1e44811647d38a15017e389b3f704}

Berkeley

比赛的时候SG👴配了一天环境,但是后来发现好像根本不需要配环境

1675702770142

1675702788739

1675702814001

先放一段简介作为了解:BPF简介 - 知乎 (zhihu.com)

先扔进ida

1675700622248

看到这个函数,点进去找一找,找到program_attach_uprobe函数,查了一下

bpf_program__attach_uprobe() 用于绑定 uprobe 和 uretprobe 追踪目标的信息。

是个注册ebpf的函数,再往下找就能找到下面这个函数

1675700656790

点进 unk_30060 一看,发现 ELF 标志,显然是在程序中内嵌了一个 ELF 文件然后通过 bpf 得以在 x86_64 指令集下运行,文件大小为 8472 。

我们用 idapython 把 ELF 文件给 dump 出来,也可以用 010 editor

1
2
3
import idc
with open("bpf", "wb") as f:
f.write(idc.get_bytes(0x30060, 8472))

得到的 ELF 文件用 ida 无法分析,需要用 ghidra 进行分析,同时还要用到 分析 bpf 文件的插件 or 这个插件和安装说明

这个文件中主要有两个函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
undefined8 LBB0_1(longlong lParm1)
{
longlong lVar1;
ulonglong uVar2;
ulonglong uVar3;
undefined8 uVar4;
undefined4 local_4;

uVar4 = *(undefined8 *)(lParm1 + 0x70);
local_4 = FUN_syscall_0000000e();
lVar1 = FUN_syscall_00000002(0,&local_4,0,1);
if ((lVar1 == 0) && (lVar1 = FUN_syscall_00000001(0,&local_4), lVar1 != 0)) {
FUN_syscall_00000072(lVar1 + 0x20,0x80,uVar4); // 猜测 lVar1 + 0x20是我们输入的flag,然后 lVar1 + 0x120 存的是加密结果 enflag
uVar2 = 0;
do {
uVar3 = *(ulonglong *)(lVar1 + 0x20 + (uVar2 >> 3 & 0x1fffffff)); // uVar3 = flag[i / 8]
*(undefined *)(lVar1 + 0x120 + uVar2) =
(char)*(undefined8 *)
((uVar3 ^ uVar3 + *(longlong *)((uVar2 & 7) * 4) ^ 0xffffffffffffffff) & 0xff); // 注意这里有个 *(longlong *)((uVar2 & 7) * 4),应该是某个数组 unk[uVar2 & 7],我们可以在 RAM 段找到该数据,可以翻译成 enflag[i] = key[(uVar3 ^ uVar3 + data[i & 7] ^ 0xffffffffffffffff) & 0xff]
uVar2 = uVar2 + 1;
} while (uVar2 != 0x100);
}
return 0;
}

1676025575084

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
undefined8 LBB0_2(void)

{
longlong lVar1;
ulonglong *puVar2;
undefined8 uVar3;
longlong *plVar4;
ulonglong *puVar5;
undefined4 local_4;

local_4 = FUN_syscall_0000000e();
lVar1 = FUN_syscall_00000001(0,&local_4);
if (lVar1 != 0) {
puVar2 = (ulonglong *)0x0;
do {
puVar5 = (ulonglong *)(lVar1 + 0x120 + (longlong)puVar2);
*(char *)puVar5 = (char)*(undefined8 *)((*puVar2 ^ *puVar5) & 0xff); // enflag[i] = key[key[i] ^ enflag[i]] & 0xff
puVar2 = (ulonglong *)((longlong)puVar2 + 1);
} while (puVar2 != (ulonglong *)0x100);
plVar4 = (longlong *)0x0;
do {
uVar3 = 0x220;
if (*(longlong *)(lVar1 + 0x120 + (longlong)plVar4) != *plVar4) break;
uVar3 = 0x22b;
plVar4 = (longlong *)((longlong)plVar4 + 1);
} while (plVar4 != (longlong *)0x100);
FUN_syscall_00000006(uVar3,0xb);
FUN_syscall_00000003(0,&local_4);
}
return 0;
}

而在下发的附件中,我们还能找到一个虚假的 check 函数,里面有一个虚假的 cipher,但是我们点进去发现真正的 keycipher 和其是相邻的

1676025626706

1676025644376

1676025719726

我们把 ghidra 反编译出来难看的代码复写一下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void check(char* flag)
{
uint8_t enflag[256];
for (int i = 0; i < 256; ++i) enflag[i] = key[flag[i / 8] ^ ((flag[i / 8] + data[i & 7]) ^ 0xFFFFFFFFFFFFFFFF) & 0xFF];
for (int i = 0; i < 256; ++i) enflag[i] = key[key[i] ^ enflag[i] & 0xFF];
for (int i = 0; i < 256; ++i)
{
if (enflag[i] != cipher[i])
{
cout << "Wrong flag";
return ;
}
}
cout << "Right flag";
}

由于是逐字比较,可以直接爆破,且爆破范围都是在 key 中。

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
108
#include <bits/stdc++.h>
#include <stdint.h>
using namespace std;

unsigned char key[] =
{
0xC1, 0xD1, 0x02, 0x61, 0xD6, 0xF7, 0x13, 0xA2, 0x9B, 0x20,
0xD0, 0x4A, 0x8F, 0x7F, 0xEE, 0xB9, 0x00, 0x63, 0x34, 0xB0,
0x33, 0xB7, 0x8A, 0x8B, 0x94, 0x60, 0x2E, 0x8E, 0x21, 0xFF,
0x90, 0x82, 0xD5, 0x87, 0x96, 0x78, 0x22, 0xB6, 0x48, 0x6C,
0x45, 0xC7, 0x5A, 0x16, 0x80, 0xFD, 0xE4, 0x8C, 0xBF, 0x01,
0x1F, 0x4B, 0x79, 0x24, 0xA0, 0xB4, 0x23, 0x4D, 0x3B, 0xC5,
0x5D, 0x6F, 0x0D, 0xC9, 0xD4, 0xCA, 0x55, 0xE0, 0x39, 0xAD,
0x2B, 0xCD, 0x2C, 0xEC, 0xC2, 0x6B, 0x30, 0xE6, 0x0C, 0xA8,
0x9A, 0x2F, 0xF6, 0xE8, 0xBB, 0x32, 0x57, 0xFB, 0x0B, 0x9D,
0xF2, 0x3F, 0xB5, 0xF9, 0x59, 0xE5, 0x10, 0xCF, 0x51, 0x41,
0xE9, 0x50, 0xDF, 0x26, 0x74, 0x58, 0xCB, 0x64, 0x54, 0x73,
0xAB, 0xF4, 0xB2, 0x9F, 0x18, 0xF8, 0x4E, 0xFE, 0x08, 0x1D,
0x4F, 0x49, 0xD3, 0xAC, 0x38, 0x12, 0x77, 0x11, 0x69, 0x07,
0x1C, 0x99, 0xB3, 0xE7, 0x3D, 0x05, 0xD8, 0xFC, 0x70, 0x46,
0x93, 0x09, 0x65, 0x89, 0xB1, 0xC6, 0x52, 0xFA, 0xD2, 0x0E,
0xA9, 0x17, 0xE3, 0x91, 0xA1, 0x68, 0x5B, 0x2A, 0xF0, 0xC3,
0x42, 0xCC, 0x29, 0xDE, 0xDC, 0x85, 0x98, 0x31, 0x5C, 0xBC,
0x2D, 0xEF, 0x5E, 0x7E, 0xAF, 0x67, 0x62, 0xA7, 0x56, 0x88,
0xA4, 0x43, 0x40, 0xE1, 0x37, 0x9E, 0x36, 0x76, 0x71, 0x84,
0xBD, 0x06, 0x8D, 0x47, 0x7D, 0x53, 0xD7, 0xC8, 0xCE, 0x15,
0x92, 0x95, 0x4C, 0x28, 0x6D, 0x75, 0xEB, 0x7C, 0xF3, 0xBE,
0xAA, 0xB8, 0xED, 0x03, 0x3C, 0x27, 0x3E, 0x19, 0xDD, 0xA6,
0x66, 0x25, 0x1E, 0xC4, 0x6E, 0xC0, 0xE2, 0xDB, 0x3A, 0xD9,
0x81, 0xA5, 0x1B, 0xF5, 0x04, 0xAE, 0xBA, 0xEA, 0x97, 0x83,
0x35, 0x44, 0xA3, 0x7A, 0x1A, 0xF1, 0x86, 0xDA, 0x7B, 0x14,
0x72, 0x9C, 0x6A, 0x0F, 0x5F, 0x0A, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};

unsigned char cipher[] =
{
0xF3, 0x27, 0x47, 0x1B, 0x8F, 0x09, 0xFB, 0x17, 0x70, 0x48,
0xB0, 0x53, 0x32, 0xDB, 0xC0, 0xB8, 0x63, 0x2D, 0x40, 0x4B,
0xF5, 0x16, 0xF0, 0x35, 0xE7, 0xDF, 0xEA, 0xA2, 0x9C, 0x41,
0xB3, 0x25, 0xD7, 0x0C, 0x33, 0x9C, 0x7B, 0x5A, 0xCD, 0x13,
0xBB, 0xEE, 0x3E, 0x0E, 0xF2, 0xCF, 0x35, 0xDA, 0xAF, 0xA2,
0x66, 0x7D, 0x38, 0x37, 0x67, 0x1E, 0x1F, 0x6B, 0x7B, 0x30,
0x0B, 0x7A, 0x02, 0xA9, 0xC8, 0x61, 0x27, 0x41, 0xDB, 0x01,
0x22, 0x31, 0x6F, 0xB6, 0xD4, 0x1B, 0x04, 0xD3, 0x94, 0xB8,
0x46, 0xC7, 0x24, 0xCF, 0xBD, 0xAF, 0x0B, 0xDC, 0x2E, 0xBB,
0xB2, 0x71, 0xF4, 0x99, 0x57, 0x36, 0xD1, 0x95, 0x52, 0x92,
0xBA, 0x6D, 0xF3, 0x30, 0x50, 0x59, 0x9B, 0xEA, 0x2F, 0x83,
0xDC, 0xF0, 0xDE, 0x57, 0xA1, 0xAC, 0xD2, 0x51, 0xA2, 0x1D,
0x59, 0xA8, 0x00, 0xB6, 0xE2, 0x65, 0x41, 0x0C, 0x4F, 0xEB,
0xF0, 0x2E, 0x58, 0x2A, 0x1F, 0xF4, 0x95, 0x72, 0x88, 0x7C,
0xA9, 0x0E, 0xCB, 0x3C, 0x42, 0xB9, 0xF3, 0x49, 0x9B, 0x52,
0x98, 0x12, 0xA3, 0x17, 0x51, 0xC0, 0x59, 0x40, 0x0A, 0xBC,
0xE8, 0x4C, 0x04, 0xFB, 0x13, 0x0A, 0x17, 0x3F, 0xE6, 0x36,
0x97, 0xDF, 0xB3, 0xE2, 0x42, 0x7F, 0xF8, 0xCC, 0x0E, 0xD1,
0x77, 0xC4, 0xA8, 0x46, 0x48, 0xE3, 0xF1, 0x0A, 0xEF, 0x94,
0x56, 0x54, 0x5B, 0xCA, 0xBD, 0xDD, 0x7F, 0x56, 0x47, 0xC2,
0x99, 0xFA, 0x89, 0xCC, 0xE1, 0xB9, 0x3A, 0x78, 0xE2, 0x37,
0x58, 0x01, 0x1B, 0xC3, 0x4B, 0xE6, 0x8C, 0xF3, 0xE5, 0xB6,
0x71, 0x9E, 0x63, 0xAF, 0x11, 0xCE, 0x87, 0xF6, 0x6E, 0xDE,
0xC8, 0xB1, 0xD0, 0x7A, 0x15, 0x6C, 0x10, 0x08, 0x99, 0x7B,
0x22, 0x55, 0x10, 0x7A, 0x82, 0x73, 0xFC, 0x62, 0xCB, 0x34,
0xA7, 0xB7, 0x62, 0xFA, 0x6B, 0x9F
};


uint64_t data[] = {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

uint8_t enflag[256], tmp[256];
char flag[32];

bool check(int l, int r)
{
for(int i = l; i < r; i++) if(tmp[i] != enflag[i]) return 0;
return 1;
}

int main()
{
for(int i = 0; i < 256; i++)
{
for(int j = 0; j < 256; j++)
{
if((key[(key[i] ^ key[j]) & 0xFF]) == cipher[i])
{
enflag[i] = key[j]; break;
}
}
}

for(int i = 0; i < 32; i++)
{
for(unsigned int ch = 32; ch <= 127; ch++)
{
for(int j = 0; j < 8; j++)
{
tmp[i * 8 + j] = key[(ch ^ (ch + data[j & 7]) ^ 0xFFFFFFFFFFFFFFFF) & 0xFF];
}
if(check(i * 8, (i + 1) * 8))
{
flag[i] = ch; break;
}
}
}
cout << flag;
}

运行得到 DASCTF{71c2ac98ac8d99a2e8a95111449a7393}

EasyVT

参考 VT技术入门 或者 看雪上的一篇博客 ,附件有主程序和一个驱动程序,主程序逻辑比较简单

1676035764160

通过内联汇编给出十个 vm 操作依次执行,一个跑了 4 轮,但是具体干了什么发现找不到,得逆驱动程序。

ida 打开驱动程序,在 DriverEntry 附近找一找,发现核心分发函数 sub_401C90,根据宏恢复一下符号

1676037412997

1676039048101

按照主程序中的顺序,发现题目就是通过 VT 把程序拆成了 10 段,并且函数都是自写的加密,一个 tea, 一个 rc4,还有一些赋值和交换的操作,需要注意的是,每次 rc4 加密以后都会有一个 swap 操作,但是最后扔进加密函数里的顺序是原来的顺序,因此我们在 tea 解密以后,需要把每 8 位数据的前四位和后四位调换,然后再进行 rc4 解密,解密后再换回去即可,同时注意 rc4 解密的时候是分成长度为 8 的小段解密的,密钥流只用到前 8 位。

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
#include <bits/stdc++.h>
#include <stdint.h>
using namespace std;

unsigned char cipher[] =
{
0x94, 0x39, 0x07, 0x5C, 0xB3, 0x5C, 0x80, 0x0D, 0x86, 0xA5,
0xDD, 0x87, 0x8E, 0xFB, 0x17, 0x03, 0x29, 0xEF, 0x20, 0x65,
0xAF, 0x87, 0x49, 0x5A, 0xA4, 0xC2, 0x2D, 0xEB, 0x0E, 0x47,
0xCF, 0x38
};
uint32_t tea_key[] = {0x102030, 0x40506070, 0x8090A0B0, 0xC0D0E0F0};

void tea_decrypt(uint32_t &v0, uint32_t &v1, uint32_t *key)
{
uint32_t l = v0, r = v1, sum = 0x20000000, delta = 0xC95D6ABF;
sum -= delta * 32;
for (int i = 1; i <= 32; ++i)
{
sum += delta;
r -= ((l >> 5) + key[0]) ^ (l + sum) ^ ((l << 4) + key[2]);
l += ((r >> 5) + key[3]) ^ (r + sum) ^ ((r << 4) + key[1]);
}
v0 = l, v1 = r;
}

unsigned char s_box[256];
unsigned char rc4_key[] = "04e52c7e31022b0b";
unsigned char key_stream[32];
int key_index;

void rc4_init(unsigned char *s,unsigned char *key, unsigned long Len)
{
int i = 0, j = 0;
unsigned char T[256] = {0};
unsigned char tmp = 0;
for(i = 0; i < 256; i++) {
s[i] = i;
T[i] = rc4_key[i % Len];
}
for(i = 0; i < 256; i++) {
j = (j + s[i] + T[i]) % 256;
swap(s[i], s[j]);
}
}

void rc4_decrypt(unsigned char *s,unsigned char *Data,unsigned long Len)
{
int i = 0, j = 0, t = 0;
unsigned long k = 0;
unsigned char tmp;
for(k = 0; k < Len; k++)
{
i = (i + 1) % 256;
j = (j + s[i]) % 256;
swap(s[i], s[j]);
t = (s[i] + s[j]) % 256;
key_stream[key_index++] = s[t];
}
for(k = 0; k < Len; k++)
{
cipher[k] ^= key_stream[k % 8];
}
}

int main()
{
for (int i = 0; i < 8; i += 2)
{
swap(*(uint32_t*)(cipher + i * 4), *(uint32_t*)(cipher + (i + 1) * 4));
tea_decrypt(*(uint32_t*)(cipher + (i + 1) * 4), *(uint32_t*)(cipher + i * 4), tea_key);
cout << hex << "0x" << *(uint32_t*)(cipher + i * 4) << ", 0x" << *(uint32_t*)(cipher + (i + 1) * 4) << "," << endl;
}

rc4_init(s_box, rc4_key, 16);
rc4_decrypt(s_box, cipher, 32);

for (int i = 0; i < 8; i += 2) swap(*(uint32_t*)(cipher + i * 4), *(uint32_t*)(cipher + (i + 1) * 4));

for(int i = 0; i < 32; i++) printf("%c", cipher[i]);
return 0;
}

运行得到 DASCTF{81920c3758be43705ba154bb8f599846}