H&NCTF 2025 WriteUp

5.6k 词

仅给出我个人解出的题目的WriteUp.

Crypto方向:

lcgp:

n-1光滑,先恢复c:

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

random_outputs = [
11250327355112956284720719987943941825496074893551827972877616718074592862130806975889275745497426515405562887727117008818863728803549848574821067056997423443681347885027000632462241968640893471352200125748453396098854283137158609264944692129301617338233670002547470932851350750870478630955328653729176440142198779254117385657086615711880537380965161180532127926250520546846863536247569437,
1289730679860726245234376434590068355673648326448223956572444944595048952808106413165882424967688302988257332835229651422892728384363094065438370663362237241013242843898967355558977974152917458085812489310623200114007728021151551927660975648884448177346441902806386690751359848832912607313329587047853601875294089502467524598036474193845319703759478494109845743765770254308199331552085163360820459311523382612948322756700518669154345145757700392164795583041949318636,
147853940073845086740348793965278392144198492906678575722238097853659884813579087132349845941828785238545905768867483183634111847434793587821166882679621234634787376562998606494582491550592596838027522285263597247798608351871499848571767008878373891341861704004755752362146031951465205665840079918938797056361771851047994530311215961536936283541887169156535180878864233663699607369701462321037824218572445283037132205269900255514050653933970174340553425147148993214797622395988788709572605943994223528210919230924346860415844639247799805670459,
7426988179463569301750073197586782838200202717435911385357661153208197570200804485303362695962843396307030986052311117232622043073376409347836815567322367321085387874196758434280075897513536063432730099103786733447352512984165432175254784494400699821500026196293994318206774720213317148132311223050562359314735977091536842516316149049281012797103790472349557847649282356393682360276814293256129426440381745354969522053841093229320186679875177247919985804406150542514337515002645320320069788390314900121917747534146857716743377658436154645197488134340819076585888700553005062311578963869641978771532330577371974731136,
10389979373355413148376869524987139791217158307590828693700943753512488757973725227850725013905113587408391654379552713436220790487026223039058296951420273907725324214990441639760825661323514381671141482079783647253661594138658677104054180912818864005556386671430082941396497098166887200556959866845325602873713813206312644590812141400536476615405444030140762980665885244721798105034497461675317071497925846844396796854201566038890503298824928152263774446268093725702310124363765630370263370678902342200494544961012407826314577564991676315451785987248633724138137813024481818431889574317602521878974976264742037227074
]
n = 604805773885048132038788501528078428693141138274580426531445179173412328238102786863592612653315029009606622583856638282837864213048342883583286440071990592001905867027978355755042060684149344414810835371740304319571184567860694439564098306766474576403800046937218588251809179787769286393579687694925268985445059

s1, s2, s3, s4, s5 = random_outputs
d1 = s2 - s1
d2 = s3 - s2
d3 = s4 - s3
d4 = s5 - s4
x = d2**2 - d1 * d3
y = d3**2 - d2 * d4
m = gcd(x, y)
inv_d1 = inverse(d1, m)
a = (d2 * inv_d1) % m
b = (s2 - a * s1) % m
inv_a = inverse(a, m)
c = (s1 - b) * inv_a % m
print("Recovered c:", c)

得到

1
c=98136663393066487319477131255488756533037186459124433869847045986870213783395243380337142782779765255670853582334927187474123853371504168896312528278296763527266828907487342102002206806408616944398694810398049626860321901229014612541564249969665358849039818103044159048535403863928440335143886672949700153798350

接下来求解:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from Crypto.Util.number import long_to_bytes

n = 604805773885048132038788501528078428693141138274580426531445179173412328238102786863592612653315029009606622583856638282837864213048342883583286440071990592001905867027978355755042060684149344414810835371740304319571184567860694439564098306766474576403800046937218588251809179787769286393579687694925268985445059
e = 2024
c = 98136663393066487319477131255488756533037186459124433869847045986870213783395243380337142782779765255670853582334927187474123853371504168896312528278296763527266828907487342102002206806408616944398694810398049626860321901229014612541564249969665358849039818103044159048535403863928440335143886672949700153798350

R = Zmod(n)
c_val = R(c)
e_val = R(e)

n_minus_1 = n - 1
factors = factor(n_minus_1)
print("Factorization of n-1:", factors)
d = discrete_log(c_val, e_val)
print("Discrete logarithm d:", d)
flag = long_to_bytes(d)
print("Flag:", flag)

H&NCTF{7ecf4c8c-e6a5-45c7-b7de-2fecc31d8511}

哈基coke:

逆向猫脸变换:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import cv2
import numpy as np

def arnold_decode(encrypted_image_path, shuffle_times, a, b):
encrypted_image = cv2.imread(encrypted_image_path)
h, w = encrypted_image.shape[:2]
N = h
decoded_image = np.zeros_like(encrypted_image)
for _ in range(shuffle_times):
temp_image = np.copy(decoded_image)
for x_prime in range(N):
for y_prime in range(N):
x = (( (a*b + 1) * x_prime - b * y_prime ) % N + N) % N
y = (( -a * x_prime + y_prime ) % N + N) % N
temp_image[x, y, :] = encrypted_image[x_prime, y_prime, :]
decoded_image = temp_image
encrypted_image = np.copy(decoded_image)

return decoded_image

if __name__ == "__main__":
decrypted_image = arnold_decode('en_flag.png', 6, 9, 1)
cv2.imwrite('decrypted_coke.png', decrypted_image, [int(cv2.IMWRITE_PNG_COMPRESSION), 0])

H&NCTF{haji_coke_you_win}

数据处理:

先求解intflag:

1
2
3
4
5
6
7
8
9
10
11
12
n = 0x100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
m = 0x61125b33959f20cbab41c14ff5b1825eb553e7d076098a003acd729163343437d6cbaa0271a61e69c1f3d054dfc85d4e47e568598ffe6a80642fff9d4ad8ff67
c = 0x39141869cbba69234a5619bb0766439c2e9f5b15a9009715ed3ce38a190466c6c0fbb274274ec3ae7d25a447938db11dd340038ad553bfd470496463974dcec7

R = Integers(n)
m = R(m)
c = R(c)

from sage.groups.generic import discrete_log
flag = discrete_log(c, m)

print("flag:", flag)

得到flag值为

1
3282248010524512146638712359816289396373430161050484501341123570760619381019795910712610762203934445754701

接下来爆破映射表:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import itertools
from Crypto.Util.number import *

lowercase = '0123456789'
intflag = '3282248010524512146638712359816289396373430161050484501341123570760619381019795910712610762203934445754701'
pattern = "7***4****5"
used_digits = {4, 5, 7}
available_digits = [d for d in range(10) if d not in used_digits]
for perm in itertools.permutations(available_digits):
result = []
idx = 0
for ch in pattern:
if ch == '*':
result.append(str(perm[idx]))
idx += 1
else:
result.append(ch)
uppercase = "".join(result)
table = ''.maketrans(uppercase, lowercase)
flag = intflag.translate(table)
bflag = long_to_bytes(int(flag))
if bflag.startswith(b"H&NCTF{"):
print(bflag)

H&NCTF{cut_cut_rrioajtfijrwegeriogjiireigji}

Reverse:

F**K:

查看主函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int __fastcall main(int argc, const char **argv, const char **envp)
{
char __dst[65]; // [xsp+2Eh] [xbp-162h] BYREF
char __s_[65]; // [xsp+6Fh] [xbp-121h] BYREF
char __b_[100]; // [xsp+B0h] [xbp-E0h] BYREF
char __b[100]; // [xsp+114h] [xbp-7Ch] BYREF

memset(__b, 0, sizeof(__b));
memset(__b_, 0, sizeof(__b_));
strcpy(__dst, "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/");
func1((__int64)__dst, __s_);
if ( fgets(__b, 0x64, __stdinp) )
{
__b[strcspn(__b, "\n")] = 0;
func2(__b, (__int64)__b_, (__int64)__s_);
}
while ( !byte_100008292 )
func3((__int64)__b_);
if ( !memcmp(num1, num2, 16 * num3) )
printf("\nCongratulations!\n");
else
printf("\nSomething Wrong.\n");
return 0;
}

其中func1是按时间生成随机数种子打乱base64表,func2是标准base64编码,func3是对明文的每4字节进行md5哈希后再进行一步简单的加密:

1
2
for ( n16 = 0; n16 < 16; ++n16 )
num2[16 * num3 + n16] = (7 * (num2[16 * num3 + n16] ^ (n16 + 6)) + 0x1234 * (n16 % 0xF)) % 256;

提取密文,先进行哈希爆破:

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
import hashlib
from itertools import product
from multiprocessing import Pool, cpu_count
from tqdm import tqdm

custom_table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"

def generate_all_b64_blocks(table):
return (''.join(p) for p in product(table, repeat=4))

def transform(md5_hash):
result = []
for n16 in range(16):
val = (7 * ((md5_hash[n16] ^ (n16 + 6)) & 0xFF) + 0x1234 * (n16 % 0xF)) % 256
result.append(val)
return bytes(result)

def process_block(block):
encoded_block = block.encode()
md5_hash = hashlib.md5(encoded_block).digest()
transformed = transform(md5_hash)
return (transformed, block)

def build_rainbow_table(custom_table):
blocks = list(generate_all_b64_blocks(custom_table))
total = len(blocks)
print(f"[*] Generating rainbow table with {total} entries...")

pool = Pool(cpu_count())
rainbow_dict = {}

with tqdm(total=total, desc="Building Rainbow Table", unit="block") as pbar:
for result in pool.imap_unordered(process_block, blocks, chunksize=10000):
key, value = result
rainbow_dict[key] = value
pbar.update()

pool.close()
pool.join()
print("Rainbow table built.")
return rainbow_dict

def decrypt_num1(num1_data, rainbow_table):
b64_string = ""
block_count = len(num1_data) // 16

for i in range(block_count):
start = i * 16
end = start + 16
block = num1_data[start:end]

if block in rainbow_table:
b64_string += rainbow_table[block]
else:
print(f"[!] Block {i} not found in rainbow table.")
b64_string += "????"

return b64_string

if __name__ == "__main__":
rainbow_table = build_rainbow_table(custom_table)
num1 = bytes.fromhex("8E681BB44AFA6C03C884467B469BE7BFE7F132B5DF3916FE3B8D902088D6BC040D5001699DE9EBEEEA63FE189D75014C59B1FF9363D8CE60FD211E4A5025F5F8968C3ABFD11318BD93C11088EAD50A7FD54A12DE52F0B1158938B76CB3374F8B795DA8FAD7ED6F1FF5F1C01B54BCF774DB4556CDC4E2A693FB097EF2235C915F9300E5F9278AADC17E18C6224BD7A6CA2F100A32105E59BEAE243E082A4DD1F56AFC5D84EAEB1B278352A0BB9DF40AA95530F1701653771B2C99179A70E24090C3B1EA924B3515145A30BF56306CF0304D5B097C74989E8872666C5C38A5760BA8EE7BF1B3AD582DBFA7331183467F1D1C9D861AC6D6B299CCC782ABED3A6B12A6F8BF1C3BEBDAE0610815018B2443CA154B1D9188BC5F9261370AA2D3309651AAA5D9505B823AAA1F77FF9CA64F2328E780088BE5CE1DFC0B6866BC5FFA44C25F0F0C861462D2F4A2E8CC9B274828AE5B76E8BCE03D8B844C29C8927FDC1EA680FF783FE1394BD0CCE013F7367CDD5C9602DCF9E9DD1887E9AC4326B3DF68C2FE3010B066DD046AFFD2FAE186EFA104BCADD1CE58A85D9D37517FCFF102BB4BB4161BBFE37105D41B5832E8592CCEE3D40B9E02F4A1343EF28FA39F7F24346E29246BFE8AB3E26C37E4EA7944F9A5285DCEAE7923862F9A00456E9F8711D10FE674BD7BE5812681EDD22A23E76F7D40D608EA781278B84F85E96C8B6DE78C14218A923FBA2D7968BEA20992D5B3FE7C4E5217E2AAEAECBD021D1C80AAF333BB65CBE51D96448F4E11696DA570984DBCD57CBF2C56131FF436B464FF520544023E15A0096912D151FF8A5A5BD0694B07678AE78F16A6401560E3DC628411085141B47ABA5A473429E776B1DAE3A829B679DAA5E2244113224667CB8DF684EC0EA8DE0E01AABFBC3AD00F8E658452BF43")
print(len(num1))
b64_string = decrypt_num1(num1, rainbow_table)
print("Recovered Base64 String:", b64_string)

得到base64:

1
YIfUIfUWfUW7UW7XW7XM7XMkXMk7Mk7rk7rr7rrTrrTcrTcmTcmncmnOmnOYnOYLOYLPYLPdLPd6Pd63d63v63vg3vg5vg5Sg5S55S5SS5S75S7BS7Bo7BojBojdojdOjdO/dO/lO/lr/lr/lr/1r/1d/1dt1dt6????

但是这个base64很明显是一位一位增加的(也就是源程序在进行哈希时一次只右移了一个字节),提取得到真正的base64密文:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
message = bytearray(b"YIfUIfUWfUW7UW7XW7XM7XMkXMk7Mk7rk7rr7rrTrrTcrTcmTcmncmnOmnOYnOYLOYLPYLPdLPd6Pd63d63v63vg3vg5vg5Sg5S55S5SS5S75S7BS7Bo7BojBojdojdOjdO/dO/lO/lr/lr/lr/1r/1d/1dt1dt6????")
output = bytearray(b"")

for i in range(0, len(message), 4):
if i == 0:
output += message[i:i+4]
else:
output.append(message[i+3])

output[-1] = b"="[0]

print(output)

gen = bytearray()
for i in range(len(output)-3):
gen += output[i:i+4]
print(message)
print(gen)

得到密文为YIfUW7XMk7rrTcmnOYLPd63vg5S5S7BojdO/lr/1dt6=,接下来编写脚本爆破解密:

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
#include <iostream>
#include <string>
#include <vector>
#include <thread>
#include <mutex>
#include <atomic>
#include <cmath>
#include <iomanip>
#include <sstream>

using namespace std;

// === 配置区域 ===
const string target_b64 = "YIfUW7XMk7rrTcmnOYLPd63vg5S5S7BojdO/lr/1dt6=";
const string known_prefix = "H&NCTF";
const size_t num_threads = thread::hardware_concurrency();
const uint64_t start_seed = 1700000000;
const uint64_t end_seed = 1800000000;

mutex result_mutex;
atomic<bool> found(false);

// === macOS 版本的 rand/srand 实现 ===
uint64_t seed;

void mac_srand(uint64_t s) {
seed = s;
}

int mac_rand() {
if (seed == 0)
seed = 123459876;
uint64_t hi = seed / 127773;
uint64_t lo = seed % 127773;
int64_t x = 16807 * lo - 2836 * hi;
if (x < 0)
x += 0x7fffffff;
return ((seed = x) % ((long)RAND_MAX + 1));
}

// === Base64 编码函数(使用自定义表)===
string custom_base64_encode(const string& data, const string& table) {
string encoded;
int i = 0;
int j = 0;
unsigned char char_array_3[3];
unsigned char char_array_4[4];

for (auto c : data) {
char_array_3[i++] = c;
if (i == 3) {
char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);
char_array_4[3] = char_array_3[2] & 0x3f;

for(i = 0; (i <4) ; i++)
encoded += table[char_array_4[i]];
i = 0;
}
}

if (i)
{
for(j = i; j < 3; j++)
char_array_3[j] = '\0';

char_array_4[0] = (char_array_3[0] & 0xfc) >> 2;
char_array_4[1] = ((char_array_3[0] & 0x03) << 4) + ((char_array_3[1] & 0xf0) >> 4);
char_array_4[2] = ((char_array_3[1] & 0x0f) << 2) + ((char_array_3[2] & 0xc0) >> 6);

for (j = 0; (j < i + 1); j++)
encoded += table[char_array_4[j]];

while((i++ < 3))
encoded += '=';
}

return encoded;
}

// === 根据 seed 生成打乱后的 Base64 表 ===
string generate_custom_table(uint64_t seed_candidate) {
string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";

mac_srand(seed_candidate);

// 第一次 rand()
int v7 = mac_rand() % 64;

// Shuffle 操作
int len = base64_chars.size();
for (int i = 0; i < 59; ++i) {
int v5 = i + v7;
if (v5 >= len) continue;
swap(base64_chars[i], base64_chars[v5]);
}

return base64_chars;
}

// === 检查当前 seed 是否匹配目标 Base64 ===
bool check_seed(uint64_t seed_candidate) {
string custom_table = generate_custom_table(seed_candidate);
string encoded = custom_base64_encode(known_prefix, custom_table);

if (target_b64.find(encoded) == 0) {
lock_guard<mutex> lock(result_mutex);
cout << "[+] Found matching seed: " << seed_candidate << endl;
cout << "[+] Custom Base64 Table:" << endl << custom_table << endl;
cout << "[+] Encoded prefix: " << encoded << endl;
found = true;
return true;
}
return false;
}

// === 线程任务函数 ===
void worker(uint64_t start, uint64_t end) {
for (uint64_t i = start; i <= end && !found; ++i) {
check_seed(i);
if (i % 100000 == 0) {
cout << "\r[*] Scanning: " << fixed << (double)(i - start_seed) / (end_seed - start_seed) * 100 << "%";
cout.flush();
}
}
}

// === 主函数 ===
int main() {
cout << "[*] Starting brute-force from " << start_seed << " to " << end_seed << endl;
cout << "[*] Target Base64: " << target_b64 << endl;
cout << "[*] Known prefix: " << known_prefix << endl;
cout << "[*] Using " << num_threads << " threads" << endl;

vector<thread> workers;
uint64_t range = (end_seed - start_seed) / num_threads;

for (size_t t = 0; t < num_threads; ++t) {
uint64_t start = start_seed + t * range;
uint64_t end = (t == num_threads - 1) ? end_seed : start + range - 1;
workers.emplace_back(worker, start, end);
}

for (auto& w : workers) {
w.join();
}

if (!found) {
cout << "[-] No matching seed found." << endl;
}

return 0;
}

得到表:GHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/EFABCD
H&NCTF{Ye5h!!!I_Lik333_bur9~^o^}

HNDRIVER:

查看用户态程序:

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
int __fastcall main(int argc, const char **argv, const char **envp)
{
void *v3; // rax
void *v4; // rax
void *v6; // rax
__int64 v7; // [rsp+30h] [rbp-348h]
_BYTE v8[32]; // [rsp+38h] [rbp-340h] BYREF
_BYTE v9[40]; // [rsp+58h] [rbp-320h] BYREF
_BYTE dst_[760]; // [rsp+80h] [rbp-2F8h] BYREF

if ( argc == 2 )
{
qmemcpy(
dst_,
L" _ _ _ _ _ __ ______ _____ _ _ ______ _ \n"
"| | | || \\ | || |/ /| ____|| __ \\ | \\ | || ____|| | \n"
"| |__| || \\| || ' / | |__ | |__) || \\| || |__ | | \n"
"| __ || . ` || < | __| | _ / | . ` || __| | | \n"
"| | | || |\\ || . \\ | |____ | | \\ \\ | |\\ || |____ | |____ \n"
"|_| |_||_| \\_||_|\\_\\|______||_| \\_\\|_| \\_||______||______|\n",
0x2DEu);
v6 = (void *)sub_140004110((__int64)&qword_14003F320, (__int64)dst_);
_CallMemberFunction0(v6, (void (__fastcall *)(void *))sub_1400044C0);
sub_140003550(v8, argv[1]);
v7 = std::shared_ptr<__ExceptionPtr>::operator=(v9, v8);
if ( (unsigned __int8)sub_140003210(v7) )
sub_140004110((__int64)&qword_14003F320, (__int64)L"[R3] Backup path sent to driver successfully.\n");
else
sub_140004110((__int64)&qword_14003F320, (__int64)L"[R3] Failed to send backup info to driver.\n");
std::wstring::~wstring(v8);
return 0;
}
else
{
v3 = (void *)sub_140004110(
(__int64)&qword_14003F320,
(__int64)L"here's gift for you: A+cRZ+ofCz4yqqnHClujCqD5DF3RCW8Ltlxju5NzCqdHY/Pcu5nXul+c0l+buBCLGRXjoMp"
"cG5ZM0lmFGRmFG5oFCW+cIYpi");
_CallMemberFunction0(v3, (void (__fastcall *)(void *))sub_1400044C0);
v4 = (void *)sub_140004110((__int64)&qword_14003F450, (__int64)L"Usage: coom.exe <BackUpFileName>");
_CallMemberFunction0(v4, (void (__fastcall *)(void *))sub_1400044C0);
return 1;
}
}

发现了一个base64密文,后续分析出整个程序只是把用户输入传给了驱动,分析驱动文件,发现了一个无xref的构造函数:

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
__int64 __fastcall sub_140002010(__int64 a1, __int64 a2)
{
NTSTATUS v3; // [rsp+80h] [rbp-188h]
NTSTATUS v4; // [rsp+80h] [rbp-188h]
NTSTATUS v5; // [rsp+80h] [rbp-188h]
PFLT_CONTEXT Context; // [rsp+88h] [rbp-180h] BYREF
int v7; // [rsp+90h] [rbp-178h]
unsigned int n0x100000; // [rsp+94h] [rbp-174h]
SIZE_T NumberOfBytes; // [rsp+98h] [rbp-170h]
ULONG BytesRead; // [rsp+A0h] [rbp-168h] BYREF
PVOID Buffer_1; // [rsp+A8h] [rbp-160h]
PFILE_OBJECT FileObject; // [rsp+B0h] [rbp-158h] BYREF
PFILE_OBJECT FileObject_; // [rsp+B8h] [rbp-150h] BYREF
union _LARGE_INTEGER FileSize; // [rsp+C0h] [rbp-148h] BYREF
PVOID P; // [rsp+C8h] [rbp-140h] BYREF
void *FileHandle; // [rsp+D0h] [rbp-138h] BYREF
HANDLE FileHandle_; // [rsp+D8h] [rbp-130h] BYREF
ULONG Length[2]; // [rsp+E0h] [rbp-128h]
SIZE_T NumberOfBytes_1; // [rsp+E8h] [rbp-120h]
int v20; // [rsp+F0h] [rbp-118h]
HANDLE n4; // [rsp+F8h] [rbp-110h]
PVOID Buffer; // [rsp+100h] [rbp-108h]
unsigned __int64 v23; // [rsp+108h] [rbp-100h] BYREF
struct _OBJECT_ATTRIBUTES ObjectAttributes; // [rsp+110h] [rbp-F8h] BYREF
struct _OBJECT_ATTRIBUTES buf_; // [rsp+140h] [rbp-C8h] BYREF
__int64 v26; // [rsp+170h] [rbp-98h]
struct _IO_STATUS_BLOCK IoStatusBlock; // [rsp+178h] [rbp-90h] BYREF
struct _IO_STATUS_BLOCK buf__1; // [rsp+188h] [rbp-80h] BYREF
__m128 v29[5]; // [rsp+1A0h] [rbp-68h] BYREF

Context = 0;
if ( KeGetCurrentIrql() < 2u )
{
n4 = PsGetCurrentProcessId();
if ( (unsigned int)n4 > 4
&& FltGetStreamHandleContext(*(PFLT_INSTANCE *)(a2 + 24), *(PFILE_OBJECT *)(a2 + 32), &Context) >= 0
&& Context )
{
if ( *(_QWORD *)Context && RtlPrefixUnicodeString(&String1, *(PCUNICODE_STRING *)Context, 1u) )
*((_DWORD *)Context + 13) = 2;
*((_BYTE *)Context + 50) = 1;
if ( p_Length )
{
if ( *(_QWORD *)Context && RtlSuffixUnicodeString(p_Length, *(PCUNICODE_STRING *)Context, 1u) )
{
DbgPrintEx(0x4Du, 0, "try backup %wZ \n", *(_QWORD *)Context);
FsRtlGetFileSize(*(PFILE_OBJECT *)(a2 + 32), &FileSize);
DbgPrintEx(0x4Du, 0, "file size: %llu\n", FileSize.QuadPart);
if ( !FileSize.QuadPart )
return 0;
FileHandle = 0;
FileObject = 0;
memset(&ObjectAttributes, 0, sizeof(ObjectAttributes));
ObjectAttributes.Length = 48;
ObjectAttributes.RootDirectory = 0;
ObjectAttributes.Attributes = 576;
ObjectAttributes.ObjectName = *(PUNICODE_STRING *)Context;
ObjectAttributes.SecurityDescriptor = 0;
ObjectAttributes.SecurityQualityOfService = 0;
memset(&IoStatusBlock, 0, sizeof(IoStatusBlock));
FileHandle_ = 0;
FileObject_ = 0;
memset(&buf_, 0, sizeof(buf_));
buf_.Length = 48;
buf_.RootDirectory = 0;
buf_.Attributes = 576;
buf_.ObjectName = (PUNICODE_STRING)::P;
buf_.SecurityDescriptor = 0;
buf_.SecurityQualityOfService = 0;
memset(&buf__1, 0, sizeof(buf__1));
v26 = 0;
n0x100000 = 0x100000;
BytesRead = 0;
v20 = 0;
Buffer_1 = 0;
NumberOfBytes = FileSize.QuadPart;
Buffer_1 = ExAllocatePool(PagedPool, FileSize.QuadPart);
if ( Buffer_1 )
{
v3 = FltCreateFileEx(
*(PFLT_FILTER *)(a2 + 8),
*(PFLT_INSTANCE *)(a2 + 24),
&FileHandle,
&FileObject,
0x80000000,
&ObjectAttributes,
&IoStatusBlock,
0,
0x80u,
3u,
1u,
0x24u,
0,
0,
0x800u);
if ( v3 >= 0 )
{
v4 = FltCreateFileEx(
*(PFLT_FILTER *)(a2 + 8),
*(PFLT_INSTANCE *)(a2 + 24),
&FileHandle_,
&FileObject_,
0x40000000u,
&buf_,
&buf__1,
0,
0x80u,
0,
5u,
0x24u,
0,
0,
0);
if ( v4 >= 0 )
{
while ( NumberOfBytes )
{
Buffer = Buffer_1;
if ( n0x100000 >= NumberOfBytes )
*(_QWORD *)Length = NumberOfBytes;
else
*(_QWORD *)Length = n0x100000;
v5 = FltReadFile(*(PFLT_INSTANCE *)(a2 + 24), FileObject, 0, Length[0], Buffer, 0, &BytesRead, 0, 0);
if ( v5 < 0 )
{
_mm_lfence();
DbgPrintEx(0x4Du, 0, "[%s:%d] status: 0x%x\n", "MINIFILTER::PostWriteOperation", 334, v5);
break;
}
sub_140001894(v29);
P = 0;
v23 = 0;
if ( n0x100000 >= NumberOfBytes )
NumberOfBytes_1 = NumberOfBytes;
else
NumberOfBytes_1 = n0x100000;
v7 = sub_1400010C0((__int64)Buffer_1, NumberOfBytes_1, &P, &v23, (__int64)v29);
if ( v7 < 0 )
{
_mm_lfence();
DbgPrintEx(0x4Du, 0, "[%s:%d] status: 0x%x\n", "MINIFILTER::PostWriteOperation", 342, v7);
break;
}
v7 = FltWriteFile(*(PFLT_INSTANCE *)(a2 + 24), FileObject_, 0, BytesRead, P, 0, 0, 0, 0);
ExFreePoolWithTag(P, 0);
if ( v7 < 0 )
{
DbgPrintEx(0x4Du, 0, "[%s:%d] status: 0x%x\n", "MINIFILTER::PostWriteOperation", 347, v7);
break;
}
NumberOfBytes -= BytesRead;
}
}
else
{
_mm_lfence();
DbgPrintEx(0x4Du, 0, "[%s:%d] status: 0x%x\n", "MINIFILTER::PostWriteOperation", 329, v4);
}
}
else
{
_mm_lfence();
DbgPrintEx(0x4Du, 0, "[%s:%d] status: 0x%x\n", "MINIFILTER::PostWriteOperation", 324, v3);
}
}
if ( Buffer_1 )
ExFreePoolWithTag(Buffer_1, 0);
if ( FileHandle )
FltClose(FileHandle);
if ( FileHandle_ )
FltClose(FileHandle_);
if ( FileObject )
ObfDereferenceObject(FileObject);
if ( FileObject_ )
ObfDereferenceObject(FileObject_);
}
}
}
}
if ( Context )
FltReleaseContext(Context);
return 0;
}

其中v7 = sub_1400010C0((__int64)Buffer_1, NumberOfBytes_1, &P, &v23, (__int64)v29);是一个接受自定义base64表的base64编码函数,base64表v29由上文的sub_140001894(v29);生成,查看这个函数:

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
__int64 __fastcall sub_140001894(__m128 *a1)
{
__int64 n64; // rax
__int8 v2; // [rsp+20h] [rbp-28h]
int n63; // [rsp+24h] [rbp-24h]
unsigned int i; // [rsp+28h] [rbp-20h]
unsigned int i_4; // [rsp+2Ch] [rbp-1Ch]
__m128 *v6; // [rsp+38h] [rbp-10h]

v6 = (__m128 *)sub_1400010A4();
sub_140003640(a1, v6, 0x40u);
i = 20250603;
for ( n63 = 63; n63 > 0; --n63 )
{
i = sub_140001964(i);
i_4 = i % (n63 + 1);
v2 = a1->m128_i8[n63];
a1->m128_i8[n63] = a1->m128_i8[i_4];
a1->m128_i8[i_4] = v2;
}
n64 = 64;
a1[4].m128_i8[0] = 0;
return n64;
}
_BYTE *sub_1400010A4()
{
return sub_1400019E4((__int64)byte_1400042D0);
}
_BYTE *__fastcall sub_1400019E4(__int64 a1)
{
unsigned __int64 n0x41; // [rsp+20h] [rbp-18h]

for ( n0x41 = 0; n0x41 < 0x41; ++n0x41 )
{
_mm_lfence();
byte_140005060[n0x41] = sub_140001A4C(*(_BYTE *)(n0x41 + a1), n0x41);
}
return byte_140005060;
}
__int64 __fastcall sub_140001A4C(char a1, unsigned __int64 n0x41)
{
return (char)n0x41 ^ byte_1400042C0[n0x41 % 8] ^ (unsigned int)a1;
}
int __fastcall sub_140001964(__int64 n20250603)
{
return (0x41C64E6D * n20250603 + 114514) & 0x7FFFFFFF;
}

提取密文和密钥进行同构:

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
byte_1400042C0 = bytes([0x51, 0x23, 0x97, 0xE8, 0xDC, 0xBA, 0x45, 0x67,])

byte_1400042D0 = bytes([
0x7A, 0x14, 0xDE, 0xD3, 0xB0, 0xC9, 0x0D, 0x17,
0x1C, 0x45, 0xD0, 0x86, 0x94, 0xDD, 0x13, 0x39,
0x14, 0x61, 0xD7, 0xC8, 0xB9, 0x9F, 0x37, 0x2A,
0x10, 0x4F, 0xFD, 0x94, 0x81, 0x96, 0x6C, 0x3E,
0x03, 0x56, 0x8C, 0xFF, 0xB7, 0xEC, 0x21, 0x39,
0x2E, 0x70, 0xD6, 0xF1, 0xB3, 0xF1, 0x27, 0x2B,
0x4E, 0x55, 0xD1, 0x91, 0xBE, 0xC6, 0x23, 0x32,
0x08, 0x62, 0xC3, 0xBF, 0x8D, 0xCF, 0x12, 0x6D])

def xor_encrypt(a1: int, n0x41: int) -> int:
key = byte_1400042C0[n0x41 % 8]
return (n0x41 ^ key ^ a1) & 0xFF

def generate_encoded_table(initial_data):
encoded_table = bytearray(0x40)
for i in range(0x40):
a1 = initial_data[i]
encoded_table[i] = xor_encrypt(a1, i)
return encoded_table

def lcg(seed):
while True:
seed = (0x41C64E6D * seed + 114514) & 0x7FFFFFFF
yield seed

def shuffle_table(seed):
prng = lcg(seed)
table = list(generate_encoded_table(byte_1400042D0))[:64]
for i in range(63, 0, -1):
j = next(prng) % (i + 1)
table[i], table[j] = table[j], table[i]
return bytes(table)

custom_base64_table = shuffle_table(20250603)

print("Custom Base64 Table:")
print(custom_base64_table)

得到自定义base64表:idhR+nWSPOU0CGIrNmAqVZlYuo2sDt7yg6MBXF1aw4Kv9LHJkjb5p8/zxcefQ3ET
解密得到flag:HNCTF{3z_M1n1f1173r_C0mmun1c4710n_b9c1daa9-a2b3-491f-975b-de44e76e0a99},改为H&NCTF{3z_M1n1f1173r_C0mmun1c4710n_b9c1daa9-a2b3-491f-975b-de44e76e0a99}即可

justgame:

damctf 2025原题Is it data or data?,只改了密文:

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
void __fastcall __noreturn main(int a1, char **a2, char **a3)
{
int n7; // r13d
const char *target; // rcx
char *s1; // rdx
char n100; // al
const char *p_n15_1; // rsi
char *p_n15; // rdx
__int64 n9; // r13
unsigned __int64 i; // rax
char s1_[264]; // [rsp+0h] [rbp-138h] BYREF
unsigned __int64 v12; // [rsp+108h] [rbp-30h]

v12 = __readfsqword(0x28u);
__printf_chk(1, "please input number/n", a3);
n7 = ::n7;
while ( 1 )
{
if ( n7 == 7 )
{
p_n15 = s2;
n9 = ::n9;
i = 15;
if ( s2 != (char *)&n15 )
i = n15;
if ( ::n9 + 1LL > i )
{
std::string::_M_mutate(&s2, ::n9, 0, 0, 1);
p_n15 = s2;
}
p_n15[n9] = 'g';
::n9 = n9 + 1;
s2[n9 + 1] = 0;
}
else
{
while ( !sub_555555402020() )
;
}
target = "dfrghumrxuqh|";
s1 = s1_;
n7 = ::n7 + 1;
n100 = 'd';
++::n7;
do
{
++s1;
++target;
*(s1 - 1) = n100 - 3;
n100 = *target;
}
while ( *target );
p_n15_1 = s2;
*s1 = 0;
if ( !strcmp(s1_, p_n15_1) )
sub_555555403720();
}
}

main函数仍为等待输入检验和目标值是否相同,以及在第15步会自动插入一个字符g,查看输入接收函数:

1
2
3
4
5
6
7
8
9
输入值/控制函数	作用
1 将用户输入的另一个数字作为 ASCII 值插入到字符串尾部
4 写入字符“f”
5 对最后一个字符-1
6 对最后一个字符+3
7 写入字符“a”
11 将最后一个字符改为“t”
13 删除最后一个字符
第8步 写入字符“g”

密文dfrghumrxuqh|有一个自解密,调试得到acoderjourney,构造payload就完了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
target = b"acoderjourney"
payload = "["

for i in range(len(target)):
cand = ord("a")
payload += "'7', "
while not cand == target[i]:
if cand < target[i]:
cand += 3
payload += "'6', "
else:
cand -= 1
payload += "'5', "
print(bytes([cand]))
payload += "]"
print(payload)

得到payload['7', '7', '6', '5', '7', '6', '6', '6', '6', '6', '5', '7', '6', '7', '6', '6', '5', '5', '7', '6', '6', '6', '6', '6', '6', '5', '7', '6', '6', '6', '7', '6', '6', '6', '6', '6', '5', '7', '6', '6', '6', '6', '6', '6', '6', '5', '7', '6', '6', '6', '6', '6', '6', '5', '7', '6', '6', '6', '6', '6', '5', '5', '7', '6', '6', '5', '5', '7', '6', '6', '6', '6', '6', '6', '6', '6'],注意还要插入一个13来删掉字母g,编写脚本提交payload:

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
from pwn import *

host = "27.25.151.198"
port = 38214

payloads = ['7', '7', '6', '5', '7', '6', '6', '13', '6', '6', '6', '5', '7', '6', '7', '6', '6', '5', '5', '7', '6', '6', '6', '6', '6', '6', '5', '7', '6', '6', '6', '7', '6', '6', '6', '6', '6', '5','7', '6', '6', '6', '6', '6', '6', '6', '5', '7', '6', '6', '6', '6', '6', '6', '5', '7', '6', '6', '6', '6', '6', '5', '5', '7', '6', '6', '5', '5', '7', '6', '6', '6', '6', '6', '6', '6', '6']

conn = remote(host, port)

initial = conn.recvuntil(b">", timeout=5)
# print(initial.decode())

for i, payload in enumerate(payloads):
print(f"[+] Sending: {payload}")
conn.sendline(payload.encode())

if i < len(payloads) - 1:
#print("[+] Waiting for '>' prompt...")
response = conn.recvuntil(b">", timeout=5)
else:
final_output = conn.recvall(timeout=5).decode()
print("\n[+] Final output: ")
print(final_output)
break
conn.close()

flag{586cee19-8bf4-4027-8bf3-1fd6fa74be92}

xxxR01d:

查看mainctivity:

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
package com.aaron.xxxr01d;

import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
import android.view.View.OnClickListener;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import com.aaron.nativelib.NativeLib;
import java.nio.charset.StandardCharsets;

public class MainActivity extends Activity {
private static final byte[] FIXED_IV = null;
private static byte[] FIXED_KEY = null;
private static final String TAG = "MainActivity";
private Button buttonCheck;
private EditText editTextUserInput;
private NativeLib nativeInterface;

System.loadLibrary("Z1Y4");
MainActivity.FIXED_KEY = new byte[]{84, 104, 105, 0x73, 73, 0x73, 65, 49, 54, 66, 0x79, 0x74, 101, 75, 101, 0x79};
MainActivity.FIXED_IV = new byte[]{49, 50, 51, 52, 53, 54, 55, 56, 57, 0x30, 65, 66, 67, 68, 69, 70};

private void C73ck19unt() {
String s = this.editTextUserInput.getText().toString();
if(s.trim().isEmpty()) {
Toast.makeText(this, "不能为空哦!", 0).show();
this.editTextUserInput.setError("此字段不能为空");
return;
}

if(s.trim().length() < 16) {
Toast.makeText(this, "输入内容长度不能小于16位!", 0).show();
this.editTextUserInput.setError("长度至少为16位");
return;
}

byte[] arr_b = s.getBytes(StandardCharsets.UTF_8);
byte[] arr_b1 = this.nativeInterface.Tungtungtungsahur(MainActivity.FIXED_KEY, MainActivity.FIXED_IV, arr_b);
if(arr_b1 == null) {
Toast.makeText(this, "加密失败!", 0).show();
this.editTextUserInput.setError("加密失败");
return;
}

Log.d("MainActivity", "加密后数据 (bytes hex): " + MainActivity.bytesToHex(arr_b1));
if(this.nativeInterface.validateEncryptedData(arr_b, arr_b1)) {
Toast.makeText(this, "校验成功! flag-> " + s, 1).show();
this.editTextUserInput.setError(null);
return;
}

Toast.makeText(this, "校验失败! 输入与解密结果不匹配。", 1).show();
this.editTextUserInput.setError("校验失败");
}

public static String bytesToHex(byte[] arr_b) {
if(arr_b == null) {
return "null";
}

StringBuilder stringBuilder0 = new StringBuilder();
for(int v = 0; v < arr_b.length; ++v) {
stringBuilder0.append(String.format("%02X", ((byte)arr_b[v])));
}

return stringBuilder0.toString();
}

@Override // android.app.Activity
protected void onCreate(Bundle bundle0) {
super.onCreate(bundle0);
this.setContentView(layout.activity_main);
this.buttonCheck = (Button) this.findViewById(id.buttonCheck);
this.editTextUserInput = (EditText) this.findViewById(id.editTextUserInput);
MainActivity.FIXED_KEY = new byte[]{87, 101, 49, 99, 0x30, 109, 101, 0x5F, 52, 0x5F, 72, 38, 78, 67, 84, 70};
this.nativeInterface = new NativeLib();
this.buttonCheck.setOnClickListener(v -> {
MainActivity.this.C73ck19unt();
});
}

class com.aaron.xxxr01d.MainActivity.1 implements View.OnClickListener {
final MainActivity this$0;
@Override // android.view.View$OnClickListener
public void onClick(View view0) {
MainActivity.access$000(MainActivity.this);
}
}

}
}

发现初始化了一个密钥:ThisIsA16ByteKey和一个iv:1234567890ABCDEF,但是oncreate中又用了We1c0me_4_H&NCTF作为key,加密函数为Tungtungtungsahur,验证函数为validateEncryptedData,均为native方法,查看so层,发现有很多花指令,编写脚本去除:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from idc import *
from ida_bytes import *

start_addr = 0x13480
end_addr = 0x16310

pattern = [0x74, 0x01, 0x00, 0x0F, 0xBE]
length = len(pattern)

found_count = 0

for addr in range(start_addr, end_addr - length + 1):
bytes_here = [get_wide_byte(addr + i) for i in range(length)]
if bytes_here == pattern:
original_byte = get_wide_byte(addr + 2)
patch_byte(addr + 2, 0x90)
found_count += 1

去除后反编译函数发现是一个复杂的加密算法,查看validateEncryptedData:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
__int64 __fastcall Java_com_aaron_nativelib_NativeLib_validateEncryptedData(
__int64 a1,
__int64 a2,
__int64 a3,
__int64 a4)
{
unsigned int v4; // ebp
__int64 v8; // r12
const __m128i *v9; // r13
__m128i v10; // xmm1

v8 = (*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1472LL))(a1, a3, 0LL);
(*(void (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1368LL))(a1, a3);
v9 = (const __m128i *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1472LL))(a1, a4, 0LL);
(*(void (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1368LL))(a1, a4);
v10 = _mm_or_si128(
_mm_xor_si128(_mm_loadu_si128(v9), *(__m128i *)byte_7910),
_mm_xor_si128(_mm_loadu_si128(v9 + 1), *(__m128i *)byte_79A0));
LOBYTE(v4) = _mm_testz_si128(v10, v10);
(*(void (__fastcall **)(__int64, __int64, __int64, __int64))(*(_QWORD *)a1 + 1536LL))(a1, a3, v8, 2LL);
(*(void (__fastcall **)(__int64, __int64, const __m128i *, __int64))(*(_QWORD *)a1 + 1536LL))(a1, a4, v9, 2LL);
return v4;
}

发现存储了两部分的密文:791079A0,提取得到3205ACCD2A7471916010989C15288D8E18EC2A88F1351C46D9E38DFFF293426F,接着寻找加密函数,发现是动态注册的,找到地址:

1
2
3
4
5
6
7
8
.data.rel.ro:0000000000036CE0 off_36CE0       dq offset aTungtungtungsa
.data.rel.ro:0000000000036CE0 ; DATA XREF: JNI_OnLoad+55↑o
.data.rel.ro:0000000000036CE0 ; "Tungtungtungsahur"
.data.rel.ro:0000000000036CE8 dq offset aBBBB ; "([B[B[B)[B"
.data.rel.ro:0000000000036CF0 dq offset sub_12A10
.data.rel.ro:0000000000036CF8 dq offset aTralalerotrala ; "Tralalerotralala"
.data.rel.ro:0000000000036D00 dq offset aBBBB ; "([B[B[B)[B"
.data.rel.ro:0000000000036D08 dq offset sub_12C70

查看这两个函数,发现其分别是加密和解密函数:

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
__int64 __fastcall sub_12A10(__int64 a1, __int64 a2, __int64 a3, __int64 a4, __int64 n)
{
int v9; // r13d
__int64 v10; // rbp
const void *src; // r12
__int64 v12; // rax
unsigned int v13; // r13d
__int64 v14; // rbp
void *ptr_1; // rdi
__int64 n_1; // [rsp+8h] [rbp-70h]
__int64 v18; // [rsp+10h] [rbp-68h]
__int64 v19; // [rsp+18h] [rbp-60h]
__int64 v20; // [rsp+20h] [rbp-58h]
__int64 v21; // [rsp+28h] [rbp-50h]
void *ptr; // [rsp+30h] [rbp-48h] BYREF
__int64 v23[8]; // [rsp+38h] [rbp-40h] BYREF

v23[1] = __readfsqword(0x28u);
v21 = (*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1472LL))(a1, a3, 0LL);
v20 = a3;
v9 = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1368LL))(a1, a3);
v10 = (*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1472LL))(a1, a4, 0LL);
v19 = a4;
LODWORD(a4) = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1368LL))(a1, a4);
src = (const void *)(*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)a1 + 1472LL))(a1, n, 0LL);
n_1 = n;
LODWORD(n) = (*(__int64 (__fastcall **)(__int64, __int64))(*(_QWORD *)a1 + 1368LL))(a1, n);
v18 = v10;
sub_16830(v23, v21, v9, v10, (int)a4);
ptr = 0LL;
__android_log_print(4LL, "NativeLib", "Input data length: %zu", (int)n);
v12 = sub_16C50(v23, src, &ptr, (int)n);
v13 = v12;
if ( !v12 || !ptr )
{
__android_log_print(6LL, "NativeLib", "Encryption failed. Encode returned %zu, cEncryptedData is %p", v12, ptr);
ptr_1 = ptr;
v14 = 0LL;
if ( !ptr )
goto LABEL_7;
LABEL_6:
j_j__free(ptr_1);
goto LABEL_7;
}
__android_log_print(4LL, "NativeLib", "Encryption successful. Encrypted data length: %zu", v12);
v14 = (*(__int64 (__fastcall **)(__int64, _QWORD))(*(_QWORD *)a1 + 1408LL))(a1, v13);
if ( v14 )
(*(void (__fastcall **)(__int64, __int64, _QWORD, _QWORD, void *))(*(_QWORD *)a1 + 1664LL))(a1, v14, 0LL, v13, ptr);
else
__android_log_print(6LL, "NativeLib", "Encrypt: Failed to allocate NewByteArray for result.");
ptr_1 = ptr;
if ( ptr )
goto LABEL_6;
LABEL_7:
(*(void (__fastcall **)(__int64, __int64, __int64, __int64))(*(_QWORD *)a1 + 1536LL))(a1, v20, v21, 2LL);
(*(void (__fastcall **)(__int64, __int64, __int64, __int64))(*(_QWORD *)a1 + 1536LL))(a1, v19, v18, 2LL);
(*(void (__fastcall **)(__int64, __int64, const void *, __int64))(*(_QWORD *)a1 + 1536LL))(a1, n_1, src, 2LL);
sub_16BF0(v23);
return v14;
}

按说把加密函数hook成解密函数就行,但是这题的init_array有反hook:

1
2
3
4
5
6
.init_array:0000000000039010 _init_array     segment qword public 'DATA' use64
.init_array:0000000000039010 assume cs:_init_array
.init_array:0000000000039010 ;org 39010h
.init_array:0000000000039010 dq offset sub_133E0
.init_array:0000000000039018 dq offset sub_13480
.init_array:0000000000039018 _init_array ends
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
unsigned __int64 sub_133E0()
{
pthread_t newthread_[3]; // [rsp+0h] [rbp-18h] BYREF

newthread_[1] = __readfsqword(0x28u);
__android_log_print(4LL, "CHECK", "START CHECK MAPS");
sub_13030();
__android_log_print(4LL, "CHECK", "START PTHREAD");
if ( pthread_create(newthread_, 0LL, start_routine, 0LL) )
{
perror("Failed to create thread");
exit(1);
}
return __readfsqword(0x28u);
}
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
__int64 sub_13030()
{
FILE *stream_1; // rax
FILE *stream; // r14
char *p_needle; // rbx
unsigned int v3; // ebp
bool v4; // zf
char haystack_[512]; // [rsp+0h] [rbp-238h] BYREF
unsigned __int64 v7; // [rsp+200h] [rbp-38h]

v7 = __readfsqword(0x28u);
stream_1 = fopen("/proc/self/maps", "r");
if ( stream_1 )
{
stream = stream_1;
if ( fgets(haystack_, 512, stream_1) )
{
p_needle = needle;
v3 = 0;
do
{
if ( needle[0] )
{
do
{
if ( strstr(haystack_, p_needle) )
{
__android_log_print(4LL, "CHECK", "Find:%s", p_needle);
LOBYTE(v3) = 1;
}
v4 = p_needle[512] == 0;
p_needle += 512;
}
while ( !v4 );
}
p_needle = needle;
}
while ( fgets(haystack_, 512, stream) );
}
else
{
v3 = 0;
}
fclose(stream);
}
else
{
return 0;
}
return v3;
}
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
void __fastcall __noreturn start_routine(void *a1)
{
char *inp_1; // r15
int fd_1; // ebx
int fd_2; // edi
int fd_3; // eax
struct in_addr *inp; // rbx
FILE *stream_1; // rax
FILE *stream; // r15
int fd_4; // eax
int fd; // [rsp+Ch] [rbp-24Ch]
struct sockaddr addr; // [rsp+10h] [rbp-248h] BYREF
char s[568]; // [rsp+20h] [rbp-238h] BYREF

inp_1 = &addr.sa_data[2];
*(_DWORD *)&addr.sa_data[10] = 0;
*(_QWORD *)&addr.sa_data[2] = 0LL;
*(_DWORD *)&addr.sa_family = -1570177022;
inet_aton("127.0.0.1", (struct in_addr *)&addr.sa_data[2]);
fd_1 = socket(2, 1, 0);
if ( connect(fd_1, &addr, 0x10u) )
{
fd = fd_1;
*(_WORD *)addr.sa_data = -30115;
inet_aton("127.0.0.1", (struct in_addr *)&addr.sa_data[2]);
fd_3 = socket(2, 1, 0);
if ( !connect(fd_3, &addr, 0x10u) )
{
LABEL_4:
fd_2 = fd;
goto LABEL_5;
}
while ( 1 )
{
inp = (struct in_addr *)inp_1;
stream_1 = fopen("/proc/self/maps", "r");
if ( stream_1 )
{
stream = stream_1;
while ( fgets(s, 512, stream) )
{
if ( strstr(s, "frida")
|| strstr(s, "gadget")
|| strstr(s, "agent")
|| strstr(s, "/data/local/tmp/")
|| strstr(s, "-64.so")
|| strstr(s, "-32.so") )
{
goto LABEL_6;
}
}
fclose(stream);
}
sleep(1u);
inp_1 = (char *)inp;
inp[2].s_addr = 0;
*(_QWORD *)&inp->s_addr = 0LL;
*(_DWORD *)&addr.sa_family = -1570177022;
inet_aton("127.0.0.1", inp);
fd = socket(2, 1, 0);
if ( !connect(fd, &addr, 0x10u) )
break;
*(_WORD *)addr.sa_data = -30115;
inet_aton("127.0.0.1", inp);
fd_4 = socket(2, 1, 0);
if ( !connect(fd_4, &addr, 0x10u) )
goto LABEL_4;
}
fd_1 = fd;
}
__android_log_print(4LL, "CHECK", "FIND PORT");
fd_2 = fd_1;
LABEL_5:
close(fd_2);
LABEL_6:
exit(0);
}

尝试绕过并调用tralalero tralala解密:

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
function hexToBytes(hex) {
for (var bytes = [], c = 0; c < hex.length; c += 2)
bytes.push(parseInt(hex.substr(c, 2), 16));
return bytes;
}

function bytesToHex(bytes) {
return Array.prototype.map.call(new Uint8Array(bytes), function (b) {
return ('0' + b.toString(16)).slice(-2);
}).join('').toUpperCase();
}

var cipherHex = "3205ACCD2A7471916010989C15288D8E18EC2A88F1351C46D9E38DFFF293426F";
var cipherBytes = hexToBytes(cipherHex);

var libName = "libZ1Y4.so";

Module.findExportByName(libName, "sub_13030") && Interceptor.attach(Module.findExportByName(libName, "sub_13030"), {
onLeave: function(retval) {
retval.replace(0);
}
});

Module.findExportByName(libName, "start_routine") && Interceptor.replace(Module.findExportByName(libName, "start_routine"), new NativeCallback(function(arg0) {
return;
}, 'void', ['pointer']));

var fgetsPtr = Module.findExportByName("libc.so", "fgets");
if (fgetsPtr) {
Interceptor.attach(fgetsPtr, {
onEnter: function(args) {
this.buf = args[0];
this.size = args[1];
this.stream = args[2];
},
onLeave: function(retval) {
if (retval.toInt32() !== 0) {
var bufStr = Memory.readUtf8String(this.buf);
if (bufStr && (
bufStr.includes("frida") ||
bufStr.includes("gadget") ||
bufStr.includes("agent") ||
bufStr.includes("/data/local/tmp/") ||
bufStr.includes("-64.so") ||
bufStr.includes("-32.so")
)) {
Memory.writeUtf8String(this.buf, "sth");
}
}
}
});
}

var connectPtr = Module.findExportByName("libc.so", "connect");
if (connectPtr) {
Interceptor.attach(connectPtr, {
onEnter: function(args) {
var addr = args[1];
var port = Memory.readU16(addr.add(2));
if (port === 0x69A6 || port === 0x6A6A) {
this.block = true;
}
},
onLeave: function(retval) {
if (this.block) {
retval.replace(-1);
}
}
});
}

Java.perform(function () {
var EditText = Java.use("android.widget.EditText");
var SpannableStringBuilder = Java.use("android.text.SpannableStringBuilder");
EditText.getText.overload().implementation = function () {
var fakeInput = Java.use("java.lang.String").$new("sth");
return SpannableStringBuilder.$new.overload('java.lang.CharSequence').call(SpannableStringBuilder, fakeInput);
};
EditText.toString.overload().implementation = function () {
return Java.use("java.lang.String").$new("sth");
};
});

Java.perform(function () {
var MainActivity = Java.use("com.aaron.xxxr01d.MainActivity");
var Toast = Java.use("android.widget.Toast");
MainActivity.C73ck19unt.implementation = function () {
console.log("[+] Hooked C73ck19unt(), using fixed ciphertext as input");
var arr_b = cipherBytes;
var nativeInterface = this.nativeInterface.value;
var decryptedArr = nativeInterface.Tralalerotralala.overload('[B', '[B', '[B').call(nativeInterface,
MainActivity.FIXED_KEY.value,
MainActivity.FIXED_IV.value,
arr_b
);
if (decryptedArr == null) {
Toast.makeText(this, "解密失败", Toast.LENGTH_SHORT).show();
return;
}
var decryptedFlag = Java.use("java.lang.String").$new(decryptedArr);
console.log("[+] 解密后的 flag 是:", decryptedFlag);
console.log("[DEBUG] 解密结果 (hex):", bytesToHex(decryptedArr));
Toast.makeText(this, "flag: " + decryptedFlag, Toast.LENGTH_LONG).show();
if (this.nativeInterface.value.validateEncryptedData(arr_b, decryptedArr)) {
Toast.makeText(this, "校验成功!", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(this, "校验失败!", Toast.LENGTH_SHORT).show();
}
};
});

H&NCTF{Xxx_1s_And_F0r_Android^^}

签到re:

查看程序主函数:

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
__int64 __fastcall main(int a1, char **a2, char **a3)
{
void *ptr; // [rsp+0h] [rbp-50h] BYREF
unsigned int v5; // [rsp+Ch] [rbp-44h]
char s[40]; // [rsp+10h] [rbp-40h] BYREF
int v7; // [rsp+38h] [rbp-18h]
unsigned int v8; // [rsp+3Ch] [rbp-14h]
const char *MySecretKey123; // [rsp+40h] [rbp-10h]
int i; // [rsp+4Ch] [rbp-4h]

MySecretKey123 = "MySecretKey123!"; // 密钥
fgets(s, 38, stdin);
v8 = strlen(s);
v5 = sub_11B9(MySecretKey123);
v7 = sub_1452(s, v8, v5, &ptr);
putchar(10);
for ( i = 0; i < v7; ++i )
{
if ( *((_BYTE *)ptr + i) != byte_4080[i] ) // 密文
{
puts("wrong");
free(ptr);
return 0xFFFFFFFFLL;
}
}
puts("right");
free(ptr);
return 0;
}

查看sub_11B9:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
__int64 __fastcall sub_11B9(const char *MySecretKey123)
{
size_t v1; // rax
unsigned int v3; // [rsp+1Ch] [rbp-24h]
char v4; // [rsp+20h] [rbp-20h] BYREF
__int16 v5; // [rsp+21h] [rbp-1Fh]
char v6; // [rsp+23h] [rbp-1Dh]

v1 = strlen(MySecretKey123);
SHA256(MySecretKey123, v1, &v4);
LOBYTE(v3) = v4 | 1;
*(_WORD *)((char *)&v3 + 1) = v5 & 0xFEFE;
HIBYTE(v3) = v6 | 1;
return v3;
}

是取了硬编码密钥字符串生成了一个新的密钥,动态调试得到密钥51h, 16h, 34h, FBh,查看加密函数:

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
__int64 __fastcall sub_1452(const void *p_s, int n, int a3, __int64 p_ptr)
{
_BYTE v7[2]; // [rsp+20h] [rbp-20h] BYREF
_BYTE v8[2]; // [rsp+22h] [rbp-1Eh] BYREF
unsigned __int8 v9[2]; // [rsp+24h] [rbp-1Ch] BYREF
unsigned __int8 v10[2]; // [rsp+26h] [rbp-1Ah] BYREF
void *dest; // [rsp+28h] [rbp-18h]
int size; // [rsp+34h] [rbp-Ch]
int n4; // [rsp+38h] [rbp-8h]
int size_1; // [rsp+3Ch] [rbp-4h]

n4 = 4;
size = 4 * ((n + 3) / 4);
*(_QWORD *)p_ptr = malloc(size + 4);
if ( !*(_QWORD *)p_ptr )
return 0xFFFFFFFFLL;
**(_BYTE **)p_ptr = HIBYTE(n);
*(_BYTE *)(*(_QWORD *)p_ptr + 1LL) = BYTE2(n);
*(_BYTE *)(*(_QWORD *)p_ptr + 2LL) = BYTE1(n);
*(_BYTE *)(*(_QWORD *)p_ptr + 3LL) = n;
dest = malloc(size);
memcpy(dest, p_s, n);
memset((char *)dest + n, 0, size - n);
for ( size_1 = 0; size_1 < size; size_1 += n4 )
{
v10[0] = *((_BYTE *)dest + size_1);
v10[1] = *((_BYTE *)dest + size_1 + 1);
v9[0] = *((_BYTE *)dest + size_1 + 2);
v9[1] = *((_BYTE *)dest + size_1 + 3);
sub_13AC(a3, v10, v8);
sub_13AC(a3, v9, v7);
*(_BYTE *)(size_1 + 4 + *(_QWORD *)p_ptr) = v8[0];
*(_BYTE *)(*(_QWORD *)p_ptr + size_1 + 4 + 1LL) = v8[1];
*(_BYTE *)(*(_QWORD *)p_ptr + size_1 + 4 + 2LL) = v7[0];
*(_BYTE *)(*(_QWORD *)p_ptr + size_1 + 4 + 3LL) = v7[1];
}
free(dest);
return (unsigned int)(size + 4);
}

发现是对明文的每两个字节和密钥加密,查看sub_13AC:

1
2
3
4
5
6
7
8
9
_BYTE *__fastcall sub_13AC(int a1, unsigned __int8 *a2, _BYTE *a3)
{
_BYTE *result; // rax

*a3 = *a2 * a1 + BYTE1(a1) * a2[1];
result = a3 + 1;
a3[1] = (*a2 * BYTE2(a1) + HIBYTE(a1) * a2[1]) % 256;
return result;
}

是一个矩阵运算,提取密文编写脚本解密:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
def decrypt_block(out0, out1, k0=81, k1=22, k2=52, k3=251):
for x in range(256):
for y in range(256):
o0 = (x * k0 + y * k1) % 256
o1 = (x * k2 + y * k3) % 256
if o0 == out0 and o1 == out1:
return (x, y)
return None

cipher = bytearray.fromhex("000000250CE2708998B2BBE494A095AC389222F80E7B761A66C803052E7DA1043DC062FE6667028781F40000")
for i in range(0, len(cipher), 2):
plain = decrypt_block(cipher[i], cipher[i+1])
if plain:
print(f"{chr(plain[0])}{chr(plain[1])}",end="")

H&NCTF{840584fb08a26f01c471054628e451}