ProHoster > Blog > Administrácia > Reverzovanie a hackovanie Aigo samošifrovacieho externého HDD disku. Časť 2: Prevzatie skládky z Cypress PSoC
Reverzovanie a hackovanie Aigo samošifrovacieho externého HDD disku. Časť 2: Prevzatie skládky z Cypress PSoC
Toto je druhá a posledná časť článku o hackovaní externých samošifrovacích jednotiek. Dovoľte mi pripomenúť, že kolega mi nedávno priniesol pevný disk Patriot (Aigo) SK8671 a rozhodol som sa ho obrátiť a teraz sa podelím o to, čo z neho vzišlo. Pred ďalším čítaním si určite prečítajte prvá časť článok.
4. Začneme vytvárať výpis z interného flash disku PSoC
Takže všetko naznačuje (ako sme zistili v [prvej časti]()), že PIN kód je uložený v hĺbkach blesku PSoC. Preto musíme tieto hĺbky blesku prečítať. Predná časť potrebných prác:
prevziať kontrolu nad „komunikáciou“ s mikrokontrolérom;
nájsť spôsob, ako skontrolovať, či je táto „komunikácia“ chránená pred čítaním zvonku;
nájsť spôsob, ako obísť ochranu.
Existujú dve miesta, kde má zmysel hľadať platný PIN kód:
interná flash pamäť;
SRAM, kde je možné uložiť kód PIN na porovnanie s kódom PIN zadaným používateľom.
Pri pohľade do budúcnosti si všimnem, že sa mi po zvrátení nezdokumentovaných možností protokolu ISSP stále podarilo urobiť výpis z interného flash disku PSoC - obísť jeho bezpečnostný systém pomocou hardvérového útoku nazývaného „sledovanie studeného zavádzania“. To mi umožnilo priamo vypísať skutočný PIN kód.
$ ./psoc.py
syncing: KO OK
[...]
PIN: 1 2 3 4 5 6 7 8 9
„Komunikácia“ s mikrokontrolérom môže znamenať rôzne veci: od „dodávateľa k predajcovi“ až po interakciu pomocou sériového protokolu (napríklad ICSP pre PIC spoločnosti Microchip).
Cypress má na to vlastný proprietárny protokol nazývaný ISSP (in-system serial programming protocol), ktorý je čiastočne opísaný v technická špecifikácia. Patent US7185162 poskytuje aj nejaké informácie. Existuje aj ekvivalent OpenSource s názvom HSSP (použijeme ho o niečo neskôr). ISSP funguje nasledovne:
reštartujte PSoC;
vypíšte magické číslo na sériový dátový kolík tohto PSoC; pre vstup do externého programovacieho režimu;
odosielať príkazy, čo sú dlhé bitové reťazce nazývané "vektory".
Dokumentácia ISSP definuje tieto vektory len pre malú hŕstku príkazov:
Inicializovať-1
Inicializovať-2
Inicializácia-3 (možnosti 3V a 5V)
ID-SETUP
PREČÍTAJ-ID-SLOVO
SET-BLOCK-NUM: 10011111010dddddddd111, kde dddddddd=blok #
HROMADNÉ VYMAZANIE
PROGRAMOVÝ BLOK
VERIFY-SETUP
READ-BYTE: 10110aaaaaaZDDDDDDDDZ1, kde DDDDDDDD = výstup dát, aaaaaa = adresa (6 bitov)
WRITE-BYTE: 10010aaaaaaddddddd111, kde dddddddd = dáta v, aaaaaa = adresa (6 bitov)
SECURE
KONTROLNÝ SÚČET-NASTAVENIE
READ-CHECKSUM: 10111111001ZDDDDDDDDZ110111111000ZDDDDDDDDZ1, kde DDDDDDDDDDDDDDDDDD = výstup dát: kontrolný súčet zariadenia
Všetky vektory majú rovnakú dĺžku: 22 bitov. Dokumentácia HSSP obsahuje ďalšie informácie o ISSP: „Vektor ISSP nie je nič iné ako bitová sekvencia, ktorá predstavuje súbor inštrukcií.
5.2. Demystifikačné vektory
Poďme zistiť, čo sa tu deje. Spočiatku som predpokladal, že tie isté vektory sú nespracované verzie inštrukcií M8C, ale po kontrole tejto hypotézy som zistil, že operačné kódy operácií sa nezhodujú.
Potom som vygooglil vektor vyššie a narazil som toto štúdia, kde autor, hoci nejde do detailov, uvádza niekoľko užitočných rád: „Každá inštrukcia začína tromi bitmi, ktoré zodpovedajú jednej zo štyroch mnemotechnických pomôcok (čítať z RAM, zapisovať do RAM, čítať register, zapisovať register). Potom je tu 8 adresových bitov, nasleduje 8 dátových bitov (čítanie alebo zápis) a nakoniec tri stop bity.
Potom sa mi podarilo získať niekoľko veľmi užitočných informácií zo sekcie Supervisory ROM (SROM). technický manuál. SROM je pevne zakódovaná ROM v PSoC, ktorá poskytuje pomocné funkcie (podobným spôsobom ako Syscall) pre programový kód bežiaci v užívateľskom priestore:
00h:SWBootReset
01h: Blok čítania
02h: WriteBlock
03h: EraseBlock
06h: Čítanie tabuľky
07h: Kontrolný súčet
08h: Kalibrácia0
09h: Kalibrácia1
Porovnaním názvov vektorov s funkciami SROM môžeme mapovať rôzne operácie podporované týmto protokolom na očakávané parametre SROM. Vďaka tomu môžeme dekódovať prvé tri bity ISSP vektorov:
100 => „wrem“
101 => „rdmem“
110 => „wrreg“
111 => „rdreg“
Úplné pochopenie procesov na čipe však možno získať iba prostredníctvom priamej komunikácie s PSoC.
5.3. Komunikácia s PSoC
Keďže Dirk Petrautsky už prenesené Kód HSSP Cypress na Arduine som použil Arduino Uno na pripojenie ku konektoru ISSP dosky klávesnice.
Upozorňujeme, že v priebehu môjho výskumu som dosť zmenil Dirkov kód. Moju úpravu nájdete na GitHub: tu a zodpovedajúci skript Python na komunikáciu s Arduinom v mojom úložisku cypress_psoc_tools.
Takže pomocou Arduina som najprv použil iba „oficiálne“ vektory na „komunikáciu“. Pokúsil som sa prečítať internú ROM pomocou príkazu VERIFY. Podľa očakávania som to nedokázal. Pravdepodobne kvôli tomu, že vo vnútri flash disku sú aktivované bity ochrany proti čítania.
Potom som vytvoril niekoľko vlastných jednoduchých vektorov na zápis a čítanie pamäte/registrov. Upozorňujeme, že môžeme prečítať celú pamäť SROM, aj keď je jednotka flash chránená!
5.4. Identifikácia registrov na čipe
Po pohľade na „rozložené“ vektory som zistil, že zariadenie používa nezdokumentované registre (0xF8-0xFA) na špecifikáciu operačných kódov M8C, ktoré sa vykonávajú priamo, čím sa obchádza ochrana. To mi umožnilo spúšťať rôzne operačné kódy ako „ADD“, „MOV A, X“, „PUSH“ alebo „JMP“. Vďaka nim (pri pohľade na vedľajšie účinky, ktoré majú na registre) som mohol určiť, ktoré z nezdokumentovaných registrov boli vlastne bežné registre (A, X, SP a PC).
Výsledkom je, že „rozložený“ kód vygenerovaný nástrojom HSSP_disas.rb vyzerá takto (pre prehľadnosť som pridal komentáre):
V tejto fáze už môžem komunikovať s PSoC, ale stále nemám spoľahlivé informácie o bezpečnostných bitoch flash disku. Veľmi ma prekvapila skutočnosť, že Cypress neposkytuje užívateľovi zariadenia žiadne prostriedky na kontrolu, či je ochrana aktivovaná. Ponoril som sa hlbšie do Google, aby som konečne pochopil, že kód HSSP poskytnutý Cypressom bol aktualizovaný po tom, čo Dirk vydal svoju modifikáciu. A tak! Objavil sa tento nový vektor:
Pomocou tohto vektora (pozri read_security_data v psoc.py) dostaneme všetky bezpečnostné bity v SRAM pri 0x80, kde sú dva bity na chránený blok.
Výsledok je deprimujúci: všetko je chránené v režime „zakázať externé čítanie a zápis“. Preto nielenže nemôžeme nič čítať z flash disku, ale nemôžeme ani nič písať (napríklad nainštalovať tam dumper ROM). A jediný spôsob, ako vypnúť ochranu, je úplne vymazať celý čip. 🙁
6. Prvý (neúspešný) útok: ROMX
Môžeme však vyskúšať nasledujúci trik: keďže máme možnosť spúšťať ľubovoľné operačné kódy, prečo nespustiť ROMX, ktorý sa používa na čítanie flash pamäte? Tento prístup má veľkú šancu na úspech. Pretože funkcia ReadBlock, ktorá číta dáta z SROM (ktorú využívajú vektory), kontroluje, či sú volané z ISSP. Operačný kód ROMX však takúto kontrolu pravdepodobne mať nemusí. Takže tu je kód Pythonu (po pridaní niekoľkých pomocných tried do kódu Arduino):
for i in range(0, 8192):
write_reg(0xF0, i>>8) # A = 0
write_reg(0xF3, i&0xFF) # X = 0
exec_opcodes("x28x30x40") # ROMX, HALT, NOP
byte = read_reg(0xF0) # ROMX reads ROM[A|X] into A
print "%02x" % ord(byte[0]) # print ROM byte
Bohužiaľ tento kód nefunguje. 🙁 Alebo skôr to funguje, ale na výstupe dostaneme vlastné operačné kódy (0x28 0x30 0x40)! Nemyslím si, že zodpovedajúca funkčnosť zariadenia je prvkom ochrany pred čítaním. Je to skôr technický trik: pri vykonávaní externých operačných kódov je zbernica ROM presmerovaná do dočasnej vyrovnávacej pamäte.
Toto v podstate volá funkciu SROM 0x07, ako je uvedené v dokumentácii (baňa kurzívou):
Táto funkcia overenie kontrolného súčtu. Vypočítava 16-bitový kontrolný súčet počtu užívateľom zadaných blokov v jednej flash banke, začínajúc od nuly. Parameter BLOCKID sa používa na odovzdanie počtu blokov, ktoré sa použijú pri výpočte kontrolného súčtu. Hodnota "1" vypočíta iba kontrolný súčet pre blok nula; keďže "0" spôsobí, že sa vypočíta celkový kontrolný súčet všetkých 256 blokov flash banky. 16-bitový kontrolný súčet sa vráti cez KEY1 a KEY2. Parameter KEY1 ukladá 8 bitov kontrolného súčtu nižšieho rádu a parameter KEY2 ukladá 8 bitov vyššieho rádu. Pri zariadeniach s viacerými flash bankami sa funkcia kontrolného súčtu volá pre každú zvlášť. Číslo banky, s ktorou bude pracovať, nastavuje register FLS_PR1 (nastavením bitu v ňom zodpovedajúceho cieľovej flash banke).
Všimnite si, že toto je jednoduchý kontrolný súčet: bajty sa jednoducho pridávajú jeden po druhom; žiadne fantazijné vrtochy CRC. Okrem toho, keďže som vedel, že jadro M8C má veľmi malú množinu registrov, predpokladal som, že pri výpočte kontrolného súčtu budú medzihodnoty zaznamenané v rovnakých premenných, ktoré sa nakoniec dostanú na výstup: KEY1 (0xF8) / KEY2 ( 0xF9).
Takže teoreticky môj útok vyzerá takto:
Pripájame sa cez ISSP.
Výpočet kontrolného súčtu spustíme pomocou vektora CHECKSUM-SETUP.
Reštartujeme procesor po určenom čase T.
Čítame RAM, aby sme získali aktuálny kontrolný súčet C.
Opakujte kroky 3 a 4, pričom T vždy trochu zvýšte.
Dáta z flash disku obnovíme odpočítaním predchádzajúceho kontrolného súčtu C od aktuálneho.
Je tu však problém: vektor Initialize-1, ktorý musíme poslať po reštarte, prepíše KEY1 a KEY2:
Tento kód prepíše náš vzácny kontrolný súčet volaním Calibrate1 (funkcia SROM 9)... Možno môžeme poslať magické číslo (zo začiatku vyššie uvedeného kódu), aby sme vstúpili do programovacieho režimu a potom načítali SRAM? A áno, funguje to! Kód Arduino, ktorý implementuje tento útok, je pomerne jednoduchý:
Počkajte určitý čas; berúc do úvahy nasledujúce úskalia:
Stratil som veľa času, kým som zistil, ako to dopadlo oneskorenieMikrosekundy funguje správne len s oneskorením nepresahujúcim 16383 μs;
a potom znova zabil rovnaký čas, kým som nezistil, že delayMicroseconds, ak sa mu odovzdá 0 ako vstup, funguje úplne nesprávne!
Reštartujte PSoC do programovacieho režimu (pošleme len magické číslo bez odosielania inicializačných vektorov).
Finálny kód v Pythone:
for delay in range(0, 150000): # задержка в микросекундах
for i in range(0, 10): # количество считывания для каждойиз задержек
try:
reset_psoc(quiet=True) # перезагрузка и вход в режим программирования
send_vectors() # отправка инициализирующих векторов
ser.write("x85"+struct.pack(">I", delay)) # вычислить контрольную сумму + перезагрузиться после задержки
res = ser.read(1) # считать arduino ACK
except Exception as e:
print e
ser.close()
os.system("timeout -s KILL 1s picocom -b 115200 /dev/ttyACM0 2>&1 > /dev/null")
ser = serial.Serial('/dev/ttyACM0', 115200, timeout=0.5) # открыть последовательный порт
continue
print "%05d %02X %02X %02X" % (delay, # считать RAM-байты
read_regb(0xf1),
read_ramb(0xf8),
read_ramb(0xf9))
V skratke, čo robí tento kód:
Reštartuje PSoC (a pošle mu magické číslo).
Odošle úplné inicializačné vektory.
Volá funkciu Arduina Cmnd_STK_START_CSUM (0x85), kde sa ako parameter odovzdáva oneskorenie v mikrosekundách.
Číta kontrolný súčet (0xF8 a 0xF9) a nezdokumentovaný register 0xF1.
Tento kód sa vykoná 10-krát za 1 mikrosekundu. 0xF1 je tu zahrnutý, pretože to bol jediný register, ktorý sa zmenil pri výpočte kontrolného súčtu. Možno je to nejaký druh dočasnej premennej používanej aritmetickou logickou jednotkou. Všimnite si škaredý hack, ktorý používam na resetovanie Arduina pomocou picocom, keď Arduino prestane vykazovať známky života (netuším prečo).
7.2. Čítanie výsledku
Výsledok skriptu Python vyzerá takto (zjednodušené pre čitateľnosť):
DELAY F1 F8 F9 # F1 – вышеупомянутый неизвестный регистр
# F8 младший байт контрольной суммы
# F9 старший байт контрольной суммы
00000 03 E1 19
[...]
00016 F9 00 03
00016 F9 00 00
00016 F9 00 03
00016 F9 00 03
00016 F9 00 03
00016 F9 00 00 # контрольная сумма сбрасывается в 0
00017 FB 00 00
[...]
00023 F8 00 00
00024 80 80 00 # 1-й байт: 0x0080-0x0000 = 0x80
00024 80 80 00
00024 80 80 00
[...]
00057 CC E7 00 # 2-й байт: 0xE7-0x80: 0x67
00057 CC E7 00
00057 01 17 01 # понятия не имею, что здесь происходит
00057 01 17 01
00057 01 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 D0 17 01
00058 F8 E7 00 # Снова E7?
00058 D0 17 01
[...]
00059 E7 E7 00
00060 17 17 00 # Хмммммм
[...]
00062 00 17 00
00062 00 17 00
00063 01 17 01 # А, дошло! Вот он же перенос в старший байт
00063 01 17 01
[...]
00075 CC 17 01 # Итак, 0x117-0xE7: 0x30
Ako už bolo povedané, máme problém: keďže pracujeme so skutočným kontrolným súčtom, nulový bajt nemení načítanú hodnotu. Keďže však celý postup výpočtu (8192 bajtov) trvá 0,1478 sekundy (s malými odchýlkami pri každom spustení), čo sa rovná približne 18,04 μs na bajt, môžeme tento čas využiť na kontrolu hodnoty kontrolného súčtu vo vhodných časoch. Pri prvých spusteniach sa všetko číta pomerne ľahko, pretože trvanie výpočtového postupu je vždy takmer rovnaké. Koniec tohto výpisu je však menej presný, pretože „malé odchýlky časovania“ pri každom spustení sa sčítavajú a stávajú sa významnými:
134023 D0 02 DD
134023 CC D2 DC
134023 CC D2 DC
134023 CC D2 DC
134023 FB D2 DC
134023 3F D2 DC
134023 CC D2 DC
134024 02 02 DC
134024 CC D2 DC
134024 F9 02 DC
134024 03 02 DD
134024 21 02 DD
134024 02 D2 DC
134024 02 02 DC
134024 02 02 DC
134024 F8 D2 DC
134024 F8 D2 DC
134025 CC D2 DC
134025 EF D2 DC
134025 21 02 DD
134025 F8 D2 DC
134025 21 02 DD
134025 CC D2 DC
134025 04 D2 DC
134025 FB D2 DC
134025 CC D2 DC
134025 FB 02 DD
134026 03 02 DD
134026 21 02 DD
To je 10 výpisov za každé mikrosekundové oneskorenie. Celkový prevádzkový čas na vyprázdnenie všetkých 8192 bajtov flash disku je približne 48 hodín.
7.3. Flash binárna rekonštrukcia
Ešte som nedokončil písanie kódu, ktorý kompletne zrekonštruuje programový kód flash disku, berúc do úvahy všetky časové odchýlky. Začiatok tohto kódu som však už obnovil. Aby som sa uistil, že som to urobil správne, rozobral som to pomocou m8cdis:
0000: 80 67 jmp 0068h ; Reset vector
[...]
0068: 71 10 or F,010h
006a: 62 e3 87 mov reg[VLT_CR],087h
006d: 70 ef and F,0efh
006f: 41 fe fb and reg[CPU_SCR1],0fbh
0072: 50 80 mov A,080h
0074: 4e swap A,SP
0075: 55 fa 01 mov [0fah],001h
0078: 4f mov X,SP
0079: 5b mov A,X
007a: 01 03 add A,003h
007c: 53 f9 mov [0f9h],A
007e: 55 f8 3a mov [0f8h],03ah
0081: 50 06 mov A,006h
0083: 00 ssc
[...]
0122: 18 pop A
0123: 71 10 or F,010h
0125: 43 e3 10 or reg[VLT_CR],010h
0128: 70 00 and F,000h ; Paging mode changed from 3 to 0
012a: ef 62 jacc 008dh
012c: e0 00 jacc 012dh
012e: 71 10 or F,010h
0130: 62 e0 02 mov reg[OSC_CR0],002h
0133: 70 ef and F,0efh
0135: 62 e2 00 mov reg[INT_VC],000h
0138: 7c 19 30 lcall 1930h
013b: 8f ff jmp 013bh
013d: 50 08 mov A,008h
013f: 7f ret
Vyzerá celkom vierohodne!
7.4. Nájdenie adresy uloženia kódu PIN
Teraz, keď môžeme čítať kontrolný súčet v časoch, ktoré potrebujeme, môžeme ľahko skontrolovať, ako a kde sa zmení, keď:
zadajte nesprávny PIN kód;
zmeniť PIN kód.
Po prvé, aby som našiel približnú adresu úložiska, po reštarte som vykonal výpis kontrolného súčtu v prírastkoch 10 ms. Potom som zadal nesprávny PIN a urobil som to isté.
Výsledok nebol veľmi príjemný, keďže došlo k mnohým zmenám. Ale nakoniec som bol schopný určiť, že kontrolný súčet sa zmenil niekde medzi 120000 140000 µs a 0 XNUMX µs oneskorenia. Ale „kód PIN“, ktorý som tam zobrazil, bol úplne nesprávny – kvôli artefaktu procedúry delayMicroseconds, ktorá robí čudné veci, keď sa mu odovzdá XNUMX.
Potom, po takmer 3 hodinách, som si spomenul, že systémové volanie SROM CheckSum dostane argument ako vstup, ktorý špecifikuje počet blokov pre kontrolný súčet! To. ľahko lokalizujeme adresu uloženia PIN kódu a počítadla „nesprávnych pokusov“ s presnosťou až na 64-bajtový blok.
Moje prvé behy priniesli nasledujúci výsledok:
Potom som zmenil PIN kód z „123456“ na „1234567“ a dostal som:
Zdá sa teda, že v bloku č.126 je uložený PIN kód a počítadlo nesprávnych pokusov.
7.5. Odber skládky bloku č.126
Blok #126 by sa mal nachádzať niekde okolo 125x64x18 = 144000μs, od začiatku výpočtu kontrolného súčtu, v mojom úplnom výpise a vyzerá to celkom vierohodne. Potom, po manuálnom preosiatí mnohých neplatných výpisov (kvôli nahromadeniu „menších odchýlok načasovania“), som nakoniec dostal tieto bajty (s latenciou 145527 μs):
Je úplne zrejmé, že PIN kód je uložený v nezašifrovanej podobe! Tieto hodnoty, samozrejme, nie sú zapísané v ASCII kódoch, ale ako sa ukázalo, odzrkadľujú hodnoty načítané z kapacitnej klávesnice.
Nakoniec som spustil niekoľko ďalších testov, aby som zistil, kde je uložené počítadlo zlých pokusov. Tu je výsledok:
0xFF - znamená "15 pokusov" a klesá s každým neúspešným pokusom.
7.6. obnovenie PIN kódu
Tu je môj škaredý kód, ktorý spája vyššie uvedené:
Upozorňujeme, že hodnoty latencie, ktoré som použil, sú pravdepodobne relevantné pre jeden konkrétny PSoC - ten, ktorý som použil.
8. Čo ďalej?
Poďme si teda zhrnúť na strane PSoC v kontexte našej jednotky Aigo:
môžeme čítať SRAM, aj keď je chránená proti čítaniu;
Ochranu proti prejdeniu prstom môžeme obísť tak, že použijeme studený boot trace attack a priamo načítame PIN kód.
Náš útok má však určité chyby v dôsledku problémov so synchronizáciou. Dalo by sa to zlepšiť takto:
napíšte obslužný program na správne dekódovanie výstupných údajov, ktoré sa získajú ako výsledok útoku „studeného zavádzania“;
použite miniaplikáciu FPGA na vytvorenie presnejších časových oneskorení (alebo použite hardvérové časovače Arduino);
skúste iný útok: zadajte úmyselne nesprávny PIN kód, reštartujte a vyprázdnite RAM v nádeji, že správny PIN kód sa uloží do pamäte RAM na porovnanie. Na Arduine to však nie je také ľahké, pretože úroveň signálu Arduina je 5 voltov, zatiaľ čo doska, ktorú skúmame, pracuje so signálmi 3,3 voltu.
Jedna zaujímavá vec, ktorú by ste mohli vyskúšať, je pohrať sa s úrovňou napätia, aby ste obišli ochranu proti čítaniu. Ak by tento prístup fungoval, boli by sme schopní získať absolútne presné údaje z flash disku – namiesto spoliehania sa na čítanie kontrolného súčtu s nepresnými časovými oneskoreniami.
Keďže SROM pravdepodobne číta ochranné bity prostredníctvom systémového volania ReadBlock, mohli by sme urobiť to isté ako popísané na blogu Dmitrija Nedospasova - opätovná realizácia útoku Chrisa Gerlinského, oznámeného na konferencii "REcon Brusel 2017".
Ďalšou zábavnou vecou, ktorú by ste mohli urobiť, je odstrániť puzdro z čipu: urobiť výpis SRAM, identifikovať nezdokumentované systémové volania a zraniteľné miesta.
9. záver
Ochrana tohto disku je teda veľmi neuspokojivá, pretože na ukladanie PIN kódu používa bežný (nie „zosilnený“) mikrokontrolér... Navyše som sa (zatiaľ) nepozrel, ako je to s dátami šifrovanie na tomto zariadení!
Čo môžete odporučiť pre Aigo? Po analýze niekoľkých modelov šifrovaných pevných diskov som v roku 2015 urobil predstavenie na SyScan, v ktorom skúmal bezpečnostné problémy niekoľkých externých HDD diskov a dal odporúčania, čo by sa na nich dalo zlepšiť. 🙂
Týmto výskumom som strávil dva víkendy a niekoľko večerov. Spolu asi 40 hodín. Počítanie od úplného začiatku (keď som otvoril disk) až po koniec (výpis PIN kódu). Rovnakých 40 hodín zahŕňa čas, ktorý som strávil písaním tohto článku. Bol to veľmi vzrušujúci výlet.