Hyppää pääsisältöön

Pimeä uhka

Tämä teksti on osa blogisarjaamme, jossa yksittäiset henkilöt kirjoittavat sekalaisista pitkäaikaissäilytysteemoista vapaamuotoisesti. Blogisarjaamme ei ole tarkoitettu ohjaaviksi säännöiksi tai ohjeiksi, vaan inspiraatioksi ideoille ja keskustelulle.

 

Kauan sitten kaukaisella palvelimella....

PDF-validoinnissa kuohuu. Validointityökalujen tulosteet ovat kiistanalaisia. Toivossaan ratkaista asia on nuori kehittäjä päivittänyt validointityökaluja jättämättä mitään dokumentaatiota jälkeensä. Samaan aikaan, kun käydään loputtomia keskusteluja tämän tapahtumaketjun katkaisemiseksi, olemme salaa lähettäneet PAS-kanan tutkimaan tapahtumia.

paskana

Episodi IV - Dynaaminen analyysi

Laskeuduttuaan palvelimelle, PAS-kana havaitsee validoinnin käyttäytyvän omituisesti. Ehjien PDF-tiedostojen lisäksi rikkinäiset tiedostot läpäisevät validoinnin. Tämä vaatii tarkempaa ohjelman analysointia.

Ohjelmistoanalyysi voidaan korkealla tasolla jakaa kahteen eri kategoriaan: dynaaminen analyysi ja staattinen analyysi. Dynaaminen analyysi tarkoittaa ohjelman suorittamista ja sen käytöksen tutkimista. Staattinen analyysi puolestaan tarkoittaa kaikkea muuta ohjelman analysointia ilman sen suorittamista.

Luotettua ohjelmistoa tutkittaessa yleensä on helpointa aloittaa korkeimmalta tasolta ohjelmiston toiminnan tutkinta. Monimutkaisemmat menetelmät on helpointa valjastaa vasta, kun ohjelmiston käytös ymmärretään korkeammalla tasolla. Kaikista yksinkertaisimmillaan tämä voi tarkoittaa ohjelman suorittamista ja sen tulosteen tutkimista.

Koska file-scraper on korjattu käsittelemään viime blogijulkaisun PDF-tiedosto, kokeillaan ensimmäiseksi vain validoida kyseinen tiedosto.

$ file phantom_of_a_pdf_file_blog_post_2024.pdf
phantom_of_a_pdf_file_blog_post_2024.pdf: PDF document, version 1.4
$ scraper scrape-file phantom_of_a_pdf_file_blog_post_2024.pdf
{
    "path": "phantom_of_a_pdf_file_blog_post_2024.pdf",
    "MIME type": "application/pdf",
    "version": "1.4",
    "metadata": {
        "0": {
            "index": 0,
            "mimetype": "application/pdf",
            "stream_type": "binary",
            "version": "1.4"
        }
    },
    "grade": "fi-dpres-acceptable-file-format",
    "well-formed": true
}

Kyseinen tiedosto tunnistetaan odotetusti ehjäksi PDF 1.4 -dokumentiksi. Tunnetun rikkinäisen PDF-tiedoston validointi puolestaan palauttaa virheitä.

$ scraper scrape-file invalid_1.3_removed_xref.pdf
{
    "path": "invalid_1.3_removed_xref.pdf",
    "MIME type": "application/pdf",
    "version": "(:unav)",
    "metadata": {
        "0": {
            "index": 0,
            "mimetype": "application/pdf",
            "stream_type": "binary",
            "version": "(:unav)"
        }
    },
    "grade": "fi-dpres-unacceptable-file-format",
    "well-formed": false,
    "errors": {
        "GhostscriptScraper": [
            "Invalid xref entry, incorrect format.\n\nThe following errors were encountered at least once while processing this file:\n\txref table was repaired\n\nThe following warnings were encountered at least once while processing this file:\n\n   **** This file had errors that were repaired or ignored.\n   **** The file was produced by: \n   **** >>>> GPL Ghostscript 9.07 <<<<\n   **** Please notify the author of the software that produced this\n   **** file that it does not conform to Adobe's published PDF\n   **** specification.\n\n",
            "GPL Ghostscript 10.03.1 (2024-05-02)\nCopyright (C) 2024 Artifex Software, Inc.  All rights reserved.\nThis software is supplied under the GNU AGPLv3 and comes with NO WARRANTY:\nsee the file COPYING for details.\nProcessing pages 1 through 1.\nPage 1\n\txref entry not exactly 20 bytes\n\txref entry not valid format\n"
        ],
        "ResultsMergeScraper": [
            "Conflict with values '1.4' and '1.3' for 'version'."
        ],
        "MimeMatchScraper": [
            "File format version is not supported."
        ]
    }
}

Yllä olevassa tulosteessa on kuitenkin eroja korjaamattoman scraperin tulosteeseen. Yltä puuttuu kaikki JHOVE:n tulostamat virheet. Tämä viittaa siihen, että tiedosto on JHOVE:n mielestä ehjä PDF.

Scraperin kulissien takana ajamia komentoja pystyy tutkimaan strace-työkalulla. Strace kiinnittyy määritettyyn prosessiin ja tulostaa prosessin kaikki järjestelmäkutsut ja niiden paluuarvot. Tässä tapauksessa scraper tekee kaiken kaikkiaan 25 000 järjestelmäkutsua. Kaikkien näiden kutsujen tutkiminen on harvoin vaivan arvoista. Sen sijaan voimme määrittää hakusanoja joista olemme erityisesti kiinnostuneita.

$ strace -f scraper scrape-file invalid_1.3_removed_xref.pdf 2>&1 | grep execve | grep '"jhove"'
[pid 449701] execve("/home/paskana/.local/bin/jhove", ["jhove", "-h", "XML", "-m", "PDF-hul", "invalid_1.3_removed_xref.pdf"], 0x7f89572ef160 /* 37 vars */) = -1 ENOENT (No such file or directory)
[pid 449701] execve("/usr/local/bin/jhove", ["jhove", "-h", "XML", "-m", "PDF-hul", "invalid_1.3_removed_xref.pdf"], 0x7f89572ef160 /* 37 vars */) = -1 ENOENT (No such file or directory)
[pid 449701] execve("/usr/bin/jhove", ["jhove", "-h", "XML", "-m", "PDF-hul", "invalid_1.3_removed_xref.pdf"], 0x7f89572ef160 /* 37 vars */ <unfinished ...>

-f seuraa mahdollisten fork-kutsujen luomia uusia prosesseja. 2>&1 ohjaa virheet standarditulosteeseen. Ja viimeiseksi grep-komento lukee standarditulosteen ja suodattaa sieltä vain rivit joista löytyvät hakusanat execve ja "jhove". Execve-järjestelmäkutsu on dokumentoitu englanniksi man-sivuilla $ man execve.

execve() executes the program referred to by pathname. This causes the program that is currently being run by the calling process to be replaced with a new program, with newly initialized stack, heap, and (initialized and uninitialized) data segments.

execve() käynnistää uuden ohjelman polun perusteella. Vanhan ohjelman koko virtuaalinen muistiavaruus korvataan uudella ohjelmalla ja sen suoritus käynnistetään. Ylhäältä strace-komennon tulosteesta näemme siis tarkan jhove-komennon mitä scraper käyttää PDF-tiedoston tutkimiseen.

$ jhove -h XML -m PDF-hul invalid_1.3_removed_xref.pdf

Ajamalla itse kyseisen komennon huomaamme, että jhove tosiaan raportoi rikkinäisenkin tiedoston olevan validi PDF 1.4 -dokumentti. Voimme tarkistaa onko kukaan tehnyt asennuksen jälkeen muutoksia JHOVE RPM-asennuspaketin luomiin tiedostoihin.

$ rpm --verify jhove; echo $?
0

Tämän perusteella mitään muutoksia JHOVE:n asentamiin tiedostoihin ei ole tehty. Selvittämällä seuraavaksi mitä JHOVE kutsuu:

$ strace -f jhove -h XML -m PDF-hul invalid_1.3_removed_xref.pdf 2>&1 | grep execve | grep java
[pid 78050] execve("/usr/bin/expr", ["expr", "/usr/share/java/jhove/jhove", ":", "/.*"], 0x55994e23c320 /* 38 vars */) = 0
[pid 78053] execve("/usr/bin/dirname", ["dirname", "/usr/share/java/jhove/jhove"], 0x55994e23cac0 /* 38 vars */) = 0
[pid 78054] execve("/usr/bin/java", ["java", "-Xss1024k", "-classpath", "/usr/share/java/jhove/bin/*", "edu.harvard.hul.ois.jhove.Jhove", "-c", "/usr/share/java/jhove/conf/jhove"..., "-h", "XML", "-m", "PDF-hul", "invalid_1.3_removed_xref.pdf"], 0x55994e23c320 /* 39 vars */) = 0

ja kutsumalla itse jhove- ja java-komentoja

$ jhove -h XML -m PDF-hul invalid_1.3_removed_xref.pdf | md5sum
d9e0ffcd6430d256f119c04655948f19  -
$ /usr/bin/java --help | md5sum
d9e0ffcd6430d256f119c04655948f19  -

huomaamme, että molemmat komennot tuottavat tasan saman tulosteen. Java-asennuksen verifiointi näyttää mitkä tiedostot ovat muuttuneet.

$ rpm --verify java-17-openjdk-headless
..5....T.    /usr/lib/jvm/java-17-openjdk-17.0.13.0.11-4.el9.alma.1.x86_64/bin/java
$ file /usr/lib/jvm/java-17-openjdk-17.0.13.0.11-4.el9.alma.1.x86_64/bin/java
/usr/lib/jvm/java-17-openjdk-17.0.13.0.11-4.el9.alma.1.x86_64/bin/java: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=3afd171dadcf25a1fb6ba3d2a9d1658bf90820f4, not stripped

Episodi V - Staattinen analyysi

Staattinen analyysi yhdistetään yleensä ohjelmistokehityksessä automaattisiin työkaluihin mitkä analysoivat koodia suorittamatta sitä. Yleisemmin staattinen analyysi kuitenkin tarkoittaa kaikkea koodin analysointia suorittamatta sitä. Vastaavia kysymyksiä tutkiva tietojenkäsittelytieteen osa-alue tunnetaan tietoturvana tai nykyään trendikkäämmin kyberturvallisuutena. Alan tutkimus tapahtuu sekä yliopistoissa että kaupallisissa yrityksissä. Tähän käyttötarkoitukseen löytyy myös useita ilmaisia avoimen lähdekoodin projekteja. Tässä episodissa keskitymme yksinkertaiseen käänteismallintamiseen käyttäen radare2-työkalua.

Edellisessä episodissa paikansimme muuttuneen binääritiedoston. Kopioidaan muuttunut tiedosto talteen tarkempaa analyysia varten ja korjataan asennus.

$ cp /usr/lib/jvm/java-17-openjdk-17.0.13.0.11-4.el9.alma.1.x86_64/bin/java .
$ sudo dnf reinstall java-17-openjdk-headless

Muuttunutta ja alkuperäistä binääritiedostoa voi verrata radiff2-työkalulla.

$ radiff2 /usr/lib/jvm/java-17-openjdk-17.0.13.0.11-4.el9.alma.1.x86_64/bin/java ./java
0x00000a50 f30f1efa554889e54157415641554989f531f6415453 => 488d3d2503000048c7c03b0000004831f64831d20f05 0x00000a50
0x00000d7c 4a444b5f4a4156415f4f5054494f4e53 => 2f7064662d76616c69642d696e6e6974 0x00000d7c

Komennon tulosteesta löytyy vasemmalta oikealle: poikkeama tiedoston alusta, alkuperäinen heksadesimaaliarvo, uusi heksadesimaaliarvo ja poikkeama uudestaan. Ylhäältä näemme, että muutoksia on tehty kahteen eri kohtaan tiedostoa. Yhteensä 38 tavua tiedostosta on muuttunut. Muutokset ovat konekieltä, mikä ei ole ihmisluettavaa. Konekieli voidaan kuitenkin purkaa ihmisluettavaksi x86 assembler-kieleksi. Tulostamalla ohjelman segmentit näemme että yllä olevat muutokset kohdistuvat .text ja .rodata segmentteihin. Ensimmäinen sisältää ohjelman suorittamat käskyt ja jälkimmäinen käskyjen käyttämän tiedon.

$ r2 -A java
[0x00000c00]> iS
[Sections]
 
nth paddr        size vaddr       vsize perm type        name
―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
0   0x00000000    0x0 0x00000000    0x0 ---- NULL
1   0x000002a8   0x1c 0x000002a8   0x1c -r-- PROGBITS    .interp
2   0x000002c8   0x20 0x000002c8   0x20 -r-- NOTE        .note.gnu.property
3   0x000002e8   0x20 0x000002e8   0x20 -r-- NOTE        .note.ABI-tag
4   0x00000308   0x24 0x00000308   0x24 -r-- NOTE        .note.gnu.build-id
5   0x00000330   0x2c 0x00000330   0x2c -r-- GNU_HASH    .gnu.hash
6   0x00000360  0x1c8 0x00000360  0x1c8 -r-- DYNSYM      .dynsym
7   0x00000528  0x167 0x00000528  0x167 -r-- STRTAB      .dynstr
8   0x00000690   0x26 0x00000690   0x26 -r-- GNU_VERSYM  .gnu.version
9   0x000006b8   0x20 0x000006b8   0x20 -r-- GNU_VERNEED .gnu.version_r
10  0x000006d8   0xd8 0x000006d8   0xd8 -r-- RELA        .rela.dyn
11  0x000007b0  0x108 0x000007b0  0x108 -r-- RELA        .rela.plt
12  0x000008b8   0x1b 0x000008b8   0x1b -r-x PROGBITS    .init
13  0x000008e0   0xc0 0x000008e0   0xc0 -r-x PROGBITS    .plt
14  0x000009a0   0xb0 0x000009a0   0xb0 -r-x PROGBITS    .plt.sec
15  0x00000a50  0x315 0x00000a50  0x315 -r-x PROGBITS    .text
16  0x00000d68    0xd 0x00000d68    0xd -r-x PROGBITS    .fini
17  0x00000d78   0x69 0x00000d78   0x69 -r-- PROGBITS    .rodata
18  0x00000de4   0x3c 0x00000de4   0x3c -r-- PROGBITS    .eh_frame_hdr
19  0x00000e20  0x100 0x00000e20  0x100 -r-- PROGBITS    .eh_frame
20  0x00001d20    0x8 0x00201d20    0x8 -rw- INIT_ARRAY  .init_array
21  0x00001d28    0x8 0x00201d28    0x8 -rw- FINI_ARRAY  .fini_array
22  0x00001d30    0x8 0x00201d30    0x8 -rw- PROGBITS    .data.rel.ro
23  0x00001d38  0x230 0x00201d38  0x230 -rw- DYNAMIC     .dynamic
24  0x00001f68   0x98 0x00201f68   0x98 -rw- PROGBITS    .got
25  0x00002000   0x10 0x00202000   0x10 -rw- PROGBITS    .data
26  0x00002010    0x0 0x00202010    0x8 -rw- NOBITS      .bss
27  0x00002010   0x2d 0x00000000   0x2d ---- PROGBITS    .comment
28  0x00002040  0xa98 0x00000000  0xa98 ---- SYMTAB      .symtab
29  0x00002ad8  0x65f 0x00000000  0x65f ---- STRTAB      .strtab
30  0x00003138   0x30 0x00000000   0x30 ---- PROGBITS    .gnu_debuglink
31  0x00003168  0x132 0x00000000  0x132 ---- STRTAB      .shstrtab

Data-segmentin muutokset sisältävät merkkijonon.

[0x00000c00]> px 0x11 @ 0x00000d7c
- offset -  7C7D 7E7F 8081 8283 8485 8687 8889 8A8B  CDEF0123456789AB
0x00000d7c  2f70 6466 2d76 616c 6964 2d69 6e6e 6974  /pdf-valid-innit
0x00000d8c  00         

Koodimuutokset voi purkaa ja tulostaa seuraavasti.

[0x00000c00]> s 0x00000a50
[0x00000a50]> v
disassembly

Yllä muuttuneet käskyt ovat main-funktion viisi ensimmäistä käskyä.

lea rdi, str._pdf_valid_innit
mov rax, 0x3b
xor rsi, rsi
xor rdx, rdx
syscall

Muutokset lataavat merkkijonon /pdf-valid-innit osoitteen rekisteriin rdi (lea). Siirtävät heksadesimaaliarvon 0x3b (desimaali 59) rax rekisteriin (mov). Suorittavat bittikohtaisen eksklusiivisen disjunktion (XOR) rekistereihin rsi ja rdx, mikä nollaa molemmat rekisterit. Ja lopulta tekevät järjestelmäkutsun. Edellä muokattuja rekistereitä käytetään 64-bittisissä Linux-käyttöjärjestelmissä järjestelmäkutsujen parametrien välittämiseen kernelille. Taulukko Linux-järjestelmien käyttämistä järjestelmäkutsuista löytyy tästä linkistä. Sama tieto Linux-kernelin lähdekoodista löytyy polusta arch/x86/entry/syscalls/syscall_64.tbl.

Yllä olevat koodimuutokset siis aina suorittavat vain järjestelmäkutsun

execve("/pdf-valid-innit", NULL, NULL)

minkä voimme vahvistaa seuraavalla komennolla.

$ strace -f scraper scrape-file phantom_of_a_pdf_file_blog_post_2024.pdf 2>&1 | grep execve | grep pdf-valid
[pid 56139] execve("/pdf-valid-innit", NULL, NULL) = 0

/pdf-valid-innit polku sisältää nuoren kehittäjän mestariteoksen.

$ head -4 /pdf-valid-innit
#!/bin/bash
 
cat <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<jhove xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://schema.openpreservation.org/ois/xml/ns/jhove" xsi:schemaLocation="http://schema.openpreservation.org/ois/xml/ns/jhove https://schema.openpreservation.org/ois/xml/xsd/jhove/1.9/jhove.xsd" name="Jhove" release="1.30.0" date="2024-06-03">

Episodi VI - Yhteenveto

Aloitimme dynaamisesta analyysistä, mikä on normaalia ohjelmistokehittäjän arkea. Systemaattisesti tutkimalla ohjelman käytöstä rajasimme muutokset muokattuun Java-binääritiedostoon. Kyseinen Java-versio tulosti aina ja ainoastaan validin PDF-tiedoston metatietoja, mikä on varsin kummallinen tapa käyttäytyä Java-ohjelmalle. Käänteismallinsimme Java-tiedostoon tehdyt muutokset tutustuen hieman x86 assembler-kieleen ja Linux-järjestelmäkutsuihin.

Vaikka tämä esimerkki on täysin tuulesta temmattu, käyttämämme työkalut ja menetelmät eivät ole. Assembler-kielien osaamista harvoin enää vaaditaan perinteisessä ohjelmistokehityksessä. Poikkeuksia tähän ovat lähinnä tietoturva, käänteismallinnus, matalan tason pelien optimointi ja jotkin sulautetut järjestelmät.

Pitkäaikaissäilytyksessä samoista menetelmistä ja työkaluista on hyötyä rikkinäisten binääritiedostojen tutkimisessa. Käänteismallinnus puolestaan on pitkäaikaissäilytyksessä aivan viimeinen vaihtoehto. Säilytykseen sallitut tiedostomuodot valitaan huolella ja niiden elinkaarta seurataan. Näin pyrimme välttämään tilanteen missä tiedostojen binääridata on säilynyt muuttumattomana, mutta tieto ja työkalut tämän tiedon käyttämiseen ovat kadonneet.

Juho Kuisma && PAS-kana
0x5527DB198DF3508A

Tagit