我们是由Eur3kA和flappypig组成的联合战队r3kapig。本周末,我们参与了趋势科技举办的TrendMicro CTF 2018 Qualifier 并以第十名的成绩成功晋级12月在日本东京举办的TrendMicro CTF 2018 Final。我们决定把我们做出来的题目的writeup发出来分享给大家。
另外我们战队目前正在招募队员,欢迎想与我们一起玩的同学加入我们,尤其是Misc/Crypto的大佬,有意向的同学请联系[email protected]。给大佬们递茶。
由于是国际比赛,所以我们的首发wp为英文版,中文版正在路上~

Analysis-Offense

200

I just modified my callgrind solver to solve this challenge.

$ cat oracle.py
#!/usr/bin/python -u
#-*- coding:utf-8 -*-

# Let's exploit easy and quick!
# 1) apt install valgrind
# 2) use callgrind to find instruction count

flag = 'TMCTF{'
n = 0

import os
import sys

# format given by admin
charset = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789{}"
while True:
    n += 1
    total_call_count = {}
    for i in charset:
        cmd = "valgrind --tool=callgrind --dump-instr=yes --callgrind-out-file=temp/call_count ./oracle '" + flag + i + "A'  2>&1"
        # print(cmd)
        res =  os.popen(cmd).read()

        call_count = res.split("Collected : ")[1].split()[0]
        call_count = int(call_count)
        # total_call_count { 'call_count': [occured_count, occured_by], ... }
        if not total_call_count.get(call_count):
                total_call_count[call_count] = [1, [i]]
        else:
                total_call_count[call_count][0] += 1
                total_call_count[call_count][1].append(i)
        print(n, i, call_count)
    ## get lowest/highest idx,
    idx_call_count = total_call_count.keys()
    print(idx_call_count)
    idx_call_count.sort()
    highest_count_idx = idx_call_count[-1]
    lowest_count_idx = idx_call_count[0]
    # get highest idx
    flag_char = total_call_count[highest_count_idx][1][0]
    flag += flag_char
    print(n, total_call_count, highest_count_idx, flag)

300

We get 3 rsa public keys here, and there are no other attack method, just GCD them and found the GCD number to factor 3 n.

c1=18700320110367574655449823553009212724937318442101140581378358928204994827498139841897479168675123789374462637095265564472109735802305521045676412446455683615469865332270051569768255072111079626023422

e1=65537
n1=23795719145225386804055015945976331504878851440464956768596487167710701468817080174616923533397144140667518414516928416724767417895751634838329442802874972281385084714429143592029962130216053890866347



c2=27979368157170890767030069060194038526134599497456846620984054211906413024410400026053694007247773572972357106574636186987337336771777265971389911503143036021889778839064900818858188026318442675667707

e2=65537
n2=46914096084767238967814493997294740286838053572386502727910903794939283633197997427383196569296188299557978279732421725469482678512672280108542428152186999218210536447287087212703368704976239539968977



c3=24084879450015204136831744759734371350696278325227327049743434712309456808867398488915798176282769616955247276506807739249439515225213919008982824219656080794207250454008942016125074768497986930713993

e3=65537
n3=24543003393712692769038137223030855401835344295968717177380639898023646407807465197761211529143336105057325706788229129519925129413109571220297378014990693203802558792781281981621549760273376606206491


def int2text(message):
    result=""
    while message>0:
        result = chr(int(message)%int(256))+ result
        message=int(message)/int(256)
    return result

import primefac

p1=primefac.gcd(n1,n2)
q1=n1/p1
d=primefac.modinv(e1,(p1-1)*(q1-1))%((p1-1)*(q1-1))
m1=pow(c1,d,n1)
print int2text(m1)

p2=p1
q2=n2/p2
d=primefac.modinv(e2,(p2-1)*(q2-1))%((p2-1)*(q2-1))
m2=pow(c2,d,n2)
print int2text(m2)


p3=primefac.gcd(n2,n3)
q3=n3/p3
d=primefac.modinv(e3,(p3-1)*(q3-1))%((p3-1)*(q3-1))
m3=pow(c3,d,n3)
print int2text(m3)

400

This challenge is a white-box protocol analysis aimed to break the authentication system.

Following is the work flow of this authenticatoin system:

  1. the user send a login request with username to the server
  2. the server send Nonce and ChallengeCookie = Base64Encode(RandomIV | AES128-CBC(RandomIV,Nonce | U | Timestamp, KS)) back to the user
  3. the user send the challenge response(R = SHA256(Nonce | P), where P is the password for authentication) to the server
  4. the server verify whether the password and username is right or not. if right the server will issue a ticket to user, Ticket = Base64Encode(RandomIV | AES128-CBC(RandomIV,Identity | TicketTimestamp, KS)) where Identity = JSON string: { user: U, groups: [ G1, G2, ... ] }
    where G1, G2, ... are the names of the groups that U belongs to

  5. the user can use the ticket to run some command, if the username in the ticket is admin, we can run the command "getflag"

to break this authentication protocol, we can send a login request with username 'AAAAAAAA' + '{"user": "admin", "groups": ["admin"]}\x00' to the server.

the server will response with Base64Encode(RandomIV | AES128-CBC(RandomIV, Nonce | 'AAAAAAAA{"user": "admin", "groups": ["admin"]}\x00 | Timestamp, KS)).

since the AES128-CBC is a block cipher with CBC mode, we can use the AES128-CBC(RandomIV, Nonce | 'AAAAAAAA) as the newIV, and the remain part will be AES128-CBC(newIV,{"user": "admin", "groups": ["admin"]}\x00 | Timestamp), which is a valid admin ticket.

then we can use the ticket to run getflag command and get the flag.

from pwn import *
import base64
from Crypto.Cipher import AES


io=remote("localhost",9999)

def toNullTerminatedUtf8(s):
    return unicode(s).encode("utf-8") + "\x00"
payload="\x01"+"A"*8+'{"user": "admin", "groups": ["admin"]}\x00'
io.send(payload)
data=io.recv(1000)
nounce=data[1:9]
cookie_b64=data[9:]
cookie = base64.b64decode(cookie_b64)
iv=cookie[:16]


fake_ticket=cookie[16:]
fake_ticket_b64=base64.b64encode(fake_ticket)

cmd="\x06"+fake_ticket_b64+"\x00"+"getflag\x00"
io.send(cmd)
io.interactive()

Reverse-Binary

100

We first find base64-encoded data from the pcap file.

Then, reverse the pyinstaller binary and modify the script to solve the challenge.

import struct, os, time, threading, urllib, requests, ctypes, base64
from Cryptodome.Cipher import AES, ARC4
from Cryptodome.Hash import SHA

infile = 'flag'
encfile = 'orig.CRYPTED'
keyfile = 'keyfile'
sz = 1024
bs = 16

def decrypt_request():
    pcap_req = "35998fdb7fe3b7940b9375a68a654ff949c58dcb9b1aebb048d6aa74d905b7b0c6e04b404eb61129f92ad912703850201582ce39e77bfe739fec528741b202f8923a9f8d6303617d8e6e35a0d644115e238522c6d0cacd1afdae23050452c998e39a"
    _hash_chksum = pcap_req[:40]
    _hash_content = pcap_req[40:]
    dec = ARC4.new(_hash_chksum.decode('hex'))
    return dec.decrypt(_hash_content.decode('hex'))
    # 'id=d1&key=2f87011fadc6c2f7376117867621b606&iv=95bc0ed56ab0e730b64cce91c9fe9390'

def generate_keyfile():
    # n = hex(ord(id) + bs)
    n = hex(ord('d1'.decode('hex')) + 16)
    iv = "95bc0ed56ab0e730b64cce91c9fe9390".decode('hex')
    key = "2f87011fadc6c2f7376117867621b606".decode('hex')

    key = ''.join((chr(ord(x) ^ int(n, 16)) for x in key))
    iv = ''.join((chr(ord(y) ^ int(n, 16)) for y in iv))
    keyfile = open("keyfile", "wb")
    keyfile.write(key + iv)
    keyfile.close()
    print(n, iv, key)
    return True

def decrypt():
    global keyfile
    key = ''
    iv = ''
    if not os.path.exists(encfile):
        exit(0)
    while True:
        time.sleep(10)
        if os.path.exists(keyfile):
            keyin = open(keyfile, 'rb')
            key = keyin.read(bs)
            iv = keyin.read(bs)
            if len(key) != 0 and len(iv) != 0:
                aes = AES.new(key, AES.MODE_CBC, iv)
                fin = open(encfile, 'r')
                fsz = struct.unpack('<H', fin.read(struct.calcsize('<H')))[0]
                fout = open(infile, 'w')
                fin.seek(2, 0)
                while True:
                    data = fin.read(sz)
                    n = len(data)
                    if n == 0:
                        break
                    decrypted = aes.decrypt(data)
                    n = len(decrypted)
                    if fsz > n:
                        fout.write(decrypted)
                    else:
                        fout.write(decrypted[:fsz])
                    fsz -= n

                fin.close()
                os.remove(encfile)
                break

print(decrypt_request())
generate_keyfile()
decrypt()

# ----Trend Microt CTF 2018. Flag for this challenge is: TMCTF{MJB1200}

200

A 32-bit shellcode injector using IAT hook. DragQueryFileW in notepad.exe is hooked. The shellcode is written into .text section of shell32.dll .

from ida_bytes import get_bytes, patch_bytes
buf = bytearray(get_bytes(0x4031A0, 376))
for i in xrange(len(buf)):
    buf[i] ^= [0xDE, 0xAD, 0xF0, 0x0D][i % 4]
patch_bytes(0x4031A0, str(buf))

Run 32-bit notepad.exe and drop a file named "zdi_ftw", the rot13-enctypted flag is shown.

TMCTF{want_sum_iat_hooking}

300

The PE file is packed but it's easy to unpack. It detects debugger by IsDebuggerPresent, and Virtual Machine by checking the presense of specific .sys file. Checks whether the hour field of current time is 5.

TMCTF{F14g1s::____1G}

400

A river crossing puzzle with a servant(7), a dog(4), a father(6), a mother(5), two sons(2,3) and two daughters(0,1).

sidev = 1
def side():
    global sidev
    t = sidev
    sidev = 0 if sidev else 1
    return t

def a(x, y = None):
    if(y == None):
        return "\xD0" + chr(side()) + chr(1 << x)
    else:
        return "\xD1" + chr(side()) + chr(1 << x) + chr(1 << y)

s = a(7, 4) + a(7)
s += a(7, 0) + a(7, 4)
s += a(5, 1) + a(5)
s += a(6, 5) + a(6)
s += a(7, 4) + a(5)
s += a(6, 5) + a(6)
s += a(6, 2) + a(7, 4)
s += a(7, 3) + a(7)
s += a(7, 4)
print(s.encode("hex").lower())

Reverse-Other

100

Run the binary directly, it ouput "flag is here", but run the binary under debugger, it output "nice try, punk".

so it seems like there are anti-dbg techniques deployed on this binary.

No worry, there are various ways to patch the binary and bypass the anti-debugging. once we find the function that output "flag is here", we found the flag. since the flag is reside in that function.

200

Simply unpack upx, some weird strings appear in main function.
I noticed this program will self-open and read all data in memory by using API monitor.
After some reversing , I found that it will compare the section name in memory.
Simply modify the code , let program compare the encrypted flag string with section name, you got flag in debugger instantly.

400

This Challenge is a python bytecode reversing challenge.

import sys


def verify_flag(flag): pass
verify_flag.__code__ = verify_flag.__code__.__class__( 1 , 20 , 9 , 67 , 'y\x0c\x00|\x00\x00d\x01\x00\x17\x01Wn%\x00\x01\x01\x01x9\x00|\x00\x00D]\x10\x00}\x01\x00|\x01\x00|\x01\x007}\x01\x00q\x19\x00W~\x01\x00n\x1b\x00Xx\x17\x00t\x00\x00rJ\x00|\x00\x00|\x00\x007}\x00\x00q7\x00W~\x00\x00y\x08\x00t\x01\x00\x01Wn\x07\x00\x01\x01\x01n\x01\x00Xt\x02\x00|\x00\x00\x83\x01\x00d\x01\x00k\x02\x00sx\x00t\x03\x00r|\x00t\x03\x00S|\x00\x00j\x04\x00d\x02\x00\x83\x01\x00s\x8f\x00t\x03\x00S|\x00\x00j\x05\x00d\x03\x00\x83\x01\x00s\xb4\x00t\x03\x00S|\x00\x00j\x06\x00d\x02\x00\x83\x01\x00}\x00\x00na\x00t\x02\x00|\x00\x00\x83\x01\x00}\x02\x00|\x00\x00j\x07\x00d\x02\x00d\x04\x00\x83\x02\x00d\x05\x00\x19j\x08\x00d\x03\x00d\x04\x00\x83\x02\x00d\x01\x00\x19}\x00\x00y \x00t\x02\x00|\x00\x00\x83\x01\x00d\x06\x00\x17|\x02\x00k\x02\x00s\x05\x01t\t\x00\x82\x01\x00Wn\x08\x00\x01\x01\x01t\x03\x00SXd&\x00\x01|\x00\x00d\x08\x00j\x06\x00d\t\x00d\n\x00\x83\x02\x00k\x02\x00r1\x01t\x03\x00St\n\x00t\x0b\x00|\x00\x00\x83\x02\x00}\x00\x00t\x02\x00|\x00\x00\x83\x01\x00}\x02\x00|\x02\x00d\x0b\x00k\x03\x00r\\\x01t\x03\x00St\x0c\x00|\x00\x00\x83\x01\x00}\x03\x00|\x03\x00|\x02\x00\x16d\x0c\x00k\x03\x00r|\x01t\x03\x00S|\x03\x00|\x02\x00\x15}\x04\x00t\r\x00|\x04\x00\x83\x01\x00d\r\x00k\x03\x00r\x9c\x01t\x03\x00Sg\x00\x00|\x00\x00D]\x10\x00}\x05\x00|\x05\x00|\x04\x00A^\x02\x00q\xa3\x01}\x00\x00t\x0e\x00t\x0f\x00|\x00\x00\x83\x01\x00\x83\x01\x00}\x06\x00d\x01\x00g\x01\x00d\x07\x00\x14}\x07\x00d\x01\x00g\x01\x00d\x07\x00\x14}\x08\x00d\x01\x00g\x01\x00d\x07\x00\x14}\t\x00d\x01\x00g\x01\x00d\x07\x00\x14}\n\x00d\x01\x00g\x01\x00d\x07\x00\x14}\x0b\x00d\x01\x00g\x01\x00d\x07\x00\x14}\x0c\x00d\x01\x00g\x01\x00d\x07\x00\x14}\r\x00d\x01\x00g\x01\x00d\x07\x00\x14}\x0e\x00d\x01\x00g\x01\x00d\x07\x00\x14}\x0f\x00d\x01\x00g\x01\x00d\x07\x00\x14}\x10\x00xS\x02t\x10\x00t\x02\x00|\x07\x00\x83\x01\x00\x83\x01\x00D]?\x02}\x11\x00x\xc6\x01t\x10\x00t\x02\x00|\x08\x00\x83\x01\x00d\x04\x00\x18\x83\x01\x00D]\xae\x01}\x12\x00|\x07\x00|\x11\x00c\x02\x00\x19|\x00\x00|\x11\x00|\x12\x00\x17\x19N\x03<|\x08\x00|\x11\x00\x19|\x00\x00|\x11\x00|\x12\x00\x17\x19\x17d\x0e\x00k\x04\x00r\xbb\x02t\x03\x00S|\x08\x00|\x11\x00c\x02\x00\x19|\x00\x00|\x11\x00|\x12\x00\x17\x197\x03<|\t\x00|\x11\x00c\x02\x00\x19|\x00\x00|\x11\x00|\x12\x00\x14\x19N\x03<|\n\x00|\x11\x00\x19|\x00\x00|\x11\x00|\x12\x00\x14\x19\x17d\x0e\x00k\x04\x00r\x0b\x03t\x03\x00S|\n\x00|\x11\x00c\x02\x00\x19|\x00\x00|\x11\x00|\x12\x00\x14\x197\x03<|\x0b\x00|\x11\x00c\x02\x00\x19|\x00\x00d\x0f\x00|\x11\x00|\x12\x00\x14\x17\x19N\x03<|\x0c\x00|\x11\x00\x19|\x00\x00d\x0f\x00|\x11\x00|\x12\x00\x14\x17\x19\x17d\x0e\x00k\x04\x00rc\x03t\x03\x00S|\x0c\x00|\x11\x00c\x02\x00\x19|\x00\x00d\x0f\x00|\x11\x00|\x12\x00\x14\x17\x197\x03<|\r\x00|\x11\x00c\x02\x00\x19|\x06\x00d\x0f\x00|\x11\x00|\x12\x00\x14\x17\x19N\x03<|\x0e\x00|\x11\x00\x19|\x06\x00d\x0f\x00|\x11\x00|\x12\x00\x14\x17\x19\x17d\x0e\x00k\x04\x00r\xbf\x03t\x03\x00S|\x0e\x00|\x11\x00c\x02\x00\x19|\x06\x00d\x0f\x00|\x11\x00|\x12\x00\x14\x17\x197\x03<|\x0f\x00|\x11\x00c\x02\x00\x19|\x06\x00|\x11\x00|\x12\x00\x17\x19N\x03<|\x10\x00|\x11\x00\x19|\x06\x00|\x11\x00|\x12\x00\x17\x19\x17d\x0e\x00k\x04\x00r\x13\x04t\x03\x00S|\x10\x00|\x11\x00c\x02\x00\x19|\x06\x00|\x11\x00|\x12\x00\x17\x197\x03<q}\x02W|\x07\x00|\x11\x00c\x02\x00\x19d\x10\x007\x03<|\t\x00|\x11\x00c\x02\x00\x19d\x10\x007\x03<|\x0b\x00|\x11\x00c\x02\x00\x19d\x10\x007\x03<|\r\x00|\x11\x00c\x02\x00\x19d\x10\x007\x03<|\x0f\x00|\x11\x00c\x02\x00\x19d\x10\x007\x03<|\x0c\x00|\x11\x00c\x02\x00\x19d\x0f\x007\x03<|\x10\x00|\x11\x00c\x02\x00\x19d\x04\x007\x03<q`\x02WxJ\x00|\x07\x00|\t\x00|\x0b\x00|\r\x00|\x0f\x00|\n\x00|\x0c\x00|\x0e\x00|\x10\x00g\t\x00D]\'\x00}\x13\x00x\x1e\x00|\x13\x00D]\x16\x00}\x05\x00|\x05\x00d\x0e\x00k\x04\x00r\xd2\x04t\x03\x00Sq\xd2\x04Wq\xc5\x04Wd\x11\x00j\x11\x00t\n\x00t\r\x00|\x07\x00\x83\x02\x00\x83\x01\x00d\x12\x00k\x03\x00r\x12\x05t\x03\x00Sy&\x00d\x11\x00j\x11\x00t\n\x00t\r\x00|\x08\x00\x83\x02\x00\x83\x01\x00d\x13\x00k\x03\x00r7\x05t\x03\x00SWn\x12\x00\x04t\x12\x00k\n\x00rL\x05\x01\x01\x01t\x03\x00SXd\x11\x00j\x11\x00t\n\x00t\r\x00|\t\x00\x83\x02\x00\x83\x01\x00d\x14\x00k\x03\x00ro\x05t\x03\x00St\x13\x00|\n\x00\x83\x01\x00d\'\x00k\x03\x00r\x85\x05t\x03\x00Sd\x11\x00j\x11\x00t\n\x00t\r\x00|\x0b\x00\x83\x02\x00\x83\x01\x00d\x1a\x00k\x03\x00r\xac\x05d\x1b\x00GHt\x03\x00Sd\x11\x00j\x11\x00t\n\x00t\r\x00|\x0c\x00\x83\x02\x00\x83\x01\x00d\x1c\x00k\x03\x00r\xd3\x05d\x1d\x00GHt\x03\x00Sd\x11\x00j\x11\x00t\n\x00t\r\x00|\r\x00\x83\x02\x00\x83\x01\x00d\x1e\x00k\x03\x00r\xfa\x05d\x1f\x00GHt\x03\x00Sd\x11\x00j\x11\x00t\n\x00t\r\x00|\x0e\x00\x83\x02\x00\x83\x01\x00d \x00k\x03\x00r!\x06d!\x00GHt\x03\x00Sd\x11\x00j\x11\x00t\n\x00t\r\x00|\x0f\x00\x83\x02\x00\x83\x01\x00d"\x00k\x03\x00rH\x06d#\x00GHt\x03\x00Sd\x11\x00j\x11\x00t\n\x00t\r\x00|\x10\x00\x83\x02\x00\x83\x01\x00d$\x00k\x03\x00ro\x06d%\x00GHt\x03\x00St\x00\x00S' , (None, 0, 'TMCTF{', '}', 1, -1, 7, 5, 'ReadEaring', 'adEa', 'dHer', 24, 9, 'h', 255, 8, 32, '', 'R) +6', 'l1:C(', ' RP%A', 236, 108, 102, 169, 93, ' L30Z', 'X2', ' j36~', 's2', ' M2S+', 'X3', '4e\x9c{E', 'S3', '6!2$D', 'X4', ']PaSs', 'S4', 10, (236, 108, 102, 169, 93)) , ('True', '\xe0\xa1\xb5\xe0\xa1\xb5HA', 'len', 'False', 'startswith', 'endswith', 'replace', 'split', 'rsplit', 'AssertionError', 'map', 'ord', 'sum', 'chr'

       
       
       

    

Hacking more

...