我们是由Eur3kA和flappypig组成的联合战队r3kapig。本周末,我们参与了趋势科技举办的TrendMicro CTF 2018 Qualifier 并以第十名的成绩成功晋级12月在日本东京举办的TrendMicro CTF 2018 Final。我们决定把我们做出来的题目的writeup发出来分享给大家。
另外我们战队目前正在招募队员,欢迎想与我们一起玩的同学加入我们,尤其是Misc/Crypto的大佬,有意向的同学请联系[email protected]。给大佬们递茶。
由于是国际比赛,所以我们的首发wp为英文版,中文版正在路上~
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)
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)
This challenge is a white-box protocol analysis aimed to break the authentication system.
Following is the work flow of this authenticatoin system:
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
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()
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}
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}
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}
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())
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.
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.
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'