Linux memiliki domain eksekusi yang disebut READ_IMPLIES_EXEC
, yang menyebabkan semua halaman dialokasikan dengan PROT_READ
untuk juga diberikan PROT_EXEC
. Kernel Linux lama menggunakan ini untuk executable yang setara dengan gcc -z execstack
. Program ini akan menunjukkan kepada Anda apakah itu diaktifkan untuk dirinya sendiri:
#include <stdio.h>
#include <sys/personality.h>
int main(void) {
printf("Read-implies-exec is %s\n", personality(0xffffffff) & READ_IMPLIES_EXEC ? "true" : "false");
return 0;
}
Jika Anda mengompilasinya bersama dengan .s
kosong file, Anda akan melihat bahwa itu diaktifkan, tetapi tanpa itu, itu akan dinonaktifkan. Nilai awal ini berasal dari informasi meta ELF di biner Anda. Lakukan readelf -Wl example
. Anda akan melihat baris ini saat mengompilasi tanpa .s
kosong berkas:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW 0x10
Tapi yang ini saat Anda mengompilasinya:
GNU_STACK 0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10
Perhatikan RWE
bukan hanya RW
. Alasan untuk ini adalah bahwa linker mengasumsikan bahwa file rakitan Anda memerlukan read-implies-exec kecuali secara eksplisit diberitahu bahwa mereka tidak melakukannya, dan jika ada bagian dari program Anda yang memerlukan read-implies-exec, maka itu diaktifkan untuk seluruh program Anda . File rakitan yang dikompilasi oleh GCC mengatakan bahwa ia tidak memerlukan ini, dengan baris ini (Anda akan melihat ini jika Anda mengompilasi dengan -S
):
.section .note.GNU-stack,"",@progbits
Izin bagian default tidak menyertakan ex
ec. Lihat bagian ELF dari .section
dokumentasi untuk arti dari "flags" dan @attributes.
(Dan jangan lupa untuk beralih ke bagian lain seperti .text
atau .data
setelah itu .section
direktif, jika .s
Anda mengandalkan .text
karena bagian default di bagian atas file.)
Letakkan baris itu di example.s
(dan setiap .s
lainnya file dalam proyek Anda). Keberadaan .note.GNU-stack
itu bagian akan berfungsi untuk memberi tahu linker bahwa file objek ini tidak bergantung pada tumpukan yang dapat dieksekusi, sehingga linker akan menggunakan RW
bukannya RWE
pada GNU_STACK
metadata, dan program Anda kemudian akan berfungsi seperti yang diharapkan.
Demikian pula untuk NASM, sebuah section
direktif dengan flag yang tepat menentukan tumpukan yang tidak dapat dieksekusi.
Kernel Linux modern antara 5.4 dan 5.8 mengubah perilaku pemuat program ELF. Untuk x86-64, tidak ada yang mengaktifkan READ_IMPLIES_EXEC
lagi. Paling banyak (dengan RWE GNU_STACK
ditambahkan oleh ld
), Anda akan mendapatkan tumpukan itu sendiri dapat dieksekusi, bukan setiap halaman yang dapat dibaca. (Jawaban ini mencakup perubahan terakhir, di 5.8, tetapi pasti ada perubahan lain sebelumnya, karena pertanyaan itu menunjukkan eksekusi kode yang berhasil di .data
di x86-64 Linux 5.4)
exec-all
(READ_IMPLIES_EXEC
) hanya terjadi untuk executable 32-bit lawas yang tautannya tidak menambahkan GNU_STACK
entri header sama sekali. Tapi seperti yang ditunjukkan di sini, ld
modern selalu menambahkan itu dengan satu pengaturan atau yang lain, bahkan ketika input .o
file tidak memiliki catatan.
Anda tetap harus menggunakan .note
ini bagian untuk memberi sinyal tumpukan yang tidak dapat dieksekusi dalam program normal. Tetapi jika Anda berharap untuk menguji kode modifikasi sendiri di .data
atau mengikuti beberapa tutorial lama untuk menguji shellcode, itu bukan opsi pada kernel modern.
Sebagai alternatif untuk memodifikasi file rakitan Anda dengan varian direktif bagian khusus GNU, Anda dapat menambahkan -Wa,--noexecstack
ke baris perintah Anda untuk membuat file rakitan. Sebagai contoh, lihat bagaimana saya melakukannya di configure
musl :
https://git.musl-libc.org/cgit/musl/commit/configure?id=adefe830dd376be386df5650a09c313c483adf1a
Saya percaya setidaknya beberapa versi dentang dengan assembler terintegrasi mungkin memerlukannya untuk diteruskan sebagai --noexecstack
(tanpa -Wa
), jadi skrip konfigurasi Anda mungkin harus memeriksa keduanya dan melihat mana yang diterima.
Anda juga dapat menggunakan -Wl,-z,noexecstack
pada waktu penautan (dalam LDFLAGS
) untuk mendapatkan hasil yang sama. Kerugiannya adalah tidak membantu jika proyek Anda menghasilkan statis (.a
) file pustaka untuk digunakan oleh perangkat lunak lain, karena Anda tidak mengontrol opsi waktu penautan saat digunakan oleh program lain.