---
# System prepended metadata

title: PicoCTF - Compress and Attack
tags: [PicoCTF, Crypto, CTF]

---

# PicoCTF - Compress and Attack
###### tags: `PicoCTF` `CTF` `Crypto`

## Background
* zlib compression property
詳細說明一下zlib的壓縮特性是當壓縮的內容出現重複字元的時候，壓縮過後的長度會不變
```bash
>>> import zlib
>>> enc = zlib.compress(bytes("picoCTF{picoCTF{testing_123456}".encode("utf-8")))
>>> len(enc)
33
>>> enc = zlib.compress(bytes("picoCTF{tepicoCTF{testing_123456}".encode("utf-8")))
>>> len(enc)
33
>>> enc = zlib.compress(bytes("picoCTF{tekpicoCTF{testing_123456}".encode("utf-8")))
>>> len(enc)
34
```
此時重複的部分就是`picoCTF{`，若是繼續增加重複的部分(例如：`picoCTF{te`)，壓縮後的長度也不會變，這樣就可以當作一個oracle，也就是利用長度來判斷增加的字元是不是flag重複的一部分

## Source code
:::spoiler
```python=
#!/usr/bin/python3 -u

import zlib
from random import randint
import os
from Crypto.Cipher import Salsa20

flag = open("./flag").read()


def compress(text):
    return zlib.compress(bytes(text.encode("utf-8")))

def encrypt(plaintext):
    secret = os.urandom(32)
    cipher = Salsa20.new(key=secret)
    return cipher.nonce + cipher.encrypt(plaintext)

def main():
    while True:
        usr_input = input("Enter your text to be encrypted: ")
        compressed_text = compress(flag + usr_input)
        encrypted = encrypt(compressed_text)
        
        nonce = encrypted[:8]
        encrypted_text =  encrypted[8:]
        print(nonce)
        print(encrypted_text)
        print(len(encrypted_text))

if __name__ == '__main__':
    main()
```
:::

## Recon
這一題很有趣，可以看一下source code發現他特意把`encrypted_text`的長度leak出來當作解題的一部分資訊，透過上述提到的`zlib`特性，可以把這個資訊當成一個oracle
```bash
$ nc mercury.picoctf.net 33976
Enter your text to be encrypted: p
b'0\xc17\x10?%a\xeb'
b"'\xdf\x99\xb0\xd99dvf\xf6\x88\xbfl\xc3\x10\xff\x16,\xf7*\xad\xb3\xb1\xc7\x94\xaam\xc5\xac\xfat^]\x0e\xd8\xfbV\xed\xdd\xf7\xbe\xb0\xed\x8ff\x9e"
46
Enter your text to be encrypted: pi
b'H\x03l\x16\xb12S\xad'
b'\xc4\x0c\xb1\x9e{q\x9e\x93Q\xeb\xa5G\xbb\x01%\xe6\x1d4\x96\xf3\r+C4\x1c\xe9-\x99ghC\x0c\xef\xec\xba\xb1\x1b\xfa\xa2\x16\xda\x00\x85tq\x02@'
47
Enter your text to be encrypted: pic
b'\xa4\x81\x7f\xb6\x9e\xadW\xef'
b"\xd2\xe4\x8a2WY]^0$g\x17\xd0\xe8\xd1\x95\xbd \x9eX+\x06\xf8\xcc\x8e\xa8\xfa\xdf\xb3\xac:k\x15\xdb\xa0#'\xb7\xf7^\x06\xce!it\x11\xdd\xa3"
48
Enter your text to be encrypted: pico
b'<\x06\x8f\x18\xcf\xf3\x91\x11'
b'\x84k\xc9\xf4~\x81\xdar\x9bR\x08\x87K\xb7\x1c\xda\x18\x08+\xc1\xfa\x9c\xce\xe1\x7f\x93\xd9\xe6\xf4Jmv\x08\x9b\xaa\xb4\xc0\xb6\xa6f\xdb\x9acF\x0e\x8eF\x98'
48
Enter your text to be encrypted: picoC
b'\x18\x07\xd4"C\x94\xd8\xfe'
b'g\tWkH\x10\xa4\x8a\x80\xcc\xd8\x94\x02\x08T\x93AV\x0f\x97\xca\x82\xf3\xd1\xd8\xb0\r\xb2\x05\xc6\xbe{\x00\xd8\xc4\xbd\x84\x0fn\x14\xb6\xcf|\x15\xf5\xf2\xf9l'
48
...
```
透過上述測試，可以判斷長度應該就是固定48，此時我們就可以依序加入guessing character，並透過oracle判斷加入的字元是對的還是錯的

## Exploit
```python=
from pwn import *

context.arch = 'amd64'

r = remote("mercury.picoctf.net", 33976)

def oracle(plaintext):
    r.recvuntil(b"Enter your text to be encrypted: ")
    r.sendline(plaintext.encode())
    nonce = r.recvline().strip().hex()
    encrypted_text = r.recvline().strip().hex()
    return r.recvline().strip().decode()


current_char = ""
guessing_flag = "picoCTF{"
fit_length = oracle(guessing_flag)
print(guessing_flag)
while current_char != "}":
    for i in string.printable:
        if oracle(guessing_flag + i) == fit_length:
            print(i)
            guessing_flag += i
            current_char = i
            break
        
print(guessing_flag)
r.close()
```
Flag: `picoCTF{sheriff_you_solved_the_crime}`

## Reference
[maple3142 - compress-and-attack](https://blog.maple3142.net/2021/03/30/picoctf-2021-writeups/#compress-and-attack)
[CompressAndAttack Write up](https://github.com/apoirrier/CTFs-writeups/blob/master/PicoCTF/Crypto/CompressAndAttack.md)