BuatRemoteThread untuk Linux

Mitsuha membawa aliran baru WinAPI memiliki fungsi CreateRemoteThread yang memungkinkan Anda untuk memulai utas baru di ruang alamat proses lain. Ini dapat digunakan untuk berbagai injeksi DLL, baik untuk tujuan buruk (menipu dalam game, pencurian kata sandi, dll.), Dan untuk memperbaiki bug dalam program yang sedang berjalan saat itu juga, atau menambahkan plugin ke tempat-tempat yang tidak disediakan.


Secara umum, fungsi ini memiliki utilitas aplikasi yang meragukan, oleh karena itu tidak mengherankan bahwa Linux tidak memiliki analog CreateRemoteThread yang sudah jadi. Namun, saya bertanya-tanya bagaimana itu bisa diterapkan. Mempelajari topik itu berubah menjadi petualangan yang baik.


Saya akan berbicara secara rinci tentang bagaimana, dengan bantuan spesifikasi ELF, beberapa pengetahuan tentang arsitektur x86_64 dan panggilan sistem Linux, menulis sepotong kecil debugger Anda sendiri yang dapat memuat dan mengeksekusi kode arbitrer dalam proses yang sudah berjalan dan bekerja.


Memahami teks akan membutuhkan pengetahuan dasar tentang pemrograman sistem untuk Linux: bahasa C, menulis dan debugging program di atasnya, pemahaman tentang peran kode mesin dan memori di komputer, konsep panggilan sistem, keakraban dengan perpustakaan utama, dan membaca dokumentasi.



Hasilnya, saya dapat "menambahkan" kemampuan untuk mempratinjau kata sandi di Pusat Kontrol Gnome:


demonstrasi injeksi di Pusat Kontrol Gnome


Ide utama


Jika tidak ada klausa dalam persyaratan tentang memuat kode ke dalam proses yang sudah berjalan, maka solusinya akan sangat sederhana: LD_PRELOAD. Variabel lingkungan ini memungkinkan memuat pustaka sewenang-wenang dengan aplikasi. Di pustaka bersama, Anda bisa menentukan fungsi konstruktor yang dijalankan saat pustaka memuat.


Bersama-sama, LD_PRELOAD dan konstruktor memungkinkan kode arbitrer untuk dieksekusi dalam proses apa pun menggunakan loader dinamis. Ini adalah fitur yang relatif terkenal yang sering digunakan untuk debugging. Misalnya, Anda dapat mengunduh perpustakaan Anda sendiri dengan aplikasi, yang mendefinisikan fungsi malloc () dan free (), yang dapat membantu menangkap kebocoran memori.


Sayangnya, LD_PRELOAD hanya berfungsi saat proses dimulai. Itu tidak bisa digunakan untuk memuat perpustakaan ke dalam proses yang sudah berjalan. Ada fungsi dlopen () untuk memuat pustaka saat proses sedang berjalan, tetapi jelas proses itu sendiri harus memanggilnya untuk memuat plugin.


Tentang executable statis

LD_PRELOAD hanya berfungsi dengan program yang menggunakan pemuat dinamis. Jika program dibangun dengan sakelar -static , maka ia menyertakan semua pustaka yang diperlukan. Dalam hal ini, resolusi dependensi di perpustakaan dilakukan pada waktu pembangunan dan program biasanya tidak siap dan tidak dapat memuat perpustakaan secara dinamis setelah perakitan, pada waktu berjalan.

Dalam program yang dirakit secara statis, Anda dapat menyuntikkan kode saat runtime, tetapi ini harus dilakukan dengan cara yang sedikit berbeda. Dan ini tidak sepenuhnya aman, karena program mungkin tidak siap untuk belokan seperti itu.

Secara umum, tidak ada solusi nyaman yang siap pakai, Anda harus menulis sepeda Anda. Kalau tidak, Anda tidak akan membaca teks ini :)


Secara konseptual, untuk memaksa proses orang lain menjalankan beberapa jenis kode, Anda perlu melakukan tindakan berikut:


  1. Dapatkan kontrol dalam proses target.
  2. Muat kode ke dalam memori proses target.
  3. Siapkan kode yang diunduh untuk dieksekusi dalam proses target.
  4. Atur eksekusi kode yang diunduh dengan proses target.

Ayo pergi ...


Mendapatkan kontrol dalam proses


Pertama-tama, kita perlu mensubordinasikan proses target sesuai keinginan kita. Lagi pula, biasanya proses hanya mengeksekusi kode mereka sendiri, atau kode pustaka yang dimuat, atau hasil kompilasi JIT. Tapi yang pasti bukan kode kita.


Salah satu opsi adalah menggunakan semacam kerentanan dalam proses yang memungkinkan Anda mengambil kendali. Contoh klasik dari tutorial: buffer overflow, memungkinkan untuk menulis ulang alamat pengirim di stack. Ini menyenangkan, kadang-kadang bahkan berhasil, tetapi tidak cocok untuk kasus umum.


Kami akan menggunakan cara lain yang jujur ​​untuk mendapatkan kendali: men-debug panggilan sistem . Debuger interaktif dapat dengan sempurna menghentikan proses pihak ketiga, mengevaluasi ekspresi, dan banyak hal lainnya. Mereka bisa - kita bisa.


Di Linux, panggilan sistem debugging utama adalah ptrace () . Ini memungkinkan Anda untuk terhubung ke proses, memeriksa status mereka, dan mengontrol kemajuan eksekusi mereka. ptrace () didokumentasikan dengan cukup baik, tetapi rincian penggunaannya hanya jelas dalam praktiknya.


Memuat kode ke dalam memori proses


Dalam kasus buffer overflows, payload ( kode shell ) biasanya termasuk dalam konten yang meluap buffer yang sama. Saat menggunakan debugger, kode yang diperlukan dapat ditulis ke memori proses secara langsung. Di WinAPI ada fungsi khusus WriteProcessMemory untuk ini. Linux untuk tujuan ini sesuai dengan cara UNIX: untuk setiap proses dalam sistem terdapat file / proc / $ pid / mem , yang menampilkan memori dari proses ini. Dimungkinkan untuk menulis sesuatu ke memori proses menggunakan input-output yang biasa.


Mempersiapkan kode untuk dieksekusi


Menulis kode saja ke dalam memori tidak cukup. Masih perlu ditulis ke memori yang dapat dieksekusi . Dalam hal merekam melalui kerentanan, ada kesulitan non-sepele dengan ini, tetapi karena kita dapat sepenuhnya mengontrol proses target, itu tidak akan menjadi masalah bagi kita untuk menemukan atau mengalokasikan memori "yang benar" untuk diri kita sendiri.


Poin penting persiapan lainnya adalah kode shell itu sendiri. Di dalamnya, kita mungkin ingin menggunakan beberapa fungsi dari perpustakaan, seperti input-output, primitif grafik, dan sebagainya. Namun, kita harus menuliskan kode mesin telanjang, yang dengan sendirinya tidak tahu tentang alamat semua fungsi keren ini di perpustakaan. Dari mana Anda mendapatkannya?


Untuk menyederhanakan masa pakai sistem operasi dan menyulitkan masa pakai kode jahat, perpustakaan biasanya tidak menggunakan alamat tetap (dan berisi apa yang disebut kode posisi-independen ). Jadi alamat tidak bisa ditebak.


Ketika proses dimulai secara normal, loader yang melakukan relokasi bertanggung jawab untuk menentukan alamat perpustakaan yang tepat. Namun, ia memenuhi hanya sekali di awal. Jika proses memungkinkan pemuatan dinamis perpustakaan, maka ada pemuat dinamis di dalamnya yang dapat melakukan hal yang sama saat proses sedang berjalan. Namun, alamat loader dinamis juga tidak diperbaiki.


Secara umum, dengan perpustakaan, ada empat opsi:


  • jangan gunakan pustaka sama sekali, lakukan segalanya pada panggilan sistem yang bersih
  • letakkan salinan semua perpustakaan yang diperlukan ke dalam kode shell
  • kerjakan sendiri loader dinamis
  • temukan bootloader yang dinamis dan buat itu memuat perpustakaan kami

Kami akan memilih yang terakhir, karena kami ingin perpustakaan, dan menulis bootloader penuh kami untuk waktu yang lama. Ini bukan metode yang paling rahasia, dan bukan yang paling menarik, tetapi yang paling sederhana, kuat dan dapat diandalkan.


Transfer kendali ke kode


ptrace () memungkinkan Anda untuk mengubah register prosesor, jadi seharusnya tidak ada masalah dengan mentransfer kontrol ke kode yang dimuat dan disiapkan: cukup tulis alamat kode kami di register% rip - dan voila! Namun, pada kenyataannya, semuanya tidak sesederhana itu. Kesulitan terhubung dengan fakta bahwa proses debug sebenarnya belum hilang dan juga memiliki beberapa jenis kode yang telah dieksekusi dan akan terus dieksekusi.


Sketsa solusi


Total, kami akan menerapkan aliran kami dalam proses pihak ketiga sebagai berikut:


  1. Kami terhubung ke proses target untuk debugging.
  2. Kami menemukan perpustakaan yang diperlukan dalam memori:
    • libdl - untuk memuat perpustakaan baru
    • libpthread - untuk memulai utas baru
  3. Kami menemukan fungsi yang diperlukan di perpustakaan:
    • libdl: dlopen (), dlsym ()
    • libpthread: pthread_create (), pthread_detach ()
  4. Kami memperkenalkan kode shell ke dalam memori proses target:


     void shellcode(void) { void *payload = dlopen("/path/to/payload.so", RTLD_LAZY); void *entry = dlsym(payload, "entry_point"); pthread_t thread; pthread_create(&thread, NULL, entry, NULL); pthread_detach(thread); } 

  5. Kami memberikan kode shell yang harus dipenuhi.

Sebagai hasilnya, perpustakaan akan melakukan hal yang benar untuk kita: mereka akan memuat perpustakaan kita dengan kode yang kita butuhkan dalam memori dan memulai utas baru yang mengeksekusi kode ini.


Keterbatasan


Pendekatan yang dijelaskan di atas memberikan batasan tertentu:


  • Bootloader harus memiliki hak yang memadai untuk men-debug proses target.
  • Prosesnya harus menggunakan libdl (siap untuk memuat modul secara dinamis).
  • Prosesnya harus menggunakan libpthread (siap untuk multithreading).
  • Aplikasi statis tidak didukung.

Selain itu, saya pribadi terlalu malas untuk repot dengan dukungan semua arsitektur, jadi kami akan membatasi diri hingga x86_64. (Bahkan x86 32-bit akan lebih rumit.)


Seperti yang Anda lihat, semua ini mengakhiri penggunaan rahasia dengan target jahat. Namun, tugas tersebut masih mempertahankan minat penelitian dan bahkan menyisakan peluang yang lemah untuk penggunaan industri.


Digression: tentang menggunakan libdl dan libpthread


Pembaca-pembaca berpengalaman mungkin bertanya-tanya: mengapa memerlukan libdl jika __libc_dlopen_mode () dan __libc_dlsym () fungsi internal sudah dibangun ke dalam glibc, dan libdl hanya membungkus mereka? Demikian pula, mengapa perlu libpthread jika utas baru dapat dengan mudah dibuat menggunakan panggilan sistem clone ()?


Memang, di Internet ada jauh dari satu contoh bagaimana mereka digunakan:



Mereka bahkan disebutkan dalam literatur hacker populer:


  • Belajar Analisis Biner Linux
  • Seni forensik memori

Jadi mengapa tidak? Yah, setidaknya karena kami tidak menulis kode berbahaya di mana solusi cocok yang menghilangkan 90% dari pemeriksaan, memakan ruang 20 kali lebih sedikit, tetapi juga berfungsi dalam 80% kasus. Selain itu, saya ingin mencoba semuanya dengan tangan saya sendiri.


Memang, libdl tidak perlu memuat perpustakaan dalam kasus glibc. Penggunaannya oleh proses menunjukkan bahwa itu jelas siap untuk memuat kode dinamis. Meskipun demikian, pada prinsipnya, Anda dapat menolak untuk menggunakan libdl (mengingat bahwa kita masih perlu mencari glibc nanti juga).


Kenapa dlopen () di dalam glibc sama sekali?

Ini pertanyaan yang menarik dengan caranya sendiri. Jawaban singkat: detail implementasi.

Intinya adalah saklar layanan nama (NSS) - salah satu bagian dari glibc yang menyediakan terjemahan berbagai nama: nama mesin, protokol, pengguna, server mail, dll. Dialah yang bertanggung jawab untuk fungsi seperti getaddrinfo () untuk mendapatkan alamat IP dengan nama domain dan getpwuid () untuk mendapatkan informasi tentang pengguna dengan pengenal numeriknya.

NSS memiliki arsitektur modular dan memuat modul secara dinamis. Sebenarnya, untuk ini, glibc juga membutuhkan mekanisme untuk memuat perpustakaan secara dinamis. Itulah sebabnya ketika Anda mencoba menggunakan getaddrinfo () dalam aplikasi yang dirakit secara statis, linker mencetak peringatan yang "tidak bisa dipahami":
 /tmp/build/socket.o: Dalam fungsi `Socket :: bind ':
 socket.o :(. teks + 0x374): peringatan: Menggunakan 'getaddrinfo' di ditautkan secara statis
 aplikasi membutuhkan saat runtime perpustakaan bersama dari versi glibc
 digunakan untuk menghubungkan

Adapun utas, utas biasanya tidak hanya tumpukan dan kode yang dapat dieksekusi, tetapi juga data global yang disimpan dalam penyimpanan utas-lokal (TLS). Inisialisasi yang benar dari utas baru membutuhkan operasi terkoordinasi dari kernel OS, pemuat kode biner, dan runtime bahasa pemrograman. Oleh karena itu, panggilan sederhana untuk mengkloning () sudah cukup untuk membuat aliran yang dapat menulis ke file β€œHello world!”, Tetapi ini mungkin tidak berfungsi untuk kode yang lebih kompleks yang memerlukan akses ke TLS dan hal-hal menarik lainnya yang tersembunyi dari mata programmer aplikasi.


Poin lain yang terkait dengan multithreading adalah proses single-threaded. Apa yang terjadi jika kita membuat utas baru dalam proses yang tidak dipahami sebagai multithreaded? Perilaku yang benar dan tidak jelas. Memang, dalam proses tersebut tidak ada sinkronisasi kerja antara utas, yang cepat atau lambat akan menyebabkan korupsi data. Jika kami menuntut agar aplikasi menggunakan libpthread, maka kami dapat memastikan bahwa aplikasi tersebut siap untuk bekerja di lingkungan multi-utas (setidaknya harus siap).


Langkah 1. Koneksi ke proses


Pertama, kita perlu terhubung ke proses target untuk debugging, dan kemudian, putuskan dari sana kembali. Di sinilah panggilan sistem ptrace () masuk


Kontak pertama dengan ptrace ()


Dalam dokumentasi untuk ptrace () Anda dapat menemukan hampir semua informasi yang diperlukan:


   Melampirkan dan melepaskan
        Utas dapat dilampirkan ke pelacak menggunakan panggilan

            ptrace (PTRACE_ATTACH, pid, 0, 0);

        atau

            ptrace (PTRACE_SEIZE, pid, 0, PTRACE_O_flags);

        PTRACE_ATTACH mengirim SIGSTOP ke utas ini.  Jika pelacak menginginkan ini
        SIGSTOP tidak memiliki efek, perlu menekannya.  Perhatikan bahwa jika
        sinyal lain secara bersamaan dikirim ke utas ini selama pemasangan, the
        pelacak dapat melihat jejak memasuki penghentian pengiriman sinyal dengan sinyal lain
        nal (s) pertama!  Praktik yang biasa dilakukan adalah memasukkan kembali sinyal-sinyal ini sampai
        SIGSTOP terlihat, kemudian menekan injeksi SIGSTOP.  Bug desain
        di sini adalah bahwa ptrace melampirkan dan SIGSTOP yang dikirimkan secara bersamaan dapat
        ras dan SIGSTOP bersamaan dapat hilang.

Jadi langkah pertama adalah menggunakan PTRACE_ATTACH:


 int ptrace_attach(pid_t pid) { /*     */ if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) return -1; /*    */ if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; return 0; } 

Setelah ptrace (), proses target tidak cukup siap untuk debugging. Kami terhubung ke sana, tetapi untuk studi interaktif dari keadaan proses, itu harus dihentikan. ptrace () mengirimkan sinyal SIGSTOP ke proses, tetapi kita masih harus menunggu sampai proses tersebut benar-benar berhenti.


Untuk menunggu, gunakan panggilan sistem waitpid (). Pada saat yang sama, beberapa kasus batas yang menarik patut dicatat. Pertama, proses tersebut dapat berakhir atau mati tanpa menerima SIGSTOP. Dalam hal ini, kita tidak bisa berbuat apa-apa. Kedua, beberapa sinyal lain mungkin sebelumnya dikirim ke proses. Dalam hal ini, kita harus membiarkan proses memprosesnya (menggunakan PTRACE_CONT), dan diri kita sendiri, terus menunggu lebih lanjut untuk SIGSTOP kami:


 static int wait_for_process_stop(pid_t pid, int expected_signal) { for (;;) { int status = 0; /* ,    -  */ if (waitpid(pid, &status, 0) < 0) return -1; /*      β€”   */ if (WIFSIGNALED(status) || WIFEXITED(status)) return -1; /*   ,     */ if (WIFSTOPPED(status)) { /* *  WSTOPSIG()   , *   ptrace()   *     . */ int stop_signal = status >> 8; /*    ,    */ if (stop_signal == expected_signal) break; /*        */ if (ptrace(PTRACE_CONT, pid, 0, stop_signal) < 0) return -1; continue; } /*   β€”   */ return -1; } return 0; } 

Proses pemutusan


Menghentikan proses debugging jauh lebih sederhana: cukup gunakan PTRACE_DETACH:


 int ptrace_detach(pid_t pid) { if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return -1; return 0; } 

Sebenarnya, menonaktifkan debugger secara eksplisit tidak selalu diperlukan. Ketika proses debugger berakhir, secara otomatis terputus dari semua proses debug, dan proses dilanjutkan jika mereka dihentikan oleh ptrace (). Namun, jika proses debugged dihentikan secara eksplisit oleh debugger menggunakan sinyal SIGSTOP tanpa menggunakan ptrace (), itu tidak akan bangun tanpa sinyal SIGCONT atau PTRACE_DETACH yang sesuai. Karena itu, lebih baik untuk memutuskan sambungan dari proses secara budaya.


Pengaturan Ptrace_scope


Debugger memiliki kontrol penuh atas proses yang sedang di-debug. Jika ada yang bisa men-debug apa pun, apa yang akan menjadi perluasan untuk kode berbahaya! Jelas bahwa debugging interaktif adalah kegiatan yang agak spesifik, biasanya hanya diperlukan untuk pengembang. Selama operasi normal sistem, paling sering tidak perlu proses debug.


Untuk alasan ini, karena alasan keamanan, sistem biasanya menonaktifkan kemampuan untuk men-debug proses apa pun secara default. Modul keamanan Yama bertanggung jawab untuk ini, dikelola melalui file / proc / sys / kernel / yama / ptrace_scope. Ini memberikan empat perilaku:


  • 0 - pengguna dapat men-debug setiap proses yang ia mulai
  • 1 - mode default, hanya proses yang dimulai oleh debugger yang dapat di-debug
  • 2 - hanya administrator sistem root yang dapat men-debug proses
  • 3 - debugging dilarang untuk semua orang sama sekali, mode tidak mati sampai sistem reboot

Tentunya, untuk keperluan kami, akan perlu untuk dapat men-debug proses yang diluncurkan sebelum debugger kami, jadi untuk percobaan Anda perlu beralih sistem ke mode pengembangan dengan menulis 0 ke file ptrace_scope khusus (yang memerlukan hak administrator):


 $ sudo sh -c 'echo 0 > /proc/sys/kernel/yama/ptrace_scope' 

atau jalankan debugger sebagai administrator:


 $ sudo ./inject-thread ... 

Hasil langkah pertama


Sebagai hasilnya, pada langkah pertama, kami dapat terhubung ke proses target sebagai debugger dan kemudian memutusnya.


Proses target akan dihentikan dan kami dapat memastikan bahwa sistem operasi benar-benar melihat kami sebagai debugger:


 $ sudo ./inject-thread --target $(pgrep docker) $ cat /proc/$(pgrep docker)/status | head Name: docker State: t (tracing stop) <---    Tgid: 31330 Ngid: 0 Pid: 31330 PPid: 1 TracerPid: 2789 <--- PID   Uid: 0 0 0 0 Gid: 0 0 0 0 FDSize: 64 $ ps a | grep [2]789 2789 pts/5 S+ 0:00 ./inject-thread --target 31330 

Langkah 2. Cari perpustakaan di memori


Langkah selanjutnya lebih sederhana: Anda perlu mencari di memori proses target perpustakaan dengan fungsi yang kita butuhkan. Tetapi ada banyak memori, di mana mulai mencari dan apa sebenarnya?


File / proc / $ pid / maps


Sebuah file khusus akan membantu kita dengan ini, di mana kernel memberi tahu tentang apa dan di mana proses tersebut berada dalam memori. Seperti yang Anda ketahui , di direktori / proc untuk setiap proses terdapat subdirektori. Dan ada file di dalamnya yang menggambarkan proses kartu memori :


 $ cat / proc / self / maps
 00400000-0040c000 r-xp 00000000 fe: 01 1044592 / bin / cat
 0060b000-0060c000 r - p 0000b000 fe: 01 1044592 / bin / cat
 0060c000-0060d000 rw-p 0000c000 fe: 01 1044592 / bin / cat
 013d5000-013f6000 rw-p 00000000 00:00 0 [tumpukan]
 7f9920bd1000-7f9920d72000 r-xp 00000000 fe: 01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920d72000-7f9920f72000 --- p 001a1000 fe: 01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920f72000-7f9920f76000 r - p 001a1000 fe: 01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7f9920f76000-7f9920f78000 rw-p 001a5000 fe: 01 920019 /lib/x86_64-linux-gnu/libc-2.19.so
 7fc3f8381000-7fc3f8385000 rw-p 00000000 00:00 0
 7fc3f8385000-7fc3f83a6000 r-xp 00000000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f83ec000-7fc3f840e000 rw-p 00000000 00:00 0
 7fc3f840e000-7fc3f8597000 r - p 00000000 fe: 01 657286 / usr / lib / lokal / arsip-lokal
 7fc3f8597000-7fc3f859a000 rw-p 00000000 00:00 0
 7fc3f85a3000-7fc3f85a5000 rw-p 00000000 00:00 0
 7fc3f85a5000-7fc3f85a6000 r - p 00020000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f85a6000-7fc3f85a7000 rw-p 00021000 fe: 01 920012 /lib/x86_64-linux-gnu/ld-2.19.so
 7fc3f85a7000-7fc3f85a8000 rw-p 00000000 00:00 0
 7ffdb6f0e000-7ffdb6f2f000 rw-p 00000000 00:00 0 [tumpukan]
 7ffdb6f7f000-7ffdb6f81000 r-xp 00000000 00:00 0 [vdso]
 7ffdb6f81000-7ffdb6f83000 r - p 00000000 00:00 0 [vvar]
 ffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0 [vsyscall]

Isi file ini dihasilkan dengan cepat oleh kernel sistem operasi dari struktur internal yang menggambarkan wilayah memori dari proses yang menarik bagi kami, dan berisi informasi berikut:


  • rentang alamat yang dialokasikan untuk wilayah tersebut
  • hak akses ke wilayah tersebut
    • r/- : baca
    • w/- : tulis
    • x/- : eksekusi
    • p/s : berbagi memori dengan proses lain
  • offset file (jika ada)
  • kode perangkat tempat file yang ditampilkan berada
  • nomor inode file (jika ada)
  • path ke file yang ditampilkan (jika ada)

Beberapa wilayah memori dipetakan ke file: ketika suatu proses membaca memori tersebut, itu sebenarnya membaca data dari file yang sesuai pada offset tertentu. Jika Anda dapat menulis ke suatu wilayah, maka perubahan dalam memori dapat dilihat hanya oleh proses itu sendiri (mekanisme copy-on-write , mode p bersifat pribadi), atau disinkronkan dengan disk (mode s dibagikan).


Wilayah lain anonim - memori ini tidak sesuai dengan file apa pun. Sistem operasi hanya memberikan proses sepotong memori fisik yang digunakannya. Daerah tersebut digunakan, misalnya, untuk memori proses "normal": stack dan heap. Wilayah anonim dapat bersifat pribadi untuk suatu proses atau dibagi di antara beberapa proses (mekanisme memori bersama ).


Selain itu, ada beberapa daerah khusus dalam memori yang ditandai dengan nama semu [vdso] dan [vsyscall]. Mereka digunakan untuk mengoptimalkan beberapa panggilan sistem.


Kami tertarik pada wilayah tempat konten file perpustakaan ditampilkan. Jika kita membaca kartu memori dan menyaring entri di dalamnya dengan nama file yang ditampilkan, maka kita akan menemukan semua alamat yang ditempati oleh perpustakaan yang kita butuhkan. Format kartu memori secara khusus dibuat nyaman untuk pemrosesan program dan mudah dipahami menggunakan fungsi keluarga scanf ():


 static bool read_proc_line(const char *line, const char *library, struct memory_region *region) { unsigned long vaddr_low = 0; unsigned long vaddr_high = 0; char read = 0; char write = 0; char execute = 0; int path_offset = 0; /*    /proc/$pid/maps */ sscanf(line, "%lx-%lx %c%c%c%*c %*lx %*x:%*x %*d %n", &vaddr_low, &vaddr_high, &read, &write, &execute, &path_offset); /* ,       */ if (!strstr(line + path_offset, library)) return false; /*           */ if (region) { region->vaddr_low = vaddr_low; region->vaddr_high = vaddr_high; region->readable = (read == 'r'); region->writeable = (write == 'w'); region->executable = (execute == 'x'); region->content = NULL; } return true; } 


, libc-2.19.so, :


libc-2.19.so


2 - ? 51? ? ?


, , .


, , . , , , (, , ).


, ( 4 ). , .


, . β€” β€” . 2 β€” , ( x86_64 4 , 2 , 1 ). .



, :


  • libdl: dlopen() dlsym()
  • libpthread: pthread_create() pthread_detach()

, , . Linux ( address space layout randomization , ASLR). (- , ), β€” - .


, , , /proc/$pid/maps. , .


3. ELF-


, , , .


:


 $ nm -D /lib/x86_64-linux-gnu/libdl-2.19.so | grep dlopen 0000000000001090 T dlopen 

nm . .


- , nm , . , dlsym().



β€” ELF-, . procfs. UNIX way, /proc/$pid/mem , β€” ( /proc/$pid/maps).


Linux mmap(), ( , ). :


 static int map_region(pid_t pid, struct memory_region *region) { size_t length = region->vaddr_high - region->vaddr_low; off_t offset = region->vaddr_low; char path[32] = {0}; snprintf(path, sizeof(path), "/proc/%d/mem", pid); /*     */ int fd = open(path, O_RDONLY); if (fd < 0) goto error; /*      */ void *buffer = malloc(length); if (!buffer) goto error_close_file; /*   */ if (read_region(fd, offset, buffer, length) < 0) goto error_free_buffer; region->content = buffer; close(fd); return 0; error_free_buffer: free(buffer); error_close_file: close(fd); error: return -1; } static int read_region(int fd, off_t offset, void *buffer, size_t length) { /*      */ if (lseek(fd, offset, SEEK_SET) < 0) return -1; size_t remaining = length; char *ptr = buffer; /* *     .   , *      ,  . */ while (remaining > 0) { ssize_t count = read(fd, ptr, remaining); if (count < 0) return -1; remaining -= count; ptr += count; } return 0; } 

ELF- . , -, , -, .


ELF


ELF β€” Linux. , , .


ELF . ELF . β€” , . , β€” . ELF-.


, libdl-2.19.so :


libdl-2.19.so


( readelf --headers .)


, , (29 9). β€” , , . ELF β€” , . Linux, , LOAD, ( ).


ELF- , . , .


, . «» . .bss, , ( ).


, ELF β€” , . ...


?


() . , dlsym(), . - .


ELF (. 2-10). , .dynamic , DYNAMIC . .dynamic , :


  • .dynsym β€” ;
  • .dynstr β€” ;
  • .hash β€” -, .

, , ELF:


DYNAMIC


ELF, (1), (2), (3), (4) , .


ELF β†’


() ELF <elf.h>, , , . , ELF β€” . 32- 64- , , , . x86_64, ELF .


ELF- ( Elf64_Ehdr ). ( program headers ), e_phoff e_phnum :


Header ELF


β€” , , ELF- β€” , , , , .


e_phoff, , . e_phnum e_phentsize .


( ), ELF β€” 64 .


β†’ DYNAMIC


. β€” Elf64_Phdr ( 64- ELF-), . PT_DYNAMIC p_type :


Tabel segmen ELF


:


  • p_vaddr β€” , ;
  • p_memsz β€” .

.dynamic 0x2D88 ( ). DYNAMIC β€” 0x202D88. 0x210 (8448) . .


DYNAMIC β†’ .dynsym, .dynstr, .hash


.dynamic, DYNAMIC, . Elf64_Dyn , :


Tag bagian DINAMIS


8 d_val d_ptr , 8- d_tag , , . :


  • DT_HASH (4) β€” .hash ( d_ptr)
  • DT_STRTAB (5) β€” .dynstr ( d_ptr)
  • DT_SYMTAB (6) β€” .dynsym ( d_ptr)
  • DT_STRSZ (10) β€” .dynstr ( d_val)
  • DT_NULL (0) β€”

. .dynamic : , , , .


, DYNAMIC , . , , - , .


.dynamic , . -, .dynstr , ? .



. , .dynsym , . ( «» .symtab, , , . .)



Elf64_Sym , ELF β€” , , , . dlopen :


Tabel karakter ELF


:


  • st_name β€” ,
  • st_info β€” ( )
  • st_value β€”

( , nm , dlopen() .text, 0x1090 .)


, .



β€” - , . ( ). .dynstr , libdl-2.19.so :


Tabel baris ELF


, ( Β«dlopenΒ», 0xA5) , . .


-


.hash - , . - β€” β€” ELF-, . , .dynsym, , . ( ) - .


- <elf.h>, (. 2-19). - , :


tabel hash ELF


dimana


  • nbuckets β€” buckets
  • nchains β€” chains ( )
  • buckets β€”
  • chains β€”

- :


  1. h .
  2. i buckets[h % nbuckets] , .
  3. ( ) , .
  4. β€” chains[i % nchains] .
  5. 3β€”4 , .

-, ELF:


 static uint32_t elf_hash(const char *name) { uint32_t h = 0; uint32_t g; while (*name) { h = (h << 4) + *name++; g = h & 0xF0000000; if (g) h ^= g >> 24; h &= ~g; } return h; } 

, "dlopen" - 112420542 :


mencari karakter di perpustakaan


libdl β€” , 39 , . - .



, :


  • dlopen() dlsym() libdl
  • pthread_create() pthread_detach() libpthread

, .


. . , .


ELF- . , ( ). , . , , . .


4. -


, , - , : , . - .


-


, -:


 void shellcode(void) { void *payload = dlopen("/path/to/payload.so", RTLD_LAZY); void (*entry)(void) = dlsym(payload, "entry_point"); pthread_t thread; pthread_create(&thread, NULL, entry, NULL); pthread_detach(thread); } 

?


, β€” . , , , - β€” - ! .


β€” - . , , : .


 /* *      .rodata:   * .         , *        . */ .section .rodata /* *   .       . *      -:    ,  *  ,       . */ .global shellcode_start .global shellcode_address_dlopen .global shellcode_address_dlsym .global shellcode_address_pthread_create .global shellcode_address_pthread_detach .global shellcode_address_payload .global shellcode_address_entry .global shellcode_end /* *   dlopen().     #include <dlfcn.h>, *       . */ .set RTLD_LAZY, 1 .align 8 shellcode_start: /* * void *payload = dlopen(shellcode_address_payload, RTLD_LAZY); * *        x86_64: * * -     %rdi, %rsi, %rdx, %rcx * -     %rax * -      * *         . * *       %rax,    *     . */ lea shellcode_address_payload(%rip),%rdi mov $RTLD_LAZY,%rsi mov shellcode_address_dlopen(%rip),%rax callq *%rax /* * void (*entry)(void) = dlsym(payload, shellcode_address_entry); */ mov %rax,%rdi lea shellcode_address_entry(%rip),%rsi mov shellcode_address_dlsym(%rip),%rax callq *%rax /* * pthread_t thread; * pthread_create(&thread, NULL, entry, NULL); * *            * ,     pthread_create(). */ sub $8,%rsp mov %rsp,%rdi xor %rsi,%rsi mov %rax,%rdx xor %rcx,%rcx mov shellcode_address_pthread_create(%rip),%rax callq *%rax /* * pthread_detach(thread); * *    ,   ,  *     . */ mov (%rsp),%rdi add $8,%rsp mov shellcode_address_pthread_detach(%rip),%rax callq *%rax /* *   - β€”    ,     *      ret.    *     ,  *      . */ int $3 /* *       ,   *   ,    - *     .   β€œ  *  ” (global offset table, GOT),   *           . */ .align 8 shellcode_address_dlopen: .space 8 shellcode_address_dlsym: .space 8 shellcode_address_pthread_create: .space 8 shellcode_address_pthread_detach: .space 8 shellcode_address_payload: .space 256 shellcode_address_entry: .space 256 /* *  - . */ shellcode_end: .end 

, . :


 $ as -o shellcode.o shellcode.S 

, , , . : (procedure linkage table, PLT), .


- , (, ) . - .


-


- . , , , . ?


-


, . , . , . , .


(- ), : , , . , , JIT- , . ?



:


  • - ,
  • - ,

, . -, - , . -, . -, , - -, .


, . . x86_64 int $3 β€” 0xCC β€” . ptrace() PTRACE_POKETEXT β€” , 8 , . , , .


, , , : . - , .


?


, ! malloc()!


. , -, . . , mmap():


 void inject_shellcode(const void *shellcode_src, size_t shellcode_size) { void *shellcode_dst = mmap(NULL, shellcode_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANONYMOUS | MAP_PRIVATE, -1, 0); copy_shellcode(shellcode_dst, shellcode_src, shellcode_size); } 

, ptrace() , .



, ? , . Linux x86_64 :


  • %rax
  • β€” β€” %rsi, %rdi, %rdx, %r10, %r8, %r9
  • SYSCALL,
  • %rax

ptrace() PTRACE_GETREGS PTRACE_SETREGS. , . - SYSCALL.


: , %rip. , , SYSCALL.


SYSCALL


SYSCALL? , . - , - . β€” libc. , , , :


 unsigned long find_syscall_instruction(struct library *library) { for (size_t i = 0; i < library->region_count; i++) { struct memory_region *region = &library->regions[i]; if (!(region->readable && region->executable)) continue; const uint8_t *region_data = region->content; size_t region_size = region->vaddr_high - region->vaddr_low; if (region_size < 2) continue; /* * 0F 05 syscall */ for (size_t offset = 0; offset < region_size - 1; offset++) { if (region_data[offset + 0] == 0x0F && region_data[offset + 1] == 0x05) { return region->vaddr_low + offset; } } } return 0; } 

, /proc/$pid/maps . x86_64 , - . , 0x0F 0x05. , , ARM, 0xDF 0x00 ( SVC #0), .


PTRACE_{GET,SET}REGS


:


 int get_registers(pid_t pid, struct user_regs_struct *registers) { int err = 0; if (ptrace(PTRACE_GETREGS, pid, registers, registers) < 0) err = -errno; return err; } 

struct user_regs_struct , <sys/user.h>. . . , varargs :


 static int set_regs_for_syscall(struct user_regs_struct *registers, unsigned long syscall_insn_vaddr, long syscall_number, int args_count, va_list args) { registers->rip = syscall_insn_vaddr; registers->rax = syscall_number; for (int i = 0; i < args_count; i++) { switch (i) { case 0: registers->rdi = va_arg(args, long); break; case 1: registers->rsi = va_arg(args, long); break; case 2: registers->rdx = va_arg(args, long); break; case 3: registers->r10 = va_arg(args, long); break; case 4: registers->r8 = va_arg(args, long); break; case 5: registers->r9 = va_arg(args, long); break; default: return -E2BIG; } } return 0; } static long perform_syscall(pid_t pid, unsigned long syscall_insn_vaddr, long syscall_number, int args_count, ...) { struct user_regs_struct old_registers; struct user_regs_struct new_registers; /* *    ,   *      . */ get_registers(pid, &old_registers); /* *      ,   * ,     . */ new_registers = old_registers; va_list args; va_start(args, args_count); set_regs_for_syscall(&new_registers, syscall_insn_vaddr, syscall_number, args_count, args); va_end(args); set_registers(pid, &new_registers); /* *    ,    *   ,    * (  ),    . *     . */ wait_for_syscall_completion(pid); /* *       *    . *        . */ get_registers(pid, &new_registers); long result = new_registers.rax; set_registers(pid, &old_registers); return result; } 

PTRACE_SYSCALL


: , ?


PTRACE_SYSCALL. PTRACE_CONT, . , - : , .


PTRACE_SYSCALL SIGTRAP : ( ) ( ). , ptrace() , , .


, SIGTRAP:


 static int wait_for_syscall_enter_exit_stop(pid_t pid) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) return -1; if (wait_for_process_stop(pid, SIGTRAP) < 0) return -1; return 0; } void wait_for_syscall_completion(pid_t pid) { wait_for_syscall_enter_exit_stop(pid); wait_for_syscall_enter_exit_stop(pid); } 

β€” , β€” (wait_for_process_stop() ). . , .


PTRACE_O_TRACESYSGOOD


, PTRACE_SYSCALL : , , - . , SIGTRAP ( ).


SIGTRAP . PTRACE_O_TRACESYSGOOD, :


  • SIGTRAP β€” -
  • SIGTRAP | 0x80 β€”

  int ptrace_attach(pid_t pid) { if (ptrace(PTRACE_ATTACH, pid, 0, 0) < 0) return -1; if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; + /*     */ + unsigned long options = PTRACE_O_TRACESYSGOOD; + if (ptrace(PTRACE_SETOPTIONS, pid, 0, options) < 0) + return -1; return 0; } static int wait_for_syscall_enter_exit_stop(pid_t pid) { if (ptrace(PTRACE_SYSCALL, pid, 0, 0) < 0) return -1; - if (wait_for_process_stop(pid, SIGTRAP) < 0) + if (wait_for_process_stop(pid, SIGTRAP | 0x80) < 0) return -1; return 0; } 

-


- :


 void write_shellcode(void) { char shellcode_text[SHELLCODE_TEXT_SIZE]; size_t shellcode_size = shellcode_end - shellcode_start; /*   ,  ,  . . */ prepare_shellcode(shellcode_text, shellcode_size); /*   -   */ write_remote_memory(target, shellcode_text_vaddr, shellcode_text, shellcode_size); } 

- : dlopen(), .


 static inline void copy_shellcode(char *shellcode_text, const char *shellcode_addr, const void *data, size_t length) { ptrdiff_t offset = shellcode_addr - shellcode_start; memcpy(shellcode_text + offset, data, length); } static void prepare_shellcode(char *shellcode_text, size_t shellcode_size) { copy_shellcode(shellcode_text, shellcode_start, shellcode_start, shellcode_size); copy_shellcode(shellcode_text, shellcode_address_dlopen, &dlopen_vaddr, sizeof(dlopen_vaddr)); copy_shellcode(shellcode_text, shellcode_address_dlsym, &dlsym_vaddr, sizeof(dlsym_vaddr)); copy_shellcode(shellcode_text, shellcode_address_pthread_create, &pthread_create_vaddr, sizeof(pthread_create_vaddr)); copy_shellcode(shellcode_text, shellcode_address_pthread_detach, &pthread_detach_vaddr, sizeof(pthread_detach_vaddr)); copy_shellcode(shellcode_text, shellcode_address_payload, payload, sizeof(payload)); copy_shellcode(shellcode_text, shellcode_address_entry, entry, sizeof(entry)); } 

, , -:


 extern const char shellcode_start[]; extern const char shellcode_address_dlopen[]; extern const char shellcode_address_dlsym[]; extern const char shellcode_address_pthread_create[]; extern const char shellcode_address_pthread_detach[]; extern const char shellcode_address_payload[]; extern const char shellcode_address_entry[]; extern const char shellcode_end[]; 

, .


- . /proc/$pid/mem, :


 int write_remote_memory(pid_t pid, unsigned long vaddr, const void *data, size_t size) { char path[32] = {0}; snprintf(path, sizeof(path), "/proc/%d/mem", pid); /*       */ int fd = open(path, O_WRONLY); if (fd < 0) return -1; /*     */ if (lseek(fd, vaddr, SEEK_SET) < 0) { close(fd); return -1; } /*    */ int err = do_write_remote_memory(fd, data, size); close(fd); return err; } static int do_write_remote_memory(int fd, const void *data, size_t size) { size_t left = size; /* *    ,  ,     *   ,       *      . */ while (left > 0) { ssize_t wrote = write(fd, data, left); if (wrote < 0) return -1; data += wrote; left -= wrote; } return 0; } 


, - β€” Β« Β» . . - , .


5.


- . , : %rip -, PTRACE_SETREGS, PTRACE_CONT . .


, , . -? ?



, . , Β« Β» . , . :


  • (async-signal-safe)

β€” . dlopen() pthread_create() . - dlopen(), dlopen() ?


-, , , . , pthread_create() . , ( ). clone().


pthread_create()?

, - , ?

: clone().

, (libc) (pthread). clone() (thread control block, TCB) (thread-local storage, TLS), , . . pthread_create() , .

«», clone() libc pthread. , .


clone() :


  • ?
  • ?
  • -?


: -?


, - : , , , .


. , , . Bagaimana? : exit(). , .


. exit() -:


 +.set __NR_exit, 60 .set RTLD_LAZY, 1 @@ - /* - *  . - */ - int $3 + /* + * exit(0); + */ + xor %rdi,%rdi + mov $__NR_exit,%rax + syscall 

: exit() β€” exit() . exit() , exit() β€” . Linux exit_group().



. . , , PROT_EXEC:


 shellcode_stack_vaddr = remote_mmap(target, syscall_vaddr, 0, SHELLCODE_STACK_SIZE, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_STACK | MAP_GROWSDOWN, -1, 0); 

, Linux x86_64 β€” «» , . mmap() , clone() . , mmap() MAP_GROWSDOWN, , .


PTRACE_O_TRACECLONE


. , - . waitpid(), : , .


β€” PTRACE_O_TRACECLONE. . , . , , , . , PTRACE_ATTACH , .


-, :


 - unsigned long options = PTRACE_O_TRACESYSGOOD; + unsigned long options = PTRACE_O_TRACESYSGOOD | PTRACE_O_TRACECLONE; if (ptrace(PTRACE_SETOPTIONS, pid, 0, options) < 0) return -1; 

-, clone(), PTRACE_EVENT_CLONE, , PTRACE_SYSCALL. :


 -void wait_for_syscall_completion(pid_t pid) +void wait_for_syscall_completion(pid_t pid, long syscall) { wait_for_syscall_enter_exit_stop(pid); + + /*  clone()   PTRACE_EVENT_CLONE */ + if (syscall == __NR_clone) + wait_for_clone_event(pid); wait_for_syscall_enter_exit_stop(pid); } 

:


 static int wait_for_clone_event(pid_t pid) { if (ptrace(PTRACE_CONT, pid, 0, 0) < 0) return -1; int event = SIGTRAP | (PTRACE_EVENT_CLONE << 8); if (wait_for_process_stop(pid, event) < 0) return -1; return 0; } 

clone() PID , . :


 void clear_ptrace_options(pid_t pid) { ptrace(PTRACE_SETOPTIONS, pid, 0, 0); } 

, clone() ptrace(), PTRACE_O_TRACECLONE. , , - .



, - . clone() :


 static int spawn_shell_thread() { shell_tid = remote_clone(target, syscall_ret_vaddr, CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD | CLONE_VM, /*   **  */ shellcode_stack_vaddr + SHELLCODE_STACK_SIZE); if (!shell_tid) return -1; return 0; } 

clone() : , , , . , .


CLONE_FILES, CLONE_FS, CLONE_IO, CLONE_SIGHAND, CLONE_SYSVSEM, CLONE_VM β€” . , CLONE_FILES , ( fork()). β€” β€” , . . , CLONE_VM , , .


CLONE_THREAD : Linux β€” Β« Β», . , , getpid() , kill() β€” - , execve() β€” , .


, clone() fork(): , . clone() : , β€” . . ( , , .)


, pthread_create() , , . ?



fork() :


 pid_t child = fork(); if (child < 0) { /* fork() ,    */ } if (child == 0) { /*     execve() */ } /*     */ 

, . clone() . .


. , clone() , . syscall ret, , . .


SYSCALL + RET


, . , syscall ret:


 -if (region_size < 2) +if (region_size < 3) continue; /* * 0F 05 syscall + * C3 retq */ -for (size_t offset = 0; offset < region_size - 1; offset++) { +for (size_t offset = 0; offset < region_size - 2; offset++) { if (region_data[offset + 0] == 0x0F && - region_data[offset + 1] == 0x05) + region_data[offset + 1] == 0x05 && + region_data[offset + 2] == 0xC3) { return region->vaddr_low + offset; } } 

, .



. prepare_shellcode() , , :


  void write_shellcode(void) { char shellcode_text[SHELLCODE_TEXT_SIZE]; size_t shellcode_size = shellcode_end - shellcode_start; /*   ,  ,  . . */ prepare_shellcode(shellcode_text, shellcode_size); /*   -   */ write_remote_memory(target, shellcode_text_vaddr, shellcode_text, shellcode_size); + /*    «»   */ + unsigned long retaddr_vaddr = + shellcode_stack_vaddr + SHELLCODE_STACK_SIZE - 8; + write_remote_memory(target, retaddr_vaddr, + &shellcode_text_vaddr, sizeof(shellcode_text_vaddr)); } 

, , .


, , . System V ABI , ( %rsp) 16 . shellcode_stack_vaddr + SHELLCODE_STACK_SIZE : ( 4096 ), 1 . 8 , , retq, - . - :


 - sub $8,%rsp + sub $16,%rsp /*   */ mov %rsp,%rdi xor %rsi,%rsi mov %rax,%rdx xor %rcx,%rcx mov shellcode_address_pthread_create(%rip),%rax callq *%rax 

, %rsp 16 pthread_create(). SIGSEGV, β€” pthread_create() , .



, - , clone():


  static int spawn_shell_thread() { shell_tid = remote_clone(target, syscall_ret_vaddr, CLONE_FILES | CLONE_FS | CLONE_IO | CLONE_SIGHAND | CLONE_SYSVSEM | CLONE_THREAD | CLONE_VM, /*   **  */ - shellcode_stack_vaddr + SHELLCODE_STACK_SIZE); + shellcode_stack_vaddr + SHELLCODE_STACK_SIZE - 8); if (!shell_tid) return -1; return 0; } 

ptrace() SIGSTOP, :


 int ignore_thread_stop(pid_t pid) { return wait_for_process_stop(pid, SIGSTOP); } 

Itu saja. ptrace():


 void resume_thread(pid_t pid) { ptrace(PTRACE_CONT, pid, 0, 0); } 


, , , exit(). waitpid(). β€” CLONE_THREAD wait() ,β€” PTRACE_O_TRACECLONE, :


 int wait_for_process_exit(pid_t pid) { int status = 0; if (waitpid(pid, &status, 0) < 0) return -1; if (!WIFEXITED(status)) return -1; return WEXITSTATUS(status); } 

pthread , , pthread_join() pthread , . , β€” . , , .


Memori bebas


, - . , - , munmap():


 void remote_munmap(pid_t pid, unsigned long syscall_insn_vaddr, unsigned long addr, size_t len) { perform_syscall(pid, syscall_insn_vaddr, __NR_munmap, 2, (long) addr, (long) len); } static void unmap_shellcode() { remote_munmap(target, syscall_ret_vaddr, shellcode_text_vaddr, SHELLCODE_TEXT_SIZE); remote_munmap(target, syscall_ret_vaddr, shellcode_stack_vaddr, SHELLCODE_STACK_SIZE); } 

, , , β€” ptrace() . (, SIGSTOP), , ( ):


 int stop_thread(pid_t pid) { if (kill(pid, SIGSTOP) < 0) return -1; if (wait_for_process_stop(pid, SIGSTOP) < 0) return -1; return 0; } 


, , . PTRACE_DETACH:


 int ptrace_detach(pid_t pid) { if (ptrace(PTRACE_DETACH, pid, 0, 0) < 0) return -1; return 0; } 


, . , . , .


Kesimpulan


? . , , .


demonstrasi injeksi di Pusat Kontrol Gnome


Linux . GTK+ . , make:


 libpayload.so: payload.c $(CC) $(CFLAGS) $(shell pkg-config --cflags --libs gtk+-3.0) -shared -o $@ $< 

entry() GTK- β€” GTK UI , :


 #include <glib.h> #include <gtk/gtk.h> static gboolean actual_entry(gpointer _arg) { /*       : */ hook_gtk_entry_constructor(); /*   FALSE,       */ return FALSE; } void entry(void) { /*    -,   */ g_idle_add_full(G_PRIORITY_DEFAULT_IDLE, actual_entry, NULL, NULL); } 

, GTK , GtkEntry . "input-purpose" . «», , .


GTK glib β€” β€” GtkEntry . constructed(), . :


 static void (*old_gtk_entry_constructed)(GObject *object); static void new_gtk_entry_constructed(GObject *object) { GtkEntry *entry = GTK_ENTRY(object); /*    */ old_gtk_entry_constructed(object); /*    ,  ,   entry */ } static void hook_gtk_entry_constructor(void) { /*     GtkEntry */ GTypeClass *entry_type_class = g_type_class_peek(GTK_TYPE_ENTRY); GObjectClass *entry_object_class = G_OBJECT_CLASS(entry_type_class); /* *     "constructed"     . */ old_gtk_entry_constructed = entry_object_class->constructed; entry_object_class->constructed = new_gtk_entry_constructed; } 

GtkEntry :


  • , ,

, GtkEntry , , . , :


 static void new_gtk_entry_constructed(GObject *object) { GtkEntry *entry = GTK_ENTRY(object); old_gtk_entry_constructed(object); /*       */ g_signal_connect(entry, "notify::input-purpose", G_CALLBACK(input_purpose_changed), NULL); /*      */ g_signal_connect(entry, "icon-press", G_CALLBACK(icon_pressed), NULL); /*      */ g_signal_connect(entry, "icon-release", G_CALLBACK(icon_released), NULL); } 

. , . .


 static void input_purpose_changed(GtkEntry *entry) { GtkInputPurpose purpose = gtk_entry_get_input_purpose(entry); if (purpose == GTK_INPUT_PURPOSE_PASSWORD) { gtk_entry_set_icon_activatable(entry, GTK_ENTRY_ICON_PRIMARY, TRUE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-remove"); } else { gtk_entry_set_icon_activatable(entry, GTK_ENTRY_ICON_PRIMARY, FALSE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, NULL); } } 

: , , , - , :


 static void icon_pressed(GtkEntry *entry, GtkEntryIconPosition position) { if (position != GTK_ENTRY_ICON_PRIMARY) return; gtk_entry_set_visibility(entry, TRUE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-add"); } static void icon_released(GtkEntry *entry, GtkEntryIconPosition position) { if (position != GTK_ENTRY_ICON_PRIMARY) return; gtk_entry_set_visibility(entry, FALSE); gtk_entry_set_icon_from_icon_name(entry, GTK_ENTRY_ICON_PRIMARY, "list-remove"); } 

Itu saja.


GitHub (GPLv2).


, . gdb :


 $ gdb --pid $(pgrep target) \ --batch \ -ex 'compile file -raw shell-code.c' 

Source: https://habr.com/ru/post/id473740/


All Articles