校队参赛,Re方向拿了一个一血一个二血。这次Re题目质量还可以,没有那种特别恶心的乱七八糟算法来恶心人,考的都是一些针对性的知识点。
Dual personality
经典天堂之门反调试题。算法很简单,主要依赖天堂之门干扰动态调试,花指令+SMC反IDA静态分析。整体处理方法:CE调试+对比各部分加密前后内容,对照CE代码修复IDA代码段进行静态分析。
加密过程分三个阶段
第一部分
scanf后下硬断直接追出来是这一部分
然后这里有个注意点是 dword_407058 这个常量,它会在前面的x64代码中被赋予一个初始值,并有一个反调试。可以跟随到前面的天堂之门跳转中,看到x64代码检测了PEB.BeingDebugged位,若该位为0则会赋予初始值0x5DF966AE,这个才是正确的初始值
还原出加密流程的伪代码
1
2
3
4
5
6
7
|
key = 0x3CA7259D
flag = [0x64636261, 0x68676665, 0x6C6B6A69, 0x706F6E6D, 0x74737271, 0x78777675, 0x31307A79, 0x35343332]
for i in range(len(flag)):
flag[i] = (flag[i] + key) & 0xFFFFFFFF
key ^= flag[i]
print("flag[%d] = %s" % (i, hex(flag[i])))
print("key =", hex(key))
|
马上可以写出这一部分的exp脚本
1
2
3
4
5
6
7
8
9
|
for (int i = 0; i < 8; ++i){
DWORD bak = ans[i];
ans[i] = (ans[i] - key) & 0xFFFFFFFF;
key ^= bak;
}
BYTE *p = (BYTE *)ans;
for(int i=0;i<0x20;++i)
printf("%c", *(p+i));
|
第二部分
然后进入第二部分,对应这个call
这里由于段选择子被切换调试不了,对着该函数修复代码段后F5大致还原出函数内容
修出来的逻辑很乱看不太明白,就直接对照变换前后内容写了转化脚本
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
|
def rolCustomBit(v,bit,shift):
shift &= (bit-1)
if shift == 0:
return v
HightBit = v >> (bit - shift)
LowBit = v << shift
l = [x for x in range(4, bit + 1) if x % 4 == 0]
LCount = len(l)
_ = '0x' + 'F' * LCount
FfValue = int(_, 16)
Value = (HightBit | LowBit) & FfValue
return Value
def rorCustomBit(v, Bytebit, shift):
shift &= (Bytebit - 1) # 按照bit值 来进行设置移动位数
if shift == 0:
return v
a1 = (v >> shift) # 右移shift位 空出高位shift位
a2 = (v << ((Bytebit) - shift)) # 计算出剩下要移动的位数
l = [x for x in range(4, Bytebit + 1) if x % 4 == 0]
LCount = len(l)
_ = '0x' + 'F' * LCount
FfValue = int(_, 16)
value = (a2 | a1) & FfValue
return value
flag=[0xA88D38AE1E1B36E0,0x34065C283209F8B8,0x140A04D73A0730AA,0x848BC435808F8217,]
for i in range(len(flag)):
if i==0:
print(hex(rorCustomBit(flag[i],64,12)))
if i==1:
print(hex(rorCustomBit(flag[i],64,34)))
if i==2:
print(hex(rorCustomBit(flag[i],64,56)))
if i==3:
print(hex(rorCustomBit(flag[i],64,14)))
|
第三部分
通过切段选择子jmp到sub_401291初始化异或的key
然后回来继续执行,此时段选择子没有恢复所以依然无法调试,但可以通过IDA修复代码段看出做了什么
IDA对0x40146E建立函数,可以看到就是使用前面获得的四字节key异或
完事后memcpy校验flag。故可以写出第三部分exp
1
2
3
4
5
6
7
8
9
10
11
12
|
DWORD right_key[8] = {0xE20F4FAA, 0x549941E4, 0x7E842B2C, 0x788B8FBC, 0x5E8873D3, 0x708547AE, 0xCE09B331, 0xCA0DF513};
BYTE key_part3[] = {0x4, 0x77, 0x82, 0x4A};
int main() {
BYTE *p = (BYTE *)right_key;
for (int i = 0; i < 0x20; ++i){
*(p+i) ^= key_part3[i%4];
printf("0x%02X\n", *(p+i));
}
for (int i = 0; i < 0x8; ++i)
printf("0x%X, ", right_key[i]);
return 0;
}
|
最终得到flag:6cc1e44811647d38a15017e389b3f704
Berkeley
队友拿了一血,太猛了!
程序加载了一段bpf,其中包含两个函数LBB0_1和LBB0_2,分别注册为check_flag的uprobe和uretprobe,因此check_flag中的代码实际上是没用的,真正的校验在这两个函数里。将bpf代码提出出来。
用ghidra以及Nalen98/eBPF-for-Ghidra插件将bpf文件打开。两个函数反编译结果如下
写出伪代码如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
int check(char*flag){
unsigned char output[32];
for (int i=0; i<256; i++) {
unsigned char uc1 = flag[i/8];
unsigned char uc2 = ~(flag[i/8] + arr[i%8]);
output[i] = key[uc1 ^ uc2];
}
for (int i=0; i<256; i++) {
output[i] = key[output[i] ^ key[i]];
}
for (int i=0; i<256; i++) {
if(output[i]!=cipher[i]){
printf("error!");
exit(1);
}
}
}
|
其中arr是在访问地址为0的地方,我并不知道他是什么,但flag是逐字节检验的,所以patch bpf中校验的字符数量,然后爆破。下图为patch位置:
bpf_trace_printk的输出可以通过/sys/kernel/debug/tracing/trace_pipe管道读取
写出exp如下
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
import os, string
from pwn import *
with open('Berkeley','rb') as f:
c=f.read()
flag=''
p=process(['cat', '/sys/kernel/debug/tracing/trace_pipe'])
for i in range(len(flag)*8+8,0x108,8):
with open(f'a/test{i}','wb') as f:
f.write(c[:197468])
f.write(i.to_bytes(2,'little',signed=False))
f.write(c[197470:])
os.chmod(f'a/test{i}',0o777)
for ch in string.printable:
os.system(f'a/test{i} "{(flag+ch).ljust(32)}" > /dev/null')
buf=p.recvuntil('\n',timeout=1)
if b'Right' in buf:
print(ch,end='')
flag+=ch
break
print()
|
EasyVT
拿了个二血顺便review了一下VT。
我只能说出题人牛逼,题目用MD模式编译还不给链接库,合着就是让人静态呗,那出VT有啥意义,费这么大功夫还不如第一题的天堂之门有用(×
首先,可以很容易的看出这个题是在周壑的 VT_Learn 项目上改的(如果学过他的VT课程的话),甚至连pool tag的名字都没改
其实大部分东西都没什么用,就是最最简单基本的VT配置,在SetupVMCS函数(sub_402240)中主要关注HOST_RIP的设置。这个域设置VMMEntryPoint(sub_401C10),也就是当VmExit发生时,会先进入到这个位置,做状态的保存恢复和VM处理函数的Dispatch分发。
所以对于VM事件的核心分发函数在sub_401C90中,并根据宏可以还原出符号
然后回过头来看一下Guest.exe。三环程序非常简单,输入32字节,分四组,每组8字节。从Vmxon开始到Vmoff结束,每条vm指令都会发起一次VmExit进入Host进行对应的处理。若四次均校验通过则为flag
从三环程序可以看出每组输入经过的Host处理流程都是一样的,Vmxon-Vmclear-Vmptrld-Vmwrite-Vmlaunch-Vmread-Vmcall-Vmptrst-Vmresume-Vmoff,总共10步。然后再去分析驱动程序,可以知道vmxon~vmread 在读取配置数据,vmcall~vmresume在处理加密,vmxoff判断运算结果。实际上,说白了,这道题跟VT就没什么关系,只是靠VT把正常的一个输入、加密、校验流程拆成了10步而已
然后就是从后往前一步一步看,先是魔改的TEA解密,然后是一个RC4,完事。其中穿插着各种换位,懒得分析交换规则了,直接手动换一换就出了
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
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
|
#include <cstdio>
#include <windows.h>
DWORD ans[] = {0x5C073994, 0x0D805CB3, 0x87DDA586, 0x317FB8E, 0x6520EF29, 0x5A4987AF, 0x0EB2DC2A4, 0x38CF470E};
DWORD g_tea_sum = 0x20000000;
DWORD TEA_delta = 0xC95D6ABF;
DWORD TEA_key[] = {0x00102030, 0x40506070, 0x8090A0B0, 0xC0D0E0F0};
DWORD tea_k0, tea_k1, tea_k2, tea_k3;
DWORD tea_v0, tea_v1;
void vmhandler_vmresume()
{
unsigned int i;
tea_k2 = TEA_key[1];
tea_k3 = TEA_key[3];
tea_k0 = TEA_key[2];
tea_k1 = TEA_key[0];
for ( i = 0; i < 0x20; ++i )
{
tea_v1 -= (tea_k3 + ((unsigned int)tea_v0 >> 5)) ^ (g_tea_sum + tea_v0) ^ (tea_k2 + 16 * tea_v0);
tea_v0 += (tea_k1 + ((unsigned int)tea_v1 >> 5)) ^ (g_tea_sum + tea_v1) ^ (tea_k0 + 16 * tea_v1);
g_tea_sum -= TEA_delta;
}
}
void rc4_encrypt () {
g_tea_sum = 0x20000000;
TEA_delta = 0xC95D6ABF;
tea_k2 = TEA_key[1];
tea_k3 = TEA_key[3];
tea_k0 = TEA_key[2];
tea_k1 = TEA_key[0];
g_tea_sum -= 0x20*TEA_delta;
for (int i=0; i < 0x20; i++) {
g_tea_sum += TEA_delta;
tea_v0 -= ((tea_v1<<4) + tea_k0) ^ (tea_v1 + g_tea_sum) ^ ((tea_v1>>5) + tea_k1);
tea_v1 += ((tea_v0<<4) + tea_k2) ^ (tea_v0 + g_tea_sum) ^ ((tea_v0>>5) + tea_k3);
}
}
int main() {
tea_v0 = 0x0D805CB3;
tea_v1 = 0x5C073994;
rc4_encrypt();
printf("0x%X 0x%X\n", tea_v0, tea_v1);
tea_v0 = 0x317FB8E;
tea_v1 = 0x87DDA586;
rc4_encrypt();
printf("0x%X 0x%X\n", tea_v0, tea_v1);
tea_v0 = 0x5A4987AF;
tea_v1 = 0x6520EF29;
rc4_encrypt();
printf("0x%X 0x%X\n", tea_v0, tea_v1);
tea_v0 = 0x38CF470E;
tea_v1 = 0x0EB2DC2A4;
rc4_encrypt();
printf("0x%X 0x%X\n", tea_v0, tea_v1);
return 0;
}
|
RC4解密
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
109
110
111
|
#include<cstdio>
#include<cstring>
#include<windows.h>
#define MAX 65534
int S[256]; //向量S
char T[256]; //向量T
int KeyStream[MAX]; //密钥
BYTE CryptoText[MAX];
BYTE Key[] = {48, 52, 101, 53, 50, 99, 55, 101, 51, 49, 48, 50, 50, 98, 48, 98};
int GetLength(BYTE* list) {
int cnt = 0;
for(int i=0;i<MAX;++i) {
if(list[i] == '\x00')
break;
cnt++;
}
return cnt;
}
void init_S()
// 初始化S;
{
for (int i = 0; i < 256; i++) {
S[i] = i;
}
}
void init_Key(BYTE* key) { // 拓展密钥
int d, keylen = 16;
for (int i = 0; i < 256; i++) { //初始化T[]
T[i] = key[i % keylen];
}
}
void permute_S()
{
// 置换S;
int temp;
int j = 0;
for (int i = 0; i < 256; i++) {
j = (j + S[i] + T[i]) % 256;
temp = S[i];
S[i] = S[j];
S[j] = temp;
}
}
void create_key_stream(BYTE* text, int textLength)
{
// 生成密钥流
int i, j;
int temp, t, k;
int index = 0;
i = j = 0;
while (textLength--) { //生成密钥流
i = (i + 1) % 256;
j = (j + S[i]) % 256;
temp = S[i];
S[i] = S[j];
S[j] = temp;
t = (S[i] + S[j]) % 256;
KeyStream[index] = S[t];
index++;
}
}
void Rc4EncryptText(BYTE* text)
{
int textLength = GetLength(text);
printf("%d\n", textLength);
init_S();
init_Key(Key);
permute_S();
create_key_stream(text, textLength);
int plain_word;
// 每次进vm会刷新状态
// 所以总是用密钥流的前8字节解密
for (int i = 0; i < textLength; i++) {
CryptoText[i] = char(KeyStream[i%8] ^ text[i]);
}
}
int main() {
DWORD text[] = {
0xB89C12D5, 0xB17E7A2C,
0xBF9842D1, 0xE6257321,
0xEDCD45D0, 0xB2262921,
0xB99B49DC, 0xBA722D2C
};
Rc4EncryptText((BYTE*)text);
// 四个四个调换顺序
for(int i = 0; i < 4; i++)
for(int j = 1; j >= 0; j--)
for(int k = 0; k<4;++k)
printf("%c", CryptoText[i*8 + j*4 + k]);
return 0;
}
|