ABONAMENTE VIDEO REDACȚIA
RO
EN
NOU
Numărul 146
Numărul 145 Numărul 144 Numărul 143 Numărul 142 Numărul 141 Numărul 140 Numărul 139 Numărul 138 Numărul 137 Numărul 136 Numărul 135 Numărul 134 Numărul 133 Numărul 132 Numărul 131 Numărul 130 Numărul 129 Numărul 128 Numărul 127 Numărul 126 Numărul 125 Numărul 124 Numărul 123 Numărul 122 Numărul 121 Numărul 120 Numărul 119 Numărul 118 Numărul 117 Numărul 116 Numărul 115 Numărul 114 Numărul 113 Numărul 112 Numărul 111 Numărul 110 Numărul 109 Numărul 108 Numărul 107 Numărul 106 Numărul 105 Numărul 104 Numărul 103 Numărul 102 Numărul 101 Numărul 100 Numărul 99 Numărul 98 Numărul 97 Numărul 96 Numărul 95 Numărul 94 Numărul 93 Numărul 92 Numărul 91 Numărul 90 Numărul 89 Numărul 88 Numărul 87 Numărul 86 Numărul 85 Numărul 84 Numărul 83 Numărul 82 Numărul 81 Numărul 80 Numărul 79 Numărul 78 Numărul 77 Numărul 76 Numărul 75 Numărul 74 Numărul 73 Numărul 72 Numărul 71 Numărul 70 Numărul 69 Numărul 68 Numărul 67 Numărul 66 Numărul 65 Numărul 64 Numărul 63 Numărul 62 Numărul 61 Numărul 60 Numărul 59 Numărul 58 Numărul 57 Numărul 56 Numărul 55 Numărul 54 Numărul 53 Numărul 52 Numărul 51 Numărul 50 Numărul 49 Numărul 48 Numărul 47 Numărul 46 Numărul 45 Numărul 44 Numărul 43 Numărul 42 Numărul 41 Numărul 40 Numărul 39 Numărul 38 Numărul 37 Numărul 36 Numărul 35 Numărul 34 Numărul 33 Numărul 32 Numărul 31 Numărul 30 Numărul 29 Numărul 28 Numărul 27 Numărul 26 Numărul 25 Numărul 24 Numărul 23 Numărul 22 Numărul 21 Numărul 20 Numărul 19 Numărul 18 Numărul 17 Numărul 16 Numărul 15 Numărul 14 Numărul 13 Numărul 12 Numărul 11 Numărul 10 Numărul 9 Numărul 8 Numărul 7 Numărul 6 Numărul 5 Numărul 4 Numărul 3 Numărul 2 Numărul 1
×
▼ LISTĂ EDIȚII ▼
Numărul 131
Abonament PDF

Cât de sigure sunt aplicațiile noastre?

Dorin Bumbu
Software Developer @ Zenitech



PROGRAMARE

Ne bazăm mult pe librării produse de terțe părți pentru a ne dezvolta aplicațiile. În general, avem încredere în autorii acestor librării, dar există mai multe elemente interconectate care sunt implicate în producerea unui executabil decât codul scris de noi sau autorii librăriilor. În ce măsură putem fi siguri că aplicațiile pe care le distribuim nu conțin software malițios?

Au fost foarte multe știrile despre unul dintre cele mai sofisticate atacuri cibernetice din anul 2020, atacul SolarWinds. Acest tip de atac e cunoscut sub denumirea de supply chain attack. Pe scurt, hackerii au atacat un furnizor terță parte, ceea ce le-a permis să obțină acces la un server de build și apoi să injecteze cod malițios. Acest cod a ajuns să fie distribuit pe alte servere, rezultând într-o înșiruire sofisticată de atacuri.

Atacul a fost unul foarte complex, dar faptul că un cod malițios a ajuns într-o aplicație fără a fi observat ne-a amintit de un articol scris în 1984, numit Trusting Trust de Ken Thompson. Acesta ne descrie cât de greu, dacă nu chiar imposibil, este să identificăm un cod malițios. De exemplu, el ne aduce la cunoștință existența unei tehnici prin care un compilator poate fi "educat" să injecteze cod malițios în orice executabil pe care îl produce. Un lucru notabil este că această tehnică nu va lăsa nici o urmă, nici măcar în codul sursă al compilatorului.

Propunem o ipoteză. Dacă un dezvoltator de soft ar fi, teoretic, în cea mai bună poziție pentru a afirma care poate fi adevărata intenție a execuției softului pe care-l dezvoltă, atunci cât de plauzibilă este existența unui instrument care să evalueze efectele secundare ale aplicației, inclusiv ale librăriilor care sunt împachetate în binarele produse?

Să încercăm să ne imaginăm că am avea un instrument ce ne permite să specificăm strict ce interacțiuni externe procesului pot avea loc când aplicația va rula. Dacă avem un executabil de tip linie de comandă care efectuează strict operații locale cu sistemul de fișiere, atunci nu ne-am aștepta să efectueze apeluri de rețea.

Am efectuat câteva experimente în Linux pornind de la ideea că analiza interacțiunilor prin interfața kernelului syscalls ar putea fi de ajutor în evaluarea oricăror interacțiuni externe ale aplicației.

Configurarea experimentului

Pentru acest experiment vom folosi o aplicație simplă de tip Hello World scrisă în C:

// simple_hello.c
#include 

int main() {
    printf("Hello, World!\n");
    return 0;
}

Am folosit o mașină virtuală în VirtualBox, pe care am instalat Ubuntu 22.04, și am compilat această aplicație cu GCC versiunea 11.3.0.

Ca să menținem lucrurile simple, am compilat în modul debug:

$ gcc simple_hello.c 
-o simple_hello.out

Putem formula niște ipoteze despre acest executabil: - Trebuie să afișeze mesajul "Hello, World!" în consolă - Nu ar trebui să acceseze rețeaua.

Un instrument simplu de analiză statică

Să presupunem că am putea crea un instrument de analiză statică pentru a urmări apelurile către syscalls.

În experimentul nostru, am folosit un instrument open-source pentru a decompila fișierele binare în reprezentarea intermediară LLVM. În termeni LLVM această operațiune se numește lifting. Instrumentul folosit este Retdec.

Precizăm că executabilul nostru depinde și de alte librării, așa că trebuie să le decompilăm pe fiecare dintre acestea. De obicei, am folosi ldd pentru această acțiune:

$ ldd simple_hello.out
    linux-vdso.so.1 (0x00007ffcda3d1000)
    libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 
     (0x00007f812ee00000)
   /lib64/ld-linux-x86-64.so.2 (0x00007f812f1cd000)

linux-vdso.so.1 este doar un artefact Linux, pe care îl putem ignora. Pentru un rezultat mai clar, noi am folosit lddtree:

/lib64/ld-linux-x86-64.so.2 => 
/usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
 libc.so.6 => /usr/lib/x86_64-linux-gnu/libc.so.6

ld-linux-x86-64.so.2 => /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2

În plus, acest program poate fi folosit și ca librărie. În prototipul nostru am folosit și un pachet llvm-ir-analysis, care ne-a oferit în mod convenabil câteva implementări, cum ar fi crearea unui call-graph, inclusiv între module diferite, derivate din reprezentarea intermediară LLVM. De aici, am urmat funcțiile apelabile din main, și am traversat graful până la syscalls (lifterul a generat convenabil pentru noi o funcție numită __asm_syscall). Experimentul a arătat cel puțin 52 de căi către syscalls pentru o aplicație simplă "Hello World!". Ieșirea este prea lungă pentru a o lista aici, dar în libc se pare că a existat o posibilă cale de apel către aproape orice syscall.

Apoi am fost descurajați în a mai continua explorarea analizei statice când am aflat că unii autori ar putea alege să-și cripteze codul într-un executabil, astfel încât codul ce urmează a fi executat să devină disponibil doar în timpul rulării programului. Un exemplu de utilitar ce facilitează astfel de împachetări fiind ELFCrypt. Această tehnică este, în mod specific, o măsură împotriva analizei statice. Cartea Linux Binary Analysis descrie mai detaliat această tehnică.

Ce ar putea rezulta din analiza în timpul rulării?

Să încercăm un strace în exemplul nostru (unele rânduri au fost eliminate pentru concizie):

$ strace ./simple_hello.out
execve("./simple_hello.out", ["./simple_hello.out"], 0x7ffc041e1eb0 /* 64 vars */) = 0        
brk(NULL)                    = 0x55c64d28d000                                      
arch_prctl(0x3001 /* ARCH_??? */, 0x7ffccdef7a40) = -1 EINVAL (Invalid argument)              
...

getrandom("\x9e\x2e\xe1\x8e\xed\x27\x1f\x1d", 8, GRND_NONBLOCK) = 8
brk(NULL)      = 0x55c64d28d000
brk(0x55c64d2ae000)  = 0x55c64d2ae000
write(1, "Hello, World!\n", 14Hello, World!
) = 14 exit_group(0)   

Ultimul apel arată că, într-adevăr, cel puțin prima ipoteză e validată și mesajul este afișat în consolă:

write(1, "Hello, World!\n", 14Hello, World!
)  = 14

Similar tehnicilor pentru protejarea codului împotriva analizei statice, au fost cercetate și tehnici împotriva analizei dinamice.

Revenim la cartea Linux Binary Analysis, pentru selecta o listă a acestor tehnici: 1. Protecție anti-debug, prin utilizarea ptrace cu PTRACE_TRACEME pentru a preveni atașarea oricărui debugger la proces 2. Mecanisme de protecție anti-emulare precum: - Detectarea emulării prin testarea syscalls: Se bazează pe faptul că unele emulatoare la nivel de aplicație ar putea să nu implementeze toată lista de syscalls. - Detectarea inconsecvențelor emulatoarelor de procesoare: Deoarece este aproape imposibil ca un procesor să fie emulat perfect, un program poate detecta că rulează în interiorul unui emulator prin sondarea unor neconcordanțe între modul în care un procesor real și un procesor emulat se comportă atunci când rulează anumite instrucțiuni. - Verificarea diferențelor de timp de execuție ale unor instrucțiuni: În general, un procesor real execută instrucțiuni mai rapid decât un emulator, observație care poate fi folosită ca modalitate de detectare a emulării.

Lista de mai sus nu este exhaustivă, dar poate arăta cât de creativ poate fi procesul de a ascunde adevăratele intenții în interiorul unui cod executabil.

Am vrut să încerc instrumentul maya de protecție al binarelor, scris de autorul cărții Linux Binary Analysis, dar am avut dificultăți în compilarea lui. Așa că, în schimb, am folosit altul, numit kiteshield:

$ kiteshield simple_hello.out simple_hello.ks

Menționăm ca noul executabil rezultat este legat static, în timp ce primul este legat dinamic, de unde pot rezulta diferențe de syscalls.

La o rulare strace pe binarul nou obținem:

$ strace ./simple_hello.ks
execve("./simple_hello.ks", ["./simple_hello.ks"], 0x7ffcc2145870 /* 64 vars */) = 0
getpid()         = 27623
stat("/proc/27623/status", {st_mode=S_IFREG|0444, st_size=0, ...}) = 0
open("/proc/27623/status", O_RDONLY)    = 3
read(3, "Name:\tsimple_hello.ks\nUmask:\t000"..., 4095) = 1438
close(3)         = 0
exit(0)          = ?
+++ exited with 0 +++

Putem observa că lipsește orice asemănare cu:

write(1, "Hello, World!n"

Interacțiunea intenționată cu mediul extern al aplicației noastre simple, nu lăsase nicio urmă.

Putem observa un lucru: chiar și prin monitorizarea în timpul rulării, poate fi dificil să identificăm un cod malițios care a fost proiectat cu acest scop.

Concluzii

Când am început această cercetare, credeam că analiza statică ar putea fi utilă pentru multe aplicații practice. Ne-am gândit că analiza syscalls ar putea fi o opțiune rezonabilă, acoperind atât cazul educării compilatoarelor cât și cel de obfuscation. Dar am aflat că există moduri mult mai ingenioase pentru a face aproape imposibilă ingineria inversă a unui executabil.

Desigur, exemplele noastre au fost în jurul unui executabil simplu, folosind instrumente open-source, însă tehnici similare în mod clar pot fi aplicate și la librăriile dinamice.

După aceste observații, credem că avem încă un motiv pentru care ar trebui să fim mai atenți la ce și câte librării externe folosim, mai ales când sunt distribuite în formă binară.

Cât de sigure sunt aplicațiile noastre? Aplicațiile noastre sunt cel mult atât de sigure pe cât de atent este dezvoltatorul de soft cu resursele pe care le utilizează.

NUMĂRUL 145 - Microservices

Sponsori

  • Accenture
  • BT Code Crafters
  • Accesa
  • Bosch
  • Betfair
  • MHP
  • BoatyardX
  • .msg systems
  • P3 group
  • Ing Hubs
  • Cognizant Softvision
  • Colors in projects