buu
1.test_your_nc
程序分析:main函数中只存在system("/bin/sh")函数,所以nc可直接拿到权限)
nc指令:远程连接对方服务器,对方服务器有什么程序就会执行什么程序
nc(ip + 端口号)例如在已给的靶机信息node5.buuoj.cn:29650中node5.buuoj.cn是域名,冒号后的29650是端口号(注意冒号要改为空格)
然后直接cat flag 即可
2.rip
先将文件拖到虚拟机中,在终端输入checksec +文件名的命令检查保护(可以看程序是64位还是32位,也可以查看文件开启了哪些保护--红色的为未保护)
如图,发现未开启保护且是一个64位文件
ida中的一些基操
空格键 流程图与代码 来回切换.
a:将数据转换为字符串
f5:一键反汇编,查看C代码
esc:回退键,能够倒回上一部操作的视图(只有在反汇编窗口才是这个作用,如果是在其他窗口按下esc,会关闭该窗口)
shift+f12:可以打开string窗口,一键找出所有的字符串,右击setup,还能对窗口的属性进行设置 (通常可以看到重要的信息 )
ctrl+w:保存ida数据库
ctrl+s:选择某个数据段,直接进行跳转
ctrl+鼠标滚轮:能够调节流程视图的大小
Alt + T 查找带有目标字符串的函数
x:对着某个函数、变量按该快捷键,可以查看它的交叉引用
g:直接跳转到某个地址
n:更改变量的名称
y:更改变量的类型
/ :在反编译后伪代码的界面中写下注释
\ :在反编译后伪代码的界面中隐藏/显示变量和函数的类型描述,有时候变量特别多的时候隐藏掉类型描述看起来会轻松很多
;:在反汇编后的界面中写下注释
ctrl+shift+w:拍摄IDA快照
u:undefine,取消定义函数、代码、数据的定义
将文件拖到IDA中分析,打开后先找main函数,后按F5对文件进行反汇编,将文件汇编语言转换成伪代码,便于分析
伪代码如图所示
函数很简单,就一个puts函数输出,然后gets函数输入到s当中,用puts函数输出出来。
程序关键是存在一个危险函数gets(由于存在两个参数,准确来说是gets_s函数),它从不检查输入字符串的长度,而是以回车来判断输入是否结束,所以很容易可以导致栈溢出。顺便也知道了15个字节的存储空间,那么在栈帧中系统就会给我们分配一个15个字节的存储空间
关于gets_s(buffer,size)函数:从标准输入中读取数据,size表示的最多读取的数量,在这里是argv,没有定义是多大,所以我们就可以无限的输入。但是可以发现buffer就只有0xF字节的大小,输入超过0xF个字节以后,你就会溢出,你会覆盖返回地址。所以如果你先填充字节,然后修改掉返回地址就可以调转到你想要跳转的地方。
存在溢出条件,接下来就是要找后门函数地址了(为什么/bin/sh在前面,因为64位先传参数,其次找函数的地址就是找system函数以及它的参数)
关于system函数:system是c语言下的一个可以执行shell命令的函数,目前可以简单理解为,执行了这个危险函数,我们就拿到了远端服务器的shell,也就是相当于在windows下以管理员身份开启cmd,那么我们就可以通过一系列后续指令控制远端服务器
通过shift+F12找重要函数找到system(“/bin/sh")函数,双击进入,首先给的是data,再双击即可找到system的地址(若没出现地址,空格一下即可)发现system(“/bin/sh")函数地址后,只要我们能控制程序返回到0x40118A,就可以得到系统的shell了
接着即可编写payload脚本
先新建一个python文件(在终端中输入vim 文件名.py,回车,再输入:wq即创建好了一个python文件)(更多vim编辑器命令可自查)
打开python文件,编写脚本
from pwn import * #调用pwntools库
context(os='linux',arch='arm64',log_level='debug')#可以加一个环境限定防止靶机环境与本机不一致
r = remote('node5.buuoj.cn',26753) #远程连接(定义一个变量为靶机)(打通远程)
r=process('./pwn1') #定义本地靶机的方法 (打通本地)
padding= 23 #偏移量
system_addr=0x040118A #system函数地址
payload = B'a'*padding+p64(system_addr) #漏洞利用(有效载荷)(加B表示将字符转换为byte类型,用于python3)
#注意,打通远程时system_addr要加一(至于为什么要+1,我们可以发现,不加一我们在本地可以打通,但是却打不通远程,这里+1是为了堆栈平衡)
即为payload = B'a'*padding+ p64(system_addr+1)
r.sendline(payload) #发送漏洞
r.interactive() #远程交互
我们发送了15个A用来填充s,再发送8个字节用来填充b,将地址打包位p64位的数据一起发送,就可以完成栈溢出
脚本编写完后运行,打开终端,输入python3 pwn1.py(即python版本 文件名)回车
若文件不可执行,则右击文件,点击属性-->权限,勾选可执行文件
如图所示,即成功打通(上图为本地打通,远程打通步骤一致)
打通后输入ls列出目前工作目录的全部文件及子目录
找到flag,用cat flag命令即可完成解题
3.warmup_csaw_2016
同样先检查保护
发现是64位文件,且没有开启保护
拖入IDA中分析伪代码
代码分析:俩个write函数输出,然后sprintf函数利用%p将sub_40060函数地址(其实就是system函数)打印出来放到s里面,然后由write函数打印出来,很容易发现了 gets危险函数,显然存在溢出条件,再查看是否存在后门函数(若不存在则需构造,若存在则直接调用)
发现了system函数,和第二题一样的思路(这里与第二题唯一不同的就是第二题是拿到shell,而第三题是得到一个命令 cat flag),可以在system函数中不断双击找到cat flag地址,也可以在主函数中按shift+F12找到关键字符cat flag,双击即可找到cat flag地址(还可以在虚拟机中用ROPgadget插件查找 ROPgadget --binary 文件名 --string “cat flag” 查找cat flag字符串的地址,注意打开终端后要进入与题目文件同一文件夹下的位置),发现了cat flag,只要我们能控制程序返回到0x000400611,就可以得到flag了。
下面就是找到偏移量,可以在IDA中找到(双击gets函数,鼠标点击到r行,左下角就会显示偏移量)有时会不对,如下图就不对
也可在虚拟机中打开终端用gdb调试 gdb 文件名,回车,再输入cyclic 100生成100个随机数,回车
再按r执行程序(栈满了程序会异常退出)(若文件不可执行,则右击文件,点击属性-->权限,勾选可执行文件),再将100个随机数复制粘贴,在弹出的内容中,找到ret行(适用64位程序)
复制<>框中内容,输入cyclic -l 复制内容,回车,得到结果
72即为偏移量
gdb找偏移量的另一种方法,先gdb调试,设置main函数断点,用cyclic 100生成随机数,r启动程序,c让程序继续执行,输入生成的随机数,回车,找到标红的Invalid address,复制其地址输入cyclic -l 复制内容,回车,得到结果
编写脚本
运行后即可得到flag
4.ciscn_2019_n_1
惯例先检查保护,发现是64位文件,且开启了NX保护
再用IDA分析,在func函数中发现危险函数gets要输入v1的值,但是判断的却是v2是否与11.28125相等,只有相等就直接可以获得权限,所以要想办法绕过。
法一 :可以发现的是存在gets函数,加上本题没有开启canary保护,只要能找到cat flag地址就可以直接栈溢出到返回地址(与前两题思路一致)
法二 :可以发现大小 v1<v2,gets函数 不限制 输入多少,所以它从30一直往下填充到返回地址,所以我们只要向下填充至v2时,然后将v2的内容 改成11.28125 就可以了。注意11.26125要改成16进制,这个在IDA中可以找到。
下用法一写
偏移量为0x30+8(缓冲区距离栈底的长度加上栈底的长度)
rbp-30代表距离栈底30字节,64位栈底8字节(可用p64
(0)表示),32位栈底4字节(可用p32(0)表示)
system地址为00000000004006BE
编写脚本
在终端运行打通后即可得到flag
法二脚本供参考
from pwn import *
r = remote('node3.buuoj.cn',29337)
offset = 0x30 -0x4(v1距离v2距离=v1距离栈底距离-v2距离栈底距离)
payload = offset*'a' + p64(0x41348000)
#利用gets函数不限制输入多少,先填充v1,然后v2 = 11.28125.
#这里的11.28125要转换成16进制,这个可以在IDA里找到。
r.sendline(payload)
r.interactive()
5.pwn1_sctf_2016
先检查保护,发现是32位文件且开启了NX保护
拖入IDA中分析
首先是一个fgets危险函数(但是fgets函数限制了只能输入32个字节,没办法溢出),从文件edata里面打印32个字节的内容到s里面,s能存储0x3c(60)个字节,之后就是一系列的c++语言了,大概意思就是会将 I 变成 you
认识函数
fgets
函数原型:char * fgets ( char * str, int num, FILE * stream );
函数功能:
从流中读取字符,并将它们作为C字符串存储到str中,直到已读取(num-1)个字符或到达换行符或到达文件末尾(以先发生的为准)。
换行符使fgets停止读取,但是该函数将其视为有效字符并包含在复制到str的字符串中。
复制到str的字符后会自动附加一个终止的空字符。
请注意,fgets与gets完全不同:fgets不仅接受流参数,而且还允许指定str的最大大小,并在字符串中包括任何结尾的换行符。
std:replace
函数原型:
template <class ForwardIterator, class T>
void replace (ForwardIterator first, ForwardIterator last,
const T& old_value, const T& new_value);
函数功能:
替换范围内的值
将new_value分配给[first,last)范围内所有等于old_value的元素。
该函数使用"operator ==" 将各个元素与old_value进行比较。
std::string::operator=(&input, &s);
作用: 就是 将s指针赋值到 inputs地址里了。
strcpy:
函数原型:char * strcpy ( char * destination, const char * source );
函数功能:
将source指向的C字符串复制到destination指向的数组中,包括终止的空字符(并在该位置停止)。
为避免溢出,目标指向的数组的大小应足够长,以包含与源相同的C字符串(包括终止空字符),并且在内存中不应与源重叠。
这里溢出的思路就是,s距离栈底是0x3C(60)个字节,允许输入的是32个字节,看似没有毛病,但是下面的字符替换,将一个字节的“ I ” 替换成 三个字节的“ you ”,这样只要输入20个“ I ”,就可以替换60个字符,在输入4个字节替换ebp,最后用system系统函数中的cat flag地址,来替换返回地址。
cat flag的地址为08048F13
编写脚本
运行脚本打通即可得到flag
6.jarvisoj_level0
先检查保护,是64位文件,开启了NX保护
拖入IDA中分析
发现buf有128个字节,而read读取了0x200个字节,可以造成溢出
shift+F12找关键字符,发现/bin/sh,地址为0x40059A(也可以用/bin/sh所在函数的地址,但有时因为栈平衡要取下面一个地址)
偏移量为0x80+8
编写脚本
运行脚本打通后易得flag
7.[第五空间2019 决赛]PWN5
先检查保护
发现开启了NX保护和acnary保护
拖入IDA中分析,伪代码很复杂,而且并没有栈溢出漏洞可以利用,但在其中发现了格式化字符串漏洞可以利用,并发现当v6的值等于dword_804C044的值时(dword_804C044的值为随机值),系统会调用system函数,从而我们能得到权限
关于atoi函数补充:atoi (表示 ascii to integer)是把字符串转换成整型数的一个函数
8.jarvisoj_level2
先检查保护,发现是32位程序。且开启了NX保护
拖入IDA中分析,function函数中有read危险函数会产生栈溢出漏洞
有system函数,用shift+F12查看有无/bin/sh,发现有,但system函数与/bin/sh不在一起,可通过传参将/bin/sh作为system函数参数
注意构造ROP链时,system地址可以用.plt地址(但payload需加上p32(0),p32(0)表示system函数的返回地址),也可直接用.text system地址
若想找到.text system,只需找到.plt system行,选中一行 _system 按x键,即交叉引用,找到程序中用到_system的其他位置(若有多个,注意system是在哪个函数中)
.plt system函数地址为08048320
/bin/sh地址为0804A024
因为这是32位程序,数据都储存到栈上,无需找参数地址如rdi
接着就可以编写脚本了
运行脚本,打通后易得flag
9.ciscn_2019_n_8
先检查保护,发现32位文件,保护均开启
IDA分析一下,若按F5弹出1302: call analysis failed类似报错,可以找到对应地址的函数
查看函数参数,发现函数只调用了一个参数,鼠标点到_printf函数上,按Y键,发现函数有9个参数(也可以按x键,再按F5,对_printf反编译,查看伪代码发现有9个参数),删去多余的8个,再到main函数下按F5即可成功反编译
观察伪代码 ,发现只需var[13]=17即可,而看前面的scanf就可以向var数组输入,我们只要将var这个数组里全部填上17即可
编写脚本
运行脚本打通后易得flag
10.ciscn_2019_c_1
先检查保护,是64位文件,开启了NX保护
file+文件名可以查看文件是动态链接(dynamic link)还是静态链接(static link),当然,NO PIE也能说明是静态链接
拖入IDA中分析,发现伪代码很复杂,可以在终端运行一下文件,了解流程
根据程序运行情况来看,输入1能对文字进行加密,输入2没什么用,输入3程序会退出,故该程序最重要的是encrypt函数
在encrypt函数中发现了gets危险函数,有栈溢出漏洞,利用shift+F112发现没有后门函数也没有/bin/sh字符,之前学的都用不上,那就可以利用ret2libc控制函数去libc库中找所需函数,一般是找system('/bin/sh')函数
32位:padding + 当前函数返回地址(记为函数A) + 函数A的返回地址(记为函数B) + 函数A的参数
64位:padding + 当前函数返回地址 + pop_rdi_addr + 第一个参数的地址 + 希望调用的函数amd64的参数调用顺序是如下序列即:完成传递参数(地址)->本函数地址 -> 返回地址
puts函数需要一个参数,故还需再找到rdi寄存器的地址(64位文件函数传参借助寄存器,第一个参数用rdi寄存器),可以利用ROPgadget查找,命令为ROPgadget --binary 文件名 --only 'pop|ret' |grep '寄存器名'
为了维护栈平衡,还要找到ret的地址,也可以利用ropgadget查找,命令为ROPgadget --binary 文件名 --only ‘ret’
第一行即是ret的地址
接下来就可以编写脚本了
运行脚本,打通后选择libc库,可能要多试几次,正确选择后易得flag
11.bjdctf_2020_babystack
先检查保护,是64位文件,开启了NX保护
拖入IDA中分析,首先scanf输入nbytes的值,然后再是输入read函数,而scanf输入的正好是read的第三个参数(buf读入的数据长度由我们输入的nbytes来控制,所以这里可以利用它来溢出)只要输入大于0x10+8就能覆盖返回地址了
关于read函数
ssize_t read(int fd, void *buf, size_t count);
fd:文件描述符,用来指向要操作的文件的文件结构体
buf:一块内存空间
count:希望读取的字节数
返回值表示实际读到的字节数(字符串结束符 '\0'不算)
shift+F12发现/bin/sh和system函数
偏移量为0x10+8
/bin/sh的地址为00000000004006EA
编写脚本
运行后打通易得flag
12.get_started_3dsctf_2016
先检查保护,32位文件且开启了NX保护
拖入IDA中分析
发现gets危险函数,可以利用栈溢出漏洞
再按shift+F12查找关键字,发现flag.txt,控制程序跳转到此处执行理论上就可以拿到 flag(找到相应伪代码get_flag函数,只需令a1,a2为相应的值即可),但是发现只能在本地打通。
百度发现无法拿到的原因是因为我们没有维持栈的平衡,导致程序异常退出,但为啥本地异常退出可以回显文本中的字符串而远程不可以,这就不太清楚,有可能远端做了限制。
13.jarvisoj_level2_x64
先查看保护,是64位文件,且开启了NX保护
拖入IDA中分析
function函数中有read危险函数会产生栈溢出漏洞
有system函数,用shift+F12查看有无/bin/sh,发现有,但system函数与/bin/sh不在一起,可通过传参将/bin/sh作为system函数参数
system函数地址为00000000004004C0
/bin/sh地址为0000000000600A90
还需还需借助ROPgadget通过ROPgadget --binary 文件名 --only 'pop|ret'命令找到system函数第一个参数寄存器地址即rdi地址
rdi地址为0x00000000004006b3
接着就可以编写脚本了
运行脚本打通后ls加cat flag即可得到flag
14.[HarekazeCTF2019]baby_rop
先检查保护,发现是64位文件,且开启了NX保护
拖入IDA中分析,发现只有一个scanf输入v4的值,scanf不限制输入为危险函数
再查看字符串,看看是否存在后门函数地址,发现了system函数和字符串/bin/sh,因为system函数和/bin/sh不在一起,所以需要用system函数调用字符串/bin/sh
system函数地址为0000000000400490
偏移量为0x10+8
字符串/bin/sh地址为0000000000601048
还需借助ROPgadget通过ROPgadget --binary 文件名 --only 'pop|ret'命令找到system函数第一个参数地址即rdi地址
地址为0x0000000000400683
接着可以编写脚本了
运行脚本后打通后发现该程序的flag没有放在当前目录下所以需要find -name flag查找一下
找到flag位置后,重新打开终端执行脚本,打通后,输入cat 位置(如本题cat /home/babyrop/flag )即可得到flag
16.others_shellcode
先检查保护,32位文件,开启了NX保护和PIE保护
拖入IDA中分析
观察伪代码,发现了execv,11,int 80h等字眼,而想要获得一个shell, 除了system(“/bin/sh”) 以外, 还有一种更好的方法, 就是系统调用中的 execve(“/bin/sh”, NULL, NULL)获得shell,我们可以在 Linxu系统调用号表 中找到对应的系统调用号,进行调用。其中32位程序系统调用号用 eax 储存, 第一 、 二 、 三参数分别在 ebx 、ecx 、edx中储存。 可以用 int 80 汇编指令调用,64位程序系统调用号用 rax 储存, 第一 、 二 、 三参数分别在 rdi 、rsi 、rdx中储存。 可以用 syscall 汇编指令调用。
在汇编中看看, 发现有 int 80 指令,且eax = 0FFFFFFFFh - 0FFFFFFF4h = 11即0xB,看上面函数也发现result也就是eax的值就是11,即题目通过将/bin/sh
写入了ebx
,随后调用int80
(sys_execve)系统调用,所以可以直接nc拿到权限
17.ciscn_2019_n_5
先检查保护,是64位文件,且没有开起保护(此时NX虽不是红色,但也代表没有开启保护)
拖入IDA中分析
存在read函数输入你的名字(name在bss段),存在gets危险函数会导致栈溢出,shift + f12查看是否存在后门函数,没有system函数,可以直接构造shellcode(与system函数作用类似)。shellcode = asm(shellcraft.sh(),即输入name时将构造的shellcode存放bss段里面,再将bss段的地址放到返回地址中,使用垃圾数据填充gets函数的缓冲区及栈底,将返回地址改成bss段的地址即可
使用shellcode前需要先保证shellcode 所在的区域要拥有执行权限(NX保护关闭、bss段可执行)
用gdb调试查看name所处的bss段是否可执行(gdb调试前先要下断点),先在main函数下断点即b main,按r执行程序,按vmmap查看各段的执行权限(由于虚拟机版本问题,会发现bss段无执行权限,但打远程可以打通,本地不行),也可用vmmap+name地址查相应段的权限
(r代表读,w代表写,x代表执行,-代表无此权限)
接着可以编写脚本
asm函数是对某个内容进行汇编
shellcraft:shellcraft是pwntools中的一个模块,是shellcode的生成器,我们可以使用shellcode = asm(shellcraft.sh())来生成shell。需要注意的是,对于不同的平台(AMD、Intel),不同位数的机器(32位、64位)的shellcode不一样,所以在使用shellcraft之前要设置好context。
写完后在终端执行脚本,打通后ls再cat flag即可得到flag
18.ciscn_2019_en_2
与第10题ciscn_2019_c_1一模一样,照搬即可
19.not_the_same_3dsctf_2016
先检查保护,32位文件且开启了NX保护
拖入IDA中分析(遇到复杂的伪代码,可以在虚拟机中打开终端用 ./文件名 的命令运行程序,了解程序怎么执行的)
首先在主函数中,发现gets危险函数,可以利用栈溢出漏洞
再按shift+F12查找关键字符,发现flag.txt,找到其所在函数
发现这个函数调用了fopen函数和fgets函数
- fgets()函数:
原型:char *fgets(char *str, int n, FILE *stream)
作用:从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定 - fopen()函数:
原型:FILE *fopen(const char *filename, const char *mode)
作用:使用给定的模式 mode 打开 filename 所指向的文件
所以这个函数是读取flag.txt文件的内容将内容放在fl4g这个参数中,我们可以利用程序中的write函数将fl4g这个变量的内容读出来(也可以用其他程序中带有的输出函数,如printf函数或者puts函数)
函数名:write
函数原型: int write(int handle,void *buf,int len);
功能:获取打开文件的指针位置
参数:int handle 为要获取文件指针的文件句柄(write所对应的是写,即就是1)
void *buf 为要写入的内容
int len 为要写入文件的长度
返回值:返回实际写入文件内容的长度
这题与12题类似,要用exit函数使程序能正常退出
脚本如下
运行脚本打通即可得到flag
方法二,也可用十二题类似的方法,用mprotect函数做这道题(通用于静态编译的程序)
20.ciscn_2019_ne_5
先检查保护,发现是32位程序,且开启了NX保护
拖入IDA中分析
发现程序大致意思是让用户输入密码。然后判断密码是否正确,若错误则退出程序,若正确。则可以进行不同的操作(对日志的操作),观察switch语句,若输入0则退出程序,若输入1,则执行AddLog函数(就是输入128个字节到a1,我们发现a1实际上就是v5变量)
若输入2,则进入Display函数,是输出一个数,没什么用
若输入3,则进入Print函数,在其中发现了system函数
若输入4,则进入GetFlag函数(提到了flag可能与解题有关)
可以得到提示,flag就是我们的log即(即日志),这个实际上也是输出我们之前输入的,但是他有个步骤是拷贝到局部变量再输出局部变量,那么也就是说我们有机会栈溢出,计算一下大小,我们可以输入的有128(a1即v5的大小)也就是0x80,这个局部变量距离ebp为0x48,就可以栈溢出!
那么接下来找system和/bin/sh(system函数地址可以在ida中寻找,也可以在脚本编写时用代码addr = elf.sym['函数名'](先要加载一个elf文件elf = ELF('文件名'))直接寻址,推荐后者,不会出错),用shift+F12无法找到bin/sh字符串,有两种解决办法,第一种我们找个地址然后调用read函数把/bin/sh输入进来,第二种是换个参数用sh字符串也是阔以滴,我们先去找找sh,这时候我们用ROPgadget帮我们找(也可以在脚本编写时用代码sh=next(elf.search(b'字符'))(先要加载一个elf文件elf = ELF('文件名'))
搞清楚程序逻辑后尝试构造payload,主要是利用选择1之后AddLog函数中的那一次输入将程序的返回地址覆盖成system(‘/bin/sh’),然后选4去调用读取我们构造好的栈,即可获取shell
编写脚本如下:
运行脚本打通后易得flag
21.铁人三项(第五赛区)_2018_rop
先检查保护,发现是32位程序,且开启了NX保护
拖入IDA中分析
用shift+F12没有发现system函数和/bin/sh字符串,而在vulnerable_function函数中,发现了栈溢出漏洞,read的v1长度为256,大于v1的大小,会造成缓冲区溢出,所以这道题需要通过泄露libc来获取权限
利用思路:
利用write函数来泄露程序的libc版本
知道libc版本后去计算程序里的system函数和字符串“/bin/sh”的地址
覆盖返回地址为system(‘/bin/sh’),获取shell
利用过程:
泄露libc版本
这边提一下write函数的原型
ssize_t write(int fd,const void*buf,size_t count);
参数说明:
fd:是文件描述符(write所对应的是写,即就是1)
buf:通常是一个字符串,需要写入的字符串
count:是每次写入的字节数
编写脚本如下
运行脚本后选择序号为5的libc版本即可打通。易得flag
22.bjdctf_2020_babystack2
检查保护,发现是64位程序,且开启了NX保护
拖入IDA中分析,
整个程序就是让你输入名字长度,然后根据这个长度让你输入名字,名字是存放在局部变量里的,有机会栈溢出,只不过他有限制,名字长度不能超过10,这样子的长度根本不够栈溢出,怎么办
仔细一看,这个后面有个格式转换,就在read函数那边,第三个参数是读入长度,也就是我们之前输入的名字的长度,强制类型转换成了unsigned int,原本是int类型,也就是说我可以输入一个负数,负数肯定小于10,然后转换成unsigned int的时候会变成一个很大的数,这样就可以栈溢出了,然后去找找有没有后门,用shift+F12查找关键字符,发现后门函数
接下来就可以编写脚本了
运行脚本后打通即可得到flag
23.bjdctf_2020_babyrop
先检查保护,发现是64位程序,且开启了NX保护
拖入IDA中分析,进入vuln函数
发现buf的大小为32,而read函数读入的长度为0x64,有明显的溢出漏洞,用shift+F12查找关键字符,并没有system函数和/bin/sh,所以这题应该是要泄露libc,接下来就可以编写脚本了(思路与第10题类似)
rdi寄存器地址
ret地址(ret指令的地址,该指令用于返回函数调用)
脚本如下:
运行脚本后打通选择编号为5的libc版本易得flag
25.jarvisoj_tell_me_something
检查保护,发现是64位程序,且开启了NX保护
拖入IDA中分析,看一下主函数,read
函数将直接写入 v4
,这是不安全的,因为 v4
只是一个 64 位整数,如果输入超过 8 字节的数据,将导致缓冲区溢出
按shift+F12查找关键字符,发现了flag.txt字符,进入它所在的函数
可以看到这个函数的意思就是读取本地的flag,然后进行输出,只要我们进行栈溢出,将返回地址覆盖成这个函数的地址就可以拿到flag了
编写脚本如下:
PS关于偏移是0x88而不是一般的0x88+8的原因:查看汇编语言,发现了这道题的函数开始和函数结束时候的栈的准备和平时见的不太一样
我们先来看一下平时见的多的准备
如图所示,先将ebp压入栈,然后将esp的值赋给ebp,然后esp再减去对应的栈空间的大小。这个是函数调用时候的栈准备,函数主要流程执行完成后栈恢复的过程就是面的逆过程。
接下来我们看一下这道题的汇编
起步刚开始就直接是rsp减去0x88,其实这里是没有把rbp压入栈的,所以我们只需要0x88的数据大小,就可以开始覆盖返回地址了
运行脚本后打通即可得到flag
26.ciscn_2019_es_2
检查保护,发现是32位程序,开启了NX保护
拖入IDA中分析,进入vul函数,发现read函数读取了48个字节,而v1的大小只有40个字节,可以进行栈溢出,但只能溢出8个字节,无法溢出shellcode,所以要进行栈迁移
按shift+F12找到了echo flag字符,从而找到后门函数hack,但没有bin/sh字符,需自己构造
system函数地址为0x08048400
leave-ret地址为0x08048562
编写脚本如下:
mianaddr和栈劫持步骤不懂
运行脚本打通后易得flag
27.[HarekazeCTF2019]baby_rop2
检查保护,发现是64位程序,且开启了NX保护
拖入IDA中分析,发现buf
的大小为 28 字节,但 read
函数允许读取最多 256 字节。这可能导致缓冲区溢出
按shift+F12查找关键字符,没有找到后门函数和/bin/sh,那么这道题就需要泄露libc了
我们利用printf函数来泄露libc,printf函数的原型int printf( const char* format , [argument] ... ),有两个参数,根据64位程序的传参规则,我们需要找到rdi,rsi寄存器的地址
没有直接设置rsi寄存器的指令,就选择带r15的
我们首先要设置printf的第一个参数,就是带有类似于%s这种格式的字符串,可以使用程序中自带的语句"Welcome to the Pwn World again, %s!\n"的地址将其放入printf的第一个参数如:format_str=0x400770
一开始是打算输出printf的got表地址的,但是在调试的时候发现没法使用,就换成了read函数的got表地址了
接收输出的read函数地址语句应该这样写:read_addr = u64(p.recvuntil('\x7f')[-6:].ljust(8, '\x00'))解释是接收地址的基本上都是7个字节的,7f开头,补全8个字节
编写脚本如下:
运行脚本后打通选择序号为1的libc版本即可获得权限,获得权限后不能直接cat flag,因为flag不在当前目录下,需使用命令find -name flag寻到flag的文件所在的位置,进入那个文件cat flag
28.pwn2_sctf_2016
先检查保护,发现是32位程序,且开启了NX保护
拖入IDA中分析,发现main函数就调用了一个vuln函数,进入vuln函数查看
vuln函数是先输入你要读入数据的长度,然后再读入数据,而且限制了长度不能大于32,也就是0x20,根本不够栈溢出,怎么办?之前是有强制类型转换,现在也没有啊,仔细看看发现get_n这个不是get啊,是自己写的函数,点进去看看
get_n函数是接受a2个长度的字符串并放到vuln函数的缓冲区内部即a1,但是a2传入的值类型是unsigned int,而前面判断长度的类型是int,即vuln传的参数数据类型是int,可以规避长度限制。也就是说我们这边可以输入负数来达到溢出的效果(整数溢出),再按shift+F12查找关键字符,发现没有后门函数,也没有/bin/sh字符串,那么这道题就需要泄露libc来获得shell
编写脚本如下:
运行脚本发现本地可以打通,远程打不通?
29.picoctf_2018_rop chain
先检查保护,发现是32位程序,且开启了NX保护
拖入IDA中分析,main函数调用了vuln函数,进入vuln函数查看
在vuln函数中中发现了gets危险函数,存在栈溢出漏洞,再按shift+F12查找关键字符,发现了flag.txt字符,双击跟进,发现__cdecl flag函数调用了这个字符,并且看到该函数将flag赋值给了v6而后又读入到了参数v5里面,且满足条件win1&&win2 &&a1==-59039827( 当win1为真和win2为真和a1=-559037827成立就可以获取到flag,即一个判断式(win1&&win2),另一个是(a==0x一堆),这两个的结果再去进行逻辑与),就能读出flag
查看对应汇编语言,找到-1163220307对应的16进制补码
在程序里找到了给win1,win2赋值的函数:
win_function1函数已经给win1赋值成了1即为真,那就不用管,但是记得执行一下这个函数
win_function2函数当满足win1和a1=-1163220307条件的时候会将win2赋值为1
利用思路,首先溢出后覆盖返回地址为function1函数地址,执行该函数将win1赋值为1,之后跳转到function2的地址,a1是传入的参数,将a1传入即可满足条件,之后去执行_cdecl flag函数,if要满足的条件之前都设置好了,可以直接读出flag
查看对应汇编语言,找到-1163220307对应的16进制补码
编写脚本如下
运行脚本打通后即可得到flag
30.jarvisoj_level3
先检查保护,发现是32位程序,且开启了NX保护
拖入IDA中分析,发现主函数调用了vulnerable_function函数,进入该函数看看
v1
只有 136 字节,而 read
函数允许写入 256 字节,用户可以输入超过 136 字节的数据,所以存在缓冲区溢出漏洞,按shift+F12查找关键字符,发现没有后门函数,也没有/bin/sh字符串,那么这道题就要通过泄露libc来解决
编写脚本如下
但是libc库好像出问题了,只找到3个版本好像还都不对
31.ciscn_2019_s_3
先检查保护,发现是64位程序,且开启了NX保护
拖入IDA中分析,main函数只调用了vuln函数,进入vuln函数看看
32.wustctf2020_getshell
先检查保护,发现是32位程序,且开启了NX保护
拖入IDA中分析,main函数调用了vulnerable函数,进入vulnerable函数看看
read函数读取了32字节的数据,而v1大小为24字节,所以存在栈溢出漏洞,而且正好溢出8位,能让我们覆盖到ret,接下来按shift+F12查找关键字符
有后门函数及/bin/sh字符串,双击跟进,发现是shell函数调用的
接下来就可以编写脚本了,脚本如下:
运行脚本打通后易得flag
五湖四海
1.getshell(来自polarctf)
先检查保护,64位程序且没有开启保护
拖入IDA中分析
发现gets危险函数,存在栈溢出漏洞,shift+F12查找关键字符,发现只有system函数,所以用shellcode,因为程序输出了v4的地址,所以可以将shellcode放入v4的地址中,再利用栈溢出使函数返回地址为v4,进而执行shellcode(因为v4在栈上,所以又叫栈上的shellcode,和bss上的shellcode类似)
偏移量为0x70+8
编写脚本
接收信息v4地址信息时,要进行切片操作,因为printf输出时还输出了\n回车符,而我们只需要输出的v4地址,接收到的地址是字符串格式,所以还需将地址转换成整型,用int函数,int函数转换后,地址格式为十进制,所以还要转换为16进制
示例
import numpy as np
a=[1,2,3.4,5]
print(a)[ 1 2 3 4 5 ]
print(a[-1]) 取最后一个元素
结果:[5]
print(a[:-1]) 除了最后一个取全部
结果:[ 1 2 3 4 ]
print(a[::-1]) 取从后向前(相反)的元素
结果:[ 5 4 3 2 1 ]
print(a[2::-1]) 取从下标为2的元素翻转读取
结果:[ 3 2 1 ]print(a[1:]) 取第二个到最后一个元素
结果:[2 3 4 5]
int() 函数用于将一个字符串或数字转换为整型。
以下是 int() 方法的语法:
class int(x, base=10)
x -- 字符串或数字
base -- 进制数,默认十进制
如果是带参数base的话,x要以字符串的形式进行输入,x 为 base进制
写payload时,因为shellcode要放在栈上,所以shellcode与垃圾数据一起放入缓冲区,且shellcode地址在垃圾数据下方,这时要用到ljust函数shellcode.ljust(padding,B'a')使shellcode在paadding个‘a’左边,即在栈内shellcode在垃圾数据下方,若用rjust则正好反过来
ljust() 方法返回一个原字符串左对齐,并使用空格填充至指定长度的新字符串。如果指定的长度小于原字符串的长度则返回原字符串。
以下是ljust()方法语法:
str.ljust(width,fillchar)
width -- 指定字符串长度
fillchar -- 填充字符,默认为空格
返回值:返回一个原字符串左对齐,并使用空格填充至指定长度的新字符串。如果指定的长度小于原字符串的长度则返回原字符串。
运行脚本,打通后易得flag
2.ret2syscall
先检查保护,是32位程序,且开启了NX保护(无法用shellcode)
拖入IDA中分析
发现危险函数gets,用shift+F12查找关键字符,发现没有后门函数只有/bin/sh,那也无法用re2text,这时候就要用到ret2syscall调用系统函数,我们要做的就是让程序调用execve("/bin/sh",NULL,NULL)函数即可拿到shell
因为该程序是 32 位,所以●eax 应该为 0xb( 0xb 是execve 对应的系统调用号) ●ebx 应该指向 /bin/sh 的地址,其实指向 sh 的地址也可以 ●ecx 应该为 0 (execve函数第二个参数) ●edx 应该为 0(execve函数参数) ●最后再执行int 0x80触发中断即可执行execve()获取shell
接下来获取相关数据就可以编写脚本了
偏移量为112(用gdb查,ida不一定准确,ida也可点击v4算首地址与尾地址和,但也不准确)
/bin/sh的地址为0x080BE408(可以在IDA中找,也可以用ROPgadget获取,命令为ROPgadget --binary 文件名 --string '/bin/sh')
接下来可以借助ROPgadget获取eax,ebx,ecx,edx四个寄存器的地址及int 0x80的地址
ROPgadget --binary 文件名 --only 'pop|ret' |grep '寄存器名'
ROPgadget --binary 文件名 --only 'int'
第二行的为eax寄存器的地址——0x080bb196
在找ebx的时候,能发现一个将ebx,ecx,edx都包括的,所以后面两个就不用找了,地址为0x0806eb90
int 0x80的地址为0x08049421
接着就可以编写脚本了
运行脚本,即可打通
.