ret2text
函数压入栈中是由上往下由栈底到栈顶,通过payload将数据压入栈是由下往上由栈顶往上压入到栈底
未开保护
存在可利用函数类(地址跳转)
有打印flag的函数
什么保护都没开,存在gets函数有栈溢出漏洞,有打印flag的函数。
思路:利用栈溢出覆盖原函数到其ebp,用flag的地址覆盖原函数返回地址,以达到运行打印flag函数的效果
1 | from pwn import * |
信号
程序栈保护未开,nx开启,发现栈溢出漏洞
1 | char dest[104]; // [esp+Ch] [ebp-6Ch] BYREF |
发现信号接收函数和flag打印函数
1 | signal(11, (__sighandler_t)sigsegv_handler); |
在 C 语言中,signal(11, (__sighandler_t)sigsegv_handler);
这行代码的作用是设置一个信号处理函数来处理 SIGSEGV 信号(即信号编号 11)。SIGSEGV 通常表示程序尝试访问未分配给它的内存地址,或者试图以错误的方式访问有效地址。
具体来说:
signal
是一个用于注册信号处理函数的系统调用。- 第一个参数
11
表示 SIGSEGV 信号。 - 第二个参数
(__sighandler_t)sigsegv_handler
是一个指向信号处理函数的指针,类型被强制转换为__sighandler_t
类型。 sigsegv_handler
这个函数会在发生 SIGSEGV 信号时被调用,并且会接收到信号编号作为参数。
当发生栈溢出时会发送SIGSEGV信号,接收到信号后会调用sigsegv_handler函数打印出flag故此题送入105个字符使栈溢出即可获取flag
ret2text
有栈溢出漏洞,栈保护未开,程序中有可利用的后门函数获取shell
找具体覆盖需要多少字节可以关注函数后的注释,32位是[ebp - 12h],64位是[rsp - 12h],也可以看汇编码或gdb输入一串字符查看
buf = byte prt -80即表示buf数组到ebp的距离为80h
32位
checksec
发现栈保护运行一下提示已有后门函数拖入ida分析
发现栈溢出漏洞,read能够读入0x32u的数据而buf只有14的空间,根据注释buf数组到ebp的距离为12h,故可利用该漏洞先覆盖掉buf数组到ebp的空间,因为是32位程序所以再用4个字节覆盖ebp,最后用后门函数的返回地址覆盖原函数返回地址
1 | from pwn import * |
64位
64位ubuntu18以上系统调用system函数时是需要栈对齐的。再具体一点就是64位下system函数有个mov aps指令,这个指令要求内存地址必须16字节对齐,只有当地址的末尾是0的时候,才算是与16字节对齐了,如果末尾是8的话,那就是没有对齐。而我们想要在ubuntu18以上的64位程序中执行system函数,必须要在执行system地址末尾是0
我们需要找一个地址:lea的地址或者retn的地址(函数结束的地址)添加到后门函数的返回地址前
故直接在调用system函数地址之前去调用一个ret指令。因为本来现在是没有对齐的,那我现在直接执行一条对栈操作指令(ret指令等同于pop rip,该指令使得rsp+8,从而完成rsp16字节对齐),这样system地址所在的栈地址就是0结尾,从而完成了栈对齐。
1 | from pwn import* |
寻找合适的gadgets:在二进制文件中搜索可以利用的指令序列(称为gadgets),这些gadgets可以用来控制程序执行流程。例如,“lea esp, [esp+0x10]”可以将ESP指针向前移动16字节,而“ret”或“retn”指令则用于返回到上一层调用者。
32位plus1版
system与/bin/sh分离
checksec 发现canary没开只开了nx
ida分析有后门函数但是后门函数不完整需要连接,发现可利用栈溢出漏洞如下
故构建exp
1 | from pwn import* |
payload解读b'a'*(0x12+4)
部分用于填充满buf数组且覆盖掉其ebp
(ebp (基指针寄存器)用于指向当前函数的栈帧基地址。在函数调用过程中, ebp 通常用于访问函数的局部变量和其他函数参数。每个函数都有自己的栈帧,而 ebp 指向的就是这个栈帧的起始位置。)
p32(system)
压入system函数地址
p32(0)+p32(x)
p32(x) 是 system 的参数,其中 x 是包含 “/bin/sh” 字符串的内存地址,使用任意四个字节覆盖掉call system后存储的下一条指令的地址
1 | call system= |
32位函数调用时栈上排序为:函数 下一条指令的返回地址 参数
栈上的动态变化:
1 | 1. 栈初始化:假设我们有一个函数调用,其栈布局如下: |
64位plus1版
还是system与bin/sh分离了需要连接
这题和32位的思路一样,唯一不同的是64位在进行传参时与32位不一样,因为32位是栈传参,而64位是寄存器传参+栈传参,传送的前几个参数一般使用寄存器储存,若参数果果寄存器有限会继续使用栈传参。
1 | 具体64位传参方法如下: |
获取rdi地址:
1 | ROPgadget --binary pwn --only "pop|ret" | grep rdi |
参数解释:
–binary pwn: 指定了要分析的二进制文件的文件名。
–only “pop|ret”: 指定了只查找包含”pop”和”ret”指令序列的代码片段,这些指令通常用于弹出寄存器中的值,并将控制流返回到调用函数的地址处,是ROP攻击中常用的gadgets。
| grep rdi: 将ROPgadget的输出传递给grep命令,然后使用grep命令筛选出包含”rdi”寄存器的代码片段,这样就可以只找到包含”pop|ret”指令序列并且弹出rdi寄存器的gadgets。
gadgets 指的是程序中的一些短小的代码片段,这些代码片段通常以一种特定的指令序列结尾,比如”ret”指令。(就是以 ret 结尾的指令序列)
得到rdi的地址:0x4007e3
接下来获取ret的地址:
1 | ROPgadget --binary pwn |
exp
1 | from pwn import* |
paylode中:
0xA+8个填充字节(用’a’填充):用于填充到栈溢出的位置,达到返回地址的偏移。
pop_rdi:用于将下一个值弹出到rdi寄存器中。
bin_sh:需要执行的系统命令字符串的地址。
ret:用于绕过栈中的返回地址,返回到调用者。
system:系统函数system的地址,用于执行系统命令。
动态图示如下(kimi最好用的一次)
1 | 1. 栈初始化:假设我们有一个函数调用,其栈布局如下: |
ret 指令的作用包括:
- 恢复栈指针: ret 指令会将栈指针(ESP寄存器在32位系统中,或者RSP寄存器在64位系统中)增加一个特定的值,通常是四个字节(32位系统)或八个字节(64位系统)。这个增加的值取决于在调用函数时,函数的返回类型。这样做是为了移除函数调用时压入栈中的参数和可能的其他数据。
- 跳转到返回地址: ret 指令会从栈顶弹出一个字(32位系统)或双字(64位系统),这个值是函数调用时保存的返回地址,即调用函数之前的指令地址。 ret 指令将这个地址加载到指令指针(EIP寄存器在32位系统中,或者RIP寄存器在64位系统中),从而使CPU跳转到这个地址继续执行。
32位无/bin/sh
exp
1 | from pwn import* |
分析:
走流程checeksec——>canary没开,开了nx
分析源码发现gets漏洞,后门函数system但是没有/bin/sh
因为开了NX不能直接写入所以利用gadgets尝试写入参数
先找可写入的地址,利用gdb调试
1 | $gdb pwn |
发现0x804b000到0x804c000是可写的,刚好对应到bss段,在bss段发现一个buf2变量,故可将/bin/sh写入buf2
故开始构造payload
1 | payload=b'a'*(0x6C+4)+p32(gets)+p32(system)+p32(buf2)+p32(buf2) |
(0x6C+4)填充数据到返回地址,用gets@plt的地址覆盖构造二次输入,压入system@plt作为执行完gets函数的返回地址,压入buf2作为gets函数的参数,再次压入buf2作为system的参数,遵守函数 返回地址 参数的布局 32位是栈传参所以可以直接把参数压入栈上正常模拟函数调用
压入call system_addr和压入system的区别:
• 直接调用 system :需要提供 system 函数的地址和返回地址,以及 system 函数的参数。
• 调用 call system :只需要提供 call system 指令的地址和 system 函数的参数。 call 指令会自动处理返回地址的压栈和跳转。
这里有一种通用性更强的payload的构造方式,遵循用完即丢的原则
1 | payload=b'a'*(0x6C+4)+p32(gets)+p32(pop_ebx_ret)+p32(buf2)+p32(system)+p32(0)+p32(buf2) |
64位无/bin/sh
32位为栈传参,故不需要将参数弹入寄存器中,64位前6个参数是储存在寄存器RDI 、RSI 、RDX 、RCX 、R8 和 R9 这六个寄存器传递中的存满之后再从右到左依次压入栈中
该题与上题逻辑相似只是传参方式改变了
获得ROPgadget的指令
1 | ROPgadget --binary pwn --only 'pop|ret' | grep 'rdi' |
exp
1 | from pwn import* |
payload中:
第一步劫持执行流
第一次压入pop_rdi将buf2弹入rdi寄存器中然后返回到下一个gets函数地址构造二次输入
第二次将pop_rid地址压入栈中,其位置为执行完gets函数后的返回地址,该pop_rdi用于将利用gets函数接收到的’/bin/sh’弹入rdi寄存器中,接着将执行流ret到system函数上
接着就是调用system,含有’/bin/sh’的buf2变量充当system的参数,获得shell