Description
I wrote an exploit for an 32-bit ARM Little Endian Linux based system a while back, and came across a bug in the template used to generate binaries for ELF shared object binaries.
If you generate a payload via msfvenom
for an ELF Shared Object (SO) like this:
$ ruby msfvenom --arch armle --platform linux --payload linux/armle/exec --format elf-so -o ~/hax1.so CMD="something"
A binary is created via a template (here in Ruby we get the template bin file, and here is the actual template source code).
If we load the resulting binary hax1.so
into IDA, we can see the following:
LOAD:00000000 AREA LOAD, CODE, READWRITE, ALIGN=12
LOAD:00000000 CODE32
LOAD:00000000 DCD 0x464C457F ; File format: \x7FELF
LOAD:00000004 DCB 1 ; File class: 32-bit
LOAD:00000005 DCB 1 ; Data encoding: little-endian
LOAD:00000006 DCB 1 ; File version
LOAD:00000007 DCB 0 ; OS/ABI: UNIX System V ABI
LOAD:00000008 DCB 0 ; ABI Version
LOAD:00000009 DCB 0, 0, 0, 0, 0, 0, 0 ; Padding
LOAD:00000010 DCW 3 ; File type: Shared object
LOAD:00000012 DCW 0x28 ; Machine: ARM
LOAD:00000014 DCD 1 ; File version
LOAD:00000018 DCD 0xF6 ; Entry point
LOAD:0000001C DCD 0x34 ; PHT file offset
LOAD:00000020 DCD 0x74 ; SHT file offset
LOAD:00000024 DCD 0 ; Processor-specific flags
LOAD:00000028 DCW 0x34 ; ELF header size
LOAD:0000002A DCW 0x20 ; PHT entry size
LOAD:0000002C DCW 2 ; Number of entries in PHT
LOAD:0000002E DCW 0x28 ; SHT entry size
LOAD:00000030 DCW 2 ; Number of entries in SHT
LOAD:00000032 DCW 1 ; SHT entry index for string table
LOAD:00000034 ; ELF32 Program Header
LOAD:00000034 ; PHT Entry 0
LOAD:00000034 DCD 1 ; Type: LOAD
LOAD:00000038 DCD 0 ; File offset
LOAD:0000003C DCD 0 ; Virtual address
LOAD:00000040 DCD 0 ; Physical address
LOAD:00000044 DCD 0x10E ; Size in file image
LOAD:00000048 DCD 0x126 ; Size in memory image
LOAD:0000004C DCD 7 ; Flags
LOAD:00000050 DCD 0x1000 ; Alignment
LOAD:00000054 ; PHT Entry 1
LOAD:00000054 DCD 2 ; Type: DYNAMIC
LOAD:00000058 DCD 7 ; File offset
LOAD:0000005C DCD stru_C4 ; Virtual address
LOAD:00000060 DCD 0xC4 ; Physical address
LOAD:00000064 DCD 0xC4 ; Size in file image
LOAD:00000068 DCD 0x30 ; Size in memory image
LOAD:0000006C DCD 0x30 ; Flags
LOAD:00000070 DCD 0x1000 ; Alignment
LOAD:00000074 DCD 1, 6, 0
LOAD:00000080 DCD 0xC4, 0xC4, 0x30, 0, 0
LOAD:00000094 DCD 8, 7, 0
LOAD:000000A0 DCD 3, 0
LOAD:000000A8 DCD 0xF4, 0xF4, 2, 0, 0, 0, 0
LOAD:000000C4 ; ELF Dynamic Information
LOAD:000000C4 stru_C4 Elf32_Dyn <0xC, <0xF6>> ; DATA XREF: LOAD:0000005C↑o
LOAD:000000C4 ; DT_INIT
LOAD:000000CC Elf32_Dyn <5, <0xF4>> ; DT_STRTAB
LOAD:000000D4 Elf32_Dyn <6, <0xF4>> ; DT_SYMTAB
LOAD:000000DC Elf32_Dyn <0xA, <0>> ; DT_STRSZ
LOAD:000000E4 Elf32_Dyn <0xB, <0>> ; DT_SYMENT
LOAD:000000EC Elf32_Dyn <0> ; DT_NULL
LOAD:000000F4 DCB 0, 0
LOAD:000000F6 EXPORT .init_proc
LOAD:000000F6 .init_proc DCW 0x3001
LOAD:000000F8 DCD 0xFF13E28F, 0x4678E12F, 0x9001300A, 0x1A92A901, 0xDF01270B
LOAD:0000010C DCB 0x69, 0x64
We can note that the entry point is located as 0xF6
, and the code at LOAD:000000F6
has failed to disassemble.
If we look at the documentation from ARM here we can note the following:
All A32 instructions are 32 bits long. Instructions are stored word-aligned, so the least significant two bits of instruction addresses are always zero in A32 state.
T32 instructions are either 16 or 32 bits long. Instructions are stored half-word aligned.
Note, on ARM a word here is 32 bits and a half-word is 16 bits (Confusing if you are used to Windows development, i.e. DWORD et. al.).
As the entry point is 0xF6
, the least significant bit is not set, so we are not in Thumb mode (T32), however the alignment is wrong for Arm mode (A32), it should be either 0xF4
or 0xF8
(word aligned), but it cannot be 0xF6
(half-word aligned).
During my development, a shared object binary like this would fail to load and execute.
The solution was to honor the expected alignment requirements, by adding two extra null bytes before the _start
label.
diff --git a/elf_dll_armle_template.s b/elf_dll_armle_template_fix.s
index d159a69..2c026b8 100644
--- a/elf_dll_armle_template.s
+++ b/elf_dll_armle_template_fix.s
@@ -88,5 +88,8 @@ strtab:
db 0
db 0
strtabsz equ $ - strtab
+
+ db 0x00, 0x00 ; add padding to honor T32 alignment
+
global _start
_start:
The result of this was the entry point will be 0xF8 and the SO binary loaded as expected in my target system.
This should then work for both A32 and T32 code that gets placed in the SO.
Metadata
Metadata
Assignees
Type
Projects
Status