0x00.前言

打了整整两天,re也是ak了,不容易

img

总榜11,可惜了没进前十,前十有纸质证书来着

image-20240311210618584

0x1.TEA

这题误导我了,我一直在纠结htoi函数是加密方式

刚开始对输入进行判断,前几位是否是flag{然后就是把flag的内容由-进行分段,所以flag的形式大概是flag{1111-2222}这样

然后这样输入去动调看密文

img

密文有两段,第一段有一个数组进行索引,index的下标是3,1,0,2,然后一位一位地去加进去,最后得到的密文是0xb805d767

第二段密文可以直接动调拿到: 0x63c174c3

然后就是一个简单的tea加密,秘钥2234

直接上脚本:

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
#include <stdio.h>
#include <stdint.h>

void decrypt (uint32_t *v,uint32_t *k){
uint32_t v0=v[0],v1=v[1],sum=0xC6EF3720,i;
uint32_t delta=0x9e3779b9;
uint32_t k0=k[0],k1=k[1],k2=k[2],k3=k[3];
for (i=0;i<32;i++){
v1-=((v0<<4)+k2)^(v0+sum)^((v0>>5)+k3);
v0-=((v1<<4)+k0)^(v1+sum)^((v1>>5)+k1);
sum-=delta;
}
v[0]=v0;v[1]=v1;
}
int main()
{
int i;
uint32_t v[32]={0xb805d767, 0x63c174c3},k[4]={0x2,0x2,0x3,0x4};
for(i=0;i<2;i+=2)
{
decrypt(v+i,k);
}
printf("\nHSCCTF{%02x%02x%02x%02x-%02x%02x%02x%02x}\n",(v[0] >> 24) & 0xFF,(v[0] >> 16) & 0xFF,(v[0] >> 8) & 0xFF,v[0] & 0xFF,(v[1] >> 24) & 0xFF,(v[1] >> 16) & 0xFF,(v[1] >> 8) & 0xFF,v[1] & 0xFF);

}

0x2.ROULRTTE

游戏题最简单的做法就是keypatch

通过shitf+f12看到了字符串有一个hscctf,然后题目说八次通过,所以就定位到了我们要的输出flag的位置

img

找到了这个函数,然后直接在main函数找一个地方直接jmp到这里就行了,我用的是jnz和jz改变条件

img

改这里的,直接keypatch 随便改一改指令然后call sub_E95990

img

原理大概如此,keypatch可以解决90%的问题。剩下的百分之10%是ce修改器

0x3.THE_WOLF_SONG

img

找到关键加密函数,魔改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
import urllib.parse
import base64

one_list = [0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
two_list = [0x05, 0x02, 0x03, 0x01, 0x04, 0x07, 0x08, 0x06, 0x00, 0x05, 0x0A, 0x0F, 0x14, 0x19, 0x1E, 0x00]
enc = [0xCE, 0x26, 0x9C, 0x07, 0x48, 0xD9, 0xFD, 0x23, 0xBA, 0x9A, 0x40, 0xA8, 0x2E, 0xBD, 0xFC, 0x77, 0xB7, 0x5D, 0x7E, 0x67, 0x99, 0xFD, 0xCD, 0x63, 0x13, 0x0A, 0x94, 0x5B, 0x95, 0x2C, 0x26, 0x60, 0x1E, 0x1E, 0xB4, 0x30, 0x89, 0xCF, 0xEF, 0x68]
enc2 = [0]*40
enc3 = [0xE7, 0x14, 0xBE, 0x25, 0x7D, 0xFE, 0xE7, 0x15, 0xBE, 0x97,
0x42, 0xA6, 0x22, 0xB9, 0xC2, 0x42, 0xB9, 0x63, 0x4F, 0x67,
0x8A, 0xE8, 0xC5, 0x61, 0x1B, 0x1B, 0x94, 0x4E, 0x91, 0x12,
0x0E, 0x6F, 0x00, 0x37, 0x86, 0x12, 0xAB, 0xFA, 0xC8, 0x74]
key = 'HSCCTF{FAKE_FLAG!}'
for i in range(len(enc)):
enc2[i] = enc[i] ^ i

for i in range(len(key)-1):
enc2[i] = enc2[i] ^ ord(key[i+1])
for i in range(len(key)):
enc2[i] = enc2[i] ^ ord(key[i])

for i in range(len(enc)):
enc2[i] = enc[i] ^ i
# print(hex(enc2[i]),end=',')

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

key_bytes_1 = [
53, 109, 53, 100, 53, 119, 53, 100, 53, 98,
53, 110, 53, 109, 53, 100, 53, 119, 53, 100,
53, 98, 53, 110, 53, 109, 53, 100, 53, 119,
53, 100, 53, 98, 53, 110, 142, 0
]

def RC4(s_box, enc):
i = j = 0
res = bytearray()
for s in enc:
i = (i + 1) % 256
j = (j + s_box[i]) % 256
s_box[i], s_box[j] = s_box[j], s_box[i]
t = (s_box[i] + s_box[j]) % 256
k = s_box[t]
res.append(s ^ k)
return bytes(res)

enc_flag = [0xd5,0x36,0x9c,0x10,0x5a,0xe4,0xc0,0x24,0xb0,0x94,0x5a,0xb1,0x24,0xb0,0xfa,0x11,0xeb,0x20,0x7e,0x67,0x99,0xfd,0xcd,0x63,0x13,0xa,0x94,0x5b,0x95,0x2c,0x26,0x60,0x1e,0x1e,0xb4,0x30,0x89,0xcf,0xef,0x68]
enc_flag2 = [0xFC, 0x04, 0xBE, 0x32, 0x6F, 0xC3, 0xDA, 0x12, 0xB4, 0x99,
0x58, 0xBF, 0x28, 0xB4, 0xC4, 0x24, 0xE5, 0x1E, 0x4F, 0x67,
0x8A, 0xE8, 0xC5, 0x61, 0x1B, 0x1B, 0x94, 0x4E, 0x91, 0x12,
0x0E, 0x6F, 0x20, 0x37, 0x86, 0x12, 0xAB, 0xFA, 0xC8, 0x74]
enc_flag3 = [0xce,0x27,0x9e,0x4,0x4c,0xdc,0xfb,0x24,0xb2,0x93,0x4a,0xa3,0x22,0xb0,0xf2,0x78,0xa7,0x4c,0x6c,0x74,0x8d,0xe8,0xdb,0x74,0xb,0x13,0x8e,0x40,0x89,0x31,0x38,0x7f,0x3e,0x3f,0x96,0x13,0xad,0xea,0xc9,0x4f]
dec_flag = RC4(sbox1, enc_flag)

a = list(dec_flag)
for i in range(len(a)):
a[i] = (a[i] ^ 3 ^ 6)
print(chr(a[i]),end='')

0x4.NO_PY

做过原题,python反编译的源代码是一个sm4的加密函数,但是找了半天也没什么进展,后来发现是在打包的库函数里面加了一个异或

pycdc解析一下ezpython.pyc,sercrt得到原码和密文

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
from gmssl import sm4
from secret import key, enc
import base64

def pad_pkcs7(data):
'''PKCS#7填充'''
padding_len = 16 - len(data) % 16
padding = bytes([
padding_len] * padding_len)
return data + padding


def unpad_pkcs7(padded_data):
'''PKCS#7去填充'''
padding_len = padded_data[-1]
return padded_data[:-padding_len]


class SM4:

def __init__(self):
self.gmsm4 = sm4.CryptSM4()


def encryptSM4(self, encrypt_key, value):
gmsm4 = self.gmsm4
gmsm4.set_key(encrypt_key.encode(), sm4.SM4_ENCRYPT)
padded_value = pad_pkcs7(value.encode())
encrypt_value = gmsm4.crypt_ecb(padded_value)
return base64.b64encode(encrypt_value)


if __name__ == '__main__':
print('请输入你的flag:')
flag = input()
sm4_instance = SM4()
flag_1 = sm4_instance.encryptSM4(key, flag)
if flag_1 != enc:
print('flag错误!!')
else:
print('恭喜你获得flag😊😀')
key = 'Please_Do_not_py'
enc = b'YJ+70VWYioYm3EhF6qdScVXBpCdPm+hCS/HP+Gj421RyxkZbwzni7o2zGG/Mis4Wr6sbXYr4ufnKwQk7vrG8yA=='

在SM4代码中发现他的key异或了一个102,我们手动将key异或回去就可以了,直接赛博厨子嗦了

img

0x5.VM_RE

这个题目也是让我学到了很多的知识,第一次做vm,竟然爆出来了

img

函数非常的整洁,就一个vm的加密函数

img

跟进vm的加密函数,这个函数变成了一个完整的switch case语句,刚开始我去硬分析,一个一个去找逻辑,但是实在是太麻烦了

于是在网上找到了一个插件可以来getflag

pence插件,下面这篇文章跟本题差不多

https://blog.csdn.net/Hotspurs/article/details/106039643

设置好两个断点,分别用于进行符号化和爆破flag

下面是第一个断点,用来获取输入

img

第二个断点,用来爆破flag

img

然后就开动调,输入33个1到达第一个断点,右击栈窗口存储刚刚输入的数据地址,选择jump in hex跟随16进制,找到左边红色框中16进制输入信息存储的地址

img

右击第一个1,右击如下:或者快捷键ctrl+shift+M快捷键,但是我的我的快捷键不太好使

img

将输入的部分转化成符号,可以在汇编窗口找地址会更方便一点

img

然后f9跳转到判断flag正确与否的部分,也就是第二个断点

img

这里就是函数判断和密文是否相同的地方,左边的是正确的flag的函数,会继续验证下一位,右边是错误的flag,会直接错误然后跳出 然后右击如下,就得到了第一个flag的f

img

之后复制左边下一个函数的eip,修改栈窗口的eip,让程序继续执行 (原理会在下面进行解释

img

大概的操作是这样的,现在我输入flag{111111111111111111111111111}

在确定前五位的情况下进行往后的爆破,一位一位爆就拿到了flag

大概原理我猜是用的angr进行模拟执行程序,从0开始爆破一直爆破到当前位数会跳转到flag正确走法时,才会结束当前位数的爆破

然后通过修改eip的方式强制让程序走flag输入正确的代码然后继续爆破下一位

ps:我做的时候不知道为什么不能一直爆破,我只能一位一位地来爆破…..

img

flag{as2f8v9sd67239c7s5d8901d76d}、

这个方法貌似不适用全部的vm,也没有什么手撕vm的技巧,只能做题的时候遇到vm试试这样的操作吧,如果爆不出来(比如ida一直在执行指令)那只好see you 垃圾桶了

0x6.ANDROID

java端没有什么信息,就是对一个图片和输入传入了libmidand里面

看一下so的jni函数

img

密文就是dat部分,直接提取出来

73 1E 13 3E F7 6A 5C D1 EF 96 26 A9 94 7C F4 A4 6C E2 37 B7 0D 49 05 E9 21 E3 5E 2E 7D 7A 1A 74

然后可以来hook一下murmur3_32的函数来获取v9的数值,v19的前16位拿来第一次加密,后面的16位拿来加密第二次、

so的hook和java层的hook差不多,只不过java层可以直接拿函数名字来hook(没有混淆的情况下)

so层就要先通过函数名来找到该函数的地址,然后以地址为基准进行hook

1
2
var func_addr = Module.findExportByName("libmidand.so" , "_Z10murmur3_32PKcjj");
console.log("addr:" + func_addr);

hook一下地址

img

得到了地址,然后就可以直接hook了

完整的注释代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 查找名为 "libmidand.so" 的库中导出函数名为 "_Z10murmur3_32PKcjj" 的函数,并将函数地址赋值给 func_addr 变量
var func_addr = Module.findExportByName("libmidand.so", "_Z10murmur3_32PKcjj");

// 打印找到的函数的地址
console.log("地址是:" + func_addr);

// 将拦截器附加到 func_addr 所指向的函数上
Interceptor.attach(func_addr, {
// 拦截器回调函数,在被拦截函数返回之后执行
onLeave: function(args) {
// 打印被拦截函数的返回值的十六进制表示
console.log("返回值为:\n" + hexdump(args, { offset: 0, length: 32, header: true, ansi: false }));
}
});

img

跟进encode_fun,看到里面是一个标准的sm4算法,并且是ECB/Nopadding模式

img

特征挺明显,直接拿去赛博厨子解密吧

第一次解密:

img

第二次解密:

img

两次的结果:

fad1c7e27ec411ee.8¬ô).r.dÊ{Äâ..C

..®..\XCN¿qAºb’Ýbe3a3e4419a1b3cc

第一段的前16位和第二段的后16位进行拼接,得到flag

flag{fad1c7e27ec411eebe3a3e4419a1b3cc}

总结:一般来说,如果是简单的so层hook的话,直接套函数进行hook然后输出hexdump就可以拿到想要的返回值了,有点类似于动调的作用了。(因为本人电脑不知为何,so总是动调不起来,所以只能hook了)

如果要实现更灵活的hook,类似于修改hex的值或主动调用等功能还是需要继续学习继续进步!