<-->

chall

 

#!/usr/bin/python3
import os
import socketserver
import random
import signal
import string
import tempfile
import tarfile
from hashlib import sha256
from Crypto.Cipher import ChaCha20

from secret import flag,key

MAXNUM = 9
MAXFILESZ = 100
MAXTARSZ = 100000

class LicenseRequired(Exception):
	pass

class Task(socketserver.BaseRequestHandler):
    def proof_of_work(self):
        proof = ''.join([random.choice(string.ascii_letters+string.digits) for _ in range(20)])
        digest = sha256(proof.encode('latin-1')).hexdigest()
        self.request.send(str.encode("sha256(XXXX+%s) == %s\n" % (proof[4:],digest)))
        self.request.send(str.encode('Give me XXXX:'))
        x = self.request.recv(10).decode()
        x = x.strip()
        xx = x+proof[4:]
        if len(x) != 4 or sha256(xx.encode('latin-1')).hexdigest() != digest:
            return False
        return True

    def recvint(self):
        try:
            return int(self.request.recv(10))
        except:
            return 0

    def recvfile(self, maxsz):
        self.request.sendall(b"size: ")
        sz = self.recvint()
        assert sz < maxsz
        self.request.sendall(b"file(hex): ")
        r = 2*sz+1
        res = b''
        while r > len(res):
            res += self.request.recv(r)
        dat = bytes.fromhex(res.strip().decode('latin-1'))
        return dat

    def savefile(self, name, dat):
        fname = os.path.join(self.dir, name)
        with open(fname, "wb") as f:
            f.write(dat)
        self.request.sendall(f"[OK] {name} added\n".encode('latin-1'))

    def addsec(self):
        if len(self.data) > MAXNUM:
            self.request.sendall(b"[Error] too many secrets\n")
            raise LicenseRequired
        name = os.urandom(4).hex()
        self.data[name] = 1
        dat = self.recvfile(MAXFILESZ)
        self.savefile(name, dat)

    def upload(self):
        dat = self.recvfile(MAXTARSZ)
        c = ChaCha20.new(nonce=dat[:8], key=key)
        pt = c.decrypt(dat[8:])
        self.savefile("a.tar", pt)
        tname = os.path.join(self.dir, "a.tar")
        if not tarfile.is_tarfile(tname):
            self.request.sendall(b"[Error] not tar file\n")
            self.request.sendall(pt.hex().encode('latin-1')+b'\n')
            return
        f = tarfile.open(tname)
        cnt = 0
        for fname in f.getnames():
            if fname.startswith('/') or '..' in fname:
                self.request.sendall(b"[Error] you need a license to hack us\n")
                raise LicenseRequired
            cnt += 1
            if cnt > MAXNUM:
                break
        if len(self.data) + cnt > MAXNUM:
            self.request.sendall(b"[Error] too many files\n")
            raise LicenseRequired
        for fname in f.getnames():
            self.data[fname] = 1
        f.extractall(path=self.dir)
        os.unlink(tname)
        self.request.sendall(b"[OK] upload succeeded\n")

    def readsec(self):
        raise LicenseRequired

    def download(self):
        nonce = True
        for name in self.data:
            if self.data[name] == 0:
                nonce = False
        fname = os.path.join(self.dir, "a.tar")
        with tarfile.open("a.tar", 'w') as f:
            for name in self.data:
                f.add(name)
        with open(fname, 'rb') as f:
            cont = f.read()
        c = ChaCha20.new(key=key)
        dat = c.encrypt(cont)
        if nonce:
            dat = c.nonce+dat
        self.request.sendall(f"[OK] ctar file size: {len(dat)}\n".encode('latin-1'))
        self.request.sendall(dat.hex().encode('latin-1')+b'\n')

    def addflag(self):
        if len(self.data) > MAXNUM:
            self.request.sendall(b"[Error] too many secrets\n")
            raise LicenseRequired
        name = os.urandom(4).hex()
        self.data[name] = 0
        self.savefile(name, flag)

    def handle(self):
        if not self.proof_of_work():
            return
        self.data = {}
        self.dir = tempfile.mkdtemp()
        os.chdir(self.dir)
        signal.alarm(120)
        self.request.sendall(b"*** Welcome to ctar service ***\nUpload and archive your secrets with our super fancy homemade secure tar file format. You'll like it\nNotice: the file size is limited in your free trial\n")
        while True:
            try:
                self.request.sendall(b"1. add your secret\n2. upload ctar file\n3. read secrets\n4. download ctar file\n0. add flag\n> ")
                i = self.recvint()
                if i==1:
                    self.addsec()
                elif i==2:
                    self.upload()
                elif i==3:
                    self.readsec()
                elif i==4:
                    self.download()
                elif i==0:
                    self.addflag()
                else:
                    break
            except:
                pass
        os.system(f"rm -r {self.dir}")
        self.request.close()

class ForkedServer(socketserver.ForkingTCPServer, socketserver.TCPServer):
    pass

if __name__ == "__main__":
    HOST, PORT = '0.0.0.0', 10001
    server = ForkedServer((HOST, PORT), Task)
    server.allow_reuse_address = True
    server.serve_forever()

 

 

analysis

 

We can upload any data as long as its length is less than 100, and then re-download the data encrypted with chacha20.

Since download() returns the encrypted data along with the nonce used in chacha20, we can decrypt the downloaded data by using upload() with the nonce.

 

 

 

Additionally, we can observe the result of the decryption process using one-byte error

 

 

After calling addflag(), download() does not provide us with nonce to be used in upload().

This means we cannot decrypt the output of download().

 

 

 

cause

 

upload()

 

Since we know the filename of the flag file, if we include the flag_filename in a.tar, it becomes 'self.data=1'. 

But, the flag file will be overwritten with the content of a.tar, which is different from the original flag, during decompression.

 

 

 

However, overwriting can be prevented by triggering an error within the extractall() function, as it is enclosed within a try-except statement.

 

solve

 

1. addsec()

2. download()

3. addflag()

4. modify the filename in a.tar

5. generate an error by changing the mode and typeflag

6. calculate a checksum for .tar and then modify the checksum field.

7. upload() with that file

8. download() includes the flag

9. upload() to see the decrypt output

 

An error occurred when the mode was set to 0777 and the type flag was set to 3



from pwn import *
from hashlib import sha256

r = remote("chall.ctf.0ops.sjtu.cn", "30001")


''' POW '''
r.recvuntil(b'XXXX+')
suffix=r.recvuntil(b') == ').decode().split(')')[0]
sha=r.recvline()[:-1].decode()

print(suffix,sha)

alphabet="0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
def pow():
    for a1 in alphabet:
        for a2 in alphabet:
            for a3 in alphabet:
                for a4 in alphabet:
                    proof=a1+a2+a3+a4+suffix
                    if(sha256(proof.encode('latin-1')).hexdigest()==sha):
                        print('[+]pow found')
                        r.sendline((a1+a2+a3+a4).encode())
                        return
pow()
''''''


'''addsec()'''
r.sendlineafter("> ", "1")
r.sendlineafter("size: ", "3")
r.sendlineafter("file(hex): ", "aabbcc")
''''''


'''download()'''
r.sendlineafter("> ", "4")

r.recvuntil("ctar file size: ")
siz = r.recvline()[:-1]
dat = r.recvline()[:-1]



'''get a original file using upload()'''
r.sendlineafter("> ", "2")
r.sendlineafter("size: ", siz)
r.sendlineafter("file(hex): ", dat[:16] + b"00" + dat[18:])
r.recvuntil("[Error] not tar file\n")

server_tar = r.recvline()[:-1].decode()
server_tar = "2E" + server_tar[2:]

stream = []
for i in range(len(dat[16:])//2):
    stream.append(int(server_tar[2*i:2*i+2], 16) ^ int(dat[16+2*i:16+2*i+2], 16))



'''addflag()'''
r.sendlineafter("> ", "0")
flagfile_name = r.recvline()[:-1].split(b' ')[1]
assert len(flagfile_name) == 8
''''''



'''modify a.tar'''
data = bytes.fromhex(server_tar)
our_data = data[0x400:0x400+0x1f4]

# filename
our_data = flagfile_name + our_data[8:]
# mode
our_data = our_data[:100] + b"0000777\x00" + our_data[100+8:]
# typeflag
our_data = our_data[:156] + b"3" + our_data[156+1:]
''''''


'''calc checksum'''
chk_data = our_data[:148] + b" " * 8 + our_data[148+8:]
checksum = 0

for i in range(len(our_data)):
    checksum += chk_data[i]

# final
our_data = our_data[:148] + bytes("%06o\0" % checksum, "ascii") + b"\x20" + our_data[148+8:]
attack_tar = data[:0x400] + our_data + data[0x400+0x1f4:]
''''''




'''upload()'''
r.sendlineafter("> ", "2")
r.sendlineafter("size: ", siz)

payload = []
for i in range(len(stream)):
    payload.append(stream[i] ^ attack_tar[i])
r.sendlineafter("file(hex): ", dat[:16] + bytes(payload).hex().encode('latin-1'))



'''download()'''
r.sendlineafter("> ", "4")
r.recvuntil("ctar file size: ")
siz = r.recvline()[:-1]
dat = r.recvline()[:-1]



'''upload()'''
r.sendlineafter("> ", "2")
r.sendlineafter("size: ", siz)
r.sendlineafter("file(hex): ", dat[:16] + b"00" + dat[18:])
r.recvuntil("[Error] not tar file\n")
flag = bytes.fromhex(r.recvline()[:-1].decode())
for i in range(len(flag)):
    if b"flag" == flag[i:i+4]:
        print(flag[i:i+100])
r.interactive()

'writeups' 카테고리의 다른 글

2024MoveCTF  (0) 2024.01.28
2023 X-mas CTF (web3 - alpha hunter)  (0) 2023.12.31
GlacierCTF 2023 - smartcontract  (0) 2023.11.26
N1CTF 2023 - blockchain(pool by sec3)  (0) 2023.10.23
SECCON CTF 2023 Quals - [misc, blockchain](tokyo payload)  (0) 2023.09.22

+ Recent posts