diff options
| -rw-r--r-- | makefile | 9 | ||||
| -rwxr-xr-x | sh | bin | 0 -> 1664 bytes | |||
| -rw-r--r-- | sh.asm | 465 | ||||
| -rw-r--r-- | sh.s | 388 |
4 files changed, 473 insertions, 389 deletions
@@ -1,5 +1,12 @@ all: - nasm sh.s -f elf64 -o sh.o + nasm sh.asm -f elf64 -o sh.o ld sh.o -o sh + strip sh + rm sh.o +dbg: + nasm sh.asm -f elf64 -o sh.o + ld sh.o -o sh + rm sh.o + gdb -q --eval-command="layout asm" -tui sh clean: rm -f sh.o sh Binary files differ@@ -0,0 +1,465 @@ +section .data + sigs dd 0x2 + cpid dd 0x0 + eol db `\n` + eol_mask db `\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n`,`\n` + env_str db "/etc/environment", 0x0 + + prompt_str db "[0xdeadbeef] " + prompt_str_len equ $-prompt_str + + not_func_str db "Error: program not found.", `\n` + not_func_str_len equ $-not_func_str + + no_dir_str db "cd: Unknown directory.",`\n` + no_dir_str_len equ $-no_dir_str + + invalid_int_str db "exit: Invalid integer.",`\n` + invalid_int_str_len equ $-invalid_int_str + ;Flag indicating ability to execute + X_OK equ 0x1 + + ;Indicates that a file can be accessed in the way specified + F_OK equ 0x0 + + ;option for waitid + P_PGID equ 2 + + ;Special file descriptors + fd_stdin equ 0x00 + fd_stdout equ 0x01 + fd_stderr equ 0x02 + + ;Syscall constants + sys_read equ 0x00 + sys_write equ 0x01 + sys_open equ 0x02 + sys_close equ 0x03 + sys_access equ 0x15 + stub_fork equ 0x39 + stub_execve equ 0x3b + sys_exit equ 0x3c + sys_wait4 equ 0x3d + sys_kill equ 0x3e + sys_chdir equ 0x50 + sys_waitid equ 0xf7 + +section .bss + ;These must all be divisible by 16! + input_buffer_size equ 4096 + input_buffer resb input_buffer_size + + path_buffer_size equ 4096 + path_buffer resb path_buffer_size + + concat_buffer_size equ 8192 + concat_buffer resb concat_buffer_size ;Double so the path and input can never overflow concat_buffer when combined + +section .text + global _start + +_start: + ; TODO: handle interrupt signal + + mov r15, input_buffer ;stdin input + mov r9, path_buffer ;PATH variable reader + mov rbx, concat_buffer ;Concatenated directory and command name + + ; input: none + ; output: r9 (path elements), r10 (length) + ; clobbered: r8, r12 + ; other: r15 (buffer) + _parse_path: + mov rax, sys_open + mov rdi, env_str + xor rsi, rsi + syscall + test rax, rax ;check if rax is 0 + jl _quit + + call _bzero + + _parse_path2: + mov rdi, rax + mov rax, sys_read + mov rsi, r9 + mov rdx, path_buffer_size + syscall + test rax, rax + jl _quit + mov r8, r9 + dec r8 + + ; Reads the PATH variable + mov r10, `\0\0\0PATH=` + _pathfinder: + inc r8 + + ;Check that we've found the PATH variable + mov rax, [r8] + shl rax, 24 + cmp rax, r10 + jne _pathfinder ;if we haven't, let's move right one and see if it's there + cmp byte [r8], `"` + jne _pathfinder_continue + inc r8 + cmp byte [r8], 0 + jz _quit + _pathfinder_continue: + add r8, 0x5 + mov r10, 0x1 + push 0x0 + push r8 + dec r8 + + _pathender: + inc r8 + mov al, [r8] + cmp al, `:` + je _pathender1 + cmp al, `"` + je _pathender_exit + cmp al, `\n` + je _pathender_exit + cmp al, 0 + jz _pathender_exit + jmp _pathender + _pathender_exit: + mov byte [r8], 0x0 + mov r9, rsp + jmp _read_loop + + _pathender1: + inc r10 + mov byte [r8], 0x0 + lea rcx, [r8+0x1] + push rcx + jmp _pathender + +_read_loop: + mov rax, sys_write + mov rdi, fd_stdout + mov rsi, prompt_str + mov rdx, prompt_str_len + syscall + + call _bzero + +_read_loopr: + mov rax, sys_read + xor rdi, rdi ;stdin + mov rsi, r15 + mov rdx, input_buffer_size + syscall + + ; check for _quit + mov r12, _quit + cmp byte [r15], 0x0 + je _weol + ;Start using r12 to hold the number of args + xor r12,r12 + + ; call _parse + jmp _parse + +_exec: + ; fork so we don't hurt ourselves + mov rax, stub_fork + syscall + mov dword [cpid], eax + test eax, eax ;checks if cpid is 0 + jne _wait_for_proc + + ; now that we know what to execute, do so + mov rax, stub_execve + mov rdi, r13 ;Filename + mov rsi, r14 ;argv + xor rdx, rdx ;envp + syscall + + ; and now kill the child process + jmp _quit + +;waits for the process to close +_wait_for_proc: + mov r12, r10 ;backup + + mov rsi, rax + mov rax, sys_waitid + xor rdi, rdi + xor rdx, rdx + mov r10, P_PGID + xor r8, r8 + syscall + + mov r10, r12 ;restore + + mov dword [cpid], -0x1 + jmp _read_loop + +;Writes 0s in buffer r15 until r8 reaches input_buffer_size +;input: r15 (buffer) +;output: none +_bzero: + xor r8, r8 + _bzero_main: + movups [r15+r8], xmm0 + add r8, 16 + cmp r8, input_buffer_size + jl _bzero_main + ret + +;input: r15 (buffer) +;output: r13 (path), r14 (arguments) +;clobbered: r8, rax, rbx, rcx +_parse: + ;syscall 80 is chdir, so do that first, it's easiest + ;It takes a path string. + ;also needs pipes (|, >, <) + xor r13, r13 ;for _strlen + push r13 ;push 0 + call _strlen + +;r8 holds strlen so we start at the end and parse backwards +; go through and sub/push +_parse1: + cmp byte [r15+r8], ` ` + je _subzp + cmp byte [r15+r8], `\n` + je _subz + cmp byte [r15+r8], `/` + je _sabsf + cmp byte [r15+r8], `.` + je _sabsf + +;Controls the parsing of the directory, keeps parsing each bit of the dir until +;the end of the string or directory name +_parse1r: + dec r8 + jl _parse2 + jmp _parse1 + +_sabsf: + cmp r8, 1 + jg _parse1r + mov r13, 1 + jmp _parse1r + +_subz: + mov byte [r8+r15], 0x0 + jmp _parse1 + +;Splits at the current position and puts the address of the argument on the stack +_subzp: + mov byte [r8+r15], 0x0 + lea rax, [r8+r15+1] + push rax + inc r12 + jmp _parse1 + +_parse2: + push rbx + mov r14, rsp + test r13, r13 + jg _parse7 + mov r8, -0x1 + +_parse3: + inc r8 + cmp r8, r10 + je _parse4 + ; Builtins to add: export, eval + ;r15 holds the name that the user entered, so we'll use that for shell builtins + mov rcx,`\0\0\0exit\0` + mov r13, [r15] + shl r13, 24 ;Remove the last character in r13, because it's an arg not the name of it + cmp r13, rcx + je _builtin_exit + mov rcx,`\0\0\0\0\0cd\0` + shl r13, 16 ;Remove the extra characters for cd, note how we're doing the + ;longest function names first then the shorter later + cmp r13, rcx + je _builtin_cd + ;END OF BUILTINS + ;mov r13, r15 + mov rdi, [r9+r8*8] + push r8 + ; input: rdi (a), r15 (b) + ; output: rbx (string) + ; clobbered: r8, r11 + _concat_dir: + xor r8,r8 ;new string's length (and for _dirty_strcpy_1, also the + ;initial string length) + ;Copies the contents of rdi to rbx in blocks of + ;16, doesn't bother to clean up stuff past the null + ;so moves that too, but it doesn't matter, just make + ;sure the buffer is big enough + _dirty_strcpy_1: + movDqU xmm2, [rdi+r8] + movDqU [rbx+r8],xmm2 + + pcmpistri xmm1, xmm2, 122 + jz _dirty_strcpy_1_exit + + PcmpIstrI xmm0, xmm2, 122 ;if there was a null in the + ;copied string exit loop + jz _dirty_strcpy_1_exit + add r8,15 + jmp _dirty_strcpy_1 + _dirty_strcpy_1_exit: + add r8,rcx + inc r8 + mov byte [rbx+r8], '/' + inc r8 + xor r11, r11 + _dirty_strcpy_2: + movdqu xmm2, [r15+r11] + movdqu [rbx+r8], xmm2 + + pcmpistri xmm1, xmm2, 122 + jz _parse5 + + pcmpistri xmm0, xmm2, 122 + jz _parse5 + + add r8, 15 + add r11, 15 + jmp _dirty_strcpy_2 + +_parse4: ; Function not found + mov rax, sys_write + mov rdi, fd_stdout + mov rsi, not_func_str + mov rdx, not_func_str_len + syscall + jmp _read_loop + +; Checks that the program rbx can be executed +_parse5: + mov rax, sys_access + mov rdi, rbx + mov rsi, X_OK + syscall + + pop r8 + ;Check if it can be executed + cmp rax, F_OK + jl _parse3 + +; We'll fall through to here if the file is accessible, therefore we should execute it +_parse6: + mov r13, rbx + jmp _exec + +_parse7: ; absolute path + mov rax, sys_access + mov rdi, r15 + mov rsi, X_OK + syscall + + cmp rax, F_OK + jl _parse4 + + mov r13, r15 + jmp _exec + + +;input: r15 (buffer) +;Clobbered: rsi +;output: r8 (length) +_strlen: + xor r8, r8 + xchg rsi, rcx ;bacup rcx, let rsi get clobbered + MovDqU xmm1, [eol_mask] + _strlen1: + PcmpIstrI xmm1, [r15+r8], 122 + jz _strlen_end + PcmpIstrI xmm0, [r15+r8], 122 + jz _strlen_end + add r8, 15 + jmp _strlen1 + _strlen_end: + inc rcx + add r8, rcx + xchg rsi,rcx + ret + +; interrupt signal handler +_sigint: + cmp dword [cpid], 0x0 + jl _sigint_nc + +__sigint_c: ; kill the child + mov rax, sys_kill + mov rdi, [cpid] + mov rsi, 0x2 + syscall + +_sigint_nc: + mov r12, _read_loop + jmp _weol + +; input: r12 (return address) +; output: none +; Writes end of line to the terminal +_weol: + mov rax, sys_write + mov rdi, fd_stdout + mov rsi, eol + mov rdx, 0x1 + syscall + jmp r12 + +_quit: + mov rax, sys_exit + xor rdi, rdi + syscall + +;cd [dir (rax)] +;changes to the given directory +_builtin_cd: + mov rdi,rax + mov rax, sys_chdir + syscall + test rax, rax + jz _read_loop + mov rax, sys_write + mov rdi, fd_stdout + mov rsi, no_dir_str + mov rdx, no_dir_str_len + syscall + jmp _read_loop +;usage: exit [status] +;exits with the given status or 0 if one hasn't been provided +_builtin_exit: + ;convert first arg to an int if present + ;exit with that code + _string_to_int: + xor rdi, rdi + ;accumulate in rdi since it's also the register used to provide the status + ;to sys_exit + xor rdx, rdx + test r12, r12 + jz _string_to_int_end + _string_to_int_loop: + mov dl, [rax] ; Convert this to a number + test dl, dl ;Checks for NULL + jz _string_to_int_end + sub dl, `0` ;sub because we'll need to do this anyway + jl _invalid_int + cmp dl, 9 ;since we've already subtracted we can just compase with 9 + jg _invalid_int + imul rdi, 10 + add rdi, rdx + inc rax + jmp _string_to_int_loop + _string_to_int_end: + mov rax, sys_exit + syscall +_invalid_int: + mov rax,sys_write + mov rdi, fd_stdout + mov rsi, invalid_int_str + mov rdx, invalid_int_str_len + syscall + jmp _read_loop @@ -1,388 +0,0 @@ -section .text - global _start - -_start: - ; TODO: handle interrupt signal - - push rbp - mov rbp, rsp - sub rsp, 0x110 - mov r15, rsp - sub rsp, 0x110 - mov r9, rsp - sub rsp, 0x110 - mov rbx, rsp - - mov rax, 0x1 - mov rdi, 0x1 - mov rsi, msg - mov rdx, 0x1b - syscall - - jmp parse_path - -rloop: - mov rax, 0x1 - mov rdi, 0x1 - mov rsi, prompt - mov rdx, 0xd - syscall - - mov r8, -0x1 - mov r12, rloopr - jmp bzero - -rloopr: - mov rax, 0x0 - mov rdi, 0x0 - mov rsi, r15 - mov rdx, 0xff - syscall - - ; check for quit - cmp byte [r15], 0x0 - mov r12, quit - je weol - - ; call parse - jmp parse - -exec: - ; fork so we don't hurt ourselves - mov rax, 0x39 - syscall - mov dword [cpid], eax - cmp dword [cpid], 0x0 - jne wait4_it - - ; now that we know what to execute, do so - mov rax, 0x3b - mov rdi, r13 - mov rsi, r14 - mov rdx, 0x0 - syscall - - ; and now kill the child process - jmp quit - -wait4_it: - mov rdi, rax - mov rax, 0x3d - xor rsi, rsi ; null status pointer - xor rdx, rdx - xor rcx, rcx ; null rusage pointer - syscall - - mov dword [cpid], -0x1 - jmp rloop - -; input: none -; output: r9 (path elements), r10 (length) -; clobbered: r8, r12 -; other: r15 (buffer) -parse_path: - mov rax, 0x2 - mov rdi, env - mov rsi, 0x0 - syscall - cmp rax, 0x0 - jl quit - - mov r8, -0x1 - mov r12, parse_path2 - jmp bzero - -parse_path2: - mov rdi, rax - mov rax, 0x0 - mov rsi, r9 - mov rdx, 0xff - syscall - cmp rax, 0x0 - jl quit - mov r8, r9 - dec r8 - -pathfinder: - inc r8 - xor rax, rax - mov byte al, [r8] - cmp al, 0x50 - jne pathfinder - mov byte al, [r8+1] - cmp al, 0x41 - jne pathfinder - mov byte al, [r8+2] - cmp al, 0x54 - jne pathfinder - mov byte al, [r8+3] - cmp al, 0x48 - jne pathfinder - mov byte al, [r8+4] - cmp al, 0x3d - jne pathfinder - mov byte al, [r8+5] - cmp al, 0x22 - jne pathfinder - cmp al, 0x0 - je quit - add r8, 0x6 - mov r10, 0x1 - push 0x0 - push r8 - dec r8 - -pathender: - inc r8 - mov al, [r8] - cmp al, 0x3a - je pathender1 - cmp al, 0x22 - jne pathender - mov byte [r8], 0x0 - mov r9, rsp - jmp rloop - -pathender1: - inc r10 - mov byte [r8], 0x0 - lea rcx, [r8+0x1] - push rcx - jmp pathender - -;input: r8 (iterator), r15 (buffer), r12 (return address) -;output: none -bzero: - inc r8 - mov byte [r8+r15], 0x0 - cmp r8, 0xff - jle bzero - jmp r12 - -;input: r15 (buffer) -;output: r13 (path), r14 (arguments) -;clobbered: r8, rax, rbx, rcx -parse: ; needs special commands: cd exit export echo eval pwd - ;also needs pipes (|, >, <) - push 0x0 - xor r13, r13 - jmp strlen - -; go through and sub/push -parse1: - cmp byte [r8+r15], 0x20 - je subzp - cmp byte [r8+r15], 0x0a - je subz - cmp byte [r8+r15], 0x2f - je sabsf - cmp byte [r8+r15], 0x2e - je sabsf - -parse1r: - dec r8 - cmp r8, 0x0 - jl parse2 - jmp parse1 - -sabsf: - cmp r8, 0x1 - jg parse1r - mov r13, 0x1 - jmp parse1r - -subz: - mov byte [r8+r15], 0x0 - jmp parse1 - -subzp: - mov byte [r8+r15], 0x0 - lea rax, [r8+r15+0x1] - push rax - jmp parse1 - -parse2: - push rbx - mov r14, rsp - cmp r13, 0x0 - jg parse7 - mov r8, -0x1 - -parse3: - inc r8 - cmp r8, r10 - je parse4 - mov r13, r15 - mov rcx, [r9+r8*8] - push r8 - jmp concat - -parse4: ; not found - mov rax, 0x1 - mov rdi, 0x1 - mov rsi, nfe - mov rdx, 0x1a - syscall - jmp rloop - -parse5: ; test rbx - pop r8 - - mov rax, 0x2 - mov rdi, rbx - xor rsi, rsi - xor rdx, rdx - syscall - - cmp rax, 0x0 - jl parse3 - - mov rdi, rax - mov rax, 0x3 - syscall - -parse6: ; found it - mov r13, rbx - jmp exec - -parse7: ; absolute path - mov rax, 0x2 - mov rdi, r15 - xor rsi, rsi - xor rdx, rdx - syscall - - cmp rax, 0x0 - jl parse4 - - mov rdi, rax - mov rax, 0x3 - syscall - - mov r13, r15 - jmp exec - - -;input: r12 (return address), r15 (buffer) -;output: r8 (length) -strlen: - xor r8, r8 - -strlen1: - cmp byte [r8+r15], 0x0 - je parse1 - cmp byte [r8+r15], 0xa - je parse1 - inc r8 - jmp strlen1 - -; input: rcx (a), r13 (b) -; output: rbx (string) -; clobbered: r8, r11 -concat: - mov r8, -0x1 - jmp jlen1 - -jlen1: - inc r8 - cmp byte [r8+rcx], 0x0 - je jlen02 - cmp byte [r8+rcx], 0xa - je jlen02 - jmp jlen1 - -jlen02: - dec r8 - mov r11, -0x1 - -jlen2: - inc r8 - inc r11 - cmp byte [r11+r13], 0x0 - je treg - cmp byte [r11+r13], 0xa - je treg - jmp jlen2 - -treg: - cmp r8, 0xff - jg boe - xchg r15, rbx - mov r8, -0x1 - mov r12, rsinc - jmp bzero - -rsinc: - xchg r15, rbx - mov r8, -0x1 - mov r11, -0x1 - -join1: - inc r8 - cmp byte [r8+rcx], 0x0 - je join02 - cmp byte [r8+rcx], 0xa - je join02 - mov al, [r8+rcx] - mov [r8+rbx], al - jmp join1 - -join02: - mov byte [r8+rbx], 0x2f -join2: - inc r8 - inc r11 - cmp byte [r11+r13], 0x0 - je parse5 - cmp byte [r11+r13], 0xa - je parse5 - mov al, [r11+r13] - mov [r8+rbx], al - jmp join2 - -boe: - mov rax, 0x1 - mov rdi, 0x1 - mov rsi, boem - mov rdx, 0x1f - jmp quit - -; interrupt signal handler -sigint: - cmp dword [cpid], 0x0 - jl sigint_nc - -sigint_c: ; kill the child - mov rax, 0x3e - mov rdi, [cpid] - mov rsi, 0x2 - syscall - -sigint_nc: - mov r12, rloop - jmp weol - -; input: r12 (return address) -; output: none -weol: - mov rax, 0x1 - mov rdi, 0x1 - mov rsi, eol - mov rdx, 0x1 - syscall - jmp r12 - -quit: - mov rax, 0x3c - mov rdi, 0x0 - syscall - -section .data - sigs: dd 0x2 - cpid: dd 0x0 - eol: db 0xa - msg: db "Welcome to deadbeef shell", 0x21, 0x0a - prompt: db "[0xdeadbeef]", 0x20 - nfe: db "Error: program not found", 0x2e, 0x0a - env: db "/etc/environment", 0x0 - boem: db "Error: input overflows buffer", 0x2e, 0x0a |
