第一届OpenHarmony CTF专题赛(线上选拔赛)Crypto Write UP
Weak_random
from secret import flag |
注意到产生随机数的种子是一个小于等于0xFFFF的非负整数和一个小于等于0xFF的非负整数拼接而成的,所以可以考虑爆破,而由padding
函数的实现可以发现这里是在消息的前面拼接的,而且flag的长度是32,所以密文的第一块对应的是拼接的数据,那么我们可以将密文的第一块当作IV拿来解密,所以只需要遍历seed尝试用生成的key进行解密即可:
import random |
可以得到flag:0428f4ff1a4e3d2e2326552f9db48fa9
Simple LLL
首先进行方舟字节码逆向,可以见到Index里面有关于flag的如下代码: public Object #~@0>#runMixer(Object functionObject, Object newTarget, Index this) {
obj = this.flag;
if ((this.flag.length < 6 ? 1 : 0) != 0) {
this.output = "Flag too short!";
return null;
}
if (istrue(("flag{" != obj.substring(0, 5) ? 1 : 0)) != null || isfalse(("}" != obj[obj.length - 1] ? 1 : 0)) == null) {
this.output = "Invalid flag, must starts with `flag{` and ends with `}`";
return null;
}
substring = obj.substring(5, obj.length - 1);
if ((0 != (substring.length % 3) ? 1 : 0) != 0) {
this.output = "Invalid key length (must be multiple of 3)";
return null;
}
i = 0;
getPrime = this.getPrime(215);
getPrime2 = this.getPrime(128);
getPrime3 = this.getPrime(170);
r36 = [Object];
obj2 = getiterator("Lattice-based cryptography is the generic term for constructions of cryptographic primitives that involve lattices, either in the construction itself or in the security proof.".substring(0, 50));
obj3 = obj2.next;
i2 = 0;
while (true) {
callthisN = obj3();
throw.ifnotobject(callthisN);
if (istrue(callthisN.done) != null) {
break;
}
r362 = callthisN.value;
try {
bytesToLong = this.bytesToLong(substring[i] + substring[i + 1] + substring[i + 2]);
i += 3;
r362 = (i >= substring.length ? 1 : 0);
if (r362 != 0) {
i = 0;
}
r36.push((this.getRandomBits(190) * getPrime) + ((this.modPow(getPrime2, bytesToLong, getPrime3) * BigInt(r362.charCodeAt(0))) % getPrime3));
} catch (ExceptionI0 unused) {
z = r362;
if (istrue(i2) == null) {
i2 = 1;
obj4 = null;
r363 = hole;
try {
obj5 = obj2.return;
obj3 = obj5;
r363 = (0 == obj5 ? 1 : 0);
} catch (ExceptionI0 unused2) {
}
if (r363 == 0) {
obj4 = obj3();
throw(z);
throw.ifnotobject(obj4);
}
}
throw(z);
}
}
this.output = "P: " + getPrime3 + ", G: " + getPrime2 + "\nEncrypted: [" + r36.join(", ") + "]";
console.error("P: " + getPrime3 + "");
console.error("G: " + getPrime2 + "");
i3 = 0;
obj6 = getiterator(r36);
obj7 = obj6.next;
i4 = 0;
while (true) {
callthisN2 = obj7();
throw.ifnotobject(callthisN2);
if (istrue(callthisN2.done) != null) {
return null;
}
r364 = callthisN2.value;
try {
console.error("result[" + i3 + "]: " + r36[i3] + "");
r364 = i3 + 1;
i3 = r364;
} catch (ExceptionI0 unused3) {
z2 = r364;
if (istrue(i4) == null) {
i4 = 1;
obj8 = null;
r365 = hole;
try {
obj9 = obj6.return;
obj7 = obj9;
r365 = (0 == obj9 ? 1 : 0);
} catch (ExceptionI0 unused4) {
}
if (r365 == 0) {
obj8 = obj7();
throw(z2);
throw.ifnotobject(obj8);
}
}
throw(z2);
}
}
}flag{}
去除,然后将剩余部分分为若干个长度为3的字串,设对应数值为\(x_1,x_2,\cdots\),对于\(x_i\)(\(i=1,2,\cdots\)),有: \[
c_i=q_ip_0+[s_iG^{x_i}\mod{P}]
\] 其中\(s_i\)为Lattice-based cryptography is the generic term for constructions of cryptographic primitives that involve lattices, either in the construction itself or in the security proof.
这段话的前50个字符中的第\(i\)个字符对应的ASCII码值,\(q_i\)为一个随机的190位整数,设\(s_iG^{x_i}\mod{P}=r_i\),设一共有\(n\)次这样的运算,则可以得到: \[
\begin{cases}
c_1=q_1p_0+r_1\\
c_2=q_2p_0+r_2\\
\cdots\\
c_n=q_np_0+r_n\\
\end{cases}
\] 显然这是AGCD问题,\(r_i\)大约为170位,那么可以构造如下格: \[
\left(\begin{matrix}
2^{171}&c_2&c_3&\cdots&c_{n}\\
&-c_1&&&\\
&&-c_1&&\\
&&&\ddots&\\
&&&&-c_1
\end{matrix}\right)
\] 规约后可以得到一条短向量: \[
(2^{171}q_1,q_1r_2-q_2r_1,\cdots,q_1r_n-q_nr_0)
\] 提取第一个分量就可以得到\(q_1\),从而可以从\(c_1\)中提取出\(r_1\)(有\(c_1\equiv r_1\pmod{q_1}\)),从而恢复出\(p_0\)。 在获取\(p_0\)之后可以知道对于\(i=1,2,\cdots,n\),有\(r_i=c_i\mod{p_0}\)(因为\(r_1<p_0\)),有: \[
r_i\equiv s_iG^{x_i}\pmod{P}
\] 因为\(s_i\)是已知量,所以可以知道: \[
G^{x_i}\equiv r_is_i^{-1}\pmod{P}
\] 因为\(x_i\)相对较小,所以通过求解DLP就可以得到所有\(x_i\),从而恢复出flag: from Crypto.Util.number import *
from tqdm import trange
P = 1105340037553808473854838067251286973932436127087387
G = 258698882868391024376029756014412783703
ct = [...]
x0 = ct[0]
x = ct[1:]
L = matrix(ZZ, len(x) + 1, len(x) + 1)
L[0, 0] = 2^171
for i in range(len(x)):
L[0, i+1] = x[i]
L[i+1, i+1] = -x0
res = L.LLL()
q0 = res[0, 0] // 2^171
r0 = ZZ(x0 % q0)
p = ZZ((x0 - r0) // q0)
R = GF(P)
text = "Lattice-based cryptography is the generic term for constructions of cryptographic primitives that involve lattices, either in the construction itself or in the security proof."
flag = b""
for i in trange(len(ct)):
b = ct[i] % p
y = b * inverse(ord(text[i]), P) % P
x = discrete_log(R(y), R(G))
flag += long_to_bytes(x)
print(flag)

可以看到这里得到的实际上是flag重复多次的结果,从中可以提取出真正的flag:b5f1a6ca7c28f8ee14b6ca289ed2f061f0f0a01c67e77
,包上flag{}
就可以了。
Ea5y_RSA
题目描述: 这是一个未完全开发的OpenHarmony应用,在hint.txt中泄露了部分日志记录。请根据给定的源码和日志记录得到flag
05-15 18:42:52.831 20328-20328 C03951/InputKeyFlow com.example.ea5y_rsa I [(100000:100000:scope)] Id:1513, click 0 up, state: 0
05-15 18:42:52.831 20328-20328 C0391e/AceGesture com.example.ea5y_rsa I [(100000:100000:scope)] Click try accept
05-15 18:42:52.831 20328-20328 C03951/InputKeyFlow com.example.ea5y_rsa I [(100000:100000:scope)] Click accepted, tag: Button
05-17 18:42:52.848 20328-20328 A03d00/JSAPP com.example.ea5y_rsa I my gift: 0,48,13,6,9,42,134,72,134,247,13,1,1,1,5,0,4,130,2,98,48,130,2,94,2,1,0,2,129,129,0,162,241,252,198,79,226,203,150,170,211,175,5,127,220,154,215,250,190,125,3,43,15,214,239,122,148,175,20,208,173,241,85,168,92,181,110,220,162,25,205,159,96,119,180,19,33,9,52,34,137,4,102,166,195,142,204,1,247,140,141,184,92,14,162,123,208,160,102,112,154,194,130,104,139,141,10,54,148,160,164,100,245,208,41,39,103,160,135,99,108,15,231,219,255,249,35,114,131,108,70,144,182,118,253,222,115,181,71,155,70,135,141,36,73,221,205,146,31,8,55,181,46,111,127,208,101,185,221,2,3,1,0,1,2,129,128,43,13,141,32,72,211,63,191,155,123,58,239,85,13,80,204,104,48,20,143,213,188,229,169,120,213,248,60,163,182,145,225,116,14,170,209,147,242,48,167,39,201,49,87,159,6,71,140,66,227,185,9,246,94,13,72,209,236,58,114,231,151,75,54,47,89,245,211,248,113,162,189,101,189,68,168,165,3,221,23,176,183,78,56,179,150,198,63,126,131,223,165,239,32,59,158,187,205,223,211,228,55,107,19,136,241,169,206,131,34,95,225
05-15 18:42:52.848 20328-20328 C03951/InputKeyFlow com.example.ea5y_rsa I [(100000:100000:scope)] id: 0, log: {types: Click, node: Button, prcd: Down, state: READY, prcd: Up, state: SUCCEED}
05-15 18:42:52.848 20328-20328 C03919/AceInputTracking com.example.ea5y_rsa I [(100000:100000:scope)] Consumed new event id=1513 in ace_container, lastEventInfo: id:-1
05-15 18:42:52.874 20328-20721 C01406/OHOS::RS com.example.ea5y_rsa I RSUIDirector::ProcessMessages messageId:3, cmdCount:1, instanceId:100000
05-15 18:42:52.874 20328-20328 C01406/OHOS::RS com.example.ea5y_rsa I RSUIDirector::PostTask messageId:3, cmdCount:1, instanceId:100000my gift
一段,将其转换为十六进制可以得到: 00300d06092a864886f70d0101010500048202623082025e02010002818100a2f1fcc64fe2cb96aad3af057fdc9ad7fabe7d032b0fd6ef7a94af14d0adf155a85cb56edca219cd9f6077b41321093422890466a6c38ecc01f78c8db85c0ea27bd0a066709ac282688b8d0a3694a0a464f5d0292767a087636c0fe7dbfff92372836c4690b676fdde73b5479b46878d2449ddcd921f0837b52e6f7fd065b9dd02030100010281802b0d8d2048d33fbf9b7b3aef550d50cc6830148fd5bce5a978d5f83ca3b691e1740eaad193f230a727c931579f06478c42e3b909f65e0d48d1ec3a72e7974b362f59f5d3f871a2bd65bd44a8a503dd17b0b74e38b396c63f7e83dfa5ef203b9ebbcddfd3e4376b1388f1a9ce83225fe1
3082025e
,猜测这是RSA的公钥文件或者私钥文件,提取分块有: 00300d06092a864886f70d010101050004820262
3082025e
020100
0281 # n
81
00a2f1fcc64fe2cb96aad3af057fdc9ad7fabe7d032b0fd6ef7a94af14d0adf155a85cb56edca219cd9f6077b41321093422890466a6c38ecc01f78c8db85c0ea27bd0a066709ac282688b8d0a3694a0a464f5d0292767a087636c0fe7dbfff92372836c4690b676fdde73b5479b46878d2449ddcd921f0837b52e6f7fd065b9dd
0203 # e
010001
0281 # d
80
2b0d8d2048d33fbf9b7b3aef550d50cc6830148fd5bce5a978d5f83ca3b691e1740eaad193f230a727c931579f06478c42e3b909f65e0d48d1ec3a72e7974b362f59f5d3f871a2bd65bd44a8a503dd17b0b74e38b396c63f7e83dfa5ef203b9ebbcddfd3e4376b1388f1a9ce83225fe1
但是现在缺的是密文,题目给出的工程文件还没用到,可以在Ea5y_rsa\entry\src\main\ets\pages\Index.ets
中找到密文:

处理一下就可以通过如下代码求解出flag:
from Crypto.Util.number import * |
可以得到flag:

实际上通过审计代码可以知道getGift
代码如下:
getGift(): number[]{ |
其实就是将私钥文件的前面一部分放到日志里面.
Small Message For (SM4) Encryption
from gmssl import sm4, func |
只给了加密后的flag和\(key\oplus iv\),似乎没有任何切入点,但是看key
的生成逻辑可以看到它是secret_message
重复多遍得到的,猜测secret_message
可能很短,所以考虑爆破,而且给出了一大段明文,可以通过这段明文来判断得到的secret_message
是否正确: from Crypto.Util.number import *
from gmssl import sm4, func
from pwn import xor
from string import digits, ascii_letters
from itertools import product
from tqdm import *
def decrypt(key, ciphertext, iv):
cipher = sm4.CryptSM4(sm4.SM4_ENCRYPT, 0)
cipher.set_key(key, sm4.SM4_DECRYPT)
plaintext = cipher.crypt_cbc(iv, ciphertext)
return plaintext
c = "d9ea43b0d208aa168e4a275a69df3bc86051e756f9ca7959b68c6b23c9e1b69c19e08b75938375a6be830d1844d8a6e368faf1ddffecea69b5abe00ac0d6e10d6696be33d40e83a272072fbe131f98c82587011f61f2d58a020c8c54cf9b651abd740a3d55d36daa9c88cfc10a520ce4211fba4365ce98b82355b17c64dd2de4800fc68df36cfa8a3fd05baac6970dcd"
h = "ee278c4e526ff15b8d308b6b18f83221"
head = b"My FLAG? If you want it, I'll let you have it... search for it! I left all of it at that place: "
key_xor_iv = bytes.fromhex(h)
for l in range(1, 17):
for s in tqdm(product(ascii_letters + digits, repeat=l)):
secret_message = ''.join(s).encode()
key = secret_message
while len(key) < 16:
key += secret_message
key = key[:16]
iv = xor(key_xor_iv, key)
ciphertext = bytes.fromhex(c)
pt = decrypt(key, ciphertext, iv)
if head in pt:
print(f"Secret message: {secret_message}")
print(pt)
exit(0)