GNU/Linux >> Belajar Linux >  >> Linux

Tidak dapat memanggil fungsi pustaka standar C pada Linux 64-bit dari kode rakitan (yasm).

Gcc Anda membuat PIE yang dapat dieksekusi secara default (alamat absolut 32-bit tidak lagi diizinkan di x86-64 Linux?).

Saya tidak yakin mengapa, tetapi ketika melakukan itu linker tidak secara otomatis menyelesaikan call puts ke call [email protected] . Masih ada puts Entri PLT dihasilkan, tetapi call tidak pergi ke sana.

Saat runtime, penaut dinamis mencoba menyelesaikan puts langsung ke simbol libc dari nama itu dan perbaiki call rel32 . Tapi simbolnya berjarak lebih dari +-2^31, jadi kita mendapat peringatan tentang meluapnya R_X86_64_PC32 relokasi. 32 bit rendah dari alamat target benar, tetapi bit atas tidak. (Jadi call Anda melompat ke alamat yang buruk).

Kode Anda berfungsi untuk saya jika saya membuat dengan gcc -no-pie -fno-pie call-lib.c libcall.o . -no-pie adalah bagian penting:ini adalah opsi penghubung. Perintah YASM Anda tidak perlu diubah.

Saat membuat executable yang bergantung pada posisi tradisional, linker mengubah puts simbol untuk target panggilan ke [email protected] untuk Anda, karena kami menautkan file dinamis yang dapat dieksekusi (alih-alih menautkan libc secara statis dengan gcc -static -fno-pie , dalam hal ini call bisa langsung ke fungsi libc.)

Bagaimanapun, inilah mengapa gcc memancarkan call [email protected] (sintaks GAS) saat mengompilasi dengan -fpie (default di desktop Anda, tetapi bukan default di https://godbolt.org/), tetapi hanya call puts saat mengkompilasi dengan -fno-pie .

Lihat Apa arti @plt di sini? untuk lebih lanjut tentang PLT, dan juga Maaf keadaan perpustakaan dinamis di Linux dari beberapa tahun yang lalu. (gcc -fno-plt modern seperti salah satu ide di entri blog itu.)

BTW, prototipe yang lebih akurat/spesifik akan membuat gcc menghindari zeroing EAX sebelum memanggil foo :

extern void foo(); dalam C berarti extern void foo(...);
Anda dapat mendeklarasikannya sebagai extern void foo(void); , yaitu () berarti dalam C++. C++ tidak mengizinkan deklarasi fungsi yang membiarkan argumen tidak ditentukan.

peningkatan asm

Anda juga dapat memasukkan message di section .rodata (data hanya baca, ditautkan sebagai bagian dari segmen teks).

Anda tidak memerlukan bingkai tumpukan, hanya sesuatu untuk menyelaraskan tumpukan dengan 16 sebelum panggilan. Sebuah boneka push rax akan melakukannya.

Atau kita dapat melakukan tail-call puts dengan melompat untuk itu alih-alih memanggilnya, dengan posisi tumpukan yang sama seperti saat masuk ke fungsi ini. Ini bekerja dengan atau tanpa PIE. Cukup ganti call dengan jmp , selama RSP mengarah ke alamat pengirim Anda sendiri.

Jika Anda ingin membuat PIE dapat dieksekusi (atau pustaka bersama), Anda memiliki dua opsi

  • call puts wrt ..plt - secara eksplisit menelepon melalui PLT.
  • call [rel puts wrt ..got] - secara eksplisit melakukan panggilan tidak langsung melalui entri GOT, seperti -fno-plt gcc gaya kode-gen. (Menggunakan mode pengalamatan RIP-relatif untuk mencapai GOT, karenanya rel kata kunci).

WRT =Dengan Hormat Kepada. Manual NASM mendokumentasikan wrt ..plt , dan lihat juga bagian 7.9.3:simbol khusus dan WRT.

Biasanya Anda akan menggunakan default rel di bagian atas file Anda sehingga Anda benar-benar dapat menggunakan call [puts wrt ..got] dan masih mendapatkan mode pengalamatan RIP-relatif. Anda tidak dapat menggunakan mode pengalamatan absolut 32-bit dalam kode PIE atau PIC.

call [puts wrt ..got] merakit ke panggilan tidak langsung memori menggunakan penunjuk fungsi yang tautan dinamis disimpan di GOT. (Pengikatan awal, bukan penautan dinamis lambat.)

Dokumen NASM ..got untuk mendapatkan alamat variabel di bagian 9.2.3. Fungsi di pustaka (lainnya) identik:Anda mendapatkan penunjuk dari GOT alih-alih memanggil secara langsung, karena offset bukanlah konstanta waktu tautan dan mungkin tidak muat dalam 32 bit.

YASM juga menerima call [puts wrt ..GOTPCREL] , seperti sintaks AT&T call *[email protected](%rip) , tetapi NASM tidak.

; don't use BITS 64.  You *want* an error if you try to assemble this into a 32-bit .o

default rel          ; RIP-relative addressing instead of 32-bit absolute by default; makes the [rel ...] optional

section .rodata            ; .rodata is best for constants, not .data
message:
  db 'foo() called', 0

section .text

global foo
foo:
    sub    rsp, 8                ; align the stack by 16

    ; PIE with PLT
    lea    rdi, [rel message]      ; needed for PIE
    call   puts WRT ..plt          ; tailcall puts
;or
    ; PIE with -fno-plt style code, skips the PLT indirection
    lea   rdi, [rel message]
    call  [rel  puts wrt ..got]
;or
    ; non-PIE
    mov    edi, message           ; more efficient, but only works in non-PIE / non-PIC
    call   puts                   ; linker will rewrite it into call [email protected]

    add   rsp,8                   ; remove the padding
    ret

Dalam posisi-bergantung dapat dieksekusi, Anda dapat menggunakan mov edi, message alih-alih LEA yang relatif RIP. Ukuran kodenya lebih kecil dan dapat berjalan di lebih banyak port eksekusi pada sebagian besar CPU.

Dalam executable non-PIE, Anda juga dapat menggunakan call puts atau jmp puts dan biarkan linker menyelesaikannya, kecuali jika Anda menginginkan penautan dinamis gaya no-plt yang lebih efisien. Namun jika Anda memilih untuk menautkan libc secara statis, menurut saya ini adalah satu-satunya cara agar Anda mendapatkan jmp langsung ke fungsi libc.

(Menurut saya kemungkinan penautan statis untuk non-PIE adalah mengapa ld bersedia membuat stub PLT secara otomatis untuk non-PIE, tetapi tidak untuk PIE atau pustaka bersama. Ini mengharuskan Anda untuk mengatakan apa yang Anda maksud saat menautkan objek bersama ELF.)

Jika Anda memang menggunakan call puts dalam PIE (call rel32 ), ini hanya dapat berfungsi jika Anda secara statis menautkan implementasi puts yang tidak bergantung pada posisi ke dalam PIE Anda, jadi semuanya adalah satu yang dapat dieksekusi yang akan dimuat di alamat acak saat runtime (dengan mekanisme tautan dinamis biasa), tetapi tidak memiliki ketergantungan pada libc.so.6


0xe8 opcode diikuti oleh offset yang ditandatangani untuk diterapkan ke PC (yang telah maju ke instruksi berikutnya pada saat itu) untuk menghitung target cabang. Oleh karena itu objdump menafsirkan target cabang sebagai 0x671 .

YASM merender nol karena kemungkinan telah menempatkan relokasi pada offset itu, yaitu cara meminta loader untuk mengisi offset yang benar untuk puts selama pemuatan. Loader mengalami luapan saat menghitung relokasi, yang mungkin menunjukkan bahwa puts berada pada offset yang lebih jauh dari panggilan Anda daripada yang dapat direpresentasikan dalam offset bertanda 32-bit. Karenanya loader gagal memperbaiki instruksi ini, dan Anda mengalami kerusakan.

66c: e8 00 00 00 00 menunjukkan alamat yang tidak berpenghuni. Jika Anda melihat tabel relokasi Anda, Anda akan melihat relokasi di 0x66d . Tidak jarang assembler mengisi alamat/offset dengan relokasi karena semuanya nol.

Halaman ini menunjukkan bahwa YASM memiliki WRT direktif yang dapat mengontrol penggunaan .got , .plt , dll.

Per S9.2.5 pada dokumentasi NASM, sepertinya Anda dapat menggunakan CALL puts WRT ..plt (menganggap YASM memiliki sintaks yang sama).


Linux
  1. Bagaimana cara memuat modul kernel Linux dari kode C?

  2. x86_64 Perakitan Kebingungan Panggilan Sistem Linux

  3. Pustaka C untuk membaca versi EXE dari Linux?

  1. Memanggil fungsi userspace dari dalam modul kernel Linux

  2. Memanggil fungsi C dari kode C++

  3. Bagaimana pustaka bersama (.so) memanggil fungsi yang diimplementasikan dalam program pemuatannya?

  1. Bagaimana saya bisa menghapus jenkins sepenuhnya dari linux

  2. Bisakah saya mem-boot Linux dari VHD?

  3. Memanggil syscall Linux dari bahasa scripting