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

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

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

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,

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.

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

2. Spawning a Shell
2.1 Startup Code (labsh.asm)
- The process number of both the calling shell and the spawned shell using “
echo $$”.

- The passed environment variables to the spawned shell using “
/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.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.4 Using the Relative Addressing Technique
First, the program jumps to a call instruction at line 19, right before the the string.

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.

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

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.

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.

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

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

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

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

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