BuuCTF Pwn WP
BuuCTF Pwn WP
WP作者:WZN
题目地址:
rip
知识点:栈溢出 re2text
先看一下文件进制和保护措施
很好,什么都没开,我们使用64位的ida分析一下文件
主函数如图
gets()函数存在明显风险,双击s,发现占15个字符
查看函数,发现已有后门函数fun
构造exp,因为pwn1为64位程序,所以要补充8字节填上esp,返回fun的地址
1 | from pwn import * |
本脚本在本地打通了程序,但是当笔者打靶机(Unbuntu18)的时候,却怎么也打不通,为什么呢?
栈对齐
—— ubuntu libc 为 libc2.27,高版本的libc要求是返回地址必须是16字节对齐(也可以说,远程环境是 ubuntu18,64位的程序则需要考虑堆栈平衡的问题)
我们通过添加一个 ret 指令来使16字节对齐
ROPgadget —binary 文件名 —only “pop|ret”
找到ret地址,再次构造exp
1 | from pwn import * |
打通了!!!
warmup_csaw_2016
知识点:栈溢出 re2text
首先检查保护措施
什么都没开,我直呼好耶!
接着ida分析一下,F5查看伪代码
可以很明显的的发现gets函数存在明显的栈溢出风险,同时我们注意到了sub_40060D函数
可以看到该段是具有可执行的权限,而0x0804A080
正好在此区间
1 | CODE |
其中明显有获取flag的指令,我们的思路很明确了,通过栈溢出,返回到system函数的地址即可
在这里给出两个获取偏移地址的方法:
获取偏移地址
方法一
我们观察到gets函数读入的是v5,而v5偏移量为0x40,再加上64位ELF文件填充esp需要8字节,即72字节
方法二
使用gdb来获取
使用pattern create 200
生成溢出字符,但注意,在生成时要保证其能覆盖到RIP
执行 r
或者 start
命令让程序运行。//注意 start
命令执行后,还需执行 contin
命令。
在 please input
命令后,将之前生成的溢出字符串粘贴上去。
(1)得到RBP寄存器中 ‘AAdAA3AA’ 。往该字符串后,随便复制一串,进行偏移量计算
执行 pattern offset xxxxxx
命令
(2)复制 stack
复制栈顶的字符串 前四个字节(==64 bits为前8个字节==) 计算偏移量
如上
构造exp即可
1 | from pwn import * |
ciscn_2019_n_1
知识点:栈溢出 覆盖变量值
首先查看保护机制
跟上面几题不同,本题开启了栈不可执行(NX),这就是说,上两题中通过直接栈溢出执行shellcode的思路不再适用,下面我们通过ida来分析,首先看主函数
主函数并没有什么明显的突破点,不过主函数中间还有个func()函数,让我们去看看它
可以发现,func()函数中存在着明显的漏洞,而且,函数中包括了查看flag的命令,通过观察函数我们知道,当v2的值为11.28125时会执行此命令,至此,我们的思路已经非常清晰了——通过v1进行栈溢出,改写v2的值,使cat flag
的命令执行
观察栈
可知,v1所占的空间为0x30 - 0x04
(0x2c)(这里还有第二种理解方法,通过本题的第一幅图我们知道,v2和esp距离为2c,所以直接填上0x2c),同时我们写要输入v2的值(16进制呦),构造如下wp
1 | from pwn import * |
pwn1_sctf_2016
知识点:栈溢出 re2text
第一步,查看保护措施
打开了栈不可执行,使用ida打开观察
主函数引我们去看vuln()函数
emmm,着实看不太懂,我们能知道的,就是fgets()函数有32字节的输入长度限制,还有明显的“I”和“you”,以及一个replace()替换函数,先去运行一下吧,
我们输入“I”、“you”和随便一个其它字符,可以明显地发现,只有当输入“I”的时候,程序输出的值发生了变化,每个“I”,都分别变成了“you”,所以上面的replace()函数作用应该就是把“I”和”you”替换
简单了解程序后,我们回到ida,继续观察程序,不难发现,在程序中,存在一个名为“get_flag”的函数
很明显,这个函数就是我们最后要返回的函数,地址为0x8048F0D
回到vuln()函数
我们可以知道,s字符串所占的字节长度为60字节(3 * 16 + 12 = 60
),而fgets()函数规定了输入的长度最长为32,这表明我们通过直接输入字符是无法将s覆盖的,该怎么办呢?
通过刚才对程序的分析,我们知道,程序会将“I”转换成“you”,这不就将1个字节转换为3个字节了吗!
需要覆盖60个字节,就只输入20个“I“即可!(==别忘了32位程序ebp的4个字节==)
构造exp如下:
1 | from pwn import * |
jarvisoj_level0
知识点:栈溢出 re2text
查看一下保护措施
hh,蛮好的,只开了NX
使用ida查看一下伪代码
main函数,pass
看一下vulnerable_function()函数
不出意外。栈溢出应该是这里发生的
在函数中,我们发现了callsystem函数,众所周知,这是个好函数名
果然如此
由上述分析过程我们可以知道,本题的思路是通过read进行栈溢出,最后返回callsystem的地址
构造exp如下
1 | from pwn import * |
ciscn_2019_c_1
知识点:栈溢出 ret2libc
首先检查保护措施
打开了栈不可执行
用ida查看函数的伪代码
主函数并没有什么明显的泄漏点,我们发现主函数引用了encrpty()函数,去看看这个函数
首先,这是个加密函数,不过由于其判定是否加密的条件在于strlen
,我们可以通过输入\0
来规避这种加密对于payload的修改;其次,这个函数里有明显的溢出点gets(),而在发现溢出点之后,我们需要寻找函数内部是否有可用的函数段
很遗憾,这个程序里既没有system函数,也没有/bin/sh命令,于是想到这道题可能需要通过libc泄露来做
具体思路如下
1.先通过一次栈溢出,将puts的plt地址放到返回处,通过代码中的puts(输出)功能泄露出执行过的函数(puts)的got地址
2.将puts的返回地址设置为_start函数(我们在ida中看到的main()函数是用户代码的入口,是对于用户而言),而start函数是系统代码入口,是程序最初被执行的地方,也就是程序真正的入口),以用来执行system(‘/bin/sh’)
3.通过泄露出的got地址计算出libc中的system和/bin/sh的地址(system 函数属于 libc,而 libc.so 动态链接库中的函数之间相对偏移是固定的。)
4.再次执行栈溢出,把返回地址换成system的地址达到getshell
有思路之后,我们回到函数进行分析
由上述思路可知,本题需要两次传入payload进行栈溢出
第一次溢出:
观察函数可知,如果v0 >= strlen(s)
,就会对我们输入的payload进行一系列”操作“,为了避免这种状况,我们可以利用strlen()函数读到’\0’停止的特性,先向其中传入’\0’
观察s
我们需要向其中输入的垃圾字节为0x50 + 0x08 - 1
64位程序中,当参数少于7个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9。
当参数为7个以上时, 前 6 个与前面一样, 但后面的依次从 “右向左” 放入栈中,即和32位汇编一样。puts只有一个参数,就找rdi就行
通过ROPgadget,我们找到了rdi的返回地址0x400c83
之后就是输入puts的got地址和plt地址,并返回主函数准备下一次溢出
第二次溢出:
大致前几步与第一次一样,最后通过LibcSearcher找到/bin/sh和system的地址,需要注意的是,由于是ubuntu,环境要求栈平衡,所以需要ret来使栈平衡
总结思路:
First
'\0'
绕过strlen()'\0'
和(0x50 + 0x08 - 1)
一起覆盖s,并进行栈溢出(”-1“是因为’\0’占了1字节)p64(pop_rdi)
p64(puts_got)
设置rdi寄存器的值为 puts 的 got 表地址p64(puts_plt)
调用puts函数,输出的是 puts 的 got 表地址p64(main_addr)
设置返回地址,上述步骤完成了输出了puts函数的地址,我们得控制程序执行流
让它返回到main函数,这样我们才可以再一次利用输入点构造rop
Second
'\0'
绕过strlen()'\0'
和(0x50 + 0x08 - 1)
一起覆盖s,并进行栈溢出- 为保持栈平衡,输入
p64(ret)
p64(pop_rdi)
p64(binsh)
p64(sys_addr)
最后很奇怪的,试了多次之后本地都没有打通,反而在使用LibcSearcher之后线上打通了,下面把两个wp放在下面,希望发现错误的师傅在下面留言,救救孩子🥰
1 | from pwn import * |
补充相关知识
(一)
- 32位系统,libc的函数地址是\xf7开头,长度4字节。
- 64位系统,libc的函数地址是\x7f开头,长度6字节。
(二) 函数调用方式的介绍
- 32位程序:先将参数压入栈中,靠近call的是第一个参数;运行执行指令的时候直接去内存地址寻址执行
- 64位程序:通过寄存器来传址(这就是本题需要rdi的地址),寄存器去内存寻址,找到地址返回给程序
以下是64位程序调用
(三) plt && got
ELF 文件中通常存在.GOT.PLT 和.PLT 这两个特殊的节,ELF 编译时无法知道libc 等动态链接库的加载地址。如果一个程序想调用动态链接库中的函数,就必须使用.GOT.PLT和.PLT 配合完成调用。
如在上图中,call _printf 并不是跳转到了实际 _printf 函数的位置。因为在编译时程序并不能确定printf 函数的地址,所以这个call 指令实际上通过相对跳转,跳转到了 FLT 表中的printt 项。下图中就是 PLT对应 printt 的项。ELF 中所有用到的外部动态链接库函数都会有对应的 PLT 项目。PLT 表还是一段代码,作用是从内存甲取出一个地址然后跳转。取出的地址便是_ printf的实际地址,而存放这个_printf 函数实际地址的地方就是最后一张图中的 GOT.PLT 表。
可以发现,.GOT.PLT 表其实是一个函数指针数组,数组中保存着 ELF 中所有用到的外部函数的地址。GOT.PLT 表的初始化工作则由操作系统来完成。
而由于 Linux 非常特殊的 Lazy Binding 机制。在没有开启 Full Rello 的 ELF中,GOT.PLT 表的初始化是在第一次调用该函数的过程中完成的。也就是说,某个函数必须被调用过, GOT.PLT 表中才会存放函数的真实地址。
GOT.PLT 和.PLT对于 PWN 来说有什么作用:
- PLT 可以直接调用某个外部函数
- .GOT.PLT 中通常会存放libc中函数的地址,在漏洞利用中可以通过读取.GOT.PLT 来获得 libc 的地址,或者通过写.GOT.PIT 来控制程序的执行流。
[第五空间2019第五主题]PWN5
知识点:格式化字符串
老规矩,先查看保护措施,可以看见,本题打开了NX和Canary保护
使用ida观察伪代码,发现Canary和NX都开了,也就是说,上面的大部分思路都不适用于本题,而此时我们也可以去考虑格式化字符串漏洞
我们可以很明显的发现两个点,一,当atoi(nptr) = dword_804C044
时,会调用system函数,而通过上部分的伪代码,我们可以得知由于Canary保护的开启,dword_804C044
为一个随机的值;二,由于printf(buf)
并未做出输出的限制,存在着明显的风险
至此,我们的思路基本就确定了:
- 通过printf判断参数在栈上的位置
- %n修改参数内容,改变数据
- 在进入
if
判断时输入我们覆盖随机数对应的数据,进而达到绕过Canary的效果
我们先找到read的地址,即0x0804928D
,接下来,就要计算偏移量了,明确参数在栈上的位置
这里提供几种方法:
参数在栈上的位置
第一种,火眼金睛
这里再查看栈
数出来就是10!!!
第二种,
AAAA对应的十六进制是41414141,可以看到我们输入的参数是在栈上的第10个位置
第三种
在确定好偏移值后,我们查看随机数的字节数——是4字节
因此,我们可以在输入地址后,分别用%(10 - 13)$hhn
去修改bss数据段里的内容,构造payload
1 | from pwn import * |
or
1 | from pwn import * |
ciscn_2019_n_8
查看保护措施
太可怕了,保护全开!!!
我们观察伪代码
运行
可以发先,本题的思路,意外的简单——让var数值的第14个值等于17,就可以执行system函数,而var在bss段,单位大小为dd即4字节,var[13]即13*4,_QWORD为8字节,构造payload即可
1 | from pwn import * |
jarvisoj_level2
查看保护措施
ida查看伪代码
进函数堪堪
这里有明显的可利用函数
查看buf所占空间
在函数中并没有/bin/sh
,不过有不少system函数,shift + 12,发现有/bin/sh
,思路至此敲定,从read入手,返回system与/bin/sh
即可
esp如下
1 | from pwn import * |
[OGeek2019]babyrop
知识点:栈溢出 re2libc
checksec一下
发现只开了NX和RELRO,接下来查看并分析ida伪代码
在参考众多WP之后,才看懂了代码的逻辑,
调用的 sub_80486BB()
函数里有一个alarm()
闹钟,会阻碍调试,而主函数中的fd
是一个文件句柄,打开了一个给定随机值的文件,截断成四字节的int赋值给buf传入sub_804871F()
这个函数内部也没有明显的点,sprintf()
将参数 a1 转换成字符串 s,下一行读入字符串 buf,v6 为其长度,接着把buf最后的字符去掉了,v1为其新长度
溢出点应该在这个函数之内,通过控制参数a1尽可能的大(0xff就不错),触发read(0, buf, a1)
的栈溢出,而想要向其输入较大的a1,通过分析函数,我们知道了,a1是上一个函数sub_804871F()
的返回值,为了通过这个函数输入较大的a1,我们需要向buf中输入\0
来防止exit(0)
执行退出,即我们需要在sub_804871F()
中也需要通过read进行一次栈溢出
我们查看buf所占的大小
让 buf 的长度达到 8 就能覆盖掉 return 的变量
同时,我们查看程序中是否存在system()函数和’\bin\sh’
很遗憾,没有 —— 不过没关系,题目给了我们对应的libc,我们可以通过执行程序获取write和system以及/bin/sh的偏差值,进而得出他们在程序中的地址,所以需要进行两次溢出,本题的类型,正是 —— ret2libc
我们构造payload的思路如下图所示
具体如下:
1 | from pwn import * |
get_started_3dsctf_2016
先查看保护措施
好耶耶耶耶耶耶耶耶·!!!只开了NX!!!
(ida打开之后,整个人都,不好,了!)
那一堆函数震惊到我,那简单至极的main函数让我欣喜,那黑深残的gets函数更让我破防
当我们按下shift和F12时,有喜亦有忧
明显的flag和不存在的binsh
我们查看flag在哪里
在这之后,我认为只要让gets函数跳转到这个函数,再将a1与a2传入即可,而这种方法,在本地是切实可行的,而在远程是不行的!!!
为什么?
在查阅大佬们的WP之后,我明白了,由于我们跳转函数并输入值的手法是栈溢出,在执行完之后,程序便会崩溃,所以在本地我们可以接到它,而在远程,由于程序的崩溃,使得后续的交互 — interactive 无法正常执行,我们便无法得到返回的flag,因此,我们要在程序中执行exit函数,保证函数的正常退出,避免崩溃
注意的是,32位程序调用函数约定:函数 返回地址 参数1 参数2 参数3…,因此我们要先把exit()的地址填上,再填a1、a2
找到地址
贡上WP
1 | from pwn import * |
EX
在看到师傅们的WP,明白了新的暴力解题方法
bjdctf_2020_babystack
知识点:栈溢出 整数溢出
终于碰上个简单题了,注意本题后面的补充有新知识点!
查看保护措施
ida伪代码
主函数让我们先输入一个值,再将这个值作为read函数的输入限制,但并不妨碍我们进行栈溢出
我们再看其它函数,发现了一个叫backdoor的函数
这个函数也确实没有欺骗我们,后门函数确实存在!
我们只需要输入一个较长的数值,保证buf被覆盖,同时,输入返回地址即可,但别忘了,本题与前面的有一点不同,是64位的,因此rbp占8字节
我们查看buf大小
构造exp
1 | from pwn import * |
整数溢出(部分)
在写完本题查看其它师傅写的WP时,发现了这个题的进阶体
此题大致思路(甚至是WP)都与上题相差不多,但是对输入的值,即read的范围做出要求,即不能大于10,而单单buf的地址就已经比10多了,我们该怎样,才能绕过这一层判断呢?答案就是整数溢出
在计算机中。有符号数用二进制表示。表示负数的时候。将二进制最高为来表示数字的符号,最高为是1就是负数。最高位是0就表是正数,当有符号数溢出时。会从最小的值开始,-xxxxx然后依次+1。
既然有“有符号数”,当然也存在着“无符号数”,他们的范围如下
其危害主要有以下几点
(1)数据截断:当发生溢出时。数据会被截断
a\b\r为3个8位无符号整数。范围大小为0-255
a = 11111111
b = 00000001
r = a + b = 100000000
由于a和b相加的值超出了8位。发生溢出。截取8位。r就变成了0
(2)宽度溢出:当一个较小宽度的操作数被提升到了较大操作数一样的宽度。然后进行计算。如果计算结果放 在较小宽度那里那么长度就会被截断为较小宽度。
比如一个32位的运算结果。放到了16位寄存器。那么就会取后16位
(3)改变符号:有符号整数溢出时。就会改变正负。
0x7fffffff + 1 = 0x80000000 = - 2147483648
(4)无符号与有符号转换:将有符号数赋给无符号数后。会从-1变成无符号数的最大数 当把无符号数赋给有符 号数,会从无符号数最大数变成-1、
因此在本题中,我们利用整数漏洞
nbytes 为4字节的 signed int 类型(有符号数),read 的输入限制为 unsigned int 类型(无符号数),因此,一种想法,我们可以输入”-1”,躲过检查,同时-1就会变成unsigned int的最大值;第二种想法,我们可以输入 => 2147483649,signed int 类型被当做负数小于10,read函数中 unsigned int 类型被当成正整数 2147483649
ciscn_2019_en_2
跟“ciscn_2019_c_1“基本一样 ,pass
not_the_same_3dsctf_2016
本题大致思路与not_the_same_3dsctf_2016,类似,不同的是,本题在找引用flag的函数地址时,发现并没有读取的函数
因此我们要在退出之前通过write()
函数进行输出,需要注意的是,write()
需要三个参数,而其第二个为要读的内容,所以我们还需要找到flag
的地址
exp如下:
1 | from pwn import * |
[HarekazeCTF2019]baby_rop
打开ida查看
主函数已经将system函数给了我们,此外,我们还可以搜到/bin/sh
字符串,确实是一道简单的rop
由于这是64位程序,我们想将/bin/sh
作为system的参数,就必须要先将字符串存放进rdi寄存器中,再通过寄存器传进system
查找rdi
EXP如下:
1 | from pwn import * |
ciscn_2019_n_5
知识点:栈溢出 re2shellcode
查看保护措施
ida查看伪代码
由于本题没有开任何保护措施,同时缺少system
和·binsh
,我们有两种思路,一种是前面的retlibc
,另一种是更为简单的retshellcode
,鉴于此WP目前还没有出现retshellcode
,本题我采取此种方法解决
我们的思路分为两步,
一、向name
注入生成的shellcode
在进行操作之前,我们先查看name所处的内存空间的读写权限
name
所在的位置可读可写
二、通过gets
返回name
地址执行shellcode
查看栈溢出需要的垃圾字节数
以下是exp
1 | from pwn import * |
others_shellcode
直接nc(疑惑),pass
ciscn_2019_ne_5
先checksec
32位,只开了NX保护
ida查看main函数
**这里用ida来F5查看main函数报错了,大概率是不知道call去哪里了,根据错误提示找到call,点进函数F5,再点回main函数就好了(雾**以下是漫长复杂的主函数
尽管代码又臭又长,但是逻辑很清晰:输入正确的管理员密码(代码已经将密码给出了),当登陆账户之后,你就有权进行三(隐藏的四)个操作
主函数并没有明显的突破点,我们将目标转换到引用的函数中,前三个函数都没有什么值得注意的地方,而第四个“Get Flag”几乎是明示这里有“问题”了
这里有个进行复制的strcpy函数,我们分别查看src和dest的大小
src:
dest:
dest大小只有48,比src小不少,所以,这里的复制可以用来制造栈溢出
查找system和/bin/sh
很遗憾,只有system
需要注意的是,只取sh也可以作为参数执行命令,而我们发现了名为“fflush”的字符串,在其地址(0x080482E6)加四,即为sh地址
b’a’*4 + p32(0x080482EA)
exp如下:
1 | from pwn import * |
铁人三项(第五赛区)_2018_rop
知识点:栈溢出 re2libc
先查看保护措施
只开了NX保护
ida查看伪代码
查看各个函数
可以发现,在vulnerable_function
函数中,buf占0x88空间,而我们要输入的为0x100u,明显多于buf的空间,可以在此进行栈溢出
看完主函数,并没有特别明显的思路,查看一下字符串
也没有system
和\bin\sh
,这应该是libc类型,我们需要通过泄露write
函数的地址对system
和\bin\sh
地址进行计算
exp:
1 | from pwn import * |
解释:
主要思路 —— 利用write的plt泄漏write函数的真实地址。通过write函数在libc中的相对偏移量计算libc的基地址
而payload1中,p32(main_addr)后面的内容,就是write的参数(我们就是通过write函数自己来泄露它的实际地址),在这里,我们重新复习一下write
函数
ssize_t write(int fd,const void*buf,size_t count);
参数说明:
- fd:是文件描述符(write所对应的是写,即就是1)
- buf:通常是一个字符串,需要写入的字符串
- count:是每次写入的字节数
bjdctf_2020_babystack2
haha,写这题的时候发现以前好像写过,往前翻了翻,果然有,这题分析步骤与bjdctf_2020_babystack区别不大,这里只放py了哈
exp1:
1 | from pwn import * |
错误的exp:
1 | from pwn import * |
在这里我们尝试使用第二种思路构造payload,但是始终不能打通,在动态调试中我们
jarvisoj_fm
知识点:栈溢出 格式化字符串
第一步,checksec
开了canary,意味着会有随机数阻碍我们进行栈溢出
第二步,查看主函数
内置system
,让我们很高兴,但是因为canary保护的存在,我们是不能通过直接进行栈溢出改写x的值的,我们观察函数上方,发现了printf(&buf)
并没有对输出的值进行限制,因此我们想到使用格式化字符串漏洞的知识点
要构造exp,我们必须知道偏移量,我们利用printf
去求
方法一
方法二
偏移量为11
构造exp如下:
1 | from pwn import * |
在这里解释下payload,我们通过%11$n
定位到了偏移值为11的位置,并向其写入数据,而正如我们所知,%n会写入到目前为止所写的字符数,而写入数据正是由%11$n前面的参数的长度决定的,而我们传入的x的地址,恰好是4字节(位),因此不需要添加a来补齐位数即可直接利用,将x参数改为4
需要a的情况
格式化字符串
上一题地址:[第五空间2019第五主题]PWN5
碰到两个格式化字符串漏洞的题目之后,也有了一些心得体会,在这里记录
参考资料:https://www.wlhhlc.top/posts/17489/ (dota爷真的写的很好!!!)
一、原理与常用函数
格式化字符串函数是将计算机内存中表示的数据转化为我们人类可读的字符串格式,可以接受可变数量的参数,并将第一个参数作为格式化字符串,根据其来解析之后的参数。
函数 | 介绍 |
---|---|
printf | 输出到 stdout |
fprintf | 输出到指定 FILE 流 |
vprintf | 根据参数列表格式化输出到 stdout |
vfprintf | 根据参数列表格式化输出到指定 FILE 流 |
sprintf | 输出到字符串 |
snprintf | 输出指定字节数到字符串 |
vsprintf | 根据参数列表格式化输出到字符串 |
vsnprintf | 根据参数列表格式化输出指定字节到字符串 |
setproctitle | 设置 argv |
syslog | 输出日志 |
err, verr, warn, vwarn 等 | …… |
由于printf较为常用,这里补充一下printf的参数:
%d : 十进制 - 输出十进制整数
%s : 字符串 - 从内存中读取字符串
%x : 十六进制 - 输出十六进制数
%c : 字符 - 输出字符
%p : 指针 - 指针地址
%n : 到目前为止所写的字符数
二、泄露内存
泄露任意内存地址在这里一般是为了寻找偏移量,这里在[第五空间2019第五主题]PWN5中已经做过三种方法的介绍。
在这里根据dota爷的blog,补充一下泄露栈内存的小结
- 利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。
- 利用 %s 来获取变量所对应地址的内容,只不过有零截断。
- 利用 %order$x 来获取指定参数的值,利用 %order$s 来获取指定参数对应地址的内容。
三、覆盖内存
- 覆盖栈内存
覆盖栈内存我们常用%n
,只要变量对应地址可写,我们就可以通过格式化字符串来改变其对应的值
利用步骤如下:
1、确定覆盖地址
2、确定相对偏移3、进行覆盖
覆盖任意内存的地址
覆盖小数字
覆盖大数字
一次性输出大数字字节来进行覆盖,这样基本不会成功,因为太长了,所以我们需要另辟蹊径。首先我们需要了解一下变量在内存中的存储格式:
首先,所有的变量在内存中都是以字节进行存储的。
此外,在 x86 和 x64 的体系结构中,变量的存储格式为以小端存储,即最低有效位存储在低地址。举个例子,0x12345678 在内存中由低地址到高地址依次为 \ x78\x56\x34\x12。所以我们想要覆盖成
0x12345678
,需要依次为\x78\x56\x34\x12
接下来我们需要利用到以下字符串格式标志
1
2
3
4hhn : 写入一字节
hn : 写入两字节
l : 写入四节写
ll : 写入八字节也就是我们需要使用 hhn 一个字节来逐次写入,再配合
%nx
会返回16进制数来构造exp
pwn2_sctf_2016
checksec
只开了NX
main函数啥也没有,跳进了vuil()函数
查看一下get_n()
可以发现,与上面的libc不同,出题人自定义了一个输入函数。
查看字符串
没有system
和bninsh
,大概率是libc了,但是我们发现vuin()函数中存在着一层判断,使我们无法填进足够长的数据进行栈溢出。
而前文中的自定义输入函数get_n(),就在这时起到了作用get_n,它接受了a2个长度的字符串并放到vuln函数的缓冲区内部,但是a2传入的值类型是unsigned int,而前面判断长度的类型是int,可以规避长度限制。也就是说我们这边可以输入负数来达到溢出的效果,这即是我们前面遇到过的整数溢出
思路:
- 输入负数,整数溢出
- 利用printf()实现libc
- 覆盖返回地址并执行
system('/bin/sh')
exp:
1 | from pwn import * |