Totally Not Malware
Challenge description
This definitely isn’t malware :)
The script looks something like this:
E=range
D=len
import base64 as H
import ctypes as A
import os
import mmap as B
def I(key,data):
A=list(E(256));C=0;F=bytearray()
for B in E(256):C=(C+A[B]+key[B%D(key)])%256;A[B],A[C]=A[C],A[B]
B=C=0
for G in data:B=(B+1)%256;C=(C+A[B])%256;A[B],A[C]=A[C],A[B];H=A[(A[B]+A[C])%256];F.append(G^H)
return bytes(F)
def C():J=H.b64decode('MASSIVE B64 STRING');K='9aad98c1'.encode('ascii');C=I(K,J);E=os.sysconf('SC_PAGE_SIZE');F=(D(C)+E-1)//E*E;L=B.mmap(-1,F,prot=B.PROT_READ|B.PROT_WRITE|B.PROT_EXEC);M=A.c_char*F;G=M.from_buffer(L);N=A.addressof(G);A.memmove(N,C,D(C));O=A.cast(G,A.CFUNCTYPE(A.c_void_p));O()
C()
With MASSIVE B64 STRING being around 4600 characters.
We can do some manual cleanup to deobfuscate the script.
import base64
import ctypes
import os
import mmap
def decrypt(key, data):
A = list(range(256))
C = 0
F = bytearray()
for B in range(256):
C = (C + A[B] + key[B % len(key)]) % 256
A[B], A[C] = A[C], A[B]
B = C = 0
for G in data:
B = (B + 1) % 256
C = (C + A[B]) % 256
A[B], A[C] = A[C], A[B]
H = A[(A[B] + A[C]) % 256]
F.append(G ^ H)
return bytes(F)
def main():
# decrypt the data
data = base64.b64decode("MASSIVE B64 STRING")
key = "9aad98c1".encode("ascii")
result = decrypt(key, data)
# allocate memory
page_size = os.sysconf("SC_PAGE_SIZE")
required_memory = (len(result) + page_size - 1) // page_size * page_size
memory = mmap.mmap(-1, required_memory, prot=mmap.PROT_READ | mmap.PROT_WRITE | mmap.PROT_EXEC)
char_array_type = ctypes.c_char * required_memory
char_array = char_array_type.from_buffer(memory)
address = ctypes.addressof(char_array)
# copy result to a function pointer
ctypes.memmove(address, result, len(result))
some_function = ctypes.cast(char_array, ctypes.CFUNCTYPE(ctypes.c_void_p))
# run the function
some_function()
main()
It decodes a large base64 encoded string and uses decrypt with a key to decrypt the data. It then loads the data into memory, casts it to a function pointer and calls it.
To be able to decompile it, we want to write it to a file instead of running it. We can do so with a small change to main.
def main():
# decrypt the data
data = base64.b64decode("MASSIVE B64 STRING")
key = "9aad98c1".encode("ascii")
result = decrypt(key, data)
# write to file
with open("totally_not_malware", "wb") as f:
f.write(result)
We can run the script and decompile the file it creates.
int64_t sub_0() {
sub_9f3();
return syscall(0);
}
void *sub_1c(int64_t arg1) {
void *result = sub_0;
while (*(result + arg1))
result += 1;
return result;
}
int64_t sub_4f(int64_t arg1) { return syscall(1, arg1, sub_1c(arg1)); }
int64_t sub_86(int64_t arg1) { return syscall(2, arg1, sub_1c(arg1)); }
void sub_bd(void *arg1, void *arg2) {
for (int32_t i = 0; i <= 3; i += 1) {
int32_t rax_5 = *(arg1 + (i << 2));
*(arg2 + (i << 2)) = rax_5;
*(arg2 + (i << 2) + 1) = rax_5 >> 8;
*(arg2 + (i << 2) + 2) = rax_5 >> 0x10;
*(arg2 + (i << 2) + 3) = rax_5 >> 0x18;
}
}
uint64_t sub_176(char arg1) __pure {
int32_t rax_3;
rax_3 = arg1 >= 0 ? 0 : 0x1b;
return rax_3 ^ (arg1 * 2);
}
uint64_t sub_1a2(char arg1, uint8_t arg2) {
char var_1c = arg1;
uint8_t i = arg2;
char var_9 = 0;
for (; i; i u >>= 1) {
if (i & 1)
var_9 ^= var_1c;
var_1c = sub_176(var_1c);
}
return var_9;
}
void sub_1ed(void *arg1, void *arg2) {
for (int32_t i = 0; i <= 0xf; i += 1)
*(arg2 + i) = *(arg1 + i);
int32_t i_1 = 0x10;
int32_t var_14 = 1;
while (i_1 <= 0xaf) {
char var_21;
for (int32_t j = 0; j <= 3; j += 1)
(&var_21)[j] = *(arg2 + j + i_1 - 4);
if (!(i_1 & 0xf)) {
char rax_12 = var_21;
char var_20;
var_21 = var_20;
var_21 = *(var_21 + &data_b00);
char var_1f;
var_20 = *(var_1f + &data_b00);
char var_1e;
var_1f = *(var_1e + &data_b00);
var_1e = *(rax_12 + &data_b00);
int32_t rax_33 = var_14;
var_14 = rax_33 + 1;
var_21 = (var_21 ^ *(rax_33 + &data_d00));
}
for (int32_t j_1 = 0; j_1 <= 3; j_1 += 1) {
*(arg2 + i_1) = *(arg2 + i_1 - 0x10) ^ (&var_21)[j_1];
i_1 += 1;
}
}
}
void sub_37a(void *arg1, void *arg2) {
for (int32_t i = 0; i <= 0xf; i += 1)
*(arg1 + i) ^= *(arg2 + i);
}
void sub_3d4(void *arg1) {
for (int32_t i = 0; i <= 0xf; i += 1)
*(arg1 + i) = *(*(arg1 + i) + &data_c00);
}
int64_t sub_426(void *arg1) {
char rax_1 = *(arg1 + 0xd);
*(arg1 + 0xd) = *(arg1 + 9);
*(arg1 + 9) = *(arg1 + 5);
*(arg1 + 5) = *(arg1 + 1);
*(arg1 + 1) = rax_1;
char rax_14 = *(arg1 + 2);
*(arg1 + 2) = *(arg1 + 0xa);
*(arg1 + 0xa) = rax_14;
char rax_21 = *(arg1 + 6);
*(arg1 + 6) = *(arg1 + 0xe);
*(arg1 + 0xe) = rax_21;
char result = *(arg1 + 3);
*(arg1 + 3) = *(arg1 + 7);
*(arg1 + 7) = *(arg1 + 0xb);
*(arg1 + 0xb) = *(arg1 + 0xf);
*(arg1 + 0xf) = result;
return result;
}
void sub_529(void *arg1) {
for (int32_t i = 0; i <= 3; i += 1) {
int32_t rax_2 = i << 2;
char rax_6 = *(arg1 + rax_2);
char rax_11 = *(arg1 + rax_2 + 1);
char rax_16 = *(arg1 + rax_2 + 2);
char rax_21 = *(arg1 + rax_2 + 3);
*(arg1 + rax_2) = sub_1a2(rax_6, 0xe) ^ sub_1a2(rax_11, 0xb) ^
sub_1a2(rax_16, 0xd) ^ sub_1a2(rax_21, 9);
*(arg1 + rax_2 + 1) = sub_1a2(rax_6, 9) ^ sub_1a2(rax_11, 0xe) ^
sub_1a2(rax_16, 0xb) ^ sub_1a2(rax_21, 0xd);
*(arg1 + rax_2 + 2) = sub_1a2(rax_6, 0xd) ^ sub_1a2(rax_11, 9) ^
sub_1a2(rax_16, 0xe) ^ sub_1a2(rax_21, 0xb);
*(arg1 + rax_2 + 3) = sub_1a2(rax_6, 0xb) ^ sub_1a2(rax_11, 0xd) ^
sub_1a2(rax_16, 9) ^ sub_1a2(rax_21, 0xe);
}
}
int64_t sub_72f(void *arg1, void *arg2) {
sub_37a(arg1, arg2 + 0xa0);
for (int32_t i = 9; i > 0; i -= 1) {
sub_426(arg1);
sub_3d4(arg1);
sub_37a(arg1, (i << 4) + arg2);
sub_529(arg1);
}
sub_426(arg1);
sub_3d4(arg1);
return sub_37a(arg1, arg2);
}
uint64_t sub_7e1(void *arg1, int32_t arg2, void *arg3, void *arg4) {
if (arg2 <= 0 || arg2 & 0xf)
return 0xffffffff;
void var_d8;
sub_1ed(arg3, &var_d8);
char var_e8[0x10];
for (int32_t i = 0; i <= 0xf; i += 1)
var_e8[i] = *(arg4 + i);
for (int32_t i_1 = 0; i_1 < arg2; i_1 += 0x10) {
char var_f8[0x10];
for (int32_t j = 0; j <= 0xf; j += 1)
var_f8[j] = *(arg1 + j + i_1);
sub_72f(i_1 + arg1, &var_d8);
for (int32_t j_1 = 0; j_1 <= 0xf; j_1 += 1)
*(arg1 + j_1 + i_1) ^= var_e8[j_1];
for (int32_t j_2 = 0; j_2 <= 0xf; j_2 += 1)
var_e8[j_2] = var_f8[j_2];
}
char rax_36 = *(arg1 + arg2 - 1);
if (!rax_36 || rax_36 > 0x10)
return 0xfffffffe;
char var_1d_1 = 0;
for (int32_t i_2 = 0; i_2 < rax_36; i_2 += 1)
var_1d_1 |= *(arg1 + arg2 - 1 - i_2) ^ rax_36;
if (!var_1d_1)
return arg2 - rax_36;
return 0xfffffffd;
}
int64_t sub_9f3() {
void var_28;
sub_bd(&data_ae0, &var_28);
void var_38;
sub_bd(&data_af0, &var_38);
int32_t var_c = 0x20;
int32_t rax = sub_7e1(&data_d60, 0x20, &var_28, &var_38);
if (rax < 0)
return sub_86("decrypt failed\n");
if (rax > 0x3f)
return sub_86("plaintext too large to NUL-terminate");
*(rax + &data_d60) = 0;
return sub_4f(&data_d60);
}
Unfortunately, since the data is not an executable, it does not have an ELF header so we cant simply use hex-rays and get the data values. On the other hand looking at this code, its relatively easy to tell that it is AES decryption, so we wont need to write much code since we can use pycryptodome AES.
Since we dont have the data given to us, we will have to find where the data we want is and load it from there.
Generally for AES decrytion we want:
- a 16 byte key
- a 16 byte iv
- a multiple of 16 byte length cyphertext
We can look at sub_9f3 and immediately tell whats what.
int64_t sub_9f3() {
void var_28;
sub_bd(&data_ae0, &var_28); // THIS LOADS 0xAE0
void var_38;
sub_bd(&data_af0, &var_38); // THIS LOADS 0xAF0
int32_t var_c = 0x20;
int32_t rax = sub_7e1(&data_d60, 0x20, &var_28, &var_38); // THIS LOADS 0xD60
if (rax < 0)
return sub_86("decrypt failed\n");
if (rax > 0x3f)
return sub_86("plaintext too large to NUL-terminate");
*(rax + &data_d60) = 0;
return sub_4f(&data_d60);
}
data_ae0, data_af0, and data_d60 tell us the locations of the data.
We can write a script to load those memory adresses from the binary and decrypt the flag.
from Crypto.Cipher import AES
def extract(blob):
key_words = blob[0xAE0:0xAE0 + 16] # 16 bytes
iv_words = blob[0xAF0:0xAF0 + 16] # 16 bytes
ciphertext = blob[0xD60 : 0xD60 + 64] # a multiple of 16 bytes
# reconstruct key/iv
# sub_bd expands each word little endian to 4 bytes
def expand_words(data):
out = bytearray()
for i in range(0, 16, 4):
word = int.from_bytes(data[i : i + 4], "little")
out.extend(word.to_bytes(4, "little"))
return bytes(out)
key = expand_words(key_words)
iv = expand_words(iv_words)
return key, iv, ciphertext
if __name__ == "__main__":
with open("totally_not_malware", "rb") as f:
blob = f.read()
key, iv, ciphertext = extract(blob)
print(f"key: {key.hex()}")
print(f"iv: {iv.hex()}")
print(f"ciphertext: {ciphertext.hex()}")
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
print("flag: ", plaintext.decode("utf-8", errors="ignore"))
Running the script gives us:
key: e79921cf440d93e720f34578427ee4ca
iv: 459ae03beb45edb844a968b2b650db84
ciphertext: d7032b4bf1fce7187040b84fdb99ed58e3bcca63373795d6d0c820ee0c75b709fcab60ad217c0073548b26e77fbf7619bc5a0c33766721c7c92727e14638ea40
flag: RISC{barely_broke_a_sweat_d0ec8cb6a9d8e9ad2ae0ba5b10db8254}
Our flag is:
RISC{barely_broke_a_sweat_d0ec8cb6a9d8e9ad2ae0ba5b10db8254} index