Zephyrnet Logosu

Gömülü aygıtlar için dinamik tersine mühendislik nasıl kullanılır | Teknik Hedef

Tarih:

IoT'nin yaygınlaşmasına güvenlik açıklarının da çoğalması eşlik ediyor. Kontrol edilmediğinde, kötü niyetli saldırganlar bu zayıflıkları kullanarak kuruluşların sistemlerine sızmaya çalışabilir.

Düzenli penetrasyon testiUzun zamandır en iyi güvenlik uygulaması olarak kabul edilen güvenlik ekiplerinin, yerleşik cihazlardaki güvenlik açıklarını ve zayıflıkları belirlemesine ve azaltmasına yardımcı olur. Ancak birçok kuruluş kalem testini şu şekilde sınırlandırıyor: ağları araştırmak ve altyapı — IoT cihazları genellikle göz ardı edilir.

Siber risk ve finansal hizmetler danışmanlığı yapan Kroll'un kıdemli başkan yardımcısı Jean-Georges Valle, güvenlik ekiplerinin gömülü cihaz pen testlerini hızlandırmak için şunları yazdı: Pratik Donanım Sızma Testi: IoT ve diğer cihazlardaki gömülü sistemler için saldırı ve savunma tekniklerini öğrenin.

Bölüm 10'dan alınan aşağıdaki alıntıda Valle, kalem test uzmanlarının gömülü cihazlarda yürütme sırasında kodun nasıl davrandığını görmek için dinamik tersine mühendisliği nasıl kullanabileceğini ayrıntılarıyla anlatıyor. Valle, kalem testçilerine kodun nasıl davrandığını gözlemlerken ortaya çıkabilecek zorlukları göstermek için dinamik tersine mühendislik örneği sunuyor.

Editörün notu: Aşağıdaki alıntı erken erişim sürümünden alınmıştır. Pratik Donanım Sızma Testi, İkinci Baskı ve değişebilir.

Dinamik tersine mühendislik kullanma — bir örnek

Önceki örneğin bize bazı zorluklar yaratacak bir versiyonunu hazırladım. Her iki durumda da gereken çaba miktarını karşılaştırabilmeniz için size bu zorlukların hem statik hem de dinamik olarak nasıl aşılacağını göstereceğim.

Dinamik ve statik yaklaşımları karşılaştırırken temel kural, %99 oranında dinamik yaklaşımların daha kolay olduğu ve mümkünse öncelik verilmesi gerektiğidir (JTAG/SWD veya diğer yaklaşımlara erişemeyebileceğinizi unutmayın). çip üzerinde hata ayıklama protokolleri).

Bu bölümde ayrıca istediğimiz yere nasıl kıracağımızı, GDB ile hafızayı nasıl inceleyeceğimizi ve tüm bu güzel şeyleri öğreneceğiz!

Hedef program burada, klonladığınız klasörde, ch12 klasöründe bulunur.

Öncelikle Ghidra’ya yükleyerek başlayalım ve yüzeysel olarak inceleyelim. Ghidra'nın yükleme penceresinde doğru mimariyi ve taban adresini ayarlamaya dikkat edin (nasıl yapacağınızı veya temel adres değerini hatırlamıyorsanız önceki bölüme bakın).

İlk Ghidra denetimi

İlk bakışta ana işlev, önceki bölümdeki ana işleve çok benziyor. Önceki bölümde olduğu gibi bir PASSWORD dizesini arayarak ana işlevin referansını bulabilir ve yapısını analiz etmeye çalışabiliriz.

Farklı işlevleri bulmak için önceki bölümde edindiğiniz beceriler üzerinde çalışmanıza izin vereceğim. Bu yürütülebilir dosyada aşağıdakileri tekrar bulacaksınız:

  • Büyük bir süre (doğru) Ana olay döngüsü görevi gören ve girilen şifreye göre hareket ederken mavi hapın LED'ini yanıp sönen döngü
  • Saati başlatmak için bir işlev
  • GPIO'ları başlatmak için bir işlev
  • UART'ı başlatmak için bir işlev
  • Çipin benzersiz tanımlayıcısına bağlı bir değer, hemen hemen aynı şekilde tekrar hesaplanır (bu değeri çipiniz için hesaplayın ve bu değeri not edin)
  • Bir işlev parolayı doğrular (büyük bir işlemden hemen önce) if bu ya yazdırılmasını tetikler SEN KAZANDIN or YOK HAYIR)
  • Doğrulama işlevi bir sonuç döndürürse, işlev kazanan dizenin şifresini çözer. (uint16_t) 0 değeri.

Yapının benzerliği kasıtlıdır çünkü bu ilk defadır. Önceki bölümdeki adımların aynısını tekrarlasaydım, bu size öğrenecek yeni bir şey vermezdi, değil mi?

Şimdi sistemle dinamik etkileşim yoluyla bu parola doğrulamasını atlamanın birden fazla yöntemini inceleyelim. Odaklanmanızı ve bilgi edinmenizi sağlamak için en karmaşıktan en basitine doğru gideceğiz (eğer benim gibiyseniz, bir şeyi atlamanın kolay bir yolu varsa, neden zor yolu seçesiniz ki?).

Beklenen şifreyi tersine çevirme

Yapacağımız ilk şey, testleri geçen bir şifrenin nasıl oluşturulacağını anlamak için şifrenin nasıl doğrulandığını görmeye çalışmak.

Ghidra'nın çıktısı olan doğrulama fonksiyonu eşdeğeri C koduna bir göz atalım:

Screenshot of Ghidra output of decompiled code
Şekil 12.2 — Derlenmemiş doğrulama işlevi aslında düşündüğünüzü yapmıyor!

Hımm… bu doğrudan parametrelerle hiçbir şey yapmıyor. Bu, bir içeriğin kopyalanmasıdır. 0x47 (71) RAM'e uzun statik bayt dizisi (ve bunu DEĞİLDİR) ve sonra onu bir işlev olarak çağırır.

Bu tuhaf.

Yoksa öyle mi?

Bu, kodu kamufle etmek için çok yaygın bir tekniktir (elbette bunun çok basit bir versiyonu). İşlem kodunun net bir sürümü .bin dosyasında mevcut değilse (ve dolayısıyla MCU'nun flaşında da yoksa), Ghidra gibi bir tersine mühendislik aracı bunun kod olduğunu tespit edemez! Burada iki olası yaklaşımımız var:

  • Ya tamponun içeriğini .bin dosyasından manuel olarak çıkarırız, şifresini çözeriz (burada, şifre bayt bayt DEĞİLDİR, bilerek önemsizdir) ve bunun Ghidra tarafından derlenmesini sağlarız.
  • Veya çipe JTAG erişimimiz olduğundan, hafızadaki doğru adrese bir kesme noktası koyabilir ve MCU'nun işin zor kısmını bizim için yapmasına izin verebiliriz.

İlk çözümü size alıştırma olarak uygulayacağım. Bu kadar basit bir görev için aşağı yukarı 10 satır Python veya C kodu gerekir! Hacker mı olmak istiyorsun? Kesmek!

Ben? Ben tembel bir adamım. Eğer bir bilgisayar işime yarayacaksa, eh… Öyle olsun! İkinci çözüme geçeceğim.

Öncelikle, şifreleri girip nasıl tepki vereceğini görebilmemiz için terminalde bir ekran oturumu başlatalım:

ekran /dev/ttyUSB0 115200

Bölümün başında yaptığımız gibi OpenOCD ve GDB'yi ikinci bir terminalde çalıştıralım ve biraz araştıralım:

openocd -f ./ftdi2232h.cfg.tcl -f ./clone_CSK.cfg & gdb-multiarch -x ./gdbinit
#openocd başlatılıyor
[...]
hedef hata ayıklama isteği nedeniyle durduruldu, mevcut mod: Konu xPSR: 0x01000000 pc: 0x080013b8 msp: 0x20005000
[...]

Ve... ve kahretsin! Bana kontrolü geri vermiyor! Eğer bu senin başına gelirse sorun değil - biraz Ctrl + C kontrolü hemen size geri verecektir:

^C
Program SIGINT sinyalini aldı, Kesinti.
0x080003aa içinde ?? ()
(gdb)

Sonra bizim Ctrl + C (^c), gdb bize yürütmenin adreste durdurulduğunu söyler 0x080003aa bilinmeyen bir fonksiyonda (??).

Özel durumunuza bağlı olarak başka bir adreste ara verebilirsiniz.

Panik yapmayın; düşünme şapkanızı takın ve havlunuzu (her zaman) yanınıza alın.

Bu sorun değil. LED'i yanıp sönen bekleme döngüsünde olduğundan ve seri arayüzde bir parolanın alınmasını beklediğinden, büyük olasılıkla bu adresin çok yakınında kırılmanız muhtemeldir.

Öncelikle kayıtlarımıza bir göz atalım:

(gdb) ir
r0 0x0 0
r1 0x8001a1d 134224413
r2 0x5b8d7f 5999999
r3 0x335d7 210391
r4 0x20004f88 536891272
r5 0x8001a74 134224500
r6 0x0 0
r7 0x20004f88 536891272
r8 0x0 0
r9 0x0 0
r10 0x0 0
r11 0x0 0
r12 0xf 15
sp 0x20004f88 0x20004f88
lr 0x80003bf 134218687
bilgisayar 0x80003aa 0x80003aa
xPSR 0x81000000 -2130706432
msp 0x20004f88 0x20004f88
[...]

Bilgisayarın gerçekten olması gerektiği yerde olduğunu, her şeyin yolunda ve şık göründüğünü görüyoruz. Şimdi bir şifre girmeyi deneyelim.

Ve… seri arayüz penceresinde hiçbir şey çalışmıyor! Düşünüyorum da… GDB aslında kodun yürütülmesini engelliyor; seri arayüz girişlerinize tepki vermeyecektir. Bu normal.

Öyleyse devam etmesine izin verelim (devam etmek or c içinde gdb pencere) ve serinin şimdi çalışıp çalışmadığını görün. Evet öyle. Tekrar kıralım ve şifre doğrulama fonksiyonunun adresine bir kesme noktası koyalım, olur mu?

Ghidra'da fonksiyonun ilk komutunun adresinin şu şekilde olduğunu görebiliriz: 0x080002b0:

Screenshot of finding a function address in Ghidra
Şekil 12.3 — Ghidra'da bir işlev adresi bulma

Oraya bir kesme noktası koyalım, hadi gdb yürütmeye devam edin ve sahte bir şifre girin:

(gdb) b * 0x080002b0
#1
1x0b80002'da kesme noktası 0
#2
(gdb)c
#3
Devam ediyor.
Not: salt okunur adresler için donanım kesme noktalarının otomatik olarak kullanılması.
#4
[seri konsola 'aaa' yazıp girin]
Kesme noktası 1, 0x080002b0 ?? ()
#5
(gdb)

Bunu inceleyelim:

  • b * 0x080002b0 sorar gdb adreste saklanan talimata bir kesme noktası koymak için 0x080002b0. İşaretçilerinizi kontrol edin.
  • gdb bana diyor ki, Tamam, oraya bir kesme noktası koydum.
  • İdama devam et lütfen canım gdb ve bunu yapmaktan mutluluk duyduğunu söylüyor.
  • AMA bana adrese yazılamadığını bildiriyor 0x080002b0 (flash'tadır ve flash bu şekilde yazılamaz; kilidinin açılması ve parça parça yazılması gerekir). Çok fazla ileri geri hareket etmekten kaçınmak için ARM yongaları, bilgisayar kolayca yazılamayan belirli adreslere çarptığında bozulmasına izin veren bazı dahili hata ayıklama sistemleriyle birlikte gelir.
  • Bam! Kırılma noktası vuruldu! Sahte bir şifre girdikten sonra yürütme durduruluyor.

Tamam, şimdi bununla ne yapabiliriz?

İlk olarak, doğrulama fonksiyonunun kodunu hatırlarsanız, argümanları doğrudan kodu çözülen koda iletildi. Gelin bunların ne olabileceğine bir bakalım (işlevler için çağrı kuralını hatırlayın: argümanlar r0-3):

(gdb) p/x $r0
2$ = 0x20000028
(gdb) p/x $r1
3$ = 0x2169

İlk argüman RAM'deki bir şeydir, ikincisi ise bir tür değerdir. (Bu not ettiğiniz çipiniz için dönüştürülmüş UUID değeridir, değil mi?)

Peki bu ilk adreste ne saklanıyor? Hadi inceleyelim:

(gdb) x/x 0x20000028
0x20000028: 0x00616161
(gdb) x/sn 0x20000028
0x20000028: "aaa"

Ah! Ah! Ah! (Orada ne yaptığımı gördün mü?) Bu bizim şifremiz. Lütfen x komutu için format değiştiricinin kullanımına dikkat edin.

Yani, bu bekleniyor.

Şimdi şifresi çözülmüş koda bakalım.

Ghidra bize kod çözme döngülerini takip eden talimatın şu adreste olduğunu söylüyor: 0x080002f0. Orada keselim:

(gdb) b * 0x080002f0
2x0f80002'da kesme noktası 0
(gdb)c
Devam ediyor.
Kesme noktası 2, 0x080002f0 ?? ()
(gdb)c
(gdb) x/4i $pc
=> 0x80002f0: movs r0, #0
   0x80002f2: blx r3
   0x80002f4: mov r3, r0
   0x80002f6: mov r0, r3

Yani çözülen kodun adresi r3'tedir. Tamponun olduğunu gördük 0x47 (71) uzun. Başparmak modundayız (yani 2 boyutlu talimatlar). Bu 47/2 olmalıdır: yaklaşık 35 talimat. Adresin son biti mod içindir; bundan kurtulabiliriz:

(gdb) x/35i ($r3 & (~1))
   0x20000128: {r4, r5, r6, r7, lr}'ye basın
   0x2000012a: eors r4, r4
   0x2000012c: eors r3, r3
   0x2000012e: eors r5, r5
   0x20000130: ldrb r5, [r1, r4]
   0x20000132: mov r8, r5
   0x20000134: mov r6, r8
   0x20000136: lsrs r6, r6, #4
   0x20000138: lsls r5, r5, #4
   0x2000013a: veya r5, r6
   0x2000013c: movs r6, #255 ; 0xff
   0x2000013e: ves r5, r6
   0x20000140: movs r6, #15
   0x20000142: mov r8, r4
   0x20000144: mov r7, r8
   0x20000146: ve r7, r6
   0x20000148: r6, pc, #16'yı ekleyin; (adr r6, 0x2000015c) #1
   0x2000014a: ldrb r6, [r6, r7]
   0x2000014c: eors r5, r6
   0x2000014e: r0, r0, r5'i ekler
   0x20000150: r4'ü ekler, #1
   0x20000152: ldrb r5, [r1, r4]
   0x20000154: cmp r5, r3
   0x20000156: bgt.n 0x20000132
   0x20000158: eors r0, r2
   0x2000015a: açılır {r4, r5, r6, r7, pc}
   0x2000015c: str r5, [r4, #36] ; 0x24
   0x2000015e: ldrb r4, [r6, #5]
   0x20000160: ldr r7, [r6, #32]
   0x20000162: alt r2, #55 ; 0x37
   0x20000164: ldr r4, [r2, r5]
   0x20000166: ldr r5, [r1, #100] ; 0x64
   0x20000168: r3, r12'yi ekle
   0x2000016a: r4, #68'i ekler; 0x44
   0x2000016c: vqadd.u8 q0, q8,

Bu daha doğru gibi! Normal bir işlev başlangıcı (işlev içi kayıtların yığına kaydedilmesi), bazı işlemler ve bir işlev dönüşü görüyoruz. Ancak GDB bizi yasa dışı talimat parametreleri konusunda uyarıyor (0x2000016c).

Listelemeye baktığımızda GDB'nin PC'ye bağlı bir veri parçasının kullanımını belirttiğini görüyoruz:

#1 : yorumlandı : adr r6, 0x2000015c)

Bu genellikle verileri bir montaj programında depolamak için kullanılır. adr montajcıya lütfen ofseti koddaki bir etikete (adlandırılmış konum) ekleyin diyen sözde bir talimattır.

Orada nelerin saklandığına bakalım:

(gdb) x/4wx 0x2000015c
0x2000015c: 0x79746265 0x3a376a37 0x6e4d5954 0x34444463
(gdb) x/sn 0x2000015c
0x2000015c: "ebty7j7:TYMncDD4"

Bu aslında süreçte bir şekilde kullanılan bir dizedir.

Bir yürütme akışının nasıl takip edileceğine örnek olarak ilk talimatların üzerinden geçelim. İlk önce kuracağız gdb bu yüzden bize her adımdaki ilginç kayıtları ve içeriği gösterir:

(gdb) disp/x $r0
1: /x $r0 = 0x20000028
(gdb) disp/x $r1
2: /x $r1 = 0x20000028
(gdb) disp/x $r2
3: /x $r2 = 0x2169
(gdb) disp/x $r3
4: /x $r3 = 0x20000129
(gdb) disp/x $r4
5: /x $r4 = 0x20004f88
(gdb) disp/x $r5
6: /x $r5 = 0x8001a74
(gdb) disp/x $r6
7: /x $r6 = 0x0
(gdb) disp/x $r7
8: /x $r7 = 0x20004f70
(gdb) disp/x $r8
9: /x $r8 = 0x2
(gdb) disp/i $pc
10: x/i $pc
=> 0x80002f0: movs r0, #0
=> 0x80002f2: blx r3

Artık kullanıma hazırız üvey (adım talimatı) neler olduğunu görmek için:

0x2000012b: eors r4, r4
0x2000012d: eors r3, r3
0x2000012f: eors r5, r5

Bu sıfırlar r4, r3, ve r5 (x^x = 0):

0x20000130: ldrb r5, [r1, r4]
0x20000132: mov r8, r5
0x20000134: mov r6, r8

Bu, parola dizesinin ilk karakterini yükler. r5 (r1 adres ve r4 bu noktada sıfırlanır) ve kopyalar r8 ve r6:

0x20000136: lsrs r6, r6, #4
0x20000138: lsls r5, r5, #4
0x2000013a: veya r5, r6
0x2000013c: movs r6, #255 ; 0xff
0x2000013e: ves r5, r6

Bu değişim r6 4 bit sağa, r5 4 bit sola ve ORed değerlerini içine koyar r4. Daha sonra ORed sonucunu şununla maskeler: 0xff, temel olarak şifre karakterinin 4 alt ve 4 yüksek bitinin değiştirilmesi ve fazla bitlerin temizlenmesi!

0x20000140: movs r6, #15
0x20000142: mov r8, r4
0x20000144: mov r7, r8
0x20000146: ve r7, r6

Bu 15 inç hareket eder r6, kopyalar r4 in r8 ve r7ve maskeler r7 15 ile. Peki neden? Bu noktada, r4 0! Bu daha sonra kullanılabilir - bunu gördüğümüzden beri r4 şifre karakterinin yüklenmesinde ofset olarak kullanıldı, r4 muhtemelen bir sayaçtır! Durum böyleyse, bu maskeleme bir tür modulo olarak kullanılabilir… (iki -1'in kuvvetleri modulo için maskeleme kullanmak çok yaygındır):

0x20000148: r6, pc, #16'yı ekleyin; (adr r6, 0x2000015c)
0x2000014a: ldrb r6, [r6, r7]

Bu, dizenin içinde gizlenen ilk karakteri yükler. r6 ve kullanır r7 ve bir ofset! r4 burada kesinlikle bir sayaç var ve r7 bunun modülolanmış versiyonu. Bu, buna yaklaşmanın çok tipik bir programlama yoludur:

0x2000014c: eors r5, r6
0x2000014e: r0, r0, r5'i ekler
0x20000150: r4'ü ekler, #1

Bu, bit değiştirilen şifre karakterinin değerinin garip dizenin mevcut sıralarıyla XORlanmasıdır ve bunu şuna ekler: r0 ve arttırılması r4 tezgah:

0x20000152: ldrb r5, [r1, r4]
0x20000154: cmp r5, r3
0x20000156: bgt.n 0x20000132

Bu, yeni dengelemeyle yeni bir şifre karakteri yükler r5. r3 0 olduğundan cmp kontrol eder r5-r3 ve bekle … bgt.n? Bu nedir? Şüphe duyduğunuzda ne yapmanız gerektiğini hatırlıyor musunuz? Gidip buradaki belgeleri okuyun: https://community.arm.com/developer/ip-products/processors/b/processors-ip-blog/posts/condition-codes-1-condition-flags-and-codes.

Yani eğer atlarsa r5 > r3. Ve r3 is 0, Bu yüzden? Bu bir testtir 0 sonlandırılmış dize!

Bu ana doğrulama mantığı döngüsüdür!

Bu yapıldıktan sonra şunu yapar:

0x20000158: eors r0, r2
0x2000015a: açılır {r4, r5, r6, r7, pc}

Hesapladığı değere göre bu toplamı UUID ile XOR'lar, arayan kayıt değerlerini geri yükler ve bu değeri döndürür. Daha sonra C kodu, kazanan dizeyi görüntülemek için bu değerin null olup olmadığını kontrol eder. Daha sonra, XOR'un boş olması için toplamımız UUID'ye bağlı değere eşit olacak şekilde ayarlamamız yeterli!

Bütün mantığımız var!

spot_img

En Son İstihbarat

spot_img