flat assembler
Message board for the users of flat assembler.

Index > Linux > [SOLVED] Can a '.text' section of an ELF binary be 'rwx'?

Author
Thread Post new topic Reply to topic
Jessé



Joined: 03 May 2025
Posts: 119
Location: Brazil
Jessé 13 Jun 2026, 06:00
Hello,

I did some trials with 'elf64.inc' original file from fasm2 headers, to figure out what appears to be the main reason it does not even try to set 'writeable executable' flags under a section: trying to do this under a '.text' section, which will be the main executable section of your dynamic address ELF, simply doesn't work, apparently. I tried many things, but the resulting executable was always 'r-x' flags for the '.text' section.
Has anyone tried something else to achieve a 'rwx' '.text' section?
My workaround is as follows, but it is done at runtime, not compile time, so the section starts as compiled with 'r-x' flags set:

Code:
; Self modifying code section example: it kind of replaces the missing '_code rwx' functionality,
; apparently unavailable to dynamic ELF '.text' section.
; After succeeded mprotect() call, this '.text' section behaves the same as '_code rwx'.

format ELF64

include 'fastcall.inc'
include 'stdmacros.inc'
include 'stdio.inc'

_code   Start entry:        lea         rdi, [$]
                            mov         r10, 0_FFFF_FFFF_FFFF_F000h
                            and         rdi, r10
                            mprotect(rdi, 4096, (PROT_READ or PROT_WRITE or PROT_EXEC));
                            test        eax, eax
                            jz          @f
                            fprintf(*stderr, &errfmt, "failed: ");
                            perror(NULL);
                            exit(1);

                    @@      fprintf(*stderr, &errfmt, "succeeded!"\n);

                            signal(SIGINT, &.break);

                    @@      inc         [count]

                            usleep(500'000);
                            test        [flags], 1
                            jnz         .end

                            fprintf(*stdout, <13,"Code section counter value: %lu",0>, *count);
                            fflush(*stdout);
                            jmp         @b

            .end:           fprintf(*stdout, "%s"\n "Finished."\n, <8,8,"  ",0>);
                            exit(0);

            .break:         or          [flags], 1
                            ret

        errfmt              xb 'Change memory protection %s', 0

        count               xq 0
        flags               xb 1111_1110b

; To compile:
;
; > ./build.sh code.rwx-dyn
;
; And then, run it:
;
; > ./code.rwx-dyn
;
; A good (and tested) way to see the change, is to look using edb-debugger under 'View->Memory Regions'
; before and after that mprotect() call, looking at the page address of that 'Start' entry point.
    


To ease understanding, that whole '_code' line macro from this example translates to:

Code:
section '.text' executable align 16
public Start
Start: lea rdi, [$]
    


Thanks in advance if anyone can help, so I can apply an improvement (not too useful, but an improvement anyways) on this beauty.

Cool

_________________
jesse6


Last edited by Jessé on 16 Jun 2026, 07:41; edited 1 time in total
Post 13 Jun 2026, 06:00
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: 20998
Location: In your JS exploiting you and your system
revolution 13 Jun 2026, 09:33
I had no trouble to get rwx
Code:
~ cat test.asm 
format elf64
section '' executable writeable
syscall    
Code:
~ fasm test.asm 
flat assembler  version 1.73.31  (16384 kilobytes memory)
1 passes, 400 bytes.    
Code:
~ objdump test -x

test:     file format elf64-x86-64
test
architecture: i386:x86-64, flags 0x00000102:
EXEC_P, D_PAGED
start address 0x0000000000400078

Program Header:
    LOAD off    0x0000000000000000 vaddr 0x0000000000400000 paddr 0x0000000000400000 align 2**12
         filesz 0x0000000000000188 memsz 0x0000000000000188 flags rwx

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
SYMBOL TABLE:
no symbols    
Post 13 Jun 2026, 09:33
View user's profile Send private message Visit poster's website Reply with quote
Jessé



Joined: 03 May 2025
Posts: 119
Location: Brazil
Jessé 13 Jun 2026, 13:00
I mean when making dynamic address executables, called PIE.
With static ones, no problem at all. But for static, I mean the ones using segments, not sections, in fasm2 using 'format ELF64 executable 3' statement.
I also got this problem when making a 'nasm' assembler version, same situation: create a 'rwx' section at the source, PIE executable linked with 'ld', and, when compiling, that section just become 'r-x' only.

But I suppose that this could be a problem on how I link using 'ld', perhaps.
I'll check it later.
I must add that I still have not checked the intermediate '.o' object file, which is the actual fasm2/nasm part on it.

Thanks anyways, the fact that you're actually using a section, but not named '.text', may give me a clue on where to look at.
Post 13 Jun 2026, 13:00
View user's profile Send private message Visit poster's website Reply with quote
Jessé



Joined: 03 May 2025
Posts: 119
Location: Brazil
Jessé 13 Jun 2026, 14:17
I have confirmed (I guess) that the '.text' section name is a protected resource, probably by the linker as I said.
And also figured out that I misread the original fasm2 section macro at 'format/elf64.inc' file (the second macro, not the first), which is doing its stuff properly, correctly setting any of the section flags.

The code below works fine:
Code:
format ELF64

public Start

extrn exit

section '.code' writeable executable align 16

    Start:          dec         [count]

                    mov         edi, [count]
                    sub         rsp, 8
                    jmp         PLT.exit

    align 4
    count           dd 1

    


The code below "seg faults" itself, because it tries to write a variable in a read-execute segment, as it is now named '.text':
Code:
format ELF64

public Start

extrn exit

section '.text' writeable executable align 16

    Start:          dec         [count]

                    mov         edi, [count]
                    sub         rsp, 8
                    jmp         PLT.exit

    align 4
    count           dd 1

    


Compiled with:
Quote:

fasm2 test.raw.rwx.asm
ld -e Start -pie -lc --dynamic-linker=/lib64/ld-linux-x86-64.so.2 -o test.raw.rwx test.raw.rwx.o
strip --strip-unneeded test.raw.rwx


So, that's the problem. Any name other than '.text' appears to work properly, as I also have tried '.rwx'.
Now, I have a plan for that feature I talk before.
Post 13 Jun 2026, 14:17
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: 20998
Location: In your JS exploiting you and your system
revolution 13 Jun 2026, 23:05
Jessé wrote:
Any name other than '.text' appears to work properly, as I also have tried '.rwx'.
So, ld is examining the section names and has a special case handler when it sees ".text"? If true then that just makes me hate C even more. All the hidden special cases makes my brain itch. Confused

Also, this kind of thing tends to become a target for AVs to trigger on. Anyone making better code gets punished by the AVs panicking over anything that used different tools. Crying or Very sad
Post 13 Jun 2026, 23:05
View user's profile Send private message Visit poster's website Reply with quote
Jessé



Joined: 03 May 2025
Posts: 119
Location: Brazil
Jessé 15 Jun 2026, 10:08
I'm trying to understand better the situation regarding the specifics about section names and 'ld' (GNU linker), and have found that the linker follows some kind of "script" to generate the final binary, and, yes, there is an even worse problem that occurs when binary does not have '.text' section: according to my attempts, if the binary doesn't have a '.text' section, a lot of things regarding symbol relocations isn't included in the binary, and one coding without a named '.text' section, for example, will not be able to use 'stdout' symbol directly, whenever binary is of type dynamic.
Yes, modern software (and modern C) sucks, no doubt. Fully agreed.

It seems that we need another linker (so far, I need to look elsewhere for other linkers to test), but, I will try to see if I can find anything that can be adjusted with 'ld' and, if found, I will post it here.

And, as always, fasm2 is doing its part flawlessly, I've checked with a hex editor, and the flag 'writeable' alongside with 'alloc' and 'execute' is at the object section correctly.
Post 15 Jun 2026, 10:08
View user's profile Send private message Visit poster's website Reply with quote
sylware



Joined: 23 Oct 2020
Posts: 598
Location: Marseille/France
sylware 15 Jun 2026, 13:00
The issue is ELF itself (same for microslope PE+): we now know that nowadays ELF is hardcore obsolete on modern CPU micro-architectures.


I am currently working with a different exe/dynamic lib format now (my own which is so simple, one RFC will be enough), but I will need to build system interface dynamic libraries (vulkan and libasound)... and the actual REAL pain is mostly... drum roll... c++ code! (which makes me hate computer languages with ultra complex syntax and/or runtime even more).
Post 15 Jun 2026, 13:00
View user's profile Send private message Reply with quote
Jessé



Joined: 03 May 2025
Posts: 119
Location: Brazil
Jessé 16 Jun 2026, 06:54
I got something that - to a certain extent - I think is worth sharing with you all: I may say that problem is solved when I use 'mold' linker instead of GNU 'ld': it makes the section '.text' with RWX flags - as I stated in my object file generated with fasm2 - and, as a very welcoming extra, the final executable is quite small, compared to the maximum you've got from 'ld'.

Example:
Code:
format ELF64

purge extrn?

; Just a slightly modified version of Tomasz' standard fasm2 macro, which also sets GOT.label size at definition
macro extrn? declaration*
    namespace ELF
        local sym,gsym,psym
        element sym : relocatable * (-1) + SYMBOL_INDEX
        element gsym : GOT + SYMBOL_INDEX
        element psym : PLT + SYMBOL_INDEX
        match str =as? name:size, declaration
            label name:size at sym
            label GOT.name:size at gsym
            label PLT.name at psym
            SYMBOL_NAME = string str
            SYMBOL_SIZE = size
        else match name:size, declaration
            label name:size at sym
            label GOT.name:size at gsym
            label PLT.name at psym
            SYMBOL_NAME = `name
            SYMBOL_SIZE = size
        else match str =as? name, declaration
            label name at sym
            label GOT.name:size at gsym
            label PLT.name at psym
            SYMBOL_NAME = string str
            SYMBOL_SIZE = 0
        else
            label declaration at sym
            label GOT.declaration:8 at gsym
            label PLT.declaration at psym
            SYMBOL_NAME = `declaration
            SYMBOL_SIZE = 0
        end match
        store STRING_POSITION at symbol_table : Elf64_Sym.st_name + SYMBOL_INDEX * sizeof Elf64_Sym
        store SYMBOL_NAME : lengthof SYMBOL_NAME at string_table:STRING_POSITION
        STRING_POSITION = STRING_POSITION + lengthof SYMBOL_NAME + 1
        store SYMBOL_SIZE at symbol_table : Elf64_Sym.st_size + SYMBOL_INDEX * sizeof Elf64_Sym
        store STT_NOTYPE + STB_GLOBAL shl 4 at symbol_table : Elf64_Sym.st_info + SYMBOL_INDEX * sizeof Elf64_Sym
        SYMBOL_INDEX = SYMBOL_INDEX + 1
    end namespace
end macro

SIG_INT = 2

public Start

extrn exit
extrn fflush
extrn fprintf
extrn signal
extrn stderr:8
extrn stdout:8
extrn usleep

section '.text' writeable executable align 16

    Start:
                    lea         rsi, [.break]
                    mov         edi, SIG_INT
                    call        [GOT.signal]

                    lea         rsi, [msg.start]
                    mov         rdi, [stdout]
                    mov         al, 0
                    call        [GOT.fprintf]

        .count:     inc         [count]

                    mov         edi, 500'000
                    call        [GOT.usleep]

                    test        [flags], 1
                    jnz         .end

                    mov         edx, [count]
                    lea         rsi, [stats]
                    mov         rdi, [stdout]
                    xor         al, al
                    call        [GOT.fprintf]

                    mov         rdi, [stdout]
                    call        [GOT.fflush]

                    jmp         .count

        .end:       lea         rsi, [erase.ctrl_c]
                    mov         rdi, [stdout]
                    sub         al, al
                    call        [GOT.fprintf]

                    xor         edi, edi
                    sub         rsp, 8
                    jmp         PLT.exit

        .break:     or          [flags], 1
                    ret

    align 4
    count           dd 0
    flags           db 1111_1110b

    msg.start       db 'Starting...',10,0
    stats           db 13,'Code section counter is: %lu ',0
    erase.ctrl_c    db 8,8,32,32,10,0
    


The above code, when linked with 'ld', results in a 13440 bytes executable that fails (for reasons mentioned above, related to the dictatorship behvior regarding not making my '.text' section as I requested). When linked with 'mold', the final executable is only 3488 bytes, and, it worked flawlessly, with section '.text' being RWX and any supported extra section also being there and properly set up.

The only minor problem with 'mold' is that it tags itself inside every binary in a '.comment' section, but this can be easily removed with:

Quote:
> strip -R .comment exec.name


By the way, 'mold' command line is quite similar to 'ld', and I let my following command line as a reference for ones using it:

Quote:

# Use (for test.raw.rwx as an example name):
> mold -s -o test.raw.rwx test.raw.rwx.o -e Start -pie -lc -L/usr/lib -I /lib64/ld-linux-x86-64.so.2
> strip -R .comment test.raw.rwx

; instead of:
> ld -s -o test.raw.rwx test.raw.rwx.o -e Start -pie -lc --dynamic-linker=/lib/ld-linux-x86-64.so.2


So, to answer myself: yes, it is possible to have a named '.text' section as read-write-execute, of course not recommended.
And also, @sylware: not a problem with ELF itself, this time. But I'm very interest in you developing a new format. Keep it going, and share with us if possible.

Thank you!
Post 16 Jun 2026, 06:54
View user's profile Send private message Visit poster's website Reply with quote
Jessé



Joined: 03 May 2025
Posts: 119
Location: Brazil
Jessé 21 Jun 2026, 11:35
I must add that 'lld' linker (invoked as 'ld.lld' on Linux) also got it right, with a slightly better executable than 'mold' (even smaller than what 'mold' delivered).
Save caveat as 'mold' with relation to tag itself in a '.comment' section, so, same procedure for the one who wants his binaries cleaner.
Post 21 Jun 2026, 11:35
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:  


< 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-2026, Tomasz Grysztar. Also on GitHub, YouTube.

Website powered by rwasa.