0x01 babyre

这个程序的关键在于几个线程的调用,和key的值。

首先key 的值在主函数的异或前是先赋值过的,不是原本静态存储的那个值,可以通过动调得到

1
0x77, 0x74, 0x78, 0x66, 0x65, 0x69

然后关于这个线程的调用我也不是很懂,大概就是创建四个线程,每个线程里存有各自的函数的地址,然后按顺序依次调用,调用了一个线程后该线程对应的信号量减一(wait),下一个线程的信号量加一(post),这时就调用下一个线程的函数,依次循环,直到全局变量 i 为31停止,最后退出线程,四个线程总共循环8次(按理来说,因为flag是32位,全部加密一轮需要8*4=32次),按照这个思路写出逆向脚本,结果也是没问题的。

一开始做题的时候是通过调试发现四个线程一轮只调用一次,于是猜测是逐个逐个进行。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include<iostream>
#include<string.h>
__int32 enc[33] = {12052,78,20467,109,13016,109,27467,-110,
9807,91,21243,-100,11121,20,10863,-107,10490,29,10633,-101,
10420,78,17670,-38,6011,-4,16590,125,10723,
15,7953,255 , 250};
__int32 key[6] = {0x77, 0x74, 0x78, 0x66, 0x65, 0x69};
int main(){
int i = 31;
while(i >= 0){
enc[i] ^= (enc[i + 1] - key[(i + 1) % 6]);
i--;
enc[i] /= (enc[i + 1] + key[(i + 1) % 6]);
i--;
enc[i] += (key[(i + 1) % 6] ^ enc[i + 1]);
i--;
enc[i] -= (key[(i + 1) % 6] * enc[i + 1]);
i--;
}
for(int j=0;j<32;j++){
printf("%c",enc[j]);
}
return 0;
}

hgame{you_are_3o_c1ever2_3Olve!}

0x02 ezcpp

一进去看了一眼就是TEA加密,不过这个TEA加密和普通的不太一样,他是依次分别取input(char类型)(用户输入值)的下标为0和4,1和5,2和6,3和7的首地址,然依此地址为最低位,向后取3个char值和自己本身拼合成32位的int数,比如0x11,0x22,0x33,0x44,就以小端序存储成0x44332211。

在这个加密里,取flag[0]的地址解引用,然后传入某个变量v,其实就是将flag0,1,2,3 的值以上述方式拼合成int类型然后传入变量v,知道这个后,其实不难发现该加密只加密了前11个字符(因为以flag[7]的地址加密,最多拼合7到10,总共4个字符成int,也就是最多加密到flag[10]也就是第11的字符)

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
#include<iostream>
#include<string.h>
//密文flag
int flag[32] = {0x88, 0x6A, 0xB0, 0xC9, 0xAD, 0xF1, 0x33, 0x33, 0x94, 0x74,
0xB5, 0x69, 0x73, 0x5F, 0x30, 0x62, 0x4A, 0x33, 0x63, 0x54,
0x5F, 0x30, 0x72, 0x31, 0x65, 0x6E, 0x54, 0x65, 0x44, 0x3F,
0x21, 0x7D};
int v20,v21,v18,v19,v10,v9,v5,v6,sum,delta;
//四个密钥
int key1 = 2341, key2 = 1234, key3 = 4123, key4 = 3412;
//将int类型拆分成字符的函数
void trans(int v,int i){
flag[i] = v & 0xff;
flag[i+1] = (v>>8) & 0xff;
flag[i+2] = (v>>16) & 0xff;
flag[i+3] = (v>>24) & 0xff;
}
int main(){
//按照原加密的顺序逆过来
delta = -559038737;
//first
sum = delta * 32;
//将字符拼合成int的整数
v20 = flag[3]|(flag[4]<<8)|(flag[5]<<16)|(flag[6]<<24);
v21 = flag[7]|(flag[8]<<8)|(flag[9]<<16)|(flag[10]<<24);
//TEA加密的部分
for(int i = 0;i < 32;i++){
v21 -= (sum + v20) ^ (key3 + 32 * v20) ^ (key4 + 16 * v20);
v20 -= (sum + v21) ^ (key1 + 32 * v21) ^ (key2 + 16 * v21);
sum -= delta;
}
//调用函数将int拆分成原来的字符
trans(v20,3);
trans(v21,7);
//second
sum = delta * 32;
v18 = flag[2]|(flag[3]<<8)|(flag[4]<<16)|(flag[5]<<24);
v19 = flag[6]|(flag[7]<<8)|(flag[8]<<16)|(flag[9]<<24);
for(int i = 0;i < 32;i++){
v19 -= (sum + v18) ^ (key3 + 32 * v18) ^ (key4 + 16 * v18);
v18 -= (sum + v19) ^ (key1 + 32 * v19) ^ (key2 + 16 * v19);
sum -= delta;
}
trans(v18,2);
trans(v19,6);
//third
sum = delta * 32;
v9 = flag[1]|(flag[2]<<8)|(flag[3]<<16)|(flag[4]<<24);
v10 = flag[5]|(flag[6]<<8)|(flag[7]<<16)|(flag[8]<<24);
for(int i = 0;i < 32;i++){
v10 -= (sum + v9) ^ (key3 + 32 * v9) ^ (key4 + 16 * v9);
v9 -= (sum + v10) ^ (key1 + 32 * v10) ^ (key2 + 16 * v10);
sum -= delta;
}
trans(v9,1);
trans(v10,5);
//forth
sum = delta * 32;
v5 = flag[0]|(flag[1]<<8)|(flag[2]<<16)|(flag[3]<<24);
v6 = flag[4]|(flag[5]<<8)|(flag[6]<<16)|(flag[7]<<24);
for(int i = 0;i < 32;i++){
v6 -= (sum + v5) ^ (16 * v5 + key4) ^ (32 * v5 + key3);
v5 -= (sum + v6) ^ (16 * v6 + key2) ^ (32 * v6 + key1);
sum -= delta;
}
trans(v5,0);
trans(v6,4);
//print
for(int i = 0;i < 32;i++){
printf("%c",flag[i]);
}
return 0;
}
//hgame{#Cpp_is_0bJ3cT_0r1enTeD?!}

0x03 arithmetic

先upx脱壳,由于被改了特征值导致没法用工具upx -d脱壳,用010editor打开,然后修改,将3E0h行的首四个十六进制数改成55 50 58 21,对应字符UPX!,若没有这个特征值将无法upx -d脱壳。

关于upx加壳的文章

之后便是求解数塔问题了,根据提示是求最大路径和,左1右2,在大佬朋友的帮助下也是把脚本弄出来了。

out里面存着数塔的数据,500行,粘贴到崩溃。

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<iostream>  
#include<algorithm>
using namespace std;
int n;
//dp数塔
int a[1000][1000];
//存放着最大路径
int step[1000];
//原数塔
int b[1000][1000];
int main(){
cin >> n;
//读取数塔
for (int i = 0; i < n; i++){
for (int j = 0; j <= i; j++){
cin >> a[i][j];
b[i][j] = a[i][j];
}
}
//计算最大和
for (int i = n - 2; i >= 0; i--){
for (int j = 0; j <= i; j++){
a[i][j] += max(a[i + 1][j], a[i + 1][j + 1]);
}
}
//求最长路径
int x = 0, y = 0;
//根据a数组来求,a数组是已经被修改过的数组,每一层的值是从底层开始求和的最优解
step[0] = b[0][0];
for (int i = 0; i < n - 1; i++){
//若左边大时
if (a[x + 1][y] > a[x + 1][y + 1]){
step[i + 1] = b[x + 1][y];
x++;
cout<<"1";
}
//右边的数据较大时
else{
step[i + 1] = b[x + 1][y + 1];
x++;
y++;
cout<<"2";
}
}
return 0;
}

得出1,2串后,再去在线网站md5加密一下就是flag值了

在线md5加密解密网站

hgame{934f7f68145038b3b81482b3d9f3a355}

0x04 findme

文件用ida打开,两个fake_flag,然后就不知道该怎么做了,经过式神的hint,才知道put(buffer)的端倪。

上网查MZ,出现dos头和pe文件等的东西,可以找这篇文件了解一下,讲的很好:PE文件详解

用010editor打开文件,果不其然,文件的前面果然有mz头等数据文件

image-20240217012122559

记住这些东西对接下来的逆向有很大的帮助。

上文提到buffer,从这个地址开始,有M…Z等字符,猜测是把一个可执行PE文件用某些方式藏在了数据段里,在010editor里查找4D 00 00 00 5A 00 00 00,果不其然,确实有这样一个东西。

image-20240217012417034

而且我惊喜地发现,这些数据文件竟然就是dos头的那些数据,只不过把原本8位的数据用32位的方式存储了,也就是这里显示出来的,一个有用十六进制数再加6个0。从上往下,搜寻一下这种存储方式的所有数据,把他们全部复制到新的文件里(注意要复制直到没有这种存储规则的数据),我这里命名为 1 。(随便名的),接下来就是用个脚本删去这些无意义的00 00 00了。

由于不会写修改二进制文件的脚本,我干脆用python把他读取出来,然后再把读出来的文件粘贴到一个十六进制文本文件里。

1
2
3
4
5
6
7
8
cnt = 0
with open(r"C:\Users\28382\Desktop\1",'rb') as file:
content = file.read()
for i in content:
#用这个方法筛掉00 00 00
if cnt % 4 == 0:
print(hex(i)[2:])
cnt += 1

运行后的十六进制数直接粘贴到010editor里新建的十六进制文本文件就好了。

注意:粘贴十六进制数要用shift+ctrl+V,不然就会粘贴成文本

然后保存,用die查一下文件类型,

image-20240217013008652

真是非常amazing啊,变成了应该PE文件,这下就可以用ida开始逆向了。

打开后去除一堆花指令,(这里只有jz和jnz),然后f5看看伪代码。有两个函数,看着像是rc4加密。

直到我发现有负数下标的数组,我就知道这东西不简单,

51bb215441e3538249c21cd3da60511

以input的首地址为基准,负数代表向后取地址,调试后发现input后面存放的便是key数组,由此推断result的取值和你input什么东西是没有任何关系的,因为result 的值都来自于key数组,而且向后取地址的多少也取决于key,也就是这个-(v4+ key[v1]) 的值,只和key数组有关系。动调把result 的值都跑出来,然后用enc减去他就好了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include<iostream>
int enc[32] = {0x7D, 0x2B, 0x43, 0xA9, 0xB9, 0x6B, 0x93, 0x2D, 0x9A, 0xD0,
0x48, 0xC8, 0xEB, 0x51, 0x59, 0xE9, 0x74, 0x68, 0x8A, 0x45,
0x6B, 0xBA, 0xA7, 0x16, 0xF1, 0x10, 0x74, 0xD5, 0x41, 0x3C,
0x67, 0x7D};
int result[32] = {0x15,0xC4,0xe2,0x3c,0x54,0xf0,0x4d,0xc1,
0x6a,0x59,0x15,0x56,0x78,0xf2,0x18,0x77,0x41,0x09,
0x34,0xe0,0xf9,0x41,0x48,0xb0,0x7f,0xdc,0xd,0x63,0xe0,
0xce,0xf3,0x0};
int temp;
int main(){
for(int i = 0;i <32;i++){
temp = enc[i] - result[i];
if(temp < 0){
temp += 256;
}
printf("%c",temp);
}
return 0;
}
//hgame{Fl0w3rs_Ar3_Very_fr4grant}

0x05 mystery

这题一打开就发现是个RC4加密。

image-20240222132959481

这里的key实际上被修改过,按x查找引用,

image-20240222133036979

这些全都不管,反正大概知道这么个事情,然后直接点开sub_1500 的加密函数,和rc4相差的地方在于最后一步是减法而不是异或,原理一样,直接开虚拟机动调把每一轮的result跑出来,然后直接和密文相加就能得到flag了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
result = [0x18,0x25,0x29,0x20,0x19,
0x27,0xb9,0xc9,0x34,0xc7,
0x71,0xc9,0xac,0x17,0xb4,
0x1e,0xe5,0xe9,0xfc,0x2a,
0x4a,0x01,0xea,0x79,0xc7,
0x82,0xfe,0x51,0xe7,0xb1,
0xae,0x28]
enc = [0x50, 0x42, 0x38, 0x4D, 0x4C, 0x54, 0x90, 0x6F, 0xFE, 0x6F,
0xBC, 0x69, 0xB9, 0x22, 0x7C, 0x16, 0x8F, 0x44, 0x38, 0x4A,
0xEF, 0x37, 0x43, 0xC0, 0xA2, 0xB6, 0x34, 0x2C]
flag = ''
for i in range(28):
flag += chr((enc[i] + result[i]) & 0xff)
print(flag)
#hgame{I826-2e904t-4t98-9i82}

0x06 change

其实这题挺简单的,但是加了很多混淆视听的东西。

image-20240226163834401

题目里有大量的这种函数,一开始看着懵懵的,后面调试才发现这是一个取地址的函数,第一个参数是地址,第二个参数是偏移量,返回偏移后的地址,实际上就是一个数组enc[i]。

image-20240226164002861

加密函数里也一堆这种逻辑的函数,这里简化后就是分别取input[i]和key[i%key_len],然后进去beep加密,对,按理来说应该是这样的,不过不知道为什么,这个beep函数里面调用的那个函数里面啥也没有,静态上是一个变量,没有被赋值。这里猜测是使用了函数指针,在之前给这个beep里面的变量赋值了一个函数指针,向上查找还真有,这里的sub_7FF7A…的函数,点进去,将传入的函数指针,也就是这个sub…443650,赋值给了beep里面的变量,然后通过函数指针调用这个加密函数。

image-20240226164656792

点进sub…443650就能发现是什么了,另外一个是a2 ^ a1,没有加10,判断一下i的奇偶就行了。

1
2
3
4
5
6
7
8
9
10
11
enc = [0x13, 0x0A, 0x5D, 0x1C, 0x0E, 0x08, 0x23, 0x06, 0x0B, 0x4B,
0x38, 0x22, 0x0D, 0x1C, 0x48, 0x0C, 0x66, 0x15, 0x48, 0x1B,
0x0D, 0x0E, 0x10, 0x4F]
key = "am2qasl"
flag = ""
for i in range(24):
if i % 2 == 0:
enc[i] -= 10
flag += chr(ord(key[i%7])^enc[i])
print(flag)
#hgame{ugly_Cpp_and_hook}