EJTAG: atraksi untuk peretas-2


Dalam publikasi EJTAG saya sebelumnya : daya tarik bagi peretas dan Black Swift: menggunakan EJTAG dipertimbangkan , cara termudah untuk menggunakan EJTAG dipertimbangkan - memuat ke dalam RAM dan meluncurkan program pengguna. Namun, kemampuan EJTAG tidak terbatas pada hal ini. Publikasi ini menjelaskan cara mengatur debug kode sederhana menggunakan EJTAG, menggunakan alat freeware openocd dan GDB.

Saya diminta untuk menulis publikasi ini dengan sepucuk surat dari seorang pembaca yang meminta saya untuk tetap anonim dan yang meminta saya untuk membantu - perangkat berbasis AR9344 tidak bisa di-boot (tergantung pada tahap inisialisasi U-boot) - bagaimana saya bisa mengetahui apa masalahnya dengan EJTAG?
Karena saya tidak memiliki perangkat yang didasarkan pada AR9344 yang ada, tetapi papan Black Swift Pro berdasarkan chip AR9331 yang terkait ternyata menjadi narasi dengan memperhatikannya. Saya pikir perubahan yang harus dilakukan untuk AR9344 tidak signifikan.
Mari kita
beralih ke pernyataan masalah: Ada papan Black Swift Pro, yang kami hubungkan menggunakan openocd melalui EJTAG dan menempatkan prosesor ke mode berhenti.
DIPERLUKAN untuk menjalankan beberapa instruksi prosesor secara berurutan, berhenti setelah setiap instruksi dijalankan dan, jika perlu, memeriksa isi RAM, boot ROM, register pengontrol periferal atau register prosesor.
Untuk mengatasi masalah, saya melihat setidaknya dua pendekatan:
  • sederhana - hanya menggunakan openocd - fungsionalitas dasar untuk melakukan tindakan yang diperlukan sudah ada di openocd. Hanya perlu untuk bisa menggunakannya;
  • kompleks - dengan bantuan bundel openocd + GDB - pengguna akan mengontrol proses eksekusi instruksi prosesor melalui GDB, dan openocd akan mengonversi permintaan GDB menjadi perintah EJTAG.

Sekarang pertimbangkan kedua solusi secara lebih rinci.
, Black Swift: EJTAG.


1: openocd


Mereka yang membaca publikasi saya sebelumnya tentang EJTAG harus ingat bahwa openocd muncul di dalamnya sebagai pelaksana skrip (file konfigurasi) yang bodoh, yang tampaknya bekerja dalam mode batch dan tidak menyediakan interaksi pengguna. Namun, tidak. Bahkan, ketika perangkat lunak openocd sedang berjalan, dimungkinkan untuk "meminta" untuk mengeksekusi perintah menggunakan antarmuka baris perintah. Untuk mengakses antarmuka baris perintah, openocd meluncurkan server telnet.
Secara default, port TCP 4444 akan digunakan untuk server telnet. Jika perlu, nomor port TCP dapat diubah menggunakan opsi telnet_port(lihat contoh di bawah).
Mari kita coba lacak bootloader dari papan Black Swift dengan openocd.
Contoh File Konfigurasiblack-swift-trace.cfg untuk openocd, yang memaksa openocd untuk server telnet untuk menggunakan port 4455:
 sumber [temukan antarmuka / ftdi / tumpa.cfg]
 
 adapter_khz 6000
 
 sumber [temukan black-swift.cfg]
 
 telnet_port 4455
 
 init
 berhenti

Menjalankan openocd 0.9.0 sebagai root terlihat seperti ini:
 # openocd -f black-swift-trace.cfg
 Open On-Chip Debugger 0.9.0 (2015-05-28-17: 08)
 Berlisensi di bawah GNU GPL v2
 Untuk laporan bug, baca
         http://openocd.org/doc/doxygen/bugs.html
 tidak ada yang terpisah
 kecepatan adaptor: 6000 kHz
 Info: memilih otomatis transportasi sesi pertama yang tersedia "jtag". Untuk mengganti gunakan 'transport, pilih <transport>'.
 Kesalahan: tidak ada perangkat yang ditemukan
 Kesalahan: tidak dapat membuka perangkat ftdi dengan vid 0403, pid 8a98, deskripsi '*' dan serial '*'
 Info: kecepatan clock 6000 kHz
 Info: JTAG ketuk: ar9331.cpu ketuk / perangkat ditemukan: 0x00000001 (mfg: 0x000, bagian: 0x0000, ver: 0x0)
 negara target: dihentikan
 target dihentikan dalam mode MIPS32 karena permintaan debug, pc: 0xbfc00000
 negara target: dihentikan
 target dihentikan dalam mode MIPS32 karena langkah-tunggal, pc: 0xbfc00404

Sekarang kita dapat membuka jendela terminal lain dan terhubung ke server telnet openocd menggunakan program telnet:
 $ telnet localhost 4455
 Mencoba :: 1 ...
 Mencoba 127.0.0.1 ...
 Terhubung ke localhost.
 Karakter melarikan diri adalah '^]'.
 Buka debugger on-chip
 >

Daftar semua perintah openocd mudah diperoleh dengan perintah help.
Untuk pelaksanaan instruksi prosesor langkah demi langkah, perintah stepberikut berguna bagi kami :
 langkah [alamat]
       jalankan satu instruksi di alamat yang ditentukan oleh register
       penghitung perintah (PC). Jika parameter alamat ditentukan, maka
       Instruksi akan dieksekusi dimulai dengan alamat alamat.

Eksekusi instruksi prosesor secara bertahap di konsol terlihat seperti ini:
 > langkah 0xbfc00400
 negara target: dihentikan
 target dihentikan dalam mode MIPS32 karena langkah-tunggal, pc: 0xbfc00404
 > langkah
 negara target: dihentikan
 target dihentikan dalam mode MIPS32 karena langkah-tunggal, pc: 0xbfc00408
 > langkah
 negara target: dihentikan
 target dihentikan dalam mode MIPS32 karena langkah-tunggal, pc: 0xbfc0040c

Perintah openocd berikut mungkin juga berguna:
 reg [(register_number | register_name) [(nilai | 'force')]]
       baca atau tulis nilai register prosesor.
       Memanggil reg tanpa parameter menghasilkan output dari semua register.
       Jika parameter 'force' digunakan, terpaksa
       mengurangi register dari prosesor (bukannya mengeluarkan cache
       nilai).
 
 mwb ['phys'] address value [count]
          address ,     value.
          phys,   address β€”  ,
          β€” .
          count    address  
           count,    
         value.
 
 mwh ['phys'] address value [count]
         mwb,     16- .
 
 mww ['phys'] address value [count]
         mwb,     32- .
 
 mdb ['phys'] address [count]
               address.
          phys,   address β€”  ,
          β€” .
          count        
       array pada alamat alamat, jumlah byte.
 
 mdh ['phys'] address [count]
       perintahnya mirip dengan mdb, tetapi kata 16-bit dibaca sebagai ganti byte.
 
 alamat mdw ['phys'] [hitung]
       perintahnya mirip dengan mdb, tetapi kata 32-bit dibaca sebagai ganti byte.

Seperti yang Anda lihat, sayangnya, versi terbaru (pada saat penulisan ini) dari openocd 0.9.0 tidak dapat membongkar instruksi prosesor dengan arsitektur MIPS, meskipun ada pembongkaran untuk prosesor dengan arsitektur ARM .
Kurangnya disassembler membuat eksekusi instruksi prosesor langkah demi langkah secara langsung dengan openocd menjadi sangat tidak nyaman. Anda dapat meningkatkan tingkat kenyamanan jika Anda menggunakan GDB.

Solusi 2: gunakan bundel openocd + GDB


Dalam bundel openocd + GDB, peran didistribusikan sebagai berikut: pengguna berkomunikasi dengan GDB, yang menyediakan antarmuka yang nyaman untuk debugging, mengabstraksi dari mekanisme di mana pelaksanaan instruksi dikontrol, dan openocd melakukan tugas langsung mengendalikan prosesor sesuai dengan instruksi GDB.
Menggunakan GDB untuk mengontrol pelaksanaan instruksi pada prosesor MIPS melalui EJTAG memiliki beberapa keunggulan dibandingkan openocd:
  • seperti yang disebutkan di atas, disassembler untuk arsitektur MIPS dibangun ke GDB;
  • dimungkinkan untuk menggunakan informasi debug dari sumber; misalnya, jika Anda men-debug program-C Anda sendiri, maka GDB akan dapat menunjukkan baris kode-C mana yang saat ini dieksekusi dan merinci keadaan variabel program secara tepat, dan bukan sel-sel memori dengan alamat misterius;
  • openocd GDB GDB Remote Serial Protocol; qemu , , β€” GDB;
  • , GDB TAB.

Harus diingat bahwa GDB beroperasi dengan konsep tingkat tinggi, dan openocd dipaksa untuk bekerja dengan peralatan yang ada dan tidak selalu. GDB Wishlist dapat diimplementasikan secara efektif menggunakan EJTAG.
Sebagai contoh, pengguna menginstruksikan GDB untuk menetapkan breakpoint pada alamat yang ditentukan, instruksi ini berlaku untuk openocd, tetapi untuk prosesor MIPS, openocd memiliki setidaknya dua cara untuk menetapkan breakpoint:
  • sdbbp, , openocd sdbbp , sdbbp , . software breakpoint. , .
  • . . , hardware breakpoint, .

Meskipun dalam Protokol Serial Remote GDB terdapat perbedaan antara breakpoint perangkat keras dan breakpoint perangkat lunak (lihat paket z dan z0 dalam uraian protokol ), dan GDB menyediakan opsi yang sesuai untuk memilih jenis breakpoint, namun mungkin ada pembatasan penggunaan titik pada prosesor tertentu. gangguan dari satu jenis atau yang lain. Dengan demikian, openocd memiliki opsi gdb_breakpoint_overrideyang memungkinkan Anda untuk memaksa salah satu dari dua metode yang dijelaskan untuk mengatur breakpoints.
Untuk menghubungkan debugger GDB, openocd mengimplementasikan server GDB, yang secara default menggunakan port TCP 3333. Jika perlu, nomor port TCP dapat diubah menggunakan opsi gdb_port.
Untuk terhubung ke server GDB openocd yang mengontrol prosesor MIPS, kami membutuhkan versi khusus GDB dengan dukungan MIPS. Saya berasumsi bahwa pembaca menggunakan komputer berbasis x86 / amd64 yang menjalankan Debian Linux untuk menjalankan openocd dan GDB, menggunakan mips-linux-gnu-gdb dari paket Sourcery CodeBench. Tentang cara menginstal paket ini ditulis di sini , Anda hanya perlu memperkenalkan amandemen, bahwa pada saat menulis baris ini, yang terakhir adalah versi Sourcery CodeBench mips-2015.05-18-18, dirilis pada Mei 2015, Anda dapat mengunduh arsip di sini menggunakan tautan ini .
Mari kita coba menggunakan bundel openocd + GDB dalam praktiknya. Jalankan openocd:
 # openocd -f run-u-boot_mod-trace.cfg \
 > -c "gdb_breakpoint_override hard" -c "step 0xbfc00400"

Dua perintah terakhir untuk openocd akan memberikan yang berikut:
  • breakpoint perangkat keras akan digunakan, terlepas dari apa yang dibayangkan GDB di sana (alamat 0xbfc0xxxx sesuai dengan ROM, sehingga breakpoint perangkat lunak tidak akan berfungsi);
  • satu instruksi akan dieksekusi dari alamat 0xbfc00400, setelah itu prosesor akan berhenti lagi.

Buka jendela terminal lain dan luncurkan GDB:
 $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb
 GNU gdb (Sourcery CodeBench Lite 2015.05-18) 7.7.50.20140217-cvs
 Hak Cipta (C) 2014 Free Software Foundation, Inc.
 Lisensi GPLv3 +: GNU GPL versi 3 atau lebih tinggi <http://gnu.org/licenses/gpl.html>
 Ini adalah perangkat lunak gratis: Anda bebas untuk mengubah dan mendistribusikannya kembali.
 TIDAK ADA GARANSI, sejauh diizinkan oleh hukum. Ketik "tampilkan penyalinan"
 dan "tunjukkan garansi" untuk detailnya.
 GDB ini dikonfigurasi sebagai "--host = i686-pc-linux-gnu --target = mips-linux-gnu".
 Ketik "tampilkan konfigurasi" untuk detail konfigurasi.
 Untuk instruksi pelaporan bug, silakan lihat:
 <https://sourcery.mentor.com/GNUToolchain/>.
 Temukan manual GDB dan sumber dokumentasi lainnya secara online di:
 <http://www.gnu.org/software/gdb/documentation/>.
 Untuk bantuan, ketik "bantuan".
 Ketik "kata yang tepat" untuk mencari perintah yang terkait dengan "kata".
 (gdb)

Sekarang kami jelaskan GDB jenis prosesor yang akan kami gunakan, kami meminta Anda untuk membongkar instruksi yang dapat dieksekusi berikutnya, prosesor, dan akhirnya, terhubung ke server GDB openocd:
 (gdb) mengatur arsitektur mips: isa32r2
 Arsitektur target diasumsikan sebagai mips: isa32r2
 (gdb) atur endian besar
 Target diasumsikan sebagai big endian
 (gdb) aktifkan disassemble-next-line
 (gdb) target jarak jauh: 3333
 Remote debugging menggunakan: 3333
 0xbfc00404 di ?? ()
 => 0xbfc00404: 40 80 08 00 mtc0 nol, c0_random

Untuk menjalankan satu instruksi prosesor di GDB, sebuah perintah digunakan stepi. Mari kita coba mengikuti beberapa instruksi dari prosesor dan jangan bingung dengan peringatan GDB tentang kurangnya informasi debug (tidak dapat menemukan awal fungsi), tidak ada tempat untuk mendapatkan informasi ini dalam situasi ini.
 (gdb) stepi
 peringatan: GDB tidak dapat menemukan awal fungsi di 0xbfc00408.
 
     GDB tidak dapat menemukan awal fungsi di 0xbfc00408
 dan dengan demikian tidak dapat menentukan ukuran bingkai tumpukan fungsi itu.
 Ini berarti bahwa GDB mungkin tidak dapat mengakses frame tumpukan itu, atau
 bingkai di bawahnya.
     Masalah ini kemungkinan besar disebabkan oleh penghitung program yang tidak valid atau
 tumpukan penunjuk.
     Namun, jika Anda berpikir GDB harus mencari lebih jauh lagi
 dari 0xbfc00408 untuk kode yang terlihat seperti awal dari a
 fungsi, Anda dapat meningkatkan jangkauan pencarian menggunakan `set
 perintah heuristic-fence-post '.
 0xbfc00408 di ?? ()
 => 0xbfc00408: 40 80 10 00 mtc0 nol, c0_entrylo0
 (gdb) stepi
 peringatan: GDB tidak dapat menemukan awal fungsi di 0xbfc0040c.
 0xbfc0040c pada ?? ()
 => 0xbfc0040c: 40 80 18 00 mtc0 nol, c0_entrylo1
 (gdb) stepi
 peringatan: GDB tidak dapat menemukan awal fungsi di 0xbfc00410.
 0xbfc00410 di ?? ()
 => 0xbfc00410: 40 80 20 00 mtc0 nol, c0_context
 (gdb)

Sekarang baca kata 32-bit di 0xbfc00408:
 (gdb) p / x * 0xbfc00408
 $ 1 = 0x40801000

Untuk mencetak status register prosesor, gunakan perintah info registers:
 (gdb) register info
           nol pada v0 v1 a0 a1 a2 a3
  R0 00000000 37c688e2 22b15a00 28252198 0c12d319 4193c014 84e49102 06193640
             t0 t1 t2 t3 t4 t5 t6 t7
  R8 00000002 9f003bc0 92061301 1201c163 31d004a0 92944911 ac031248 b806001c
             s0 s1 s2 s3 s4 s5 s6 s7
  R16 8bc81985 402da011 c94d2454 88d5a554 81808e0d cc445151 4401a826 50020402
             t8 t9 k0 k1 gp sp s8 ra
  R24 01c06b30 01000000 10000004 fffffffe 9f003bc0 54854eab 329d626b bfc004b4
         status lo hi badvaddr menyebabkan pc
       00400004 00244309 b9ca872c ed6a1f00 60808350 bfc00410
           fcsr fir
       00000000 00000000

GDB adalah alat canggih dengan sejumlah besar perintah dan opsi; untuk berkenalan lebih dekat dengan GDB, saya merujuk pembaca ke dokumentasi GDB resmi .

Menginisialisasi Pengontrol RAM AR9331


Mari kita lihat bagaimana GDB dapat memecahkan masalah tertentu: dengan menelusuri eksekusi U-boot pada papan Black Swift, kami akan mengidentifikasi urutan entri dalam register pengontrol RAM, yang mengarah ke inisialisasi. Mendeteksi urutan seperti itu sangat berguna jika kita ingin menjalankan program di Black Swift menggunakan openocd bypassing U-boot. Juga, urutan inisialisasi ini berguna ketika membuat bootloader alternatif untuk Black Swift.
Jalankan openocd (seperti di bagian sebelumnya):
 # openocd -f run-u-boot_mod-trace.cfg \
 > -c "gdb_breakpoint_override hard" -c "step 0xbfc00400"

Untuk menjalankan GDB, buat skrip bs-u-boot-trace-gdb.conf:
 atur arsitektur mips: isa32r2
 atur endian besar
 aktifkan disassemble-next-line
 
 target jarak jauh: 3333
 
 nonaktifkan pagination
 
 atur file logging bs_gdb.log
 aktifkan logging
 
 sementara $ pc! = (void (*) ()) 0x9f002ab0
         stepi
         register info
 akhir
 
 melepaskan
 
 berhenti

Dibandingkan dengan contoh di bagian sebelumnya, skrip ini menyebabkan GDB untuk menduplikasi output ke file bs_gdb.log, dan menonaktifkan pagination (konfirmasi halaman balik oleh pengguna), dan kemudian mulai menjalankan instruksi prosesor secara siklikal dan menampilkan status register prosesor setelah setiap instruksi. Ketika register PC (register alamat dari instruksi berikut) mencapai nilai 0x9f002ab0, GDB terputus dari openocd dan berhenti bekerja. Dengan demikian, pada akhir GDB, file bs_gdb.logdengan jejak penuh dari eksekusi instruksi prosesor akan dibuat .
Peluncuran GDB adalah sebagai berikut:
 $ /opt/mips-2015.05/bin/mips-linux-gnu-gdb -x bs-u-boot-trace-gdb.conf

Catatan: skrip bs-u-boot-trace-gdb.confkemungkinan besar tidak akan berfungsi segera setelah daya diterapkan ke board, karena u-boot_mod juga akan mengatur ulang kesalahan anti-misteri AR9331, yang akan menyebabkan skrip berhenti dijalankan. Dalam hal ini, hentikan openocd dan GDB, kemudian jalankan openocd dan GDB lagi.

Sekarang semuanya tentang yang kecil - Anda harus memilih bs_gdb.logsemua instruksi tulis dari file sw(simpan kata, yaitu, tulis nilai 32-bit). Register dari pengontrol memori AR9331 berukuran 32-bit, sehingga instruksi lain dari keluarga toko dapat diabaikan dengan aman.
Karena disassembler hanya menghasilkan nama-nama argumen register instruksisw
 => 0xbfc004ec: ad f9 00 00 sw t9,0 (t7)

tetapi tidak nilainya, tidak cukup bs_gdb.loguntuk memilih semua baris yang berisi pernyataan sw dari file . Untuk menentukan nilai apa yang ditulis ke alamat mana menggunakan sw, file harus dikenai bs_gdb.logpemrosesan tambahan. Pemrosesan dapat dilakukan menggunakan skrip parse_gdb_output.pl:
 #!/usr/bin/perl -w
 
 my %r;
 
 foreach $i (qw(zero at v0 v1 a0 a1 a2 a3 t0 t1 t2 t3 t4 t5 t6 t7
        s0 s1 s2 s3 s4 s5 s6 s7 t8 t9 k0 k1 gp sp s8 ra)) {
    $r{$i} = "none";
 }
 
 sub parse_reg($)
 {
    $_ = $_[0];
    if (/^ R/) {
        my @fields = split m'\s+';
        my $f = 2;
        my @rgs;
 
        @rgs = qw(zero at v0 v1 a0 a1 a2 a3) if (/^ R0/);
        @rgs = qw(t0 t1 t2 t3 t4 t5 t6 t7) if (/^ R8/);
        @rgs = qw(s0 s1 s2 s3 s4 s5 s6 s7) if (/^ R1/);
        @rgs = qw(t8 t9 k0 k1 gp sp s8 ra) if (/^ R2/);
 
        foreach $i (@rgs) {
            $r{$i} = $fields[$f];
            $f = $f + 1;
        }
    }
 }
 
 while (<>) {
    if (/^=>([^s]*)\tsw\t([^,]*),(\d+)\(([^)]*)\)/) {
        my $rs = $2;
        my $offset = $3;
        my $rd = $4;
 
        parse_reg(<>);
        parse_reg(<>);
        parse_reg(<>);
        parse_reg(<>);
 
        print("$1    sw $rs={0x$r{$rs}}, $offset($rd={0x$r{$rd}})\n");
    }
 }


Peluncurannya parse_gdb_output.pladalah sebagai berikut:
$ grep "^ = \ | ^ R" bs_gdb.log | ./parse_gdb_output.pl

Berikut adalah cuplikan output parse_gdb_output.pl(tanda ' <<< PLL' dan ' <<< DDR' dimasukkan secara manual nanti):
 ...
  0x9f002700: ad cf 00 00 sw t7 = {0x00dbd860}, 0 (t6 = {0xb8116248})
  0x9f00271c: ad f9 00 00 sw t9 = {0x000fffff}, 0 (t7 = {0xb800009c})
  0x9f0027a0: ad f9 00 00 sw t9 = {0x00018004}, 0 (t7 = {0xb8050008}) <<< PLL
  0x9f0027dc:   ad f9 00 00    sw t9={0x00000352}, 0(t7={0xb8050004}) <<<
  0x9f002840:   ad f9 00 00    sw t9={0x40818000}, 0(t7={0xb8050000}) <<<
  0x9f002898:   ad f9 00 00    sw t9={0x001003e8}, 0(t7={0xb8050010}) <<<
  0x9f0028f4:   ad f9 00 00    sw t9={0x00818000}, 0(t7={0xb8050000}) <<<
  0x9f002970:   ad cf 00 00    sw t7={0x00800000}, 0(t6={0xb8116248})
 ...
  0x9f002994:   ad cf 00 00    sw t7={0x40800700}, 0(t6={0xb8116248})
  0x9f002a54:   ad f9 00 00    sw t9={0x00008000}, 0(t7={0xb8050008})
  0x9f00309c:   af 38 00 00    sw t8={0x7fbc8cd0}, 0(t9={0xb8000000}) <<< DDR
  0x9f0030b0:   af 38 00 00    sw t8={0x9dd0e6a8}, 0(t9={0xb8000004}) <<<
  0x9f0030dc:   af 38 00 00    sw t8={0x00000a59}, 0(t9={0xb800008c}) <<<
  0x9f0030ec: af 38 00 00 sw t8 = {0x00000008}, 0 (t9 = {0xb8000010}) <<<
 ...

Karena alamat register clock signal generator (PLL) dan alamat register pengontrol memori dari tipe DDR diketahui, mudah untuk mengetahui nomor apa yang harus ditulis ke alamat mana untuk menginisialisasi pengontrol RAM dengan benar.

Ringkasan


Seperti yang Anda lihat, debug melalui JTAG menggunakan openocd dan GDB sama sekali tidak sulit, dan metode kerja yang dijelaskan cocok tidak hanya untuk AR9331, tetapi, setelah beberapa adaptasi, dan bahkan untuk prosesor dengan arsitektur yang berbeda, yang untuk itu terdapat dukungan dalam openocd dan GDB.

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


All Articles