Tags: shellcode pwn
Rating:
Description: O ho! You found me! I have a display of oddities available to you!
author: Surg
Mitigations:
Loading the binary in ghidra, we see the following in main:
The binary reads in our input to the mmap'd chunk, then executes
it (*(code *)0x123412340000)()
.
The only catch is that every byte of our shellcode must be an odd number (probably the reason for the challenge name, 'odd shell')
This has the effect of blocking important instructions like
movabs rdi, <ascii value of /bin/sh>
mov rax, rdi
Recall that in order to execve /bin/sh, we need the following conditions to be true
To get around these, I used some of the following substitute instructions for what was blocked:
Right before the shellcode is executed, observe that r15 == 0, so we can use push/pop instructions or xchg w/a zero'd register to zero out other registers.
The most difficult part of the challenge IMHO was getting the ascii value for /bin/sh (0x68732f2f6e69622f) in memory
Since 0x68 was a bad character, I followed this idea:
Then, add the next 4 chars (0x732f2f2f6d) to rdi. Note, we have to change the 0x6e from the target value to 0x6d to avoid having an even byte.
This was fixed with a single inc
instruction
Next, we have to 'setup' a register so we can do a similar addition
process for the last piece. Since 0x62 was a bad byte, I used 0x61
in its place and fixed with inc
similar to above. 0x1 is a dummy byte that will get lost in bit shifting. It was needed to prevent a null byte from appearing in the mov
instruction.
(more pseudocode, read the script for full solution)
Now that we have the /bin/sh value in RDI, all we have to do is push a zero'd out register, then push rdi and rsp. Then pop into RDI so we have a valid pointer.
Setting up RAX for a system call was fairly straight forward. 0x3b is the syscall number for execve, so I just put a 0x3b3b3b3b value in a register and shifted it left to become 0x3b. Then, swap with RAX.
At this point, I ran the script and was getting a shell locally, but not on remote. Googling the problem reminded me that I forgot to duplicate the server socket so my shell could use stdin/stdout correctly. The solution required me to invoke dup2(stdin/stdout/stderr, 0x4, 0x0)
.
I used 0x4 as the newfd
argument for dup2. Not sure if you could use a higher number for the server socket, but it worked in my case.
dup2's syscall number is 0x21, so I set up RAX similar to how I did for execve.
For stdin/stdout setup, the shellcode worked like this (high-level overview):
So, before the setup for execve with /bin/sh, I added the series of dup2() calls.
Once that was done, challenge solved.
uiuctf{5uch_0dd_by4t3s_1n_my_r3g1st3rs!}