前言

比赛时间:2025.2.3 - 2025.2.17

签到

TEST NC

1
2
cat flag
hgame{YouR_cAn-ConN3Ct_to-TH3-R3MoTe-ENvIr0nMeNT-TO-gEt_f1@g0}

从这里开始的序章。

1
2
I am the flag!
hgame{Now-I-kn0w-how-to-subm1t-my-fl4gs!}

也是我的终章:blush:

Crypto

suprimeRSA

旧附件的题目:

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 *
import random

FLAG = b"hgame{xxxxxxxxxxxxxxxxx}"
e = 0x10001


# trick
def factorial(num):
result = 1
for i in range(1, num + 1):
result *= i
return result


a = 2
b = 2
assert factorial(a) + factorial(b) == a**b
M = (a + b) << 128


def gen_key():
while True:
k = getPrime(29)
a = getPrime(random.randint(20, 62))
p = k * M + pow(e, a, M)
if isPrime(p):
return p


p, q = gen_key(), gen_key()
n = p * q
m = bytes_to_long(FLAG)
enc = pow(m, e, n)

print(f"{n=}")
print(f"{enc=}")

"""
n=669040758304155675570167824759691921106935750270765997139446851830489844731373721233290816258049
enc=487207283176018824965268172307888245763817583875071008869370172565777230514379236733742846575849
"""

新附件的题目:

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

FLAG = b"hgame{xxxxxxxxxxxxxxxxxx}"
e = 0x10001


def primorial(num):
result = 1
for i in range(1, num + 1):
result *= prime(i)
return result


M = primorial(random.choice([39, 71, 126]))
M = primorial(39)


def gen_key():
while True:
k = getPrime(random.randint(20, 40))
k = getPrime(20)
a = getPrime(random.randint(20, 60))
p = k * M + pow(e, a, M)
if isPrime(p):
return p


p, q = gen_key(), gen_key()
n = p * q
m = bytes_to_long(FLAG)
enc = pow(m, e, n)

print(f"{n=}")
print(f"{enc=}")

"""
n=787190064146025392337631797277972559696758830083248285626115725258876808514690830730702705056550628756290183000265129340257928314614351263713241
enc=365164788284364079752299551355267634718233656769290285760796137651769990253028664857272749598268110892426683253579840758552222893644373690398408
"""

先是一个小问题 a!+b!=a^b^ ,注意到右式增长速度远大于左式,a、b取值一定较小,试根得到a=2,b=2

根据源码:质数p=k*M+pow(e,a,M),k和a随机生成,几乎无法爆破。

但是发现模数为96位大整数,可以通过二次筛法分解质因数,在msieve计算1h28min后得出结果:

1
2
3
Sat Feb 08 15:00:13 2025  p48 factor: 796688410951167236263039235508020180383351898113
Sat Feb 08 15:00:13 2025 p48 factor: 839777194079410698093279448382785381699926556673
Sat Feb 08 15:00:13 2025 elapsed time 01:28:00
1
2
3
4
5
6
7
8
9
10
11
12
13
from Crypto.Util.number import *

e = 0x10001
M = 2**130
enc = 487207283176018824965268172307888245763817583875071008869370172565777230514379236733742846575849
n = 669040758304155675570167824759691921106935750270765997139446851830489844731373721233290816258049
p = 796688410951167236263039235508020180383351898113
q = 839777194079410698093279448382785381699926556673

phi = (p - 1) * (q - 1)
d = inverse(e, phi)
m = pow(enc, d, n)
print(long_to_bytes(m)) # b'hgame{ROCA_ROCK_and_ROll!}'

之后我发现https://factordb.com这个网站把这个数的分解给秒了???
估计是算出来后给记下来了,题目附件也是及时换了,一看发现n有144位,原来考点不在分解因数上?

sieve

题目:

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
# sage
from Crypto.Util.number import bytes_to_long
from sympy import nextprime

FLAG = b"hgame{xxxxxxxxxxxxxxxxxxxxxx}"
m = bytes_to_long(FLAG)


def trick(k):
if k > 1:
mul = prod(range(1, k))
if k - mul % k - 1 == 0:
return euler_phi(k) + trick(k - 1) + 1
else:
return euler_phi(k) + trick(k - 1)
else:
return 1


e = 65537
p = q = nextprime(trick(e ^ 2 // 6) << 128)
n = p * q
enc = pow(m, e, n)
print(f"{enc=}")
# enc=2449294097474714136530140099784592732766444481665278038069484466665506153967851063209402336025065476172617376546

根据题意,需要算出715849728以内欧拉数之和,其中质数和1的欧拉数再加1。这里当然用筛法做:

欧拉数 ϕ(n)=n(11p1)(11p2)(11pn))\phi(n)=n(1-\frac{1}{p_1})(1-\frac{1}{p_2})\dots(1-\frac{1}{p_n}))

先令 ϕ(n)=n\phi(n)=n ,每找到一个质数p,就将所有kp的欧拉数乘以 (11p)(1-\frac{1}{p}) 这样对于任意一个数n,它的每个质数都能被筛一遍。

那怎么找质数呢?遍历 2~n,如果发现 n=ϕ(n)n=\phi(n),那就说明n没有被更小的素数筛过,那就是质数了。

由于本题要求质数和1的欧拉数再加1,所以代码中筛的时候从2*p开始筛,最后还要加1

这会就是C语言的魅力时刻了,python跑了2个小时左右,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
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdbool.h>

void get_phi(int n, int *phi)
{
for (int i = 2; i <= n; i++)
{
phi[i] = i; // 初始化欧拉函数值
}

for (int p = 2; p <= n; p++)
{
if (phi[p] == p)
{
for (int multiple = 2 * p; multiple <= n; multiple += p)
{
phi[multiple] = phi[multiple] / p * (p - 1); // 更新欧拉函数值
}
}
}
}

int main()
{
int n = 715849728;
long long sum = 0;
int *phi = (int *)malloc((n + 1) * sizeof(int));

get_phi(n, phi);

for (int i = 2; i <= n; i++)
{
sum += phi[i];
}
printf("sum = %lld\n", sum + 1); // sum = 155763335447735055

free(phi);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
from Crypto.Util.number import *
from sympy import nextprime

p = q = nextprime(155763335447735055 << 128)
n = p * q
e = 65537
phi = (p - 1) * q
d = inverse(e, phi)
enc = 2449294097474714136530140099784592732766444481665278038069484466665506153967851063209402336025065476172617376546
m = pow(enc, d, n)
print(long_to_bytes(m)) # b'hgame{sieve_is_n0t_that_HArd}'

ezBag | Undo

题目:

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
from Crypto.Util.number import *
import random
from Crypto.Cipher import AES
import hashlib
from Crypto.Util.Padding import pad


flag = b"flag{xxxxxxxxxx}"

list = []
bag = []
kBits = 64
p = random.getrandbits(kBits)
assert len(bin(p)[2:]) == kBits
for _ in range(4):
t = p
a = [getPrime(32) for _ in range(kBits)]
b = 0
for i in a:
temp = t % 2
b += temp * i
t = t >> 1
list.append(a)
bag.append(b)
print(f"list={list}")
print(f"bag={bag}")

key = hashlib.sha256(str(p).encode()).digest()
cipher = AES.new(key, AES.MODE_ECB)
flag = pad(flag, 16)
ciphertext = cipher.encrypt(flag)
print(f"ciphertext={ciphertext}")
print(f"flag={flag}")
print(bin(p))

"""
list=[[2826962231, 3385780583, 3492076631, 3387360133, 2955228863, 2289302839, 2243420737, 4129435549, 4249730059, 3553886213, 3506411549, 3658342997, 3701237861, 4279828309, 2791229339, 4234587439, 3870221273, 2989000187, 2638446521, 3589355327, 3480013811, 3581260537, 2347978027, 3160283047, 2416622491, 2349924443, 3505689469, 2641360481, 3832581799, 2977968451, 4014818999, 3989322037, 4129732829, 2339590901, 2342044303, 3001936603, 2280479471, 3957883273, 3883572877, 3337404269, 2665725899, 3705443933, 2588458577, 4003429009, 2251498177, 2781146657, 2654566039, 2426941147, 2266273523, 3210546259, 4225393481, 2304357101, 2707182253, 2552285221, 2337482071, 3096745679, 2391352387, 2437693507, 3004289807, 3857153537, 3278380013, 3953239151, 3486836107, 4053147071], [2241199309, 3658417261, 3032816659, 3069112363, 4279647403, 3244237531, 2683855087, 2980525657, 3519354793, 3290544091, 2939387147, 3669562427, 2985644621, 2961261073, 2403815549, 3737348917, 2672190887, 2363609431, 3342906361, 3298900981, 3874372373, 4287595129, 2154181787, 3475235893, 2223142793, 2871366073, 3443274743, 3162062369, 2260958543, 3814269959, 2429223151, 3363270901, 2623150861, 2424081661, 2533866931, 4087230569, 2937330469, 3846105271, 3805499729, 4188683131, 2804029297, 2707569353, 4099160981, 3491097719, 3917272979, 2888646377, 3277908071, 2892072971, 2817846821, 2453222423, 3023690689, 3533440091, 3737441353, 3941979749, 2903000761, 3845768239, 2986446259, 3630291517, 3494430073, 2199813137, 2199875113, 3794307871, 2249222681, 2797072793], [4263404657, 3176466407, 3364259291, 4201329877, 3092993861, 2771210963, 3662055773, 3124386037, 2719229677, 3049601453, 2441740487, 3404893109, 3327463897, 3742132553, 2833749769, 2661740833, 3676735241, 2612560213, 3863890813, 3792138377, 3317100499, 2967600989, 2256580343, 2471417173, 2855972923, 2335151887, 3942865523, 2521523309, 3183574087, 2956241693, 2969535607, 2867142053, 2792698229, 3058509043, 3359416111, 3375802039, 2859136043, 3453019013, 3817650721, 2357302273, 3522135839, 2997389687, 3344465713, 2223415097, 2327459153, 3383532121, 3960285331, 3287780827, 4227379109, 3679756219, 2501304959, 4184540251, 3918238627, 3253307467, 3543627671, 3975361669, 3910013423, 3283337633, 2796578957, 2724872291, 2876476727, 4095420767, 3011805113, 2620098961], [2844773681, 3852689429, 4187117513, 3608448149, 2782221329, 4100198897, 3705084667, 2753126641, 3477472717, 3202664393, 3422548799, 3078632299, 3685474021, 3707208223, 2626532549, 3444664807, 4207188437, 3422586733, 2573008943, 2992551343, 3465105079, 4260210347, 3108329821, 3488033819, 4092543859, 4184505881, 3742701763, 3957436129, 4275123371, 3307261673, 2871806527, 3307283633, 2813167853, 2319911773, 3454612333, 4199830417, 3309047869, 2506520867, 3260706133, 2969837513, 4056392609, 3819612583, 3520501211, 2949984967, 4234928149, 2690359687, 3052841873, 4196264491, 3493099081, 3774594497, 4283835373, 2753384371, 2215041107, 4054564757, 4074850229, 2936529709, 2399732833, 3078232933, 2922467927, 3832061581, 3871240591, 3526620683, 2304071411, 3679560821]]
bag=[123342809734, 118191282440, 119799979406, 128273451872]
ciphertext=b'\x1d6\xcc}\x07\xfa7G\xbd\x01\xf0P4^Q"\x85\x9f\xac\x98\x8f#\xb2\x12\xf4+\x05`\x80\x1a\xfa !\x9b\xa5\xc7g\xa8b\x89\x93\x1e\xedz\xd2M;\xa2'
"""

Reverse

Compress dot new

脚本语言是nushell,功能就是哈夫曼编码,解密脚本在此省略huffman_tree

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
def huffman_decode(encoded_data, huffman_tree):
decoded_string = ""
current_node = huffman_tree

for bit in encoded_data:
if bit == "0":
current_node = current_node["a"]
else:
current_node = current_node["b"]

try:
current_node["s"]
decoded_string += chr(current_node["s"])
current_node = huffman_tree # 重置到根节点
except:
pass

return decoded_string


encoded_data = "00010001110111111010010000011100010111000100111000110000100010111001110010011011010101111011101100110100011101101001110111110111011011001110110011110011110110111011101101011001111011001111000111001101111000011001100001011011101100011100101001110010111001111000011000101001010000000100101000100010011111110110010111010101000111101000110110001110101011010011111111001111111011010101100001101110101101111110100100111100100010110101111111111100110001010101101110010011111000110110101101111010000011110100000110110101011000111111000110101001011100000110111100000010010100010001011100011100111001011101011111000101010110101111000001100111100011100101110101111100010110101110000010100000010110001111011100011101111110101010010011101011100100011110010010110111101110111010111110110001111010101110010001011100100101110001011010100001110101000101111010100110001110101011101100011011011000011010000001011000111011111111100010101011100000"
m = huffman_decode(encoded_data, huffman_tree)
print(m)
'''
hgame{Nu-Shell-scr1pts-ar3-1nt3r3st1ng-t0-wr1te-&-use!}
Lorem ipsum dolor sit amet, consectetur adipiscing elit.
Nulla nec ligula neque. Etiam et viverra nunc, vel bibendum risus. Donec.
'''

解出来后还有个彩蛋,知乎上的解释:https://www.zhihu.com/question/20133127?sort=created

Turtle

先手动UPX脱壳

找到源码,下面是我改过:

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
__int64 sub_401876()
{
_BYTE v1[256];
char flag[46];
char key[8];
char encrypted_flag[40] = {-8, -43, 98, -49, 67, -70, -62, 35, 21, 74, 81, 16, 39, 16, -79, -49, -60, 9, -2, -29, -97, 73, -121, -22, 89, -62, 7, 59, -87, 17, -63, -68, -3, 75, 87, -60, 126, -48, -86, 10};
_BYTE encrypted_key[7] = {-51, -113, 37, 61, -31, 'Q', 'J'};
char v8[7] = "yekyek";

puts("plz input the key: ");
scanf("%s", key);
set_v1(v8, 6, v1);
encrypt1(key, 7, v1);
if (memcmp(key, encrypted_key, 7))
{
puts("key is wrong");
}
else
{
puts("plz input the flag:");
scanf("%s", flag);
set_v1(v8, 7, v1);
encrypt2(flag, 40, v1);
if (memcmp(flag, encrypted_flag, 40))
puts("wrong, plz try again");
else
puts("Congratulate!");
}
return 0LL;
}

显然,关键函数set_v1、encrypt1、encrypt2,只要逆向后两个函数即可,注意到encrypt1是异或加密,直接可以当作解密函数用,而encrypt2跟对明文的变化只在于一行的 -= ,只要改为 += 就能用了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
int main()
{
_BYTE v1[256];
char key[7] = {-51, -113, 37, 61, -31, 'Q', 'J'};
char v8[7] = "yekyek";
char v2[40] = {-8, -43, 98, -49, 67, -70, -62, 35, 21, 74, 81, 16, 39, 16, -79, -49, -60, 9, -2, -29, -97, 73, -121, -22, 89, -62, 7, 59, -87, 17, -63, -68, -3, 75, 87, -60, 126, -48, -86, 10};

set_v1(v8, 6, v1);
decrypt1(key, 7, v1);
fwrite(key, sizeof(char), 7, stdout); // ecg4ab6
putchar('\n');

set_v1(key, 7, v1);
decrypt2(v2, 40, v1);
fwrite(v2, sizeof(char), 40, stdout); // hgame{Y0u'r3_re4l1y_g3t_0Ut_of_th3_upX!}
return 0;
}

Delta Erro0000ors

IDA打开是这样的,但运行时却有里面没有的东西,看看汇编graph,果然是异常处理。

image-20250408193528089

image-20250408193712642

这种情况直接把异常处理前面的代码给nop掉,这样异常处理的部分就会反编译到源程序的后面。

image-20250408193900895

这是异常处理:

image-20250408193952652

IDA是这样的,只要lea后面接了mov,反编译后就会隐藏这些传参,所以还是要看看汇编理解。

分析前还是得简单了解msdelta是干什么的。当要进行版本升级时,直接发送新版本的文件就太大了,msdelta提供了接口,生成两版本delta和应用delta,这样只需要发送delta文件就可以更新版本了。

ApplyDeltaB是就是应用Delta的方法,后面会校验MD5哈希值是否正确,不正确会触发异常。

程序先读取flag用包裹的部分作为source,用程序中的delta进行差异补丁,由于hash不对触发异常,用户再次输入md5值进行补救,如果成功,就生成key进行xor加密。

这里会发现一个问题,我们要找的就是source,但用来解密source的key却是用source生成的,这就像是把开锁的钥匙给锁起来了。所以就有两个猜想,一个是直接分析Delta,找找有没有破解之道;另一个是推测Delta完全覆盖了原来的source,用伪flag动态调试获得key。

一开始我是打算直接分析Delta的,无奈网络上的资源实在太少了,去逆向msdelta.dll难度大,只能放弃。而第二个更符合re手的直觉,先获取MD5的值再解密。

看其他师傅的WP学到了一点小技巧,在原来的hash处下内存断点,一定会在比较hash的时候被断,找到这个地方就能看到目标MD5。

image-20250408202139022

在这里,此时rcx就指向我们要的MD5值!

image-20250408202315968

取出来MD5:44D292FFE2E91730AE69EB50AE11D04A

image-20250408202454741

在这里断,取出来数据

image-20250408202645852

image-20250408202723174

不过还是取hex的数据吧,最后的\x00字节也是key的一部分。

1
2
3
Input: 3B 02 17 08 0B 5B 4A 52 4D 11 11 4B 5C 43 0A 13 54 12 46 44 53 59 41 11 0C 18 17 37 30 48 15 07 5A 46 15 54 1B 10 43 40 5F 45 5A
Key: 53 65 76 65 6E 20 73 61 79 73 20 79 6F 75 27 72 65 20 72 69 67 68 74 21 21 21 21 00
Flag: hgame{934b1236-a124-4150-967c-cb4ff5bcc900}

image-20250408202923150

Web

Pacman

翻源代码到/static/script/index.js,可以找到两组gift

1
2
here is your gift:aGFlcGFpZW1rc3ByZXRnbXtydGNfYWVfZWZjfQ==
here is your gift:aGFldTRlcGNhXzR0cmdte19yX2Ftbm1zZX0=

base64解码:

1
2
haepaiemkspretgm{rtc_ae_efc}
haeu4epca_4trgm{_r_amnmse}

栅栏密码解密:

1
2
hgame{pratice_makes_perfect}
hgame{u_4re_pacman_m4ster}