For a school project I have to inject any x86-64 Elf with a parasite which is basically an array of bytes (a string).
The basic behaviour is :
The method I choosed is a bit dirty. I inject a dynamically generated "shellcode" (printcode would fit better ^^) into the largest cave that I can find between two PT_LOAD segments. It is done throw a C program and nasm.
I make this segment readable/executable/writable, as well as the "real" executable PT_LOAD segment, which contains the .text section. I also change the p_filesz and p_memsz of the injected segment by adding the size of the shellcode to them.
Everything seems to work fine for 96% of the binaries inside the /bin/ directory of ubuntu (I tested it with docker).
All the faulty injections are because of lack of space between the different PT_LOAD segments, except for one program : python3.12.
With this program, when I run the resulting parasited program (I mean python3.12 with the injection code in it), I have a segmentation fault :
--- SIGSEGV {si_signo=SIGSEGV, si_code=SEGV_ACCERR, si_addr=0xa27000} ---
Here the full readelf -Wl
output for python3.12 :
$> readelf -Wl python3.12
Type de fichier ELF est EXEC (fichier exécutable)
Point d'entrée 0x657cc0
Il y a 14 en-têtes de programme, débutant à l'adresse de décalage 64
En-têtes de programme :
Type Décalage Adr. vir. Adr.phys. T.Fich. T.Mém. Fan Alignement
PHDR 0x000040 0x0000000000400040 0x0000000000400040 0x000310 0x000310 R 0x8
INTERP 0x000350 0x0000000000400350 0x0000000000400350 0x00001c 0x00001c R 0x1
[Réquisition de l'interpréteur de programme: /lib64/ld-linux-x86-64.so.2]
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x01f588 0x01f588 R 0x1000
LOAD 0x020000 0x0000000000420000 0x0000000000420000 0x2e27dd 0x2e27dd R E 0x1000
LOAD 0x303000 0x0000000000703000 0x0000000000703000 0x323fe0 0x323fe0 R 0x1000
LOAD 0x627dc8 0x0000000000a27dc8 0x0000000000a27dc8 0x17d3b8 0x180bc0 RW 0x1000
DYNAMIC 0x627dd8 0x0000000000a27dd8 0x0000000000a27dd8 0x000200 0x000200 RW 0x8
NOTE 0x000370 0x0000000000400370 0x0000000000400370 0x000020 0x000020 R 0x8
NOTE 0x000390 0x0000000000400390 0x0000000000400390 0x000044 0x000044 R 0x4
TLS 0x627dc8 0x0000000000a27dc8 0x0000000000a27dc8 0x000000 0x000010 R 0x8
GNU_PROPERTY 0x000370 0x0000000000400370 0x0000000000400370 0x000020 0x000020 R 0x8
GNU_EH_FRAME 0x5b0d84 0x00000000009b0d84 0x00000000009b0d84 0x0164c4 0x0164c4 R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x627dc8 0x0000000000a27dc8 0x0000000000a27dc8 0x000238 0x000238 R 0x1
Correspondance section/segment :
Sections de segment...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .text .fini
04 .rodata .stapsdt.base .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .got.plt .data .PyRuntime .probes .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .tbss
10 .note.gnu.property
11 .eh_frame_hdr
12
13 .init_array .fini_array .dynamic .got
The same for the parasited program :
En-têtes de programme :
Type Décalage Adr. vir. Adr.phys. T.Fich. T.Mém. Fan Alignement
PHDR 0x000040 0x0000000000400040 0x0000000000400040 0x000310 0x000310 R 0x8
INTERP 0x000350 0x0000000000400350 0x0000000000400350 0x00001c 0x00001c R 0x1
[Réquisition de l'interpréteur de programme: /lib64/ld-linux-x86-64.so.2]
LOAD 0x000000 0x0000000000400000 0x0000000000400000 0x01f588 0x01f588 RW 0x1000
LOAD 0x020000 0x0000000000420000 0x0000000000420000 0x2e27dd 0x2e27dd RWE 0x1000
LOAD (HERE) 0x303000 0x0000000000703000 0x0000000000703000 0x324227 0x324227 RWE 0x1000
LOAD 0x627dc8 0x0000000000a27dc8 0x0000000000a27dc8 0x17d3b8 0x180bc0 RW 0x1000
DYNAMIC 0x627dd8 0x0000000000a27dd8 0x0000000000a27dd8 0x000200 0x000200 RW 0x8
NOTE 0x000370 0x0000000000400370 0x0000000000400370 0x000020 0x000020 R 0x8
NOTE 0x000390 0x0000000000400390 0x0000000000400390 0x000044 0x000044 R 0x4
TLS 0x627dc8 0x0000000000a27dc8 0x0000000000a27dc8 0x000000 0x000010 R 0x8
GNU_PROPERTY 0x000370 0x0000000000400370 0x0000000000400370 0x000020 0x000020 R 0x8
GNU_EH_FRAME 0x5b0d84 0x00000000009b0d84 0x00000000009b0d84 0x0164c4 0x0164c4 R 0x4
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
GNU_RELRO 0x627dc8 0x0000000000a27dc8 0x0000000000a27dc8 0x000238 0x000238 R 0x1
Correspondance section/segment :
Sections de segment...
00
01 .interp
02 .interp .note.gnu.property .note.gnu.build-id .note.ABI-tag .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rela.dyn .rela.plt
03 .init .plt .text .fini
04 .rodata .stapsdt.base .eh_frame_hdr .eh_frame
05 .init_array .fini_array .dynamic .got .got.plt .data .PyRuntime .probes .bss
06 .dynamic
07 .note.gnu.property
08 .note.gnu.build-id .note.ABI-tag
09 .tbss
10 .note.gnu.property
11 .eh_frame_hdr
12
13 .init_array .fini_array .dynamic .got
If I'm not wrong : 0x703000 + 0x324224 = 0xA27224 and 0xA27224 - 0xA27000 = 0x224
So this specific address is inside a writable/readable/executable segment.
But I have the previously mentionned SIGSEGV with the si_code SEGV_ACCERR, which means the region where this address is has invalid permissions for mapped object.
I don't understand why, because the segment has all the needed rights (and more) and, after many hours of debugging, I'm out of ideas. I'm a beginner at asm programming, so maybe I misunderstood something.
A part of the shellcode, with the interesting addresses :
BITS 64
section .text
global inject
inject:
<SNIP>; before the next line, I save all the register except few temporary registers
mov rdi, 1
lea rsi, [rel msg] # 0xa26ffc <= 0x27000 is "inside" this instruction
mov rax, 1
mov rdx, end - msg
<SNIP>; before the next line, it's the rc4 logic, not needed for my problem
msg db '....STRNG....',0x0a,0x0 # 0xa270f4
end db 0x0
k db <SNIP>
S db <SNIP>
<EOF>
Interestingly, I'm sure that the address 0xa27000 is somehow in fault, because if I save the registers like this :
sub rsp, 88
mov [rsp], rbx
mov [rsp], rcx
mov [rsp], rdx
mov [rsp], rsi
mov [rsp], rdi
mov [rsp], rbp
mov [rsp], r8
mov [rsp], r9
mov [rsp], r13
mov [rsp], r14
mov [rsp], r15
mov [rsp], r9
is at the address 0xa27000, and it segfaults too.
If someone have an idea, I will be glad to read it. Thanks.
Edit
It appears that if I compile python3.12 it works perfectly.
The "only" difference is that the file from /bin on ubuntu is much tiny that the resulting one when I compile it manually. But if I strip the resulting compiled file, its size is lower than the python3.12 from ubuntu. I'm really lost.
Employed Russian found the culprit : it is indeed the RELRO.
The old program only tried to find the largest empty space between two PT_LOAD segments.
Now, it acts differently : it tries to find a segment where :
As is, python3.12 can be injected like practically every other binaries inside the /bin/ directory of ubuntu.
I tried to change the type of the PT_GNU_RELRO segment to PT_NOTE, but for an unknown reason, it doesn't work. I think that for my current knowledge, I found the best solution that I could think about.
Have a full RELRO, partial RELRO doesn't matter. The python3.12 program I compiled was full RELRO (the real python3.12 has partial RELRO) and was injected like any other. The crucial point is really to compute if the v_addr of the injected code is inside the RELRO range. That's it.
Thanks again to Employed Russian for his help.
If I'm not wrong : 0x703000 + 0x324224 = 0xA27224 and 0xA27224 - 0xA27000 = 0x224 So this specific address is inside a writable/readable/executable segment.
Except the RELRO
segment tells the runtime loader: after you have relocated this file, use mprotect
to set the [0xa27dc8, 0xa27dc8+0x238)
as R
(i.e. read-only).
The loader can't do exactly that since 0xa27dc8
is not page aligned. So the loader rounds down 0xa27dc8
to the page size (i.e. to 0xa27000
), and mprotect
s that page. Which means that 0xa27000
is no longer executable!
QED.
You can read about the RELRO
here.
When you build your own version of Python
, you probably end up with a binary without the RELRO
segment, because you didn't configure
that binary to use it.