pwn练习题

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保护和canary保护

拖入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')函数

ret2libc攻击思路:
通过栈溢出漏洞泄露函数的真实地址,通过Libcsearcher或者现有的libc库计算出libc基址。
然后,构造合适的ROP链,通过相对偏移量大小不变的原理来调用libc中的system函数,并
传递"/bin/sh"字符串作为参数,从而获取shell权限。
payload构造思路
payloa栈溢出 ==> 调用puts、printf……函数(fun_plt_addr) ==> 返回main函数(根据被调用函数决
定带几个参数) ==> 参数中包括泄露got表地址(fun_got_addr) ==> 接收got表中的内容(该函
数的真实地址) ==> libc寻址找到后门函数地址 ==> 之前返回main函数了,再栈溢出一次
==> 调用后门函数提权
本题通过泄露puts函数真实地址来计算libc基址
偏移量为0x50+8
为了偷懒,可以在脚本中用elf.got["puts"]命令获取puts函数的真实地址
编写payload是注意32位和64位的传参顺序

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为相应的值即可),但是发现只能在本地打通。

百度发现无法拿到的原因是因为我们没有维持栈的平衡,导致程序异常退出,但为啥本地异常退出可以回显文本中的字符串而远程不可以,这就不太清楚,有可能远端做了限制。

方法一
通过后门函数进行回显 flag
针对上面远端无法拿到结果的问题,这里给出新的解法,也就是维护好栈,使得执行完
get_flag() 后门函数让程序正常退出。打远程时,如果程序是异常退出了,最后是不给你回
显的。所以我们得想办法让程序正常退出。 C 语言有个函数是 exit ,只要执行这个只要我们
get_flag 的返回地址写成 exit 的地址,程序就可以正常结束并且有回显了。( 静态编译文
通过 GDB IDA ,我们可以拿到 exit 函数的地址
ida查找函数:Ctrl+F,在搜索框中输入相应函数名即可
那么思路就有了
我们先利用 gets 溢出控制程序打开 flag 文件(修改a1,a2的值),再用 exit 函数让程序可以正常退出,最后将返回地址溢出成 带参数的, 就能拿到 flag
偏移量为0x38(本题偏移量在IDA中直接看会有错误,得用gdb看)
设置断点,运行程序,填充垃圾数据,找到错误地址,cyclic -l 地址,找到偏移量
脚本如下
payload构造顺序应参照32位程序的传参顺序
运行脚本打通即可获得flag
方法二
检查该程序保护时,发现NO PIE,即没有开启地址随机化,所以为静态编译
这题比较巧妙的做法 程序里有一个 mprotect 函数( 以后静态编译文件可以试着找找此类库函数 ),它的作用是能够修改内存的权限为可读可写可执行,然后我们就可以往栈上写入shellcode ,执行即可获取shell
首先学习一下这个函数
int mprotect(const void *start, size_t len, int prot);
  第一个参数填的是一个地址,是指需要进行操作的地址。
  第二个参数是地址往后多大的长度。
  第三个参数的是要赋予的权限。
  mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
  prot 可以取以下几个值,并且可以用“|”将几个属性合起来使用:
  1PROT_READ:表示内存段内的内容可写;
  2PROT_WRITE:表示内存段内的内容可读;
  3PROT_EXEC:表示内存段中的内容可执行;
  4PROT_NONE:表示内存段中的内容根本没法访问
注:使用chmod命令修改文件/目录的权限(windows系统终端修改权限方式)
prot=7 是可读可写可执行
需要指出的是,指定的内存区间必须包含整个内存页(4K)。区间开始的地址start必须是一个内存页的起始地址,并且区间长度len必须是页大小的整数倍。
(通过看源码,可以发现几个参数都是整型数据,就不奇怪了)
就这样,我们就可以将一段地址弄成可以执行的了。因为程序本身也是 静态编译 ,所以地址是不会变的。
首先利用了 mprotect 函数修改内存权限,然后利用 read 读取 shellcode 到该内存区域。由于
开启了 NX 保护,所以不断利用 retn 返回地址来达到调用函数,注入 shellcode 的目的。
那么我们需要做的是 第一个参数为需要修改的内存起始地址,这个地址也就是我们接下来shellcode将要写入的地方; (一般为.got.plt表的起始地址)?
第二个参数为修改的内存大小,一般取内存的整数页(找到修改地址所处的页,将那一整页权限都修改掉)(一般取1000字节为一页,修改内存一修改就是一页);
第三个参数为0x7,表示该内存拥有可读可写可执行权限。
read 函数第一个参数是打开的文件描述符, 0 表示输入;第二参数指明读取的数据存放的内
存起始位置;第三个参数指明最大读取的字节数。而每次调用函数后都是返回到 pop_3_ret
中执行,是用该段汇编代码弹出压入栈中的三个参数来达到栈平衡。该代码的地址不唯一,
在程序中找到相应弹出三个栈的操作再加 retn 的汇编代码就行(随便找就行)
脚本如下
运行后打通即可得到flag

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函数

  1. fgets()函数:
    原型:char *fgets(char *str, int n, FILE *stream)
    作用:从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止,具体视情况而定
  2. 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函数做这道题(通用于静态编译的程序)

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

五湖四海

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

接着就可以编写脚本了

运行脚本,即可打通

 


.

  • 19
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值