格式化字符串漏洞实验(转载)

格式化字符串漏洞实验
一、 实验描述
格式化字符串漏洞是由像 printf(user_input) 这样的代码引起的,其中 user_input 是用户输入的数据,具有 Set-UID root 权限的这类程序在运行的时候,printf 语句将会变得非常危险,因为它可能会导致下面的结果:
使得程序崩溃
任意一块内存读取数据
修改任意一块内存里的数据

最后一种结果是非常危险的,因为它允许用户修改 set-UID root 程序内部变量的值,从而改变这些程序的行为。
本实验将会提供一个具有格式化漏洞的程序,我们将制定一个计划来探索这些漏洞。
二、实验预备知识讲解
2.1 什么是格式化字符串?
printf ("The magic number is: %d", 1911);

试观察运行以上语句,会发现字符串"The magic number is: %d"中的格式符%d 被参数(1911)替换,因此输出变成了“The magic number is: 1911”。
格式化字符串大致就是这么一回事啦。
除了表示十进制数的%d,还有不少其他形式的格式符,一起来认识一下吧~
格式符
含义
含义(英)

%d
十进制数(int)
decimal

%u
无符号十进制数 (unsigned int)
unsigned decimal

%x
十六进制数 (unsigned int)
hexadecimal

%s
字符串 ((const) (unsigned) char *)
string
引用(指针)

%n
%n 符号以前输入的字符数量 (* int)
number of bytes written so far
引用(指针)

( *%n的使用将在 2.5 节中做出说明)
2.2 栈与格式化字符串
格式化函数的行为由格式化字符串控制,printf 函数从栈上取得参数。
printf ("a has value %d, b has value %d, c is at address: %08x\n",a, b, &c);


2.3 如果参数数量不匹配会发生什么?
如果只有一个不匹配会发生什么?
printf ("a has value %d, b has value %d, c is at address: %08x\n",a, b);

在上面的例子中格式字符串需要 3 个参数,但程序只提供了 2 个。
该程序能够通过编译么?printf()是一个参数长度可变函数。因此,仅仅看参数数量是看不出问题的。
为了查出不匹配,编译器需要了解 printf()的运行机制,然而编译器通常不做这类分析。
有些时候,格式字符串并不是一个常量字符串,它在程序运行期间生成(比如用户输入),因此,编译器无法发现不匹配。

那么 printf()函数自身能检测到不匹配么?printf()从栈上取得参数,如果格式字符串需要 3 个参数,它会从栈上取 3 个,除非栈被标记了边界,printf()并不知道自己是否会用完提供的所有参数。
既然没有那样的边界标记。printf()会持续从栈上抓取数据,在一个参数数量不匹配的例子中,它会抓取到一些不属于该函数调用到的数据。

如果有人特意准备数据让 printf 抓取会发生什么呢?

2.4 访问任意位置内存
我们需要得到一段数据的内存地址,但我们无法修改代码,供我们使用的只有格式字符串。
如果我们调用 printf(%s) 时没有指明内存地址, 那么目标地址就可以通过 printf 函数,在栈上的任意位置获取。printf 函数维护一个初始栈指针,所以能够得到所有参数在栈中的位置
观察: 格式字符串位于栈上. 如果我们可以把目标地址编码进格式字符串,那样目标地址也会存在于栈上,在接下来的例子里,格式字符串将保存在栈上的缓冲区中。

int main(int argc, char argv[]){ char user_input[100]; ... ... / other variable definitions and statements / scanf("%s", user_input); / getting a string from user / printf(user_input); / Vulnerable place */ return 0;}

如果我们让 printf 函数得到格式字符串中的目标内存地址 (该地址也存在于栈上), 我们就可以访问该地址。(注:代码中引号内容为 user_input 数组内容的展开)
printf ("\x10\x01\x48\x08 %x %x %x %x %s");

\x10\x01\x48\x08 是目标地址的四个字节, 在 C 语言中, \x10 告诉编译器将一个 16 进制数 0x10 放于当前位置(占 1 字节)。如果去掉前缀\x10 就相当于两个 ascii 字符 1 和 0 了,这就不是我们所期望的结果了。

%x 导致栈指针向格式字符串的方向移动(参考 1.2 节)

下图解释了攻击方式,如果用户输入中包含了以下格式字符串

如图所示,我们使用四个%x 来移动 printf 函数的栈指针到我们存储格式字符串的位置,一旦到了目标位置,我们使用%s 来打印,它会打印位于地址 0x10014808 的内容,因为是将其作为字符串来处理,所以会一直打印到结束符为止。

user_input 数组到传给 printf 函数参数的地址之间的栈空间不是为了 printf 函数准备的。但是,因为程序本身存在格式字符串漏洞,所以 printf 会把这段内存当作传入的参数来匹配%x。
最大的挑战就是想方设法找出 printf 函数栈指针(函数取参地址)到 user_input 数组的这一段距离是多少,这段距离决定了你需要在%s 之前输入多少个%x。

2.5 在内存中写一个数字
%n: 该符号前输入的字符数量会被存储到对应的参数中去
int i;printf ("12345%n", &i);

数字 5(%n 前的字符数量)将会被写入 i 中
运用同样的方法在访问任意地址内存的时候,我们可以将一个数字写入指定的内存中。只要将上一小节(1.4)的%s 替换成%n 就能够覆盖 0x10014808 的内容。
利用这个方法,攻击者可以做以下事情:重写程序标识控制访问权限
重写栈或者函数等等的返回地址

然而,写入的值是由%n 之前的字符数量决定的。真的有办法能够写入任意数值么?用最古老的计数方式, 为了写 1000,就填充 1000 个字符吧。
为了防止过长的格式字符串,我们可以使用一个宽度指定的格式指示器。(比如(%0 数字 x)就会左填充预期数量的 0 符号)

三、 实验内容
实验 1
用户需要输入一段数据,数据保存在 user_input 数组中,程序会使用 printf 函数打印数据内容,并且该程序以 root 权限运行。更加可喜的是,这个程序存在一个格式化漏洞。让我们来看看利用这些漏洞可以搞些什么破坏。
程序说明:
程序内存中存在两个秘密值,我们想要知道这两个值,但发现无法通过读二进制代码的方式来获取它们(实验中为了简单起见,硬编码这些秘密值为 0x44 和 0x55)。尽管我们不知道它们的值,但要得到它们的内存地址倒不是特别困难,因为对大多数系统而言,每次运行程序,这些内存地址基本上是不变的。实验假设我们已经知道了这些内存地址,为了达到这个目的,程序特意为我们打出了这些地址。
有了这些前提以后我们需要达到以下目标:
找出 secret[1]的值
修改 secret[1]的值
修改 secret[1]为期望值

注意:因为实验环境是 64 位系统,所以需要使用%016llx 才能读取整个字。但为了简便起见,对程序进行了修改了,使用%08x 也能完成实验。

有了之前预备知识的铺垫,先自己尝试一下,祝玩的愉快:)
程序如下:
/* vul_prog.c / include <stdlib.h>include <stdio.h>define SECRET1 0x44define SECRET2 0x55int main(int argc, char argv[]){ char user_input[100]; int secret; long int_input; int a, b, c, d; / other variables, not used here./ / The secret value is stored on the heap / secret = (int ) malloc(2sizeof(int)); / getting the secret / secret[0] = SECRET1; secret[1] = SECRET2; printf("The variable secret's address is 0x%8x (on stack)\n", &secret); printf("The variable secret's value is 0x%8x (on heap)\n", secret); printf("secret[0]'s address is 0x%8x (on heap)\n", &secret[0]); printf("secret[1]'s address is 0x%8x (on heap)\n", &secret[1]); printf("Please enter a decimal integer\n"); scanf("%d", &int_input); / getting an input from user / printf("Please enter a string\n"); scanf("%s", user_input); / getting a string from user / / Vulnerable place / printf(user_input); printf("\n"); / Verify whether your attack is successful */ printf("The original secrets: 0x%x -- 0x%x\n", SECRET1, SECRET2); printf("The new secrets: 0x%x -- 0x%x\n", secret[0], secret[1]); return 0;}

(ps: 编译时可以添加以下参数关掉栈保护。)
gcc -z execstack -fno-stack-protector -o vul_prog vul_prog.c

一点小提示:你会发现 secret[0]和 secret[1]存在于 malloc 出的堆上,我们也知道 secret 的值存在于栈上,如果你想覆盖 secret[0]的值,ok,它的地址就在栈上,你完全可以利用格式化字符串的漏洞来达到目的。然而尽管 secret[1]就在它的兄弟 0 的旁边,你还是没办法从栈上获得它的地址,这对你来说构成了一个挑战,因为没有它的地址你怎么利用格式字符串读写呢。但是真的就没招了么?
3.1.1 找出 secret[1]的值
1.首先定位 int_input 的位置,这样就确认了%s 在格式字符串中的位置。



2.输入 secret[1]的地址,记得做进制转换,同时在格式字符串中加入%s。



大功告成!U 的 ascii 码就是 55。
3.1.2 修改 secret[1]的值
1.只要求修改,不要求改什么?简单!不明白%n 用法的可以往前回顾一下。

大功告成 x2!
3.1.3 修改 secret[1]为期望值
1.要改成自己期望的值,咋办?填 1000 岂不累死?!可以用填充嘛!



哦对了,0x3e8 = 1000。
大功告成 x3!
实验 2
现在让我们把第一个 scanf 语句去掉,并去掉与 int_input 变量相关的所有语句。同时设置关闭地址随机化选项。
sysctl -w kernel.randomize_va_space=0

关闭地址随机化后,这样每次运行程序得到的 secret 地址就都一样了,让我们再来一次实验 1 中的攻击吧。不过在此之前你需要知道这些:
如何让 scanf()接受任意数字?
通常,scanf 将会为你停顿,打印输入。有时,你想要编程得 到一个数 0x05 (不是字符“5”),不幸的是,当你将“5"作为输入,scanf 实际得到的是 5 的 ASCII 值 0x35,而不是 0x05。
这个问题的一个解决办法是使用文件。我们可以很容易地写一 C 程 序将 0x05 (不是“5")存入一个文件(我们叫它 my string),然后运行输入被重定向到 mystring 的漏洞程序。这样,scanf 将从文件 mystring 中获得输入而不是键盘。 你需要注意一些特殊数字,如 0x0A (新行),0x0C (换页),0x0D (返回),0x20 (空 格),scanf 将它们视为分隔符,如果在 scanf 里我们仅有一个"%s"的话,它将停止读取这些 特殊符号之后的任何内容。如果这些数字出现在地址屮,你必须想办法避开。为简化任务, 如果你不走运地在 secret 的地址中碰到这些特殊数字,我们允许你在为 secret[2]分配内存地址 之前加上一个 malloc 语句。这个额外的 malloc 语句可以改变 secret 地址的值。
以下程序将一个格式化字符串写入了一个叫 mystring 的文件,前 4 个字节由任意你想放 入格式化字符串的数字构成,接下来的字节由键盘输入。
include <sys/types.h>include <sys/stat.h>include <fcntl.h>int main(){ char buf[1000]; int fp, size; unsigned int address; / Putting any number you like at the beginning of the format string */ address = (unsigned int *) buf; address = 0x113222580; / Getting the rest of the format string / scanf("%s", buf+4); size = strlen(buf+4) + 4; printf("The string length is %d\n", size); / Writing buf to "mystring" */ fp = open("mystring", O_RDWR | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR); if (fp != -1) { write(fp, buf, size); close(fp); } else { printf("Open failed!\n"); }}

3.2.1 修改 secret[0]的值
让我们先以上面提供的写程序为基础,熟悉一下基础流程。
修改 vul_prog.c 后编译 vul_prog.c 与 write_string.c
然后通过 write_string 程序将内容输入进 mystring 文件中,文件内容包括代码中加入的头四个字节和你之后输入的内容。
写入文件后,输入以下命令:
./vul_prog < mystring

图片描述信息

大功告成!
0x4c = 76 = 88+8 个逗号+开头 4 个字节。
四、 练习
在实验楼环境安步骤进行实验,并截图
您已经完成本课程的所有实验,
干的漂亮!*
版权声明
本课程所涉及的实验来自Syracuse SEED labs,并在此基础上为适配实验楼网站环境进行修改,修改后的实验文档仍然遵循 GNU Free Documentation License。
本课程文档 github 链接:https://github.com/shiyanlou/seedlab
Syracuse SEED labs版权声明:
Copyright Statement Copyright 2006 – 2009 Wenliang Du, Syracuse University. The development of this document is funded by the National Science Foundation’s Course, Curriculum, and Laboratory Improvement (CCLI) program under Award No. 0618680 and 0231122. Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.2 or any later version published by the Free Software Foundation. A copy of the license can befound at http://www.gnu.org/licenses/fdl.html.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容