« Back to the Da Slop Pit Forum

readelf 2.30 DOS (Assertion Fail/OOB read)

Posted by yuu

posted
updated

Forum: Da Slop Pit Group

I haven't written about this anywhere but figured I should share. This is a DOS I found in readelf 2.30.


Here are the two binaries that triggered this crash


  $ readelf --version
GNU readelf (GNU Binutils for Ubuntu) 2.30
$ cat readelfcrash1.bin | base64
f0VMRgIBAQAAAAAAAAAAAAKdpwA+AAEAAAB4AEAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAQAA4
EAEAAAAA1BQAAQAAAAUAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAACAAAAAAAAAAIAAAAAAAAAA
AAAgAAAAAACwPGa/BgAPBQ==
$ xxd readelfcrash1.bin
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 029d a700 3e00 0100 0000 7800 4000 0000 ....>.....x.@...
00000020: 0000 4000 0000 0000 0000 0000 0000 0000 ..@.............
00000030: 0000 0000 0000 4000 3810 0100 0000 00d4 ......@.8.......
00000040: 1400 0100 0000 0500 0000 0000 0000 0000 ................
00000050: 0000 0000 4000 0000 0000 0000 4000 0000 ....@.......@...
00000060: 0000 8000 0000 0000 0000 8000 0000 0000 ................
00000070: 0000 0000 2000 0000 0000 b03c 66bf 0600 .... ......<f...
00000080: 0f05 ..
$ cat readelfcrash2.bin | base64
f0VMRgIBAQAAAAAAAAAAAAJFCAA+AAEAAAB4AEAAAAAAAEAAAAAAAAAAAAAAAAAAACgAAAAAQFU+
AwEAAAAAAAAAAQAAAAUAAAAAAAAAAAAAAAAAQAAAAAAAAABAAAAAAACAAAAAAAAAAIAAAAAAAAAA
AAAgAAAAAACwPGa/BgAPBQ==
$ xxd readelfcrash2.bin
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0245 0800 3e00 0100 0000 7800 4000 0000 .E..>.....x.@...
00000020: 0000 4000 0000 0000 0000 0000 0000 0000 ..@.............
00000030: 0028 0000 0000 4055 3e03 0100 0000 0000 .(....@U>.......
00000040: 0000 0100 0000 0500 0000 0000 0000 0000 ................
00000050: 0000 0000 4000 0000 0000 0000 4000 0000 ....@.......@...
00000060: 0000 8000 0000 0000 0000 8000 0000 0000 ................
00000070: 0000 0000 2000 0000 0000 b03c 66bf 0600 .... ......<f...
00000080: 0f05


What happens when you run one of them? Here is the gdb/gef output for crash1

  ...
Dynamic symbol information is not available for displaying symbols.

No version information found in this file.
readelf: Error: Too many program headers - 0x1038 - the file is not that big
readelf: ../../binutils/readelf.c:658: find_section: Assertion `filedata->section_headers != NULL' failed.

Program received signal SIGABRT, Aborted.

[ Legend: Modified register | Code | Heap | Stack | String ]
─────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0
$rbx : 0x0
$rcx : 0x00007ffff7803fb7 → <raise+199> mov rcx, QWORD PTR [rsp+0x108]
$rdx : 0x0
$rsp : 0x00007fffffffd920 → 0x0000000000000000
$rbp : 0x00007ffff797c750 → "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n"
$rsi : 0x00007fffffffd920 → 0x0000000000000000
$rdi : 0x2
$rip : 0x00007ffff7803fb7 → <raise+199> mov rcx, QWORD PTR [rsp+0x108]
$r8 : 0x0
$r9 : 0x00007fffffffd920 → 0x0000000000000000
$r10 : 0x8
$r11 : 0x246
$r12 : 0x00005555555b8473 → "../../binutils/readelf.c"
$r13 : 0x00005555555c2260 → "filedata->section_headers != NULL"
$r14 : 0x292
$r15 : 0x1
$eflags: [ZERO carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007fffffffd920│+0x0000: 0x0000000000000000 ← $rsp, $rsi, $r9
0x00007fffffffd928│+0x0008: 0x00005555557ed250 → 0x0000000000000000
0x00007fffffffd930│+0x0010: 0x0000000000000001
0x00007fffffffd938│+0x0018: 0x00007ffff784d70f → <vasprintf+335> mov rax, QWORD PTR [r12]
0x00007fffffffd940│+0x0020: 0x00007ffffbad8000
0x00007fffffffd948│+0x0028: 0x00005555557ed250 → 0x0000000000000000
0x00007fffffffd950│+0x0030: 0x00005555557ed2b5 → 0x00000a2e64656c69 ("iled.\n"?)
0x00007fffffffd958│+0x0038: 0x00005555557ed250 → 0x0000000000000000
───────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ffff7803fab <raise+187> mov edi, 0x2
0x7ffff7803fb0 <raise+192> mov eax, 0xe
0x7ffff7803fb5 <raise+197> syscall
→ 0x7ffff7803fb7 <raise+199> mov rcx, QWORD PTR [rsp+0x108]
0x7ffff7803fbf <raise+207> xor rcx, QWORD PTR fs:0x28
0x7ffff7803fc8 <raise+216> mov eax, r8d
0x7ffff7803fcb <raise+219> jne 0x7ffff7803fec <__GI_raise+252>
0x7ffff7803fcd <raise+221> add rsp, 0x118
0x7ffff7803fd4 <raise+228> ret
───────────────────────────────────────────────────────────────────────────────────────────────────── threads ────
[#0] Id 1, Name: "readelf", stopped 0x7ffff7803fb7 in __GI_raise (), reason: SIGABRT
─────────────────────────────────────────────────────────────────────────────────────────────────────── trace ────
[#0] 0x7ffff7803fb7 → __GI_raise(sig=0x6)
[#1] 0x7ffff7805921 → __GI_abort()
[#2] 0x7ffff77f548a → __assert_fail_base(fmt=0x7ffff797c750 "%s%s%s:%u: %s%sAssertion `%s' failed.\n%n",
assertion=0x5555555c2260 "filedata->section_headers != NULL", file=0x5555555b8473 "../../binutils/readelf.c",
line=0x292, function=0x5555555d0138 "find_section")
[#3] 0x7ffff77f5502 → __GI___assert_fail(assertion=0x5555555c2260 "filedata->section_headers != NULL",
file=0x5555555b8473 "../../binutils/readelf.c", line=0x292, function=0x5555555d0138 "find_section")
[#4] 0x55555556480a → nop WORD PTR [rax+rax*1+0x0]
[#5] 0x55555558d275 → test rax, rax
[#6] 0x55555555b878 → test eax, eax
[#7] 0x7ffff77e6bf7 → __libc_start_main(main=0x55555555b010, argc=0x3, argv=0x7fffffffdfc8, init=<optimized out>,
fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffdfb8)
[#8] 0x55555555ba5a → hlt
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────
__GI_raise (sig=sig@entry=0x6) at ../sysdeps/unix/sysv/linux/raise.c:51
51 ../sysdeps/unix/sysv/linux/raise.c: No such file or directory.
gef➤ hexdump byte $rsp+0x108
0x00007fffffffda28 00 e0 73 5b e6 73 e2 e2 80 da ff ff ff 7f 00 00 ..s[.s..........
0x00007fffffffda38 21 59 80 f7 ff 7f 00 00 20 00 00 00 00 00 00 00 !Y...... .......
0x00007fffffffda48 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00007fffffffda58 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................


The place it crashes is misleading, because it actually has an issue calling raise, which would indicate an error. This is the function it actually fails in, from binutils 2.30, https://github.com/bminor/binutils-gdb/blob/219d1afa89d0d53ca93a684cac341f16470f3ca0/binutils/readelf.c#L658



    static Elf_Internal_Shdr *
find_section (Filedata * filedata, const char * name)
{
unsigned int i;

assert (filedata->section_headers != NULL);

for (i = 0; i < filedata->file_header.e_shnum; i++)
if (streq (SECTION_NAME (filedata->section_headers + i), name))
return filedata->section_headers + i;

return NULL;
}
In the latest release it looks like this, https://github.com/bminor/binutils-gdb/blob/master/binutils/readelf.c#L935

    /* Return a pointer to section NAME, or NULL if no such section exists.  */

static Elf_Internal_Shdr *
find_section (Filedata * filedata, const char * name)
{
unsigned int i;

if (filedata->section_headers == NULL)
return NULL;

for (i = 0; i < filedata->file_header.e_shnum; i++)
if (SECTION_NAME_VALID (filedata->section_headers + i)
&& streq (SECTION_NAME (filedata->section_headers + i), name))
return filedata->section_headers + i;

return NULL;
}

The key difference is the removal of the assert() call that tries to make sure that the section_headers is not NULL. But that's really not a good way to do it. It completely aborts the program if there are no section headers, which may actually be a valid case. The find_section function attempts to enumerate and check section headers.


The fix also checks if the section name is valid, which is A Good Thing.


Let's take a look at the function calls that led up to this.


The layout for calls looks like this, with the registers as each of the arguments:

function(RDI, RSI, RDX, RCX, R8, R9, *Stack)


So when we check out what actually failed, we can see it's the assert().

void __assert_fail(const char * assertion, const char * file, unsigned int line, const char * function);

__GI___assert_fail(assertion=0x5555555c2260 "filedata->section_headers != NULL",
file=0x5555555b8473 "../../binutils/readelf.c",
line=0x292,
function=0x5555555d0138 "find_section")


This looks like a handler function for this specific failure

  gef➤  cs-dis --show-opcodes --location 0x5555555647ea
0x5555555647ea c3 ret
0x5555555647eb 488d0d46b90600 lea rcx, [rip + 0x6b946] ; const char * function=0x5555555d0138 "find_section"
0x5555555647f2 488d357a3c0500 lea rsi, [rip + 0x53c7a] ; const char * file=0x5555555b8473 "../../binutils/readelf.c"
0x5555555647f9 488d3d60da0500 lea rdi, [rip + 0x5da60] ; const char * assertion=0x5555555c2260 "filedata->section_headers != NULL"
0x555555564800 ba92020000 mov edx, 0x292 ; unsigned int line=0x292 (658.)
0x555555564805 e88663ffff call 0x55555555ab90 ; [#4] Call to __assert_fail in plt
0x55555556480a 660f1f440000 nop word ptr [rax + rax]
0x555555564810 4885d2 test rdx, rdx

gef➤ cs-dis --show-opcodes --location 0x55555558d257
0x55555558d257 663d90a3 cmp ax, 0xa390
0x55555558d25b 0f84bafeffff je 0x55555558d11b
0x55555558d261 e902f0ffff jmp 0x55555558c268
0x55555558d266 488d352d270300 lea rsi, [rip + 0x3272d]
0x55555558d26d 4c89ef mov rdi, r13
0x55555558d270 e87b74fdff call 0x5555555646f0 ; [#5] call to find_section
0x55555558d275 4885c0 test rax, rax
0x55555558d278 4889c3 mov rbx, rax

gef➤ cs-dis --show-opcodes --location 0x55555555b873
;-- Everything before this is 0
0x55555555b873 e8d8fa0200 call 0x55555558b350 ; [#6]
0x55555555b878 85c0 test eax, eax
0x55555555b87a 498b7e08 mov rdi, qword ptr [r14 + 8]
0x55555555b87e 0f84f3000000 je 0x55555555b977
0x55555555b884 e827f2ffff call 0x55555555aab0
0x55555555b889 4c89f7 mov rdi, r14


Put a breakpoint at 0x55555558d266 to see what section it's trying to find. rip+0x3272d is what is being located. It's in the heap somewhere

  gef➤  vmmap
[ Legend: Code | Heap | Stack ]
Start End Offset Perm Path
0x0000555555554000 0x00005555555e1000 0x0000000000000000 r-x /usr/bin/x86_64-linux-gnu-readelf
0x00005555557e1000 0x00005555557e3000 0x000000000008d000 r-- /usr/bin/x86_64-linux-gnu-readelf
0x00005555557e3000 0x00005555557e6000 0x000000000008f000 rw- /usr/bin/x86_64-linux-gnu-readelf
0x00005555557e6000 0x000055555580b000 0x0000000000000000 rw- [heap] <-- somewhere in here


At the first breakpoint this is what it looks like:

  $rsi   : 0x00005555555bf99a  →  ".nds32_e_flags"
$rdi : 0x00005555557eb420 → 0x00007fffffffe311 → "SIGABRT.PC.7ffff7803fb7.STACK.1a87da4026.CODE.-6.A[...]"
gef➤ hexdump byte 0x00005555557eb420
0x00005555557eb420 11 e3 ff ff ff 7f 00 00 d0 b4 7e 55 55 55 00 00 ..........~UUU..
0x00005555557eb430 82 00 00 00 00 00 00 00 7f 45 4c 46 02 01 01 00 .........ELF....
0x00005555557eb440 00 00 00 00 00 00 00 00 00 00 78 00 40 00 00 00 ..........x.@...
0x00005555557eb450 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 ..@.............



It's pointing at the file name which is in the argv area of the stack. This holds the arguments and afterwards, the environment variables passed to the process (envp).


  gef➤  hexdump byte 0x00007fffffffe311 --size 128
0x00007fffffffe311 53 49 47 41 42 52 54 2e 50 43 2e 37 66 66 66 66 SIGABRT.PC.7ffff
0x00007fffffffe321 37 38 30 33 66 62 37 2e 53 54 41 43 4b 2e 31 61 7803fb7.STACK.1a
0x00007fffffffe331 38 37 64 61 34 30 32 36 2e 43 4f 44 45 2e 2d 36 87da4026.CODE.-6
0x00007fffffffe341 2e 41 44 44 52 2e 30 2e 49 4e 53 54 52 2e 6d 6f .ADDR.0.INSTR.mo
0x00007fffffffe351 76 5f 5f 5f 5f 30 78 31 30 38 28 25 72 73 70 29 v____0x108(%rsp)
0x00007fffffffe361 2c 25 72 63 78 2e 66 75 7a 7a 00 4c 53 5f 43 4f ,%rcx.fuzz.LS_CO
0x00007fffffffe371 4c 4f 52 53 3d 72 73 3d 30 3a 64 69 3d 30 31 3b LORS=rs=0:di=01;
0x00007fffffffe381 33 34 3a 6c 6e 3d 30 31 3b 33 36 3a 6d 68 3d 30 34:ln=01;36:mh=0



I kept going and looked at find_section to check the point which jumps to the assertion.

  0x5555555646f8   mov    rdx, QWORD PTR [rdi+0x70]
0x5555555646fc test rdx, rdx
0x5555555646ff je 0x5555555647eb ; This jump is taken because rdx = 0, this is where the assert fails


This is the file as it should appear in memory.

  $ xxd SIGABRT.PC.7ffff7803fb7.STACK.1a87da4026.CODE.-6.ADDR.0.INSTR.mov____0x108\\(%rsp\\)\\,%rcx.fuzz
A───────┐ B┐C┐ D┐E┐ F─────────────────┐
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
G──┐ H──┐ I───────┐ J─────────────────┐
00000010: 029d a700 3e00 0100 0000 7800 4000 0000 ....>.....x.@...
K─────────────────┐ L─────────────────┐
00000020: 0000 4000 0000 0000 0000 0000 0000 0000 ..@.............
M───────┐ N──┐ O──┐ P──┐ Q──┐ R──┐ S──┐
00000030: 0000 0000 0000 4000 3810 0100 0000 00d4 ......@.8.......

00000040: 1400 0100 0000 0500 0000 0000 0000 0000 ................
00000050: 0000 0000 4000 0000 0000 0000 4000 0000 ....@.......@...
00000060: 0000 8000 0000 0000 0000 8000 0000 0000 ................
00000070: 0000 0000 2000 0000 0000 b03c 66bf 0600 .... ......<f...
00000080: 0f05


Comparing the layout to what I see in memory. This is rdi+0x70, the address is 0x00005555557eb490. This is actually pointing to a Filedata struct, which is internal to readelf.

  gef➤  hexdump byte $rdi --size 256
0x00005555557eb420 11 e3 ff ff ff 7f 00 00 d0 b4 7e 55 55 55 00 00 ..........~UUU..
A─────────┐ B┐ C┐ D┐ E┐
0x00005555557eb430 82 00 00 00 00 00 00 00 7f 45 4c 46 02 01 01 00 .........ELF....
F─────────────────────┐ J─────────────────────┐
0x00005555557eb440 00 00 00 00 00 00 00 00 00 00 78 00 40 00 00 00 ..........x.@...
K─────────────────────┐ L─────────────────────┐
0x00005555557eb450 00 00 40 00 00 00 00 00 00 00 00 00 00 00 00 00 ..@.............
I─────────────────────┐ M─────────────────────┐
0x00005555557eb460 3e 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 >...............
G───┐ H───┐ N─────────┐ O─────────┐ P─────────┐
0x00005555557eb470 02 9d a7 00 00 00 00 00 40 00 00 00 38 10 00 00 ........@...8...
Q─────────┐ R─────────┐ S─────────┐ section_headers
0x00005555557eb480 01 00 00 00 00 00 00 00 00 d4 00 00 00 00 00 00 ................
0x00005555557eb490 * 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................ <-- here
0x00005555557eb4a0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00005555557eb4b0 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0x00005555557eb4c0 00 00 00 00 00 00 00 00 31 02 00 00 00 00 00 00 ........1.......
0x00005555557eb4d0 88 24 ad fb 00 00 00 00 40 b7 7e 55 55 55 00 00 .$......@.~UUU..
0x00005555557eb4e0 82 b7 7e 55 55 55 00 00 00 b7 7e 55 55 55 00 00 ..~UUU....~UUU..
0x00005555557eb4f0 00 b7 7e 55 55 55 00 00 00 b7 7e 55 55 55 00 00 ..~UUU....~UUU..
0x00005555557eb500 00 b7 7e 55 55 55 00 00 00 b7 7e 55 55 55 00 00 ..~UUU....~UUU..
0x00005555557eb510 00 c7 7e 55 55 55 00 00 00 00 00 00 00 00 00 00 ..~UUU..........


This is the struct used in readelf.c


typedef struct filedata
{
const char * file_name; // 0x00
FILE * handle; // +0x08
bfd_size_type file_size; // +0x10
Elf_Internal_Ehdr file_header; // +0x18
Elf_Internal_Shdr * section_headers; // +0x6C
Elf_Internal_Phdr * program_headers;
char * string_table;
unsigned long string_table_length;
/* A dynamic array of flags indicating for which sections a dump of
some kind has been requested. It is reset on a per-object file
basis and then initialised from the cmdline_dump_sects array,
the results of interpreting the -w switch, and the
dump_sects_byname list. */
dump_type * dump_sects;
unsigned int num_dump_sects;
} Filedata;


Then this is what readelf uses for it's ELF layout, from include/elf/internal.h


typedef struct elf_internal_ehdr {
unsigned char e_ident[EI_NIDENT]; /* ELF "magic number" */ // +0
bfd_vma e_entry; /* Entry point virtual address */ // +0x10 K
bfd_size_type e_phoff; /* Program header table file offset */ // +0x18 K
bfd_size_type e_shoff; /* Section header table file offset */ // +0x20 L
unsigned long e_version; /* Identifies object file version */ // +0x28 I
unsigned long e_flags; /* Processor-specific flags */ // +0x30 M
unsigned short e_type; /* Identifies object file type */ // +0x38 G
unsigned short e_machine; /* Specifies required architecture */ // +0x3A H
unsigned int e_ehsize; /* ELF header size in bytes */ // +x03C N
unsigned int e_phentsize; /* Program header table entry size */ // +0x40 O
unsigned int e_phnum; /* Program header table entry count */ // +0x44 P
unsigned int e_shentsize; /* Section header table entry size */ // +0x48 Q
unsigned int e_shnum; /* Section header table entry count */ // +0x4C R
unsigned int e_shstrndx; /* Section header string table index */ // +0x50 S
} Elf_Internal_Ehdr; // Size 0x54


Why does it try to access from the pointer at +0x70 when it thats right in the middle of the section header?

  The first value in Elf_Internal_Shdr is:

typedef struct elf_internal_shdr {
unsigned int sh_name; /* Section name, index in string tbl */
unsigned int sh_type; /* Type of section */


Okay so then by checking the code, we can confirm that this is an offset it expects, but it's bigger than the size of the struct? hrmm

  gef➤  cs-dis --show-opcodes --location 0x5555555646f8
0x5555555646f8 488b5770 mov rdx, qword ptr [rdi + 0x70]
0x5555555646fc 4885d2 test rdx, rdx
→ 0x5555555646ff 0f84e6000000 je 0x5555555647eb
[!] Command 'capstone-disassemble' failed to execute properly, reason: unsupported operand type(s) for +: 'int' and 'str'

$ objdump -Mintel -d /usr/bin/readelf | grep "48 8b 57 70"
106f8: 48 8b 57 70 mov rdx,QWORD PTR [rdi+0x70]


Regardless, something was being accessed wrong as well. This is possibly an OOB read, which would be kinda kewl XD. Either way, this is an older version of readelf now (from 2019?) so idk who still even uses it. Probably a lot of people. Enjoy!


Report Topic

0 Replies