Initial commit
This commit is contained in:
commit
394e3be54d
41 changed files with 16315 additions and 0 deletions
124
wosuid/shellcode.S
Normal file
124
wosuid/shellcode.S
Normal file
|
|
@ -0,0 +1,124 @@
|
|||
; ============================================================================
|
||||
; shellcode.S -- the reference shellcode for the wosuid lab (foowosc)
|
||||
; ============================================================================
|
||||
;
|
||||
; This file exists for ONE reason: to let you prove that the `SHELLCODE[]`
|
||||
; array in foowosc.c is exactly the machine code you would get from
|
||||
; assembling these instructions. It is not used by the exploit, which carries
|
||||
; the bytes inline so it has no runtime dependency on nasm.
|
||||
;
|
||||
; make verify-shellcode assembles this and diffs it against foowosc.c
|
||||
;
|
||||
; WHAT IT DOES
|
||||
; ------------
|
||||
; execve("/bin/sh", argv = NULL, envp = NULL)
|
||||
;
|
||||
; 23 bytes that turn the process into a shell -- byte-identical to the
|
||||
; shellcode in the parent lab's fooc.c.
|
||||
;
|
||||
; WHY 23 BYTES AND NOT 32 -- the difference between the labs, in one payload
|
||||
; ---------------------------------------------------------------------------
|
||||
; The SUID lab (foosd/foosc) needed a 32-byte shellcode that prefixed
|
||||
; setreuid(0,0). Why:
|
||||
;
|
||||
; * a setuid-root binary gives the process euid 0 but LEAVES ruid = the
|
||||
; launching user (1000);
|
||||
; * bash (and dash) check `euid != ruid` at startup and, absent `-p`,
|
||||
; reset euid = ruid -- the shell's own guard against this attack;
|
||||
; * so a plain execve("/bin/sh") from a *setuid* process yields a shell
|
||||
; that has quietly dropped root; the real uid must be cleared first.
|
||||
;
|
||||
; THIS lab deliberately has NO setuid bit. The daemon is root because it was
|
||||
; STARTED as root: real uid 0, effective uid 0, saved uid 0. fork() inherits
|
||||
; all three, execve() changes none of them, and bash starts with equal uid 0s
|
||||
; -- the guard has nothing to reset, so the plain execve keeps root. The
|
||||
; same 23 bytes that pwnd the user-level `food` daemon in the parent lab
|
||||
; open a ROOT shell here, because the process they run inside is already
|
||||
; fully root.
|
||||
;
|
||||
; The setuid bit transfers privilege; it is not the privilege itself. When a
|
||||
; root-started daemon is exploited, the outcome is identical to exploiting a
|
||||
; setuid binary -- minus the need to fiddle with the real uid.
|
||||
;
|
||||
; Register usage follows the System V AMD64 ABI: first integer args in
|
||||
; rdi, rsi, rdx; syscall number in rax.
|
||||
; ============================================================================
|
||||
|
||||
BITS 64
|
||||
|
||||
; section .text -- mark it executable, the default, so `nasm -f bin` emits
|
||||
; the instruction bytes with no ELF wrapper around them.
|
||||
section .text
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; xor esi, esi
|
||||
; rsi = 0 -> argv = NULL
|
||||
;
|
||||
; Zeroing with xor instead of `mov esi, 0` is two bytes shorter (2 vs 5)
|
||||
; and the classic x86 idiom for producing a zero without a memory operand.
|
||||
; ---------------------------------------------------------------------------
|
||||
xor esi, esi
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; xor edx, edx
|
||||
; rdx = 0 -> envp = NULL
|
||||
;
|
||||
; argv = NULL lets the kernel synthesise argv[0] from the pathname, and
|
||||
; envp = NULL gives the new program an empty environment. The shell runs
|
||||
; fine but with no PATH, so `id` and `uname` work and bare `vi` does not --
|
||||
; a small detail that surprises people, and the reason the relayed local
|
||||
; side of the exploit never relies on a PATH-based command.
|
||||
; ---------------------------------------------------------------------------
|
||||
xor edx, edx
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; movabs rdi, 0x68732f6e69622f
|
||||
; rdi = the 8 bytes 2f 62 69 6e 2f 73 68 00, i.e. "/bin/sh\0"
|
||||
;
|
||||
; Read the immediate right-to-left as bytes and it spells the string out.
|
||||
; That packing is the whole trick: eight bytes of payload in a ten-byte
|
||||
; instruction, no data section, no relocation, no alignment padding.
|
||||
; ---------------------------------------------------------------------------
|
||||
movabs rdi, 0x68732f6e69622f
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; push rdi
|
||||
; Put those eight bytes on the stack, where a string has to live so that a
|
||||
; register can point at it. The stack is writable and lives at an
|
||||
; attacker-chosen address, so this is the position-independent way to
|
||||
; materialise a string constant inside a payload that has no .data.
|
||||
; ---------------------------------------------------------------------------
|
||||
push rdi
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; mov rdi, rsp
|
||||
; rdi = the address of the string we just pushed = argv[0] as well as the
|
||||
; pathname. Reusing one buffer for both is legal; the kernel only reads the
|
||||
; pathname before it sets up the new stack, and by then argv[0] is copied.
|
||||
; ---------------------------------------------------------------------------
|
||||
mov rdi, rsp
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; push 0x3b
|
||||
; pop rax
|
||||
; rax = 59 = the __NR_execve slot in the x86-64 syscall table.
|
||||
;
|
||||
; Syscall numbers are part of the kernel ABI and are frozen: 0 = read,
|
||||
; 1 = write, 2 = open, ..., 59 = execve. `push 0x3b; pop rax` is the
|
||||
; idiomatic 2-byte way to load a small constant; `mov eax, 0x3b` is 5.
|
||||
; ---------------------------------------------------------------------------
|
||||
push 0x3b
|
||||
pop rax
|
||||
|
||||
; ---------------------------------------------------------------------------
|
||||
; syscall
|
||||
; Trap into the kernel. On return, either we are a shell (success) or we
|
||||
; are handed a -errno in rax and fall off the end of the payload (failure).
|
||||
; ---------------------------------------------------------------------------
|
||||
syscall
|
||||
|
||||
; Note what is NOT here:
|
||||
; * no setreuid -- the process was started as root, so ruid is already 0
|
||||
; (compare the 32-byte variant in the suid lab, which had to clear it).
|
||||
; * no `ret` -- execve does not return.
|
||||
; * no `nop` sled -- we jump straight to the first byte.
|
||||
Loading…
Add table
Add a link
Reference in a new issue