Linux Kernel ROP - Returning to userland from kernel context?
I ended up writing my shellcode in a different way. As I could not figure out how to return, I let the kernel do the heavy lifting for me, in returning to userland. The idea was to execute my privilege escalation bit, and jump back to where the vulnerable function was supposed to return, with the registers and stack fixed up.
As soon as the kernel returned from the vulnerable function (when it was not overflowed), I noticed something via gdb
. (The addresses are imaginary, but explains the concept anyhow.)
(gdb) x/i $eip
0xadd1: ret
(gdb) x/xw $esp
0xadd1: 0xadd2
(gdb) x/6i 0xadd2
0xadd2: add esp,0x40
0xadd3: pop ...
0xadd4: pop ...
0xadd5: pop ...
0xadd6: pop ...
0xadd7: ret
So, immediately after the return, 0x40 bytes of stack is unused and would simply vanish with the add esp
instruction. Thus, taking advantage of this fact, I constructed an ROP chain (I had already constructed it while writing this question, its job was to disable SMEP and jump to my userland shellcode), which was 24 bytes long. 4 bytes would overwrite the EIP at the time of return, and the remaining 20 (0x14
) would follow it right into the unused stack. This leaves us with 0x2c
bytes of unused stack still.
But if we returned back to 0xadd2
, we would risk losing a further 0x14
bytes of valuable register information from the stack, and filling up the registers with invalid data. We could add 0x2c
to esp
in our userland shellcode, and directly jump to 0xadd3
, skipping the actual add
instruction.
Also noting from the debugging session, everything except eax
and ebx
were being restored properly. As my overflow had trashed both of them, I had to restore them with values similar to cases when the function made a clean return. (Doing this was simple: I set a breakpoint at 0xadd2
, and extracted the values from info reg
)
So, my final userland shellcode was this:
Execute privesc payload -> add esp,0x2c -> register fixup -> jump to 0xadd3
Doing this, the code path returned perfectly clean, and the kernel did the task of jumping back to user-mode for me.