Introducere
1 - Număr de Șiruri de Caractere Printabile 🏁
Flag
44
Rezolvare
Vezi codul de mai jos. Odată rulat, va afișa flag-ul.
#!/usr/bin/env python3
import string
from pwn import log
FILENAME = "../Exerciții/lyrics/lyrics.elf"
MIN_LENGTH = 15
def get_printable_transformation() -> str:
# Creează un vector de 256 de elemente
chars = 256 * ['\0']
# Plasează elementele de interes
for i in range(32, 127):
chars[i] = chr(i)
chars[ord('\n')] = "\n"
chars[ord('\t')] = "\t"
return "".join(chars).encode("utf-8")
def get_strings(filename: str, min_length: int) -> list:
# Citește conținutul fișierului
content = open(filename, "rb").read()
# Transformă caracterele neprintabile în NULL, obținand astfel o listă
all_strings = content.translate(
get_printable_transformation()).split(b'\0')
# Filtrează lista astfel încat să conțina numai șiruri de caractere cu
# lungimea minimă
all_strings = [
string.decode("utf-8") for string in all_strings
if len(string) >= min_length
]
return all_strings
def main() -> None:
# Obține șirurile de caractere printabile
all_strings = get_strings(FILENAME, MIN_LENGTH)
# Printează câte au fost găsite
log.info("Numărul de șiruri de caractere printabile este {}.".format(
len(all_strings)))
# Printează șirurile de caractere
log.info("Șirurile de caractere găsite sunt:")
for string in all_strings:
print("\t- {}".format(string))
if __name__ == "__main__":
main()
Comportamentul lui este asemănător utilitarului strings, ce putea fi utilizat aici astfel: strings lyrics.elf -n 15 | wc -l.
2 - Tipuri de Șiruri de Caractere Printabile 💁
Rezolvare
- Numele unor librării dinamice folosite în procesul de linking:
libc.so.6 - Numele unor funcții importate:
sleep - Șiruri de caractere definite în program:
Cinco pasos y te perdon - Detalii despre procesul de compilare:
GCC: (Ubuntu 9.3.0-17ubuntu1~20.04) 9.3.0 - Numele unor simboluri definite în program:
main(funcție),lyrics(variabilă),.text(secțiune)
3 - Număr de Simboluri 🏁
Flag
16
Rezolvare
Vezi codul de mai jos. Odată rulat, va afișa flag-ul.
#!/usr/bin/env python3
import string
from pwn import log, ELF
FILENAME = "lyrics.elf"
TEXT_SECTION = ".text"
EXECUTABLE_FLAG = 1
def get_symbols(filename: str) -> list:
elf = ELF(filename, checksec=False)
# Preia secțiunea .text
text_section = [
section for section in elf.sections if section.name == TEXT_SECTION
][0]
# Preia segmentele executabile
exe_segments = [
segment for segment in elf.segments
if segment.header.p_flags & EXECUTABLE_FLAG
]
# Verifică dacă segmentele executabile gasite se află în sectiunea .text
text_exe_segments = [
segment for segment in exe_segments
if segment.section_in_segment(text_section)
]
symbols = []
for segment in text_exe_segments:
# Preia adresele de start și de început ale segmentului curent
start_address = segment.header['p_vaddr']
end_address = start_address + segment.header['p_filesz']
# Preia simbolurile care se află în acest segment
for key in elf.symbols.keys():
address = elf.symbols[key]
if (address >= start_address and address < end_address):
symbols.append(key)
return symbols
def main() -> None:
all_symbols = get_symbols(FILENAME)
# Printează câte au fost găsite
log.info("Număr de simboluri gasite este {}.".format(len(all_symbols)))
# Printează simbolurile
log.info("Simbolurile găsite sunt:")
for symbol in all_symbols:
print("\t- {}".format(symbol))
if __name__ == "__main__":
main()
Comportamentul lui este asemănător utilitarului nm, ce putea fi utilizat aici astfel: nm lyrics.elf | egrep " (t|T) " | wc -l.
4 - Proveniența Simbolurilor 💁
lyrics, din zona de date, reprezintă o variabilă globală inițializată. Ca tip, era un vector de șiruri constante de caractere.puts, simbol nedefinit, este folosit la runtime, după ce linkarea dinamică alibcse produce.main, din zona de cod, reprezintă funcția principală a programului C.
5 - Automatizarea Analizei Dinamice cu pwntools 🏁
Flag
SI{CHECKING_THE_FLAG_BYTE_BY_BYTE}
Rezolvare
Simpla rulare a programului determina printarea unei erori ce specifică faptul că o librărie nu a fost găsită. Acest lucru se produce în ciuda faptului că librăria se află chiar în aceeași locație. Vom rula programul folosind prefixul LD_LIBRARY_PATH=..
Se observă faptul că executabilul afișează numai acea porțiune din șirul de caractere dat ca parametru ce corespunde cu flag-ul. Modul de rezolvare constă în efectuarea unui atac de tip brute-force, caracter cu caracter.
Se începe cu primul caracter, testându-se toate posibilitățile. La încercarea la care programul afișează un caracter, atunci îl putem seta ca primul caracter din flag-ul construit progresiv și putem trece la următorul până când caracterul } (terminatorul de flag) apare.
O implementare este oferită mai jos.
#!/usr/bin/env python3
import string
from pwn import log, context, process
FILENAME = "flag-checker.elf"
ENVIRONMENT = {'LD_LIBRARY_PATH': '.'}
CHARSET = string.ascii_uppercase + '_{}'
MAX_FLAG_LENGTH = 50
FLAG_END_CHAR = "}"
def main() -> None:
# Dezactivează mesajele de jurnalizare ale pwntools
context.log_level = "error"
current_flag = ""
for iteration in range(MAX_FLAG_LENGTH):
for char in CHARSET:
# Lansează un proces nou
p = process([FILENAME, current_flag + char], env=ENVIRONMENT)
# Preia ieșirea și verifică dacă ea produce vreo schimbare
output = p.recvall()
if len(output) > len(current_flag):
current_flag = output.decode("utf-8")
break
if (FLAG_END_CHAR in current_flag):
# Reactivează mesajele de jurnalizare ale pwntools
context.log_level = "info"
# Cum programul printează și alte caractere care nu sunt de interes,
# printăm numai prima linie
current_flag = current_flag.split("\n")[0]
log.success("Flag-ul este {}.".format(current_flag))
break
if __name__ == "__main__":
main()
6 - Librării Dinamice 💁
Rulând comanda ldd flag-checker.elf observăm faptul că executabilul folosește o librărie dinamică numită libcheckflag.so.
Pentru a identifica modul în care se apelează funcții din ea, rulăm comanda objdump --disassemble=main -M intel flag-checker.elf, ce dezasamblează codul funcției main (argumentul --disassemble) în sintaxa specifică Intel (argumentul -M). Astfel, se ajunge la concluzia că librăria este încărcată în memorie prin apelul funcției dlopen și funcția din ea, check_flag, este referențiată printr-un apel dlsym.
7 - Dezasamblarea Programelor 💁
Cum flag-ul nu poate fi găsit în strings, putem rula comanda objdump --disassemble=check_flag -M intel libcheckflag.so pentru a decompila funcția check_flag. Observăm șiruri de octeți ce sunt mutate în regiștrii (de exemplu, movabs rax,0x2a222429221a2832) și care sunt transformate ulterior prin xor-are cu 0x61 pentru a obține caracterele cu care se face compararea intrării furnizate de utilizator.
În același timp, Ghidra reușește să ușureze vizualizarea operațiunilor efectuate, prin modul său de decompilare:
[..]
local_38 = 0x2a222429221a2832;
local_30 = 0x3e2429353e262f28;
local_28 = 0x3538233e26202d27;
local_20 = 0x3538233e38233e24;
local_18 = 0x3224;
local_16 = 0x1c;
local_3c = 0;
[..]
bVar1 = *(byte *)((long)&local_38 + (long)(int)local_3c) ^ 0x61;
if (bVar1 != *(byte *)(param_1 + (int)local_3c)) {
[..]
8 - Apeluri de Sistem 💁
Verificând ieșirea comenzii strace python3 -m http.server 8080, observăm secvența de apeluri de sistem prezentă mai jos:
socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, IPPROTO_IP) = 3
[..]
bind(3, {sa_family=AF_INET, sin_port=htons(8080), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
[..]
listen(3, 5)
9 - Conexiuni 💁
Rulându-se comanda netstat -tlp, se poate identifica rapid socket-ul TCP (opțiunea -t) în modul listening (opțiunea -l). Opțiunea -p este adăugată pentru a afișa și datele de identificare ale procesului (PID și executabil).
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
[..]
tcp 0 0 0.0.0.0:http-alt 0.0.0.0:* LISTEN 53639/python3