flat assembler
Message board for the users of flat assembler.

Index > Linux > ELF executable + relocations/fixups for ASLR?

Goto page Previous  1, 2, 3, 4  Next
Author
Thread Post new topic Reply to topic
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 30 Jan 2019, 21:44
I have added DYNAMIC variant (with RVA operator available) and GNURELRO segment flag to fasm 1.73.07. You can now experiment yourself.

For instance, in my sample with writable section, you can replace WRITEABLE with GNURELRO, and example should still work. Not combined with DYNAMIC, though (because you have to remove INTERPRETER segment then).

Still, keep in mind that fasm's executable formatter was always supposed to be very simple, because I expected object ELF together with linker to be used for any more advanced things. I wonder if it would be possible to link fasm-produced ELF object with GCC and make a non-PIC PIE that way.
Post 30 Jan 2019, 21:44
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 30 Jan 2019, 23:03
Small correction: PT_GNU_RELRO is actually an area inside another segment (a PT_LOAD one). Because of how segments are defined in fasm, such special kind of segment is merged with the next "regular" segment that follows it. So you need to define another segment after GNURELRO segment, and the GNURELRO one is going to be mapped as part of that later segment in memory.

Now if you look at Furs's GCC-made example, GNU_RELRO is actually a part of PT_LOAD segment that has RW attributes. So I was wrong when I wrote earlier that loader must be temporarily applying writable attribute. The segment is writable from the start, it is only after the relocations are applied that it is set to "read only". The description of PT_GNU_RELRO is actually quite accurate here.

Therefore that GCC executable does not in fact relocate the executable segment, it only applies relocations to an RW segment and then - because of PT_GNU_RELRO mapping - gets it changed to read-only.

Back to fasm: when I wrote that in my sample you can replace WRITABLE with GNURELRO, it only worked because there is actually a regular writable segment that follows. And to make this work cleanly, you should make it like this:
Code:
segment readable gnurelro

start:

        mov     ecx,dword 0
fixup = $ - 4
        mov     edx,msg.len
        mov     eax,4
        mov     ebx,1
        int     0x80

        mov     eax,1
        xor     ebx,ebx
        int     0x80

segment readable executable writeable ; this is the actual segment that "gnurelro" one resides in

segment readable

msg db 'Hello world!',0xA
.len = $ - msg    
Now if you remove "writeable" from that "parent" segment, the relocation is going to fail (with segfault).

The simple formatter of fasm 1 is not really a good framework to play freely with all these settings, it is limited by its linear segment syntax. But with fasmg's macros I could make a different framework with more flexible options - perhaps in my tutorial's second chapter I'm going to make something like that.
Post 30 Jan 2019, 23:03
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 31 Jan 2019, 04:13
With 1.73.07 the previous code I posted doesn't compile. It gives the message "error: invalid use of symbol." Which is a good thing. We are alerted to the fact that the address isn't knowable at compile time.

So one tiny change is applied; the RVA operator. Full code below shows this in action
Code:
format ELF dynamic 0 at 0
entry start

SYS_EXIT        = 1
SYS_WRITE       = 4
STD_OUTPUT      = 1

segment readable writeable

hello_world:    db      'Running at 0x'
running_at_hex  db      '00000000 Stack at 0x'
stack_at_hex    db      '00000000',10
hello_world_len =       $ - hello_world

segment executable

irp reg,eax,ebx,ecx,edx,esi,edi,ebp {
        if used mov_#reg#_eip
            mov_#reg#_eip:
                mov     reg,[esp]
                retn
        end if
}
macro addr reg,offset {
        call    mov_#reg#_eip
        lea     reg,[reg + rva (offset - $)]    ;use lea to avoid changing the flags
}

start:
        addr    ecx,running_at_hex
        addr    edx,0
        call    write_hex
        addr    ecx,stack_at_hex
        mov     edx,esp
        call    write_hex
        mov     eax,SYS_WRITE
        mov     ebx,STD_OUTPUT
        addr    ecx,hello_world
        mov     edx,hello_world_len
        int     0x80
        mov     eax,SYS_EXIT
        xor     ebx,ebx
        int     0x80

write_hex:
        ;ecx = address
        ;edx = value
    .next_nibble:
        mov     eax,edx
        shr     eax,28
        cmp     al,10
        sbb     al,0x69
        das
        mov     [ecx],al
        inc     ecx
        shl     edx,4
        jnz     .next_nibble
        retn    
Sample output
Code:
Running at 0x56623000 Stack at 0xFFBA2850
Running at 0x56648000 Stack at 0xFF925310
Running at 0x56592000 Stack at 0xFFD23FA0
Running at 0x565E8000 Stack at 0xFFF24500
Running at 0x565E4000 Stack at 0xFFF77020
Running at 0x56651000 Stack at 0xFFC6DB00
Running at 0x56586000 Stack at 0xFFAB5F80
Running at 0x56652000 Stack at 0xFF877930
Running at 0x56620000 Stack at 0xFFC43AC0
Running at 0x565A8000 Stack at 0xFFE8CEA0    
Everything moves. So it works.

The code is PIC, the executable is PIE. There are no relocations.

Next up ... to create a relocation segment and see what is happening.
Post 31 Jan 2019, 04:13
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 31 Jan 2019, 11:59
Going back to Furs example
Furs wrote:
GCC with -fno-PIC seems to generate relocatable code:
Code:
static int var;
void test(int x)
{
  var = x;
}

int blah()
{
  return var;
}    
Code:
gcc -m32 -O2 -fno-PIC -shared test.c -o test.so
objdump -d -M intel test.so    
I see in fdbg that the code is loaded at 0x565a5000, so it was loaded somewhere else.

However, disassembly of the code shows it hasn't had the relocations applied.
Code:
c 00000000565A5510
00000000565A5510    mov eax,[530000C300002014] ; [530000C300002014]=?
;...
c 00000000565A5504
00000000565A5504    mov [00B68DC300002014],eax ; [00B68DC300002014]=?    
You'll have to ignore the 64-bit disassembly and just look at the lower 32-bits of the address. It still has the zero-based addresses in there. So this .so file will crash as it stands if there is no code to parse the '.rel.dyn' section.
Code:
Relocation section '.rel.dyn' at offset 0x330 contains 10 entries:
 Offset     Info    Type            Sym.Value  Sym. Name
00000505  00000008 R_386_RELATIVE   
00000511  00000008 R_386_RELATIVE   
;...    
I think this shows that the loader does not apply relocations. We need to include some extra code to do that.
Post 31 Jan 2019, 11:59
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 31 Jan 2019, 12:50
If you need more evidence. Looking at the memory map for a file that includes another .so library I see this before the code has started running
Code:
08048000-08049000 rwxp 00000000 fd:00 13118232                           /home/revolution/so.test
08049000-0804a000 rwxp 00000000 fd:00 13118232                           /home/revolution/so.test
0804a000-0804c000 rwxp 00000000 00:00 0 
0804c000-0804f000 r-xp 00000000 fd:00 13118232                           /home/revolution/so.test
f76dc000-f76de000 r--p 00000000 00:00 0                                  [vvar]
f76de000-f76e0000 r-xp 00000000 00:00 0                                  [vdso]
f76e0000-f7703000 r-xp 00000000 fd:00 19278360                           /lib/i386-linux-gnu/ld-2.23.so
f7703000-f7705000 rwxp 00022000 fd:00 19278360                           /lib/i386-linux-gnu/ld-2.23.so
ffd83000-ffda4000 rwxp 00000000 00:00 0                                  [stack]    
And after the code has been left to run and has started the main app we see the extra libraries loaded and linked
Code:
08048000-08049000 rwxp 00000000 fd:00 13118232                           /home/revolution/so.test
08049000-0804a000 rwxp 00000000 fd:00 13118232                           /home/revolution/so.test
0804a000-0804c000 rwxp 00000000 00:00 0 
0804c000-0804f000 r-xp 00000000 fd:00 13118232                           /home/revolution/so.test
08f42000-08f63000 rwxp 00000000 00:00 0                                  [heap]
f6400000-f6421000 rwxp 00000000 00:00 0 
f6421000-f6500000 ---p 00000000 00:00 0 
f653d000-f653e000 ---p 00000000 00:00 0 
f653e000-f6d3e000 rwxp 00000000 00:00 0 
f6d3e000-f6d3f000 ---p 00000000 00:00 0 
f6d3f000-f7541000 rwxp 00000000 00:00 0 
f7541000-f76f1000 r-xp 00000000 fd:00 19278362                           /lib/i386-linux-gnu/libc-2.23.so
f76f1000-f76f3000 r-xp 001af000 fd:00 19278362                           /lib/i386-linux-gnu/libc-2.23.so
f76f3000-f76f4000 rwxp 001b1000 fd:00 19278362                           /lib/i386-linux-gnu/libc-2.23.so
f76f4000-f76f7000 rwxp 00000000 00:00 0 
f76f7000-f76fe000 r-xp 00000000 fd:00 19278379                           /lib/i386-linux-gnu/librt-2.23.so
f76fe000-f76ff000 r-xp 00006000 fd:00 19278379                           /lib/i386-linux-gnu/librt-2.23.so
f76ff000-f7700000 rwxp 00007000 fd:00 19278379                           /lib/i386-linux-gnu/librt-2.23.so
f7700000-f7719000 r-xp 00000000 fd:00 19278361                           /lib/i386-linux-gnu/libpthread-2.23.so
f7719000-f771a000 r-xp 00018000 fd:00 19278361                           /lib/i386-linux-gnu/libpthread-2.23.so
f771a000-f771b000 rwxp 00019000 fd:00 19278361                           /lib/i386-linux-gnu/libpthread-2.23.so
f771b000-f771d000 rwxp 00000000 00:00 0 
f7741000-f7743000 rwxp 00000000 00:00 0 
f7743000-f777d000 r-xp 00000000 fd:00 18765758                           /usr/local/lib/libftd2xx.so.1.4.6
f777d000-f777e000 r-xp 00039000 fd:00 18765758                           /usr/local/lib/libftd2xx.so.1.4.6
f777e000-f777f000 rwxp 0003a000 fd:00 18765758                           /usr/local/lib/libftd2xx.so.1.4.6
f777f000-f7780000 rwxp 00000000 00:00 0 
f7780000-f7782000 r--p 00000000 00:00 0                                  [vvar]
f7782000-f7784000 r-xp 00000000 00:00 0                                  [vdso]
f7784000-f77a7000 r-xp 00000000 fd:00 19278360                           /lib/i386-linux-gnu/ld-2.23.so
f77a7000-f77a8000 r-xp 00022000 fd:00 19278360                           /lib/i386-linux-gnu/ld-2.23.so
f77a8000-f77a9000 rwxp 00023000 fd:00 19278360                           /lib/i386-linux-gnu/ld-2.23.so
ffb90000-ffbb1000 rwxp 00000000 00:00 0                                  [stack]    
When execution starts only the Linux interpreter and the main exe are loaded. All other library files are missing. So it is the interpreter that does all the dynamic linking and parses the relocations.

So all the loader is doing for us is to load the main exe and the interpreter. It then jumps to the interpreter entry point and its job is done.
Post 31 Jan 2019, 12:50
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 31 Jan 2019, 15:00
Here is code to relocate the executable. It is only a proof of concept so it has some ugly "features".
Code:
format ELF dynamic 0 at 0
entry do_relocs

macro r [inst] {
        common inst
        local .loc
        .loc = rva $ - 4
        virtual relocs
                dd .loc
        end virtual
}

virtual at relocs_data
        relocs::
end virtual

SYS_EXIT        = 1
SYS_WRITE       = 4
STD_OUTPUT      = 1

segment readable writeable

hello_world:    db      'Running at 0x'
running_at_hex  db      '00000000 Stack at 0x'
stack_at_hex    db      '00000000',10
hello_world_len =       $ - hello_world

segment executable writeable

start:
      r mov     ecx,rva running_at_hex
      r mov     edx,0
        call    write_hex
      r mov     ecx,rva stack_at_hex
        mov     edx,esp
        call    write_hex
        mov     eax,SYS_WRITE
        mov     ebx,STD_OUTPUT
      r mov     ecx,rva hello_world
        mov     edx,hello_world_len
        int     0x80
        mov     eax,SYS_EXIT
        xor     ebx,ebx
        int     0x80

write_hex:
        ;ecx = address
        ;edx = value
    .next_nibble:
        mov     eax,edx
        shr     eax,28
        cmp     al,10
        sbb     al,0x69
        das
        mov     [ecx],al
        inc     ecx
        shl     edx,4
        jnz     .next_nibble
        retn

do_relocs:
        ;this code must be PIC
        call    .get_eip
        sub     ecx,rva $
        cld
        lea     esi,[ecx + rva relocs_data]
    .reloc_loop:
        lodsd
        cmp     eax,-1
        jz      start
        add     [ecx + eax],ecx
        jmp     .reloc_loop
    .get_eip:
        mov     ecx,[esp]
        ret

segment readable

;align 4        ;enabling this instruction causes fasm v1.73.07 to crash
relocs_data:
        virtual relocs
                sizeof.relocs = $ - $$
        end virtual
        repeat sizeof.relocs / 4
                load reloc dword from relocs:relocs_data + (% - 1) * 4
                dd reloc
        end repeat
        dd -1    
It creates a reloc section manually by prefixing the affect instructions with the "r" macro. And at startup it runs a small loop to relocate all the affected places. It doesn't mark the segment un-writeable or link any .so libraries, but it does successfully relocate the immediate addressing instructions. Although it only works correctly when the origin is zero, but that could easily be fixed.

It still needs the rva in there. But if fasm could generate a reloc section like it does for PE then the rva requirement could be lifted and assume that it will be relocated.

This would also work for a normal executable (not dynamic) but since Linux won't use ASLR on such files there isn't any point.

I also discovered a crash bug in fasm.. Try enabling the line with the "align 4". It segfaults.
Post 31 Jan 2019, 15:00
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 31 Jan 2019, 15:36
revolution wrote:
I also discovered a crash bug in fasm.. Try enabling the line with the "align 4". It segfaults.
Fixed, I hope.
Post 31 Jan 2019, 15:36
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 03 Feb 2019, 18:40
I think the best approach here is to have our own relocation code running, as in my example above. Even of we are linking to other libraries and the interpreter would do the relocations for us, it gives us less flexibility and requires us to manipulate the segments to match precisely the "proper" format.

The relocation code is only 11 instructions. We can set the write permissions according to our own requirements. We don't need any interpreter when our code is stand-alone and only needs the kernel. The only remaining task is to have the assembler automatically mark all the relocatable address and write the table for us.
Post 03 Feb 2019, 18:40
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 03 Feb 2019, 18:54
revolution wrote:
The only remaining task is to have the assembler automatically mark all the relocatable address and write the table for us.
I know you have said that fasmg is not a good option for you because of macro conversion issues, but I should mention that it would be quite easy to do with it. The Adding relocations section of my PE tutorial demonstrates a simple method.
Post 03 Feb 2019, 18:54
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 03 Feb 2019, 19:02
I'll see about making some patches to fasm. If my workload remains light then I think I can make some time for it.
Post 03 Feb 2019, 19:02
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 03 Feb 2019, 19:07
Another option could be to use object ELF output and a home-brew custom linker.
Post 03 Feb 2019, 19:07
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 04 Feb 2019, 01:21
Do you mean to write our own custom interpreter .so file?
Post 04 Feb 2019, 01:21
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 04 Feb 2019, 05:56
I mean, in a simplest case, not a true linker but a converter that would translate ELF object into ELF executable with relocation table.
Post 04 Feb 2019, 05:56
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 04 Feb 2019, 17:42
Okay, so a post-processor that does the conversion. I don't see the advantage of that over having fasm create it during the assembly stage. It would be one extra step during the build phase.
Post 04 Feb 2019, 17:42
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 08 Feb 2019, 09:03
I'm quite disappointed with the way Linux sets permissions. The following code should segfault.
Code:
format ELF executable 0
entry start

SYS_EXIT        = 1
SYS_WRITE       = 4
STD_OUTPUT      = 1

segment readable

should_not_see: db "I'm running just fine",10
should_not_see_len = $ - should_not_see

start:
        mov     eax,SYS_WRITE
        mov     ebx,STD_OUTPUT
        mov     ecx,should_not_see
        mov     edx,should_not_see_len
        int     0x80
        mov     eax,SYS_EXIT
        or      ebx,-1
        int     0x80    
That code wrote:
I'm running just fine
There are no execute permissions in the code. But Linux always sets the segments to executable. And using mprotect won't change that either.

So any and all data allocated by the program can also be executed. Including the stack. The only exceptions I can find are for file mappings that are opened as read-only and the vvar segment mapped from the kernel. Everything else is freely executable.


Last edited by revolution on 08 Feb 2019, 17:20; edited 1 time in total
Post 08 Feb 2019, 09:03
View user's profile Send private message Visit poster's website Reply with quote
Tomasz Grysztar



Joined: 16 Jun 2003
Posts: 8351
Location: Kraków, Poland
Tomasz Grysztar 08 Feb 2019, 17:16
This is where Linux is in fact similar to Windows, where "executable" attribute of PE section is not really respected unless you have IMAGE_DLLCHARACTERISTICS_NX_COMPAT flag set.
In Linux equivalent of this flag is inclusion of a segment of type PT_GNU_STACK. The attributes of this segment are supposed to define access permissions of the stack, but for some reason this also enables the NX feature for ELF segments overall.

A single line is enough to set this up (like, for example, in fasmg source):
Code:
segment readable writeable gnustack    
Post 08 Feb 2019, 17:16
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 08 Feb 2019, 17:38
Tomasz Grysztar wrote:
This is where Linux is in fact similar to Windows, where "executable" attribute of PE section is not really respected unless you have IMAGE_DLLCHARACTERISTICS_NX_COMPAT flag set.
In Linux equivalent of this flag is inclusion of a segment of type PT_GNU_STACK. The attributes of this segment are supposed to define access permissions of the stack, but for some reason this also enables the NX feature for ELF segments overall.

A single line is enough to set this up (like, for example, in fasmg source):
Code:
segment readable writeable gnustack    
Okay, so there is a way to coax Linux into doing this. Smile
Post 08 Feb 2019, 17:38
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 08 Feb 2019, 17:48
Works
Code:
format ELF executable 0
entry start

SYS_EXIT        = 1
SYS_WRITE       = 4
STD_OUTPUT      = 1

segment gnustack
segment readable

should_not_see: db "I'm running just fine",10
should_not_see_len = $ - should_not_see

start:
        mov     eax,SYS_WRITE
        mov     ebx,STD_OUTPUT
        mov     ecx,should_not_see
        mov     edx,should_not_see_len
        int     0x80
        mov     eax,SYS_EXIT
        or      ebx,-1
        int     0x80    
That code wrote:
Segmentation fault (core dumped)
And checking the result shows that the stack still gets the read and write attributes but no execute attribute. So only the execute bit of the gnustack segment is honoured.

Also execute-only sections are not writeable, but they are still readable.
Code:
format ELF executable 0
entry start

SYS_EXIT        = 1
SYS_WRITE       = 4
STD_OUTPUT      = 1

segment gnustack
segment executable

should_not_see: db "I'm running just fine",10
should_not_see_len = $ - should_not_see

start:
        mov     eax,[start]     ;we can still read from execute-only sections
        push    eax             ;we can still write to the stack
        pop     eax             ;we can still read from the stack
        mov     eax,SYS_WRITE
        mov     ebx,STD_OUTPUT
        mov     ecx,should_not_see
        mov     edx,should_not_see_len
        int     0x80
        mov     eax,SYS_EXIT
        or      ebx,-1
        int     0x80    
That code wrote:
I'm running just fine
Post 08 Feb 2019, 17:48
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 08 Feb 2019, 18:39
Since there is a way to get truly non-writeable executable sections I was inspired to write this as a proof of concept.
Code:
format ELF dynamic 0 at 0
entry do_relocs                 ;the program entry address is overridden with this

macro r [inst] {
        common inst
        local .loc
        .loc = rva $ - 4
        virtual relocs
                dd .loc
        end virtual
}

virtual at do_relocs.relocs_data
        relocs::
end virtual

SYS_EXIT        = 1
SYS_WRITE       = 4
STD_OUTPUT      = 1

segment readable writeable

hello_world:    db      'Running at 0x'
running_at_hex  db      '00000000 Stack at 0x'
stack_at_hex    db      '00000000',10
hello_world_len =       $ - hello_world

segment executable

start:
      r mov     ecx,rva running_at_hex
      r mov     edx,rva hello_world and not 0xfff
        call    write_hex
      r mov     ecx,rva stack_at_hex
        mov     edx,esp
        call    write_hex
        mov     eax,SYS_WRITE
        mov     ebx,STD_OUTPUT
      r mov     ecx,rva hello_world
        mov     edx,hello_world_len
        int     0x80
        mov     eax,SYS_EXIT
        xor     ebx,ebx
        int     0x80

write_hex:
        ;ecx = address
        ;edx = value
    .next_nibble:
        mov     eax,edx
        shr     eax,28
        cmp     al,10
        sbb     al,0x69
        das
        mov     [ecx],al
        inc     ecx
        shl     edx,4
        jnz     .next_nibble
        retn

segment gnustack

;ideally the following should just be "segment fixups" and the rest of
;the section is written by the assembler, but for now we do this manually
segment executable

label Elf32_Ehdr.e_entry        dword at 0x18
label Elf32_Ehdr.e_phoff        dword at 0x1c
label Elf32_Ehdr.e_phentsize    word at 0x2a
label Elf32_Ehdr.e_phnum        word at 0x2c
label Elf32_Phdr.p_type         dword at 0x00
label Elf32_Phdr.p_offset       dword at 0x04
label Elf32_Phdr.p_paddr        dword at 0x0c
label Elf32_Phdr.p_memsz        dword at 0x14
label Elf32_Phdr.p_flags        dword at 0x18
label Elf32_auxv_t.a_type       dword at 0
label Elf32_auxv_t.a_val        dword at 4

;p_type
        PT_LOAD                 = 1
;p_flags
        PF_X                    = 1 shl 0
        PF_W                    = 1 shl 1
        PF_R                    = 1 shl 2
;protection flags
        PROT_READ               = 0x1
        PROT_WRITE              = 0x2
        PROT_EXEC               = 0x4
;syscall
        SYS_MPROTECT            = 125
        SYS_EXIT_GROUP          = 252
;auxv
        AT_NULL                 = 0
        AT_PHDR                 = 3
        Elf32_auxv_t.size       = 8
;local
        END_OF_RELOCATIONS      = -1
        PAGE_ALIGNMENT          = not 0xfff

;the following code section can run anywhere without needing
;any RVAs or relocations, it can be copied into the output as-is
do_relocs:
        pop     eax                             ;argument count (argc)
        push    eax
        cld
        lea     esi,[esp + 4 + (eax + 1) * 4]   ;skip the args and the final null
    .skip_environment:
        lodsd
        test    eax,eax                         ;last entry in environment?
        jnz     .skip_environment
    .scan_auxv:
        lodsd
        mov     ecx,eax                         ;ecx = Elf32_auxv_t.a_type
        lodsd                                   ;eax = Elf32_auxv_t.a_val
        cmp     ecx,AT_NULL                     ;end of auxv table?
        jz      .fail
        cmp     ecx,AT_PHDR
        jnz     .scan_auxv
        mov     edi,eax
        and     edi,PAGE_ALIGNMENT
        mov     esi,PROT_READ + PROT_WRITE + PROT_EXEC
        call    .protect
        mov     ebx,[edi + Elf32_Ehdr.e_phoff]
        mov     ecx,[edi + ebx + Elf32_Phdr.p_paddr]
        sub     ecx,[edi + ebx + Elf32_Phdr.p_offset]   ;ecx = program base
        xor     edx,edx
        mov     dl,.program_entry - do_relocs
        add     edx,[edi + Elf32_Ehdr.e_entry]
        sub     edi,ecx
        lea     esi,[edi + edx + .relocs_data - .program_entry]
        mov     eax,edx
    .reloc_loop:
        add     [edi + eax],edi
        lodsd
        cmp     eax,END_OF_RELOCATIONS
        jnz     .reloc_loop
        push    dword[edi + edx]                ;after retn we run the program
        add     edi,ecx
        xor     esi,esi                         ;restore to original permissions
    .protect:
        movzx   ebp,[edi + Elf32_Ehdr.e_phnum]  ;ebp = count of Phdr entries
    .loop_Phdr:
        dec     ebp
        js      .retn
        movzx   eax,[edi + Elf32_Ehdr.e_phentsize]
        imul    eax,ebp
        add     eax,edi
        add     eax,[edi + Elf32_Ehdr.e_phoff]  ;eax = current Elf32_Phdr
        cmp     [eax + Elf32_Phdr.p_type],PT_LOAD
        jnz     .loop_Phdr
        mov     ecx,[eax + Elf32_Phdr.p_flags]
        xor     edx,edx
        assert  PROT_EXEC or PF_X = PROT_READ or PF_R & PROT_WRITE = PF_W
        repeat 3
                shr     ecx,1
                rcl     edx,1
        end repeat
        or      edx,esi                         ;set the requested flags
        mov     ebx,[eax + Elf32_Phdr.p_paddr]
        mov     ecx,[edi + Elf32_Ehdr.e_phoff]
        sub     ebx,[edi + ecx + Elf32_Phdr.p_paddr]
        add     ebx,[edi + ecx + Elf32_Phdr.p_offset]
        mov     ecx,[eax + Elf32_Phdr.p_memsz]
        add     ecx,ebx
        and     ebx,PAGE_ALIGNMENT
        sub     ecx,ebx                         ;len
        add     ebx,edi                         ;addr
        xor     eax,eax
        mov     al,SYS_MPROTECT
        int     0x80
        test    eax,eax
        jz      .loop_Phdr
    .fail:
        xor     eax,eax
        mov     al,SYS_EXIT_GROUP
        or      ebx,-1
        int     0x80
    .retn:
        retn
    .program_entry:
        dd      rva start               ;the program entry address is transferred to here, and it gets relocated
    .relocs_data:
        virtual relocs
                sizeof.relocs = $ - $$
        end virtual
        repeat sizeof.relocs shr 2
                load reloc dword from relocs:do_relocs.relocs_data + (% - 1) * 4
                dd reloc
        end repeat
        dd END_OF_RELOCATIONS    
The idea is that the final section of the program is written by the assembler by including just the single line "segment fixups". And we should also be able to remove the requirement for RVA.

It runs in three main stages.
  1. Initially set all the sections to RWE so that we can apply relocations
  2. Do the relocations
  3. Set all sections back to the original permissions and run the program
That way we can set the permissions we need without needing extra tricks to induce an external loader to clear writeable bits and whatnot.
Post 08 Feb 2019, 18:39
View user's profile Send private message Visit poster's website Reply with quote
revolution
When all else fails, read the source


Joined: 24 Aug 2004
Posts: 20299
Location: In your JS exploiting you and your system
revolution 09 Feb 2019, 09:29
It makes no difference what base address we use in the dynamic executable, the loader still places it into the same range. Using setarch to disable ASLR "setarch `uname -m` -R <progname>"
Code:
format ELF dynamic 0 at 0
Running at 0x56555000 Stack at 0xFFFFCF20
...
format ELF dynamic 0 at 0x10000
Running at 0x56555000 Stack at 0xFFFFCF20
...
format ELF dynamic 0 at 0xffff0000
Running at 0x56555000 Stack at 0xFFFFCF20
...    
BTW: I edited the code above to support arbitrary base addresses.

So it would appear as though we are always stuck in the address range 0x56555000 to 0x56654000. However this isn't so. We can get another address range, but it requires changing a setting. Once again using setarch we can us the 3gb switch "setarch `uname -m` -3R <progname>"
Code:
Running at 0x41000000 Stack at 0xBFFFEF20
...
Running at 0x41000000 Stack at 0xBFFFEF20    
And we are then constrained to the range 0x41000000 to 0x410ff000.

I find the chosen address ranges disappointing. It cuts a slice in the address space and will limit the maximum size of any memory allocation. I haven't looked for the source code but it seems that it is just applying this formula.
Code:
(max_address_space / 3) and 0xfffff000 + 0x1000000 + (rand and 0xff) shl 12    
So 1/3rd of the address space is below and 2/3rds is above our code.
Post 09 Feb 2019, 09:29
View user's profile Send private message Visit poster's website Reply with quote
Display posts from previous:
Post new topic Reply to topic

Jump to:  
Goto page Previous  1, 2, 3, 4  Next

< Last Thread | Next Thread >
Forum Rules:
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum
You cannot attach files in this forum
You can download files in this forum


Copyright © 1999-2024, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.