posted on January 17, 2023, last updated on Saturday, November 23, 2024 at 10:51 AM

1. Printing on the Screen

Program using Relative Addressing technique

src/task1/print_rel.asm

[SECTION .text]
global _start
_start:
        jmp saveme			; (complete)	jump to the call instruction that right
        								;							before the "Hello, world!" string
shellcode:
				pop esi					; (complete)	pop the top value of stack (address of
												;							msg) and save to esi register
												
        mov eax, 4  		; (complete)	opcode for write system call
        mov ebx, 1  		; (complete)	1st arg is the fd (1 = STDOUT)
        mov ecx, esi  	; (complete)	2nd arg is the string address
        mov edx, 15   	; 3rd arg is len
        int 0x80      	; system call interrupt

        mov eax, 1    	; opcode for exit system call
        mov ebx, 0    	; 1st arg, exit(0)
        int 0x80      	; system call interrupt
 saveme:
        call shellcode	; (complete)	jump back to the 1st instruction after jmp
        								;							instruction, and save the address of 
        								;							msg on the top of stack
        msg db "Hello, world!", 0xA, 0xD

1-1-rel

Program using Stack methods

src/task1/print_stk.asm

[SECTION .text]
global _start
_start:
        mov eax, 0
        push eax
        push 0x000a0d21	; hex format of "!\r\n\0"
        push "orld"
        push "o, w"			; (complete)	push "o, w" to stack
        push "Hell"			; (complete)	push "Hell" to stack

        mov eax, 4			; (complete)	opcode for write system call
        mov ebx, 1			; (complete)	1st arg is the fd (1 = STDOUT)
        mov ecx, esp		; (complete)	2nd arg is the string address (stack top)
				mov edx, 15			; 3rd arg, len
        int 0x80				; system call interrupt

        mov eax, 1			; opcode for exit system call
        mov ebx, 0			; 1st arg, exit(0)
        int 0x80				; system call interrupt

1-2-stk

(a) In print_stk.asm, explain how the line “push 0x000a0d21” works. Show a screenshot from gdb to support your explanation.

From the gdb disassembler, the line push 0x000a0d21 is located in memory at 0x08048066.

1-1-1-disassembler

I set a breakpoint at _start and used si command to execute one instruction each time. When the program is running before executing the instruction of push 0x000a0d21, from the status of all registers,

1-1-2-registers-before

we can see that the current stack top address in memory is 0xbffff5fc, and the value inside is 0x0.

Then, after running the push 0x000a0d21 instruction. From the register info, we can see that the value 0x000a0d21 is pushed into stack and the value of register esp has changed to 0xbffff5f8 (deduced 4), pointing to address of the top of stack.

1-1-3-registers-after

(b) Also, in the same file, explain how you got the string address. Show a screenshot from gdb to support your explanation.

I used instruction mov ecx, esp to get the string address where it sets ecx register to the value inside esp. The esp register keeps the address of the stack top which points to one segment of the string Hello, world!\r\n.

By executing instructions one by one, from gdb, we can see that after mov ecx, esp is executed, the ecx register holds the address points to the string. Then, the system call write can use the string address as second argument.

1-2-1-register-after


2. Spawning a Shell

2.1 Startup Code (labsh.asm)

  1. The process number of both the calling shell and the spawned shell using “echo $$”.

2-1-1

  1. The passed environment variables to the spawned shell using “/usr/bin/env

2-1-2-usr-bin-env

2.2 Providing Arguments to /bin/sh

I constructed the argument string “-c” and “ls -la” by pushing them into stack and use esi register to keep their address in memory. When constructing argv, I pushed the address of each argument string into the stack to form an array of strings.

src/task2/labsh_args.asm

section .text
  global _start
    _start:
      mov  eax, 0
      push eax          ; Use 0 to terminate the string
      push "//sh"
      push "/bin"
      mov  ebx, esp     ; Get the string address of "/bin//sh"

      ; push "-c" and "ls -la" into stack
      push eax
      push "-c"
      push eax
      push "la"
      push "ls -"
      mov esi, esp			; Keep the current stack top address to esi register
      
      ; Construct the argument array argv[]
      push eax					; argv[3] = 0, EOL
      push esi					; argv[2] points to "ls -la"
      add esi, 12				; move the esi register to point to "-c"
      push esi					; argv[1] points to "-c"
      add esi, 20				; move the esi register to point to "/bin//sh"
      push esi					; argv[0] points to "/bin//sh"
      mov  ecx, esp     ; Get the address of argv[]

      ; For environment variable 
      mov edx, 0     		; No env variables 

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

2-2-1-result

2.3 Providing Env. Variable to /bin/sh

I constructed environment variable strings by pushing them into stack and use esi register to keep their address in memory as in the 2.2. When constructing envp, I pushed the address of each envrionment variable string into the stack to form an array of strings.

src/task2/labsh_env.asm

section .text
  global _start
    _start:
      ; Store the argument string on stack
      mov  	eax, 0
      push 	eax					; Use 0 to terminate the string
      push 	"//sh"
      push 	"/bin"
      mov  	ebx, esp		; Get the string address

			; Construct the argument array argv[] = { NULL }
      mov 	ecx, eax
 			
			; Construct the envrionment variable strings
      push 	eax
      push 	"4"
      push 	"=123"
      push 	"aaaa"
      push 	eax
      push 	"8"
      push 	"=567"
      push 	"bbbb"
      push 	eax
      push 	"4"
      push 	"=123"
      push 	"cccc"
      mov 	esi, esp	; Save the address of stack top to esi
      
      ; Construct the environment array envp[]
      push 	eax				; envp[3] = 0, EOL
      push 	esi				; envp[2] points to "cccc=1234"
      add 	esi, 16 	; move the esi to point to next string
      push 	esi				; envp[1] points to "bbbb=5678"
      add 	esi, 16 	; move the esi to point to next string
      push 	esi				; envp[0] points to "aaaa=1234"
      mov  	edx, esp	; Get the address of envp[]

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

2-3-1-result

2.4 Using the Relative Addressing Technique

First, the program jumps to a call instruction at line 19, right before the the string.

2-4-2-start

Then, the program executes the call instruction and jumps back to the instruction after the previous jmp instruction, and push the address of the string /bin/sh*AAAABBBB into stack.

2-4-3-one

Now, The stack top contains the address of the string:

2-4-4-stack-top

The program will pop the string address and save to esi register after the instruction pop esi. esi register now holds the address of the string.

2-4-5-esi-holds-string-addr

Then, the program copys the string address to ebx register, ready to be used as first argument of execve system call.

The first 7 characters in the string contains the filename of the shell program we want to run in the shellcode. To construct the filename string, the program rewrites the memory at [ebx+7] with \0 to terminate the filename string.

2-4-6-turncate-string

Next, the program saves the address of string /bin/sh to the memory at [ebx+8], ready to construct the argv array.

2-4-7-argv

The program terminate the argv array with 0 at [ebx+12] and assigns the ecx register with the argv array.

2-4-8-argv-array

The program save 0 to edx for no environment variable is passed to the shell program.

2-4-9-no-envp

In the last, the program save 0x0b to eax and invokes a system call interrupt to spawn a shell.

2-4-10-sh

src/task2/labsh_rel.asm

section .text
global _start
_start:
      	jmp two
one:
				pop esi
        mov ebx, esi 						; (complete) ebx should contain the string address
				mov eax, 0
        mov byte [ebx+7], 0x00  ; (complete) terminate /bin/sh with 0x00 (1 byte)
        mov [ebx+8], ebx   			; (complete) save ebx to memory at addressebx+8, points to "/bin/sh" saved at $ebx
        mov [ebx+12], eax  			; (complete) save eax to memory at address ebx+12, terminates the argv[]
        lea ecx, [ebx+8]   			; let ecx = ebx + 8
        
				mov edx, 0
        mov al, 0x0b
        int 0x80
				;	execve("/bin/sh", ["/bin/sh"], NULL)
two:
        call one
        db "/bin/sh*AAAABBBB"		; "/bin/sh" and remaining characters as placeholder

Since this program is required for writable code segment, to produce a executable binary file for this program, we need to use the --omagic flag to enable ld to do so.

1
2
3
nasm -f elf32 ./src/task2/labsh.rel.asm
ld --omagic ./src/task2/labsh_rel.o -o labsh_rel
./labsh_rel