level5

逆向一个虚拟机,编写适应于其的二进制文件,实现tea算法;

main

上图左侧为main函数,逻辑就是读取名称 “binary” 的内容,然后将其赋给code,之后将code扔进vm函数充当指令集;

每条指令分三个数值,一个指令数,两个操作数,分别给了instru和One,Two变量;

根据输入不同的instru变量来调用不同的函数,这些函数就是指令执行的操作了,翻译如下:

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
code[8] 是计数器 -> i

code[9] 是flag 控制数

code[7] 是栈针

code[6] 用于实现加法

---

0 code[one] = two ++i

1 code[code[7] + 10] = one ++code[7] ++i

2 code[code[7] + 10] = code[one] ++code[7] ++i

3 code[code[7] + 10] = code[8] + 1 ++code[7] code[8] = one

4 code[one] = code[two] ++i

5 code[8] = one

6 code[one + 10 + code[6]] += code[two] ++i

7 code[one] = code[code[6] + 10 + two] ++i

8 code[one] < < = two ++i

9 code[one] > > = two ++i

a code[one] += code[two + 10 + code[6]] ++i

b code[one] ^= code[two] ++i

c if( !code[9] ) -> code[8] = one ; else ++i

d end

e if(two < = code[one + 10 + code[6]])

​ {

if(two = code[one + 10 + code[6]])

​ code[9] = 1

else

​ code[9] = 2

​ }

else -> code[9] = 0

​ ++i

f --code[7] code[one] = code[code[7] + 10] ++i

10 --code[7] code[8] = code[code[7] + 10] ++i

11 code[code[6] + 10 + one] += two ++i

12 code[code[6] + 10 + one] += code[code[6] + 10 + two] ++i

13 code[one] = cin ++i

14 cout code[one] ++i
---

所以123都表示入栈,3表示call因为改变了计数器;f,10表示出栈,10表示return

由此对照机械码手撸汇编:

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
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
//赋初值

0 6 32 mov reg6, 50

1 11223344 push 11223344h

1 22334455 push 22334455h

1 33445566 push 33445566h

1 44556677 push 44556677h

---

//循环输入

11 0 0 add reg60, 0

tag1:

13 0 mov reg0, cin

2 0 push [reg0]

11 0 1 add reg60, 1



e 0 4 cmp reg60, 4

c 43 jnz tag1

---

//调用cry以及输出

3 4E call cry

14 0 mov cout, reg0

14 1 mov cout, reg1

14 2 mov cout, reg2

14 3 mov cout, reg3

d retn

---

//cry实现

cry:

1 9E3779B9 push delta (push后code[7] = 10)

// v[4]

4 0 E mov reg0, [esp + 6]

4 1 F mov reg1, [esp + 5]

4 2 10 mov reg2, [esp + 4]

4 3 11 mov reg3, [esp + 3]

---

//循环

0 3C 0 mov reg60, 0

11 0 0 add reg60, 0

tag2:

//sum

6 1 13 add reg61, [esp + 2]

//v0

4 3E 1 mov reg62, reg1

8 3E 4 shl reg62, 4

6 2 A add reg62, [esp + a]



4 3F 1 mov reg63, reg1

6 3 3D add reg63, reg61



4 40 1 mov reg64, reg1

9 40 5 shr reg64, 5

6 4 B add reg64, [esp + 9]



b 3E 3F xor reg62, reg63

b 3E 40 xor reg62, reg64

6 2 0 add reg62, reg0

4 0 3E mov reg0, reg62



//v1

4 3E 0 mov reg62, reg0

8 3E 4 shl reg62, 4

6 2 C add reg62, [esp + 8]



4 3F 0 mov reg63, reg0

6 3 3D add reg63, reg61



4 40 0 mov reg64, reg0

9 40 5 shr reg64, 5

6 4 D add reg64, [esp + 7]



b 3E 3F xor reg62, reg63

b 3E 40 xor reg62, reg64

6 2 1 add reg62, reg1

4 1 3E mov reg1 reg62



//v2

4 3E 3 mov reg62, reg3

8 3E 4 shl reg62, 4

6 2 A add reg62, [esp + a]



4 3F 3 mov reg63, reg3

6 3 3D add reg63, reg61



4 40 3 mov reg64, reg3

9 40 5 shr reg64, 5

6 4 B add reg64, [esp + 9]



b 3E 3F xor reg62, reg63

b 3E 40 xor reg62, reg64

6 2 2 add reg62, reg2

4 2 3E mov reg2 reg62



//v3

4 3E 2 mov reg62, reg2

8 3E 4 shl reg62, 4

6 2 C add reg62, [esp + 8]



4 3F 2 mov reg63, reg2

6 3 3D add reg63, reg61



4 40 2 mov reg64, reg2

9 40 5 shr reg64, 5

6 4 D add reg64, [esp + 7]



b 3E 3F xor reg62, reg63

b 3E 40 xor reg62, reg64

6 2 3 add reg62, reg3

4 3 3E mov reg3, reg62



11 0 1 add reg60, 1



e 0 20 cmp reg60, 32

c 55 jnz tag2

---



f 13 pop delta

10 retn

---


没实现栈平衡,不过芜锁胃;反正最后return回去输出就行;

注意:写二进制文件时用小端序,而且以DWORD为基本单位,并以3个DWORD对齐,比如 c 55 写成:0c 00 00 00 55 00 00 00 00 00 00 00 ;

还有个问题,根据调试,每次第一条指令开始是code[8] = 3C; 所以二进制文件需要填充垃圾信息,填多少?第一幅图中instru = code[3 * code[8] + 1010], 所以 括号里的内容为 : 1190 ;而写二进制文件需要以DWORD为单位,所以需要填充 1190 * 4 个 00 ;

通过:

pass

level1

ida:

main

调试可以发现main挂不上,然后就发现旁边的函数长得和main都差不多,一个一个断点试,找到第三个是真正的main函数;

主要思路就是输入15个内容,进行异或和加运算,然后和v4开始的数据比较;

写出逆运算:

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
#include <iostream>
using namespace std;

int main()
{
int i, j;
unsigned int buf[15] = {
0x04, 0x46, 0x81, 0x63, 0x14, 0x53, 0x17, 0x6D, 0x6A, 0x67, 0x76, 0x16, 0x34, 0x14, 0x34
};
int v0 = 0;
char ans[16];
for (i = 0; i <= 14; ++i) //自加v0到加密完状态
{
for (j = 0; j <= 2; ++j)
{
v0++;
}
}
v0--; //多加一个减掉
for (i = 14; i >= 0; --i) //逆
{
for (j = 2; j >= 0;--j)
{
buf[i] -= v0--;
buf[i] ^= i ^ j ^ 0x32;
}
ans[i] = buf[i];
}
ans[15] = '\0';

printf("%s", ans);
return 0;
}

得到flag:SYC{0h_y0u1_finD0V0}

level2

ida:

main

进去之后第一感觉会发现main是个scanf函数,但是点进去之后会发现这个东西,让调试才能查看代码;点进这个函数之后会发现是个线程创建,注意调试时改变if判断的变量值为1;

在线程中可以发现以下代码:

thread

在thread main 函数里有两个函数:check() 和 encrypt() ;

check一开始就执行,判断长度,以及输入的内容必须为数字;

encrypt读入key和输入的数据,将数据前12位与key加密运算;

最后比较数据;

写出encrypt的逆向算法:

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
for (i = 0; i < 12; ++i)
{
switch (key[i])
{
case 3:
v4 += 23; //无用
v3 ^= 5;
break;
case 9:
us[i] ^= key[i]; //不变
break;
case 161:
us[i] -= 2 * key[i]; //减等
break;
case 196:
us[i] -= 10; //减等
break;
case 229:
//v3 = 8 * ((v4 + 6) >> (v4 & 3)); 无用
us[i] = inter(us[i]);
break;
default:
break;
}
}

int inter(int a)
{
return a - 0x19;
}

将比较数据经过以上运算得到flag:SYC{03062639056784}

level3

ida进去可以发现是加壳了,函数很少;

打开二进制格式搜索upx,果然就找到了老朋友:

unpack

upx加壳的标识码是 UPX! 全大写,拿不准改哪个就全部 Upx 都改成 UPX;

改完就可以脱壳了;

看代码:

main

看不懂子进程作用,反正主要内容在父进程里:输入内容后,先进入change函数,把4个4个的char内容放到4单位的int里;然后把这个int数组放到xor函数里,把每个字节都和j做异或运算,最后和v19比较数据,v19的内容就是cpy的16长度的字符串;

异或的逆运算还是异或,写出复原代码:

1
2
3
4
5
6
7
8
9
10
for (i = 0; i < 16; i = i + 4)
{
for (j = 0; j <= 9; ++j)
{
we[i] = we[i] ^ j;
we[i + 1] = we[i + 1] ^ (j + 1);
we[i + 2] = we[i + 2] ^ (j + 2);
we[i + 3] = we[i + 3] ^ (j + 3);
}
}

将逆向得到的cpy字符串带入we得到flag:SYC{0k_y0u_s0lv3_it_}

level4

这道题缺库不能调,直接看静态;

ida:

main

左图main函数,右图rc4函数,将enter用rc4加密了,密钥是 syclover:)

使用大厨把enter加密后的内容烤出来:(CyberChef (gchq.github.io)

chef

发现开头是ELF,说明这加密出来的内容是个elf文件,将其写入二进制文件再用ida打开:

main

属于就正常了;

func

如上可知,输入16长度内容,然后进行tea算法(小魔改,每次异或了i),之后比较数据;

写出逆算法:

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
int i;
unsigned int j = 0,l , r ,sum = 0;
int v[4] = { 0x6A318EC6 , 0x5B898EC2 , 0x42FB5DD1 , 0x50AC4C5F };
int k[4] = {0x11 , 0x22 , 0x33 , 0x44};

while (j < 3)
{
l = v[j];
r = v[j+1];

for (i = 0; i < 32; ++i)
sum += DELTA;

for (i = 31; i >= 0; --i)
{
r -= (k[(sum >> 11) & 3] + sum) ^ (((l >> 5) ^ (16 * l)) + l) ^ i;
sum -= DELTA;
l -= (k[sum & 3] + sum) ^ (((r >> 5) ^ (16 * r)) + r) ^ i;
}

v[j] = l;
v[j + 1] = r;

sum = 0;
j += 2;
}

char* p;
p = (char *)v;
p[16] = '\0';


printf("%s", p);

得到flag:SYC{w3f-2hs-ij7-9is}

调试GLIBC_2.34小技巧

安装glibc-all-in-one

1
2
3
sudo git clone https://github.com/matrix1001/glibc-all-in-one.git 
cd glibc-all-in-one/
sudo python3 update_list

下载glibc

1
sudo ./download 2.35-0ubuntu3_amd64

安装patchelf

1
2
3
4
git clone https://github.com/NixOS/patchelf.git
cd patchelf
sudo apt-get install autoconf automake libtool
./bootstrap.sh

继续:

1
2
3
4
./configure
make
make check
sudo make install

配置ld.so

1
patchelf --set-interpreter path/to/.so the/elf/you/debug

配置环境

1
patchelf --set-rpath path/to/.so the/elf/you/debug

level6

逆向一个CPP服务器,得到flag,并编写socket客户端和远程服务器提交flag;

ida查看服务器:

main

这是一个服务端框架,首先创建套接字类型文件,并返回fd文件饰描述符;

然后和IP端口进行绑定;

之后一直监听这个端口,直到接收客户端请求,执行处理,并且是多线程的处理;

而处理的主体在 CTask_server 里可以找到:

true

先发送 “Please …” (send) 到客户端,然后等待输入,被inside变量接收,之后进入cc加密,和tt生成的v7进行比较数据;

看看里面加密吧,都tea ptsd了,不想放图了;直接来吧:

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
int i;
unsigned int j = 0, l, r, sum = 0;
int v[3] = { 0xED3E9980 , 0x57284856 ,0 }; //从tt里抄的
int k[4] = { 0x6C , 0x30 , 0x76 , 0x33 };


l = v[0];
r = v[1];

for (i = 0; i < 32; ++i)
sum += DELTA;

for (i = 31; i >= 0; --i) //小魔改tea
{
r -= ((l >> 5) + k[3]) ^ (l + sum) ^ (16 * l + k[2]) ^ i;
l -= ((r >> 5) + k[1]) ^ (r + sum) ^ (16 * r + k[0]) ^ i;
sum -= DELTA;
}

v[0] = l;
v[1] = r;

char* p = (char*)v;
p[9] = '\0';


printf("%s", p);

得到flag:D0Y0uKSk

如同服务端,自己写的客户端也需要一个框架,然后把发送的flag放到主体里就行;

根据题目中的链接,可以知道客户端只需要使用socket创建套接字后通过ip端口连接就行;

那么大概的框架就是: socket() -> 结构地址 -> connect() 连接到地址 -> 读 & 写 -> close() 结束;

内容:

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>

int main() {
printf("Client Start\n");
int fd = socket(2, 1, 0); //逆向得到的参数

char lines[] = "D0Y0uKSk";

struct sockaddr_in serv_addr; //套接字结构地址
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; //IPv4
serv_addr.sin_addr.s_addr = inet_addr("1.14.92.115"); //地址
serv_addr.sin_port = htons(1234); //端口

printf("Connecting..\n");
connect(fd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)); //接通accept
printf("Done !\n");

char buffer[40]; //回响容器
read(fd, buffer, sizeof(buffer) - 1);
printf("%s\n", buffer);
memset(&buffer, 0, sizeof(buffer));

printf("%s\n", lines);
write(fd, lines, sizeof(lines) - 1);

read(fd, buffer, sizeof(buffer) - 1);
printf("%s\n", buffer);
memset(&buffer, 0, sizeof(buffer));

close(fd);
return 0;
}

结果:

pass

更多socket学习

https://blog.csdn.net/m0_37947204/article/details/80489431

level7

给二进制加载器实现更多功能:1、转储节内容 2、输出数据符号 3、使用capstone反汇编.text段;

称之为环境恶心人之题;

题不难,但在wsl上装环境会变得千奇百怪,反正就是跑不起来,只有vm搞;

1
2
3
sudo
apt-get install binutils-dev
apt-get install libcapstone-dev

完成这道题需要知晓一点点bfd和capstone,以及更多的模仿;

注意引头 bfd.h 和 capstone/capstone.h;

题目已经把各种各样的代码都实现好了,只要求增添几个功能,逐一实现:

转储节内容

这里要求命令行输入三个参数,而第三个参数为节名称,并打印节的原始字节;

那么可以在原来打印节的地方(main.cc)镶嵌一个东西进去:

one

flag一开始设置为0,找到同名节后设置为1;

第一个判断是否有三个参数并且调控数为0,则执行这个函数;

外面的判断是如果没有找到第三个参数一样的节名称,则此时flag依然是0,所以执行打印没有找到;

下面是函数具体实现:

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
int print_origin_bytes(Section* sec, char *one)
{
int i,j = 0;
if(sec->name == one) //找到同名则进入
{
printf("\n");
printf("contents of %s section\n", one);
printf(" ");
for(i = 0 ; i < 16 ; i++) //打印开头的 00~0f
{
printf("%02jx ", i);
}
printf("\n\n");
i = 1;
printf(" ");
while(j < sec->size) //循环打印
{
printf("%02jx ", sec->bytes[j++]);
if(i == 16)
{
i = 0;
printf("\n");
printf(" ");
}
i++;
}
printf("\n\n");
return 1; //设置flag为1
}
return 0;
}

输出数据符号

找到 loader.h 中的symbol类,可以发现 SymbolType 里面只有 SYM_TYPE_UKN SYM_TYPE_FUNC 两个,需要打印 DATA 符号,则添加一个 SYM_TYPE_DATA = 2, ;

之后找到 loader.cc 中的一个函数:load_symbol_bfd,可以发现其中有一步是给函数添加 FUNC项的,镶嵌如下内容:

two

当不添加FUNC项的内容时,添加DATA就好了;

最后改变下main.cc里面打印符号的地方为:

1
2
3
4
printf(" %-40s 0x%016jx %s\n",
sym->name.c_str(),
sym->addr,
(sym->type & Symbol::SYM_TYPE_FUNC) ? "FUNC" : "DATA");

使用capstone反汇编.text段

吐槽一下,edge搜索capstone 反汇编会出现一个博客,详细的记录了如何使用capstone;

具体函数实现:

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
int disass_text(Binary bin)
{
csh dis;
cs_insn *insns;
Section *text;
size_t n, i;
Section* sec;

//获取text节
for(i = 0; i < bin.sections.size(); i++)
{
sec = &bin.sections[i];
if(sec->name == ".text")
text = sec;
}

if(!text) goto fail;

//初始化capstone
if(cs_open(CS_ARCH_X86, CS_MODE_64, &dis) != CS_ERR_OK)
goto fail;

//反汇编把内容放insns里; ( 返回0就是版本问题 ((
n = cs_disasm(dis, text->bytes, text->size, text->vma, 0, &insns);

if(n <= 0)
goto fail;

//打印
printf("disassembly of .text section:\n");
for(i = 0;i < n;i++)
{
printf("0x%016jx\t%s\t\t%s\n", insns[i].address, insns[i].mnemonic, insns[i].op_str);
}

cs_free(insns,n);
cs_close(&dis);

return 0;

fail:
printf("err\n");
return 1;

}

然后在main.cc的打印节后一部分调用就好了;

通过:

pass

总结

纯粹对今年的有兴趣,5和7都比较新鲜,也是第一次手撸汇编了解bfd库,还挺有意思;