Cara menentukan apakah sebuah pointer ada di rodata [duplikat]

Dec 08 2020

Dapatkah saya mengetahui jika ada pointer di bagian rodata yang dapat dieksekusi? Seperti dalam, mengedit data pointer itu akan menyebabkan jebakan sistem runtime.

Contoh (menggunakan penunjuk karakter C):

void foo(char const * const string) {
    if ( in_rodata( string ) ) {
        puts("It's in rodata!");
    } else {
        puts("That ain't in rodata");
    }
}

Sekarang saya berpikir bahwa, mungkin, saya dapat dengan mudah membandingkan pointer ke bagian rodata.

Sesuatu di sepanjang baris:

if ( string > start_of_rodata && string < end_of_rodata ) {
    // it's in rodata!
}

Apakah ini rencana / ide yang layak? Adakah yang punya ide bagaimana saya bisa melakukan ini?

(Apakah ada informasi sistem yang mungkin diperlukan untuk menjawab ini?) Saya menjalankan program pada platform Linux.

Jawaban

Kubahasn'tforgottenMonica Dec 08 2020 at 12:04

Saya ragu itu mungkin portabel

Jika Anda tidak ingin mengotak-atik skrip linker atau menggunakan API kueri peta memori khusus platform, pendekatan proxy cukup portabel pada platform dengan perlindungan memori, jika Anda hanya ingin mengetahui apakah lokasi dapat ditulis, hanya-baca , atau tidak keduanya. Ide umumnya adalah melakukan tes membaca dan tes menulis. Jika yang pertama berhasil tetapi yang kedua gagal, kemungkinan besar .rodataatau kode segmen. Ini tidak memberi tahu Anda "itu pasti rodata" - ini mungkin segmen kode, atau halaman hanya-baca lainnya, seperti pemetaan memori file hanya-baca yang salin-saat-tulisnya dinonaktifkan. Tetapi itu tergantung pada apa yang ada dalam pikiran Anda untuk tes ini - apa tujuan akhirnya.

Peringatan lain adalah: Agar ini aman dari jarak jauh, Anda harus menangguhkan semua utas lainnya dalam proses saat Anda melakukan tes ini, karena ada kemungkinan Anda dapat merusak beberapa status yang dieksekusi kode pada utas lain yang mungkin menjadi rujukannya. Melakukan ini dari dalam proses yang sedang berjalan mungkin memiliki kasus sudut yang sulit di-debug yang akan berhenti mengintai dan muncul dengan sendirinya selama demo pelanggan. Jadi, pada platform yang mendukung ini, selalu lebih baik untuk menelurkan proses lain yang akan menangguhkan proses pertama secara keseluruhan (semua utas), menyelidikinya, menulis hasilnya ke ruang alamat proses (ke beberapa variabel hasil), melanjutkan proses dan mengakhiri dirinya sendiri. Pada beberapa platform, tidak mungkin untuk mengubah ruang alamat proses dari luar, dan sebagai gantinya Anda perlu menangguhkan proses sebagian besar atau seluruhnya, menyuntikkan utas probe, menangguhkan utas lainnya yang tersisa, biarkan probe melakukan tugasnya, tulis jawaban untuk beberapa variabel yang disepakati, hentikan, lalu lanjutkan semua yang lain dari keamanan proses eksternal.

Demi kesederhanaan, di bawah ini akan mengasumsikan bahwa semuanya dilakukan dari dalam proses. Meskipun contoh mandiri "berkemampuan penuh" yang bekerja lintas proses tidak akan terlalu lama, menulis hal ini agak membosankan terutama jika Anda menginginkannya pendek, elegan dan setidaknya sebagian besar benar - Saya membayangkan nilai sehari penuh kerja. Jadi, sebagai gantinya, saya akan melakukan beberapa sketsa kasar dan membiarkan Anda mengisi kekosongan (ha).

Windows

Pengecualian terstruktur dilemparkan misalnya karena kesalahan perlindungan atau bagi dengan nol. Untuk melakukan tes, coba baca dari alamat yang dimaksud. Jika itu berhasil, Anda tahu itu setidaknya halaman yang dipetakan (jika tidak, itu akan membuat pengecualian yang dapat Anda tangkap). Kemudian coba tulis di sana - jika gagal, maka itu hanya bisa dibaca. Kodenya hampir membosankan:

static const int foo;
static int bar;

#if _WIN32
typedef struct ThreadState ThreadState;
ThreadState *suspend_other_threads(void) { ... }
void resume_other_threads(ThreadState *) { ... }

int check_if_maybe_rodata(void *p) {
  __try {
    (void) *(volatile char *)p;
  } __finally {
    return false;
  }
  volatile LONG result = 0;
  ThreadState *state = suspend_other_threads();
  __try {
    InterlockedExchange(&result, 1);
    LONG saved = *(volatile LONG*)p;
    InterlockedExchange((volatile LONG *)p, saved);
    InterlockedExchange(&result, 0); // we succeeded writing there
  } __finally {}
  resume_other_threads(state);
  return result;
}

int main() {
  assert(check_if_maybe_rodata(&foo));
  assert(!check_if_maybe_rodata(&bar));
}
#endif

Menangguhkan utas mengharuskan melintasi daftar utas , dan menangguhkan setiap utas yang bukan utas saat ini. Daftar semua utas yang ditangguhkan harus dibuat dan disimpan, sehingga nanti daftar yang sama dapat dilalui untuk melanjutkan semua utas.

Pasti ada peringatan, dan utas WoW64 memiliki API mereka sendiri untuk penangguhan dan dimulainya kembali, tetapi itu mungkin sesuatu yang, dalam keadaan terkendali, berfungsi dengan baik.

Unix

Idenya adalah untuk memanfaatkan kernel untuk memeriksa penunjuk untuk kami "di lengan" sehingga tidak ada sinyal yang dilemparkan. Penanganan sinyal POSIX yang dihasilkan dari kesalahan proteksi memori memerlukan perbaikan kode yang menyebabkan kesalahan, memaksa Anda untuk mengubah status proteksi dari memori kode . Tidak begitu bagus. Sebagai gantinya, berikan pointer ke syscall yang Anda tahu seharusnya berhasil dalam semua keadaan normal untuk membaca dari alamat yang diarahkan ke alamat - misalnya buka /dev/zero, dan tulis ke file itu dari buffer yang ditunjuk oleh pointer. Jika gagal EFAULT, itu karena buf [berada] di luar ruang alamat Anda yang dapat diakses. Jika Anda bahkan tidak bisa membaca dari alamat itu, itu belum .rodatapasti.

Kemudian lakukan kebalikannya: dari tempat terbuka /dev/zero, coba a readke alamat yang Anda uji. Jika pembacaan berhasil, maka itu bukan data hanya-baca. Jika pembacaan gagal dengan EFAULTitu kemungkinan besar berarti bahwa area yang dimaksud adalah hanya-baca karena berhasil membacanya, tetapi tidak menulis untuk itu.

Dalam semua kasus, akan lebih baik jika menggunakan API platform asli untuk menguji status pemetaan halaman tempat alamat yang Anda coba akses berada, atau bahkan lebih baik - untuk menjalankan daftar bagian dari executable yang dipetakan (ELF di Linux , PE di Windows), dan lihat persis apa yang terjadi di mana. Entah bagaimana, tidak ada jaminan bahwa pada semua sistem dengan perlindungan memori, .rodatabagian atau yang setara akan dipetakan hanya sebagai baca, sehingga gambar yang dapat dieksekusi seperti yang dipetakan ke dalam proses yang sedang berjalan adalah otoritas tertinggi. Itu masih tidak menjamin bahwa bagian tersebut saat ini dipetakan hanya-baca. Sebuah mprotectatau panggilan yang sama bisa berubah, atau bagian dari itu, dapat ditulis, bahkan dimodifikasi mereka, dan kemudian mungkin mengubah mereka kembali ke read-only. Anda kemudian harus memeriksa bagian tersebut jika format yang dapat dieksekusi menyediakan data seperti itu, atau mmapbiner yang sama di tempat lain dalam memori dan membandingkan bagian tersebut.

Tapi saya mencium bau samar masalah XY: apa yang sebenarnya Anda coba lakukan? Maksud saya, tentunya Anda tidak hanya ingin memeriksa apakah suatu alamat hanya .rodatauntuk penasaran. Anda harus memiliki beberapa kegunaan untuk informasi itu, dan aplikasi inilah yang pada akhirnya akan memutuskan apakah melakukan .rodatapemeriksaan ini harus ada di radar. Mungkin saja, mungkin juga tidak. Berdasarkan pertanyaan Anda saja, ini adalah pertanyaan "siapa yang tahu?"