<jupyter_start><jupyter_text>SquirrelWaffel Config Extraction> Writing a config extractor for SquirrelWaffel- toc: true - badges: true- categories: [squirrelwaffel,malware,config]  OverviewNotes from our live stream where we reverse engineered the squirrelwaffle loader and built a static config extractor for it.> youtube: https://youtu.be/9X2P7aFKSw0 References- [Malware Traffic Analysis Sample 1](https://www.malware-traffic-analysis.net/2021/09/17/index.html)- [Malware Traffic Analysis Sample 2](https://www.malware-traffic-analysis.net/2021/09/21/index2.html)- [Unpacked Sample (malshare)](https://malshare.com/sample.php?action=detail&hash=6095f96dd5eca96a3fb9338eec4ab574921c0febb36f6a6db60aae1aeb9ffcab)  SetupMake sure you have pefile installed `pip install pefile` and you change the `SAMPLE_PATH` to the squirrelwaffle sample you have downloaded.<jupyter_code>import pefile
SAMPLE_PATH = r'/tmp/squirrel.bin'
data = open(SAMPLE_PATH,'rb').read()<jupyter_output><empty_output><jupyter_text>Extract the .rdata sectionThe `.rdata` section has the encrypted data and the decryption keys so let's grab that first.<jupyter_code>pe = pefile.PE(data=data)
rdata = None
for s in pe.sections:
    if b'.rdata' in s.Name:
        rdata = s.get_data()
len(rdata)<jupyter_output><empty_output><jupyter_text>Parse .rdataThe encrypted strings in the `.rdata` section are followed by their key and all data, keys, and strings are seperated by null bytes. We can parse out the data and key by splitting the `.rdata` on null bytes then looking for the largest blocks of data. These are likely the encrypted blocklist and the encrypted c2 list. We want to maintain two lists of data blocks, one sorted and one original order. This way we can use the sorted list to get the largest blocks, and the original order list to find the key which is sequentially after the encrypted data.<jupyter_code>blocks = rdata.split(b'\x00')
blocks = [x for x in blocks if x != b'']
blocks_sorted = sorted(blocks, key=len)<jupyter_output><empty_output><jupyter_text>Decryption functionThe config decryption is a simple xor so we want to replicate this in python<jupyter_code>def decrypt(key, data):
    out = ''
    for i in range(len(data)):
        out += chr(data[i] ^ key[i % len(key)])
    return out<jupyter_output><empty_output><jupyter_text>Decrypt the blocklistThe largest encrupted block is likely the blocklist so let's decrypt that first.<jupyter_code>for i in range(len(blocks)):
    if blocks[i] == blocks_sorted[-1]:
        out = decrypt(blocks[i+1], blocks[i])
print(out)<jupyter_output>172.16.31.10
172.16.31.10
172.16.31.10
172.16.31.10
172.16.31.10
192.168.127.12
172.16.17.32
172.16.58.3
172.16.17.32
172.16.58.3
172.16.58.3
172.16.17.32
192.168.127.12
172.16.17.32
172.16.58.3
192.168.3.11
172.16.58.3
172.16.31.10
172.16.58.3
192.168.3.11
172.16.17.32
172.16.31.10
192.168.127.12
172.16.58.3
172.16.17.32
172.16.58.3
172.16.17.32
192.168.3.11
172.16.31.10
172.16.17.32
192.168.3.11
172.16.31.10
172.16.17.32
172.16.58.3
192.168.127.12
172.16.58.3
192.168.3.11
172.16.31.10
172.16.58.3
192.168.127.12
172.16.31.10
172.16.31.10
192.168.3.11
192.168.3.11
172.16.58.3
192.168.127.12
192.168.3.11
172.16.31.10
192.168.3.11
192.168.127.12
192.168.127.12
192.168.127.12
172.16.58.3
172.16.17.32
172.16.58.3
172.16.17.32
172.16.58.3
172.16.31.10
172.16.31.10
192.168.3.11
192.168.3.11
172.16.31.10
172.16.17.32
172.16.31.10
172.16.58.3
172.16.58.3
192.168.3.11
192.168.127.12
172.16.31.10
172.16.31.10
109.[...]<jupyter_text>Decrypt the c2sThe second largest encrupted block is likely the C2 list so let's decrypt that now.<jupyter_code>for i in range(len(blocks)):
    if blocks[i] == blocks_sorted[-2]:
        out = decrypt(blocks[i+1], blocks[i])
print(out)<jupyter_output>celulasmadreenmexico.com.mx/Wt793Aua|gerencial.institutoacqua.org.br/XynFkhJAxnm|dashboard.adlytic.ai/LlvLoc9O3|bussiness-z.ml/3pdEiqsni|ifiengineers.com/hGVc55g2e|bonusvulkanvegas.srdm.in/U7oOxmI1m|ebrouteindia.com/JEqGe1hNR|test.dirigu.ro/dXf4cS4GPL|cablingpoint.com/LjDG0hkp|perfectdemos.com/T6PQGYCMt|afrizam.360cyberlink.com/f36rjSN5D1|giasuphire.tddvn.com/miFO43YP9b|priyacareers.com/GiTHMPbU|assurant.360cyberlink.com/DGx4k8U9Hil|sig.institutoacqua.org.br/tM7tINg2sCU|<jupyter_text>Clean up the c2 listIt looks like the C2 list is seperated by pipes `|` so let's turn that into a nice list.<jupyter_code>print(out.replace('|','\n'))<jupyter_output>celulasmadreenmexico.com.mx/Wt793Aua
gerencial.institutoacqua.org.br/XynFkhJAxnm
dashboard.adlytic.ai/LlvLoc9O3
bussiness-z.ml/3pdEiqsni
ifiengineers.com/hGVc55g2e
bonusvulkanvegas.srdm.in/U7oOxmI1m
ebrouteindia.com/JEqGe1hNR
test.dirigu.ro/dXf4cS4GPL
cablingpoint.com/LjDG0hkp
perfectdemos.com/T6PQGYCMt
afrizam.360cyberlink.com/f36rjSN5D1
giasuphire.tddvn.com/miFO43YP9b
priyacareers.com/GiTHMPbU
assurant.360cyberlink.com/DGx4k8U9Hil
sig.institutoacqua.org.br/tM7tINg2sCU
{'fraction_non_alphanumeric': 0.12456606085358382, 'fraction_numerical': 0.15683071268123341, 'mean_word_length': 4.762352941176471, 'pattern_counts': {'":': 0, '<': 26, '<?xml version=': 0, '>': 28, 'https://': 4, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 70, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 1, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '7', 'dir': 'jupyter-structured-clean-dedup', 'id': '398515', 'license': 'Apache-2.0', 'n_tokens_mistral': 2516, 'n_tokens_neox': 2068, 'n_words': 406, 'path': '_notebooks/2021-09-27-squirrelwaffle.ipynb', 'repo_name': 'OALabs/research', 'size': '4980'}