Hello everyone. It’s me! Long time no see.

After the whole busy (and lazy) year, now I’m here - writing again 🙂

The last year was a very important milestone for me with many interesting and messy things. Putting myself into a lot of situations and feelings, nervous, happy, pressure, sick, crazy, and sweet love. I’m not trying to say I’m becoming an adult right now, but for me it’s time to take full responsibilities for the path leading me to the future, to enlighten me.

I was focusing on some private projects, self development and not taking part in too much security activities in the last year, especially my most favorite hacker game: CTF. Yeah, I know, so ashamed. But you cannot defend if you blame yourself.

In a moment, it’s still a good time than…

Not trying to explain myself to the Internet, I just wanna write some feeling for the future me and who cares about me. And the most part is all about geek for who needs it.

OK, no matter who yourself are. Welcome and thanks for being here!

So, last week, I had a chance to play with a new team (old ppl ~.~) joining in Google Capture The Flag Contest. We were set into try hard mode, despite fighting to get a ticket to America in many years, but never successed.

After 2 days long, no sleep, no shower, no gaming, no time for family & lover… sadly, we did not make it 😔. Trying to solve as fast as we could, but the best effort is Top 10 for few hours before stayed in the top 20. Poor us!

But nevermind, it’s good to try hard and fail again like we did in the past =))

As the main RE guy in my team, I had solved some RE problems. Below, I’ll give you guys a short writeup of them 😀



We have an apk file. Using java-hexr4y aka JEB or any other dalvik decompiler (dex -> jar) to get the original java code (if lucky).

You can see that there is nothing special and nothing much to do in these code. Except it loaded an external library code by System.loadLibrary("cook");

Luckily there are 2 version of cook.so in x86 and armv7, so we can install application in the faster simulator.

But actually I did not run or debug anything while solving this challenge 😂.

Let’s have a look at shared library, it contains few functions. Notice that the most important thing we must check is JNI_OnLoad.

After quickly check some sub. We see that sub_680 is somekind of string encoding and used many times by other.

_BYTE *__cdecl sub_680(int a1, _DWORD *a2)
  _BYTE *v2; // ebp@1
  int v3; // ecx@1
  unsigned int v4; // edx@2

  v2 = malloc(2 * a1 + 1);
  v3 = 0;
  if ( a1 > 0 )
      v4 = (unsigned int)(&a2)[v3];
      v2[2 * v3] = ~((BYTE1(v4) | ~LOBYTE((&a2)[v3])) & (v4 | ~BYTE1(v4)));
      v2[2 * v3++ + 1] = (v4 >> 16) ^ HIBYTE(v4);
    while ( v3 != a1 );
  v2[2 * a1] = 0;
  return v2;

The not, or, and if you are similar with boolean math. We can easily recognize it as xor operator.

Then we compute it by this script. I also decode many string using it

from ctypes import *
from struct import pack

def wtf(ar):
    st = ''
    for a in ar[::-1]:
        st += pack("<I", a)
    sv = ''
    for i in range(0, len(st), 2):
        sv += chr(ord(st[i])^ord(st[i+1]))
    return sv

print ">>> doingit"
print wtf([0x61611F6C, 0x50200061, 0x4D20416E, 0x0F694C20, 0x96C1A69, 0x406F4320, 0x1B741361, 0x15656748])
print wtf([0x1B741361])
print wtf([0x44204A65, 0x5615D73, 0x1B631075][::-1])

print "\n>>> JNI LOAD"
print wtf([0x1D650B6E, 0x1377416F, 0x16724D62, 0x5320096C, 0x5691D74, 0x86E5A75, 0x4420046B, 0x6F096F, 0x4D634620, 0x10640D6E, 0x4F614520, 0x0A660265, 0x0A650D62, 0x47204A64, 0x36E1A75, 0x0C6F5D72, 0x6675420, 0x4650C68, 0x5B744120, 0x10640564, 0x25410F20])
print wtf([0x69695820, 0x17720D69, 0x1B745C73, 0x53200164, 0x26E0861, 0x4620032C, 0x17730A65, 0x0D621375, 0x4D634620, 0x1A6E0C6F, 0x426C096C, 0x5691275, 0x6F0D62, 0x47205C72, 0x8651976, 0x0C6F0F20, 0x465186C, 0x362096D, 0x5A751372, 0x37434120, 0x4A2E4B64])
print wtf([0x72721169, 0x11743753, 0x0E204A2E, 0x5C731D65, 0x11741175, 0x16E4669, 0x1E6D4520, 0x1B770C65, 0x660F20, 0x5614F20, 0x1D72096F, 0x48664620, 0x1F6B0C6F, 0x416F0663, 0x4C20086F, 0x1B744F20, 0x2655B75, 0x36E0669, 0x1774416E, 0x0E6F1763, 0x41205F3B, 0x4A650F6E, 0x1D691677, 0x4420416E])
print wtf([0x20207F2C, 0x5C730165, 0x6F1B74, 0x761426D, 0x96F0074, 0x43204B64, 0x651C70, 0x1770006F, 0x7680463, 0x0F20036E, 0x6694320])
print wtf([0x8610B67,  0x86C1072, 0x0E631F69, 0x5320022C, 0x6E6E0E61][::-1])
print wtf([0x0B720470, 0x8610670, 0x49201C68, 0x17742869, 0x13774F20, 0x16E296F, 0x5C730561, 0x0A653C53, 0x4620012E, 0x3650074, 0x10734E61, 0x15704C20, 0x86F1B74, 0x0E610A6D, 0x406F1974, 0x4F200764])
print wtf([0x96B1E69])
print wtf([0x2700065, 0x14704120, 0x0A65226E, 0x1D6E1665, 0x18790D61, 0x20635820, 0x492C3672, 0x4A650964, 0x12771B6F, 0x3705920, 0x1A69436C, 0x2690168, 0x15634C20, 0x4D2C0561])
print wtf([0x3E680920, 0x53681567, 0x0C690C68, 0x41201D72, 0x29650576, 0x1C6F4120, 0x6C2A69, 0x406F0562, 0x4E200061, 0x4C20406F, 0x15745620, 0x667046E, 0x25694972, 0x25424E20, 0x472E0072, 0x11652370, 0x5F700265, 0x1E704120, 0x864416E, 0x615620, 0x1574066C, 0x2D614873, 0x4720006E, 0x695220, 0x6723A69, 0x5B743453, 0x4E204F2E, 0x15794A65, 0x0D6C0573, 0x13720B61, 0x3C701B20, 0x364006E, 0x8615220, 0x582C3C6F, 0x416E0661, 0x9670465, 0x1E72406F, 0x41205A2C, 0x0D6C0369, 0x3F734961])
print wtf([0x5C625420, 0x452C1C72, 0x0C654C70])
print wtf([0x6565566D, 0x1E6D1A69, 0x12734C20, 0x2764416E, 0x6614E20, 0x4D2C1E72, 0x4A651776, 0x196F2243, 0x4A20622E, 0x5E77546F, 0x0B6C4E20, 0x46D0775, 0x1D693764, 0x4A650A6D, 0x4E200E6F, 0x18740F20, 0x15741761, 0x4650268, 0x6C204D65])
print wtf([0x63630675, 0x17640465, 0x3E526320, 0x4A2E1574, 0x0E610965])
print wtf([0x73733761, 0x4A635863, 0x166F5420, 0x0E67186E, 0x690672, 0x11722869, 0x5B740373, 0x50204D2C, 0x5C730165, 0x1D741A75, 0x1C6E0D69, 0x36D4120, 0x7C301139])
print wtf([0x5C625420, 0x452C1C72, 0x0C654C70])
print wtf([0x172C0A73, 0x1A6E0861, 0x13650B62, 0x54201A79, 0x2465416E, 0x14641969, 0x0A6B0F20, 0x0A6E0069, 0x4F200072, 0x0D691A74, 0x32536C20])
print wtf([0x56C1A6C, 0x8611A6E, 0x0C6F0869])

Xref sub_680 back to JNI_OnLoad. We see that it will

  • fopen(“/data/data/com.google.ctf.food/files/d.dex”, “wb”)
  • fwrite(“dex\n035…”)
  • call a function with d.dex


It’s a new dex file. So maybe the program tries to load a new dalvik code into memory, that’s the reason why apk file does not contain anything fun.

Decompile new dex file. LoL, there are many food.


Flag and Decrypt function in here too. It uses a BroadcastReceiver to catch input by ID. And store IDs in the same class with flag.

But I can’t find the reference to flag, and also the R class too. So maybe there more secret in the shared library.

Lets take a look to another function sub_710

You will see the calling to open(/proc/self/map) and try to read the segment address of d.dex file loaded in memory.


After that it checks for DEX header and try to change a part of memory by simple xor operator.

Maybe its the point. Fixing the new dex file with this python script. We have

public void cc() {
    int v4 = 8;
    byte[] v1 = new byte[]{26, 27, 30, 4, 21, 2, 18, 7};
    int v0;
    for(v0 = 0; v0 < v4; ++v0) {
        v1[v0] = ((byte)(v1[v0] ^ this.k[v0]));

    if(new String(v1).compareTo("\u0013\u0011\u0013\u0003\u0004\u0003\u0001\u0005") == 0) {
        Toast.makeText(this.a.getApplicationContext(), new String(.(F.flag, this.k)), 1).show();

Finally we can get the right key and decrypt the flag.

SDL - Moon

SDL - Simple DirectMedia Layer is a cross-platform development library designed to provide low level access to audio, keyboard, mouse, joystick, and graphics hardware via OpenGL and Direct3D. This challenge is based on SDL Graphic Library.

It’s very interesting. I used to work with SDL for my game project in university and solve some RE problems written in shader language in OpenGL/DirectX. Maybe this challenge is similar this time.

Open program and type some random input

  • You can only hit ascii key and backspace.
  • The max input length is 32.
  • It will pop out “Nope” when max length hit

So maybe the flag checking function is triggered when input 32 bytes.

Open IDA and take a quick look. Program is written in WinMain type and main address is sub_498A10.

SDL library has been initialized here and they use gl3w as well.

For who does not know what gl3w is. It is an OpenGL wrapper.

As I said before, I used to work with SDL for a short time and I know the structure of the program. You can also learn quickly about it, very simple and clear. Ref


From the fuzzing result we found above. We must focus on keyboard handler. Check sdl tutorial for event

It’s the SDL_PollEvent main loop. You can put some breakpoint after the if statement in Pseudocode and debug to find out flag check function. There are many compare of 32 by qword_4CA088 and if they’re equal we trigger sub_401BF0 - Input processing function.


Dig into this function, many call to qword_xxxxx is very common for malware analyzer (using GetProcAddress). And correct, they all have reference from sub_4032C0 for mapping import from external dll. I write a IDAPython script to rename all load functions with a maptable (you can try).

import idc
a = open('name.txt','rb').read().split()
for i in range(0, len(a), 2):
  if not a[i].startswith("gl"):
    print "Not good", a[i]
  if not a[i+1].startswith("4"):
    print "Not good", a[i+1]
  idc.MakeNameEx(int(a[i+1], 16), "qw_{0}".format(a[i]), idc.SN_NOWARN)

Now sub_401BF0 is very clear.


The function’s structure is predictable. You can search these gl call for more info, a GPU calculation using GL Shader Language.

So flag is passed to shader script and computed by GPU, then how can we find the script?

We just need to breakpoint glShaderSource and get the script. There are 3 shader files, 1 vector shader, 1 fragment shader, and the other one is below. I’m good at static analyse this time so I try to trace back the reference and get the last script by xor decode some data in memory.

#version 430
layout(local_size_x = 8, local_size_y = 8) in ;
layout(std430, binding = 0) buffer shaderExchangeProtocol {
  uint state[64];
  uint hash[64];
  uint password[32];
vec3 calc(uint p) { //multiply matrix
  float r = radians(p);
  float c = cos(r);
  float s = sin(r);
  mat3 m = mat3(c, -s, 0.0, s, c, 0.0, 0.0, 0.0, 1.0);
  vec3 pt = vec3(1024.0, 0.0, 0.0);
  vec3 res = m * pt;
  res += vec3(2048.0, 2048.0, 0.0);
  return res;
uint extend(uint e) { //
  uint i;
  uint r = e ^ 0x5f208c26;
  for (i = 15; i < 31; i += 3) {
    uint f = e << i;
    r ^= f;
  return r;
uint hash_alpha(uint p) { //chan
  vec3 res = calc(p);
  return extend(uint(res[0]));
uint hash_beta(uint p) { //le
  vec3 res = calc(p);
  return extend(uint(res[1]));
void main() {
  uint idx = gl_GlobalInvocationID.x + gl_GlobalInvocationID.y * 8;
  uint final;
  if (state[idx] != 1) {
  if ((idx & 1) == 0) { //chan
    final = hash_alpha(password[idx / 2]);
  } else { //le
    final = hash_beta(password[idx / 2]);
  uint i;
  for (i = 0; i < 32; i += 6) {
    final ^= idx << i;
  uint h = 0x5a;
  for (i = 0; i < 32; i++) {
    uint p = password[i];
    uint r = (i * 3) & 7;
    p = (p << r) | (p >> (8 - r));
    p &= 0xff;
    h ^= p;
  final ^= (h | (h << 8) | (h << 16) | (h << 24));
  hash[idx] = final;
  state[idx] = 2;

So our 32 bytes password is passed into this script and we get hash[64] and state[64] as output.

After that, back to the main loop, we see the memcmp of the hash to a const hash embeded in executable file. I use Frida to know that, because somehow my debugger is not able to run, but I already know the above result after static analyze. Frida is a great dynamic instrument tool to do many things to running process. Hook script below

Interceptor.attach(ptr("0x41DFF8"), { //memcmp
  onEnter(args) {
    // send to parent
    var buf1 = Memory.readByteArray(args[0], 512);
    console.log(hexdump(buf1, {
      offset: 0,
      length: 64*8,
      header: true,
      ansi: true


    var buf2 = Memory.readByteArray(args[1], 512);
    console.log(hexdump(buf2, {
      offset: 0,
      length: 64*8,
      header: true,
      ansi: true


Final step here is decrypting flag from this const. If you look closely the hash function above is

  • Get 1 byte password
  • Hash_alpha and Hash_beta into 2 new bytes
  • Store 2 bytes in to hash
  • Xor all hash with 1 same byte computed by password.

So we can generate 1 hash_table for each chars in each index. Then bruteforce the last byte to compare.

My solution

from math import *
import string
from struct import pack, unpack

def radian(p):
  return p / 180.0 * pi;

def extend(e):
  r = e ^ 0x5f208c26;
  for i in range(15, 31, 3):
    f = (e << i) & 0xffffffff
    r ^= f
  return r

def hash_alpha(p):
  return extend(int( (cos(radians(p)) * 1024.0 + 2048.0) ))

def hash_beta(p):
  return extend(int( ( -sin(radians(p)) * 1024.0 + 2048.0) ))

map_table = []
for j in range(32):
  ar = []
  for c in string.printable:
    a0 = hash_alpha(ord(c))
    a1 = hash_beta(ord(c))

    for i in range(0, 32, 6):
      a0 ^= ( (j*2) << i) & 0xffffffff
      a1 ^= ( (j*2+1) << i) & 0xffffffff

    ar.append((c, a0, a1))

res = "30c7ead97107775969be4ba00cf5578f1048ab1375113631dbb6871dbe35162b1c62e982eb6a7512f3274743fb2e55c818912779ef7a34169a838666ff3994bb4d3c6e14ba2d732f14414f2c1cb5d3844935aebbbe3fb206343a004e18a092daba02e3c0969871548ed2c372eb68d1af41152cb3b61f300e3c1a8246108010d282e16df8ae7bff6cb6314d4ad38b5f9779ef23208efe3e1b699700429eae1fa93c036e5dcbe87d32be1ecfac2452ddfdc704a00ea24fbc2161b7824a968e9da1db756712be3e7b3d3420c8f33c37dba42072a941d799ba2eebbf86191cb59aa49a80ebe0b61a79741888cb62341259f62848aad44df2b809383e09437928980f"
res = res.decode("hex")

flag = ""
for xor in range(256):
  rac = ""
  for c in res:
    rac += chr(ord(c)^xor)
  rac = rac.encode('hex')

  for i in range(32):
    num1 = int(rac[16*i : 16*i + 8], 16)
    num2 = int(rac[16*i + 8: 16*i + 16], 16)
    for c, ar0, ar1 in map_table[i]:
      if ar0 == num1 and ar1 == num2:
        flag += c
print flag

Protobuf - aart_client

Yeah, I have known protobuf for a long time, but I’ve never done any RE thing about it.

Protocol Buffers (a.k.a., protobuf) are Google’s language-neutral, platform-neutral, extensible mechanism for serializing structured data. You write a data model and protobuf will compile it into C/Python/Java/…; code for parsing data structure as you defined.

The first thing we must find out is the proto this app uses. Credit to @jot, @tinduong. Actually the proto model compiled is raw model in binary file.

  • Find .proto file -> aart_message.proto
  • Extract the fews next bytes
  • Use this tool by tjac . Link

We’ve got

syntax = "proto2";

package aart_message;

message AartMessage {
         required AartMessageType type = 1;
         required string client_id = 2;
         required string content = 3;
        enum AartMessageType {
                R_HELLO = 0;
                R_CAPABILITIES = 1;
                R_OPERATION = 2;
                R_OPERATION_RESPONSE = 3;

Using sample code for generate sample protobuf message

#! /usr/bin/python
# protoc ---python_out=./ aart_message.proto

import aart_message_pb2
import sys

aart_message = aart_message_pb2.AartMessage()
aart_message.type = aart_message_pb2.AartMessage.R_HELLO
aart_message.client_id = "woxikbdgnirgqylajxaihxpsothaqoro"
aart_message.content = "HELLO"

enc = aart_message.SerializeToString()

print len(enc)
import hexdump

00000000: 08 00 12 20 77 6F 78 69  6B 62 64 67 6E 69 72 67  ... woxikbdgnirg
00000010: 71 79 6C 61 6A 78 61 69  68 78 70 73 6F 74 68 61  qylajxaihxpsotha
00000020: 71 6F 72 6F 1A 05 48 45  4C 4C 4F                 qoro..HELLO

Open pcap file. there are 6 http request, data sent are in hex format like this

00000000: 00 4C 4C 48 45 1A 05 75  65 6E 79 6C 6D 7A 64 61  .LLHE..uenylmzda
00000010: 68 6C 62 6C 70 69 63 6C  77 79 62 6D 65 76 68 79  hlblpiclwybmevhy
00000020: 65 6D 62 76 6D 70 7A 12  20 08 64                 embvmpz. .d

Again, it’s very simillar as serialized message above, but a little bit difference. So after serializing, aart_client encrypt the result a little bit and send to server. And also the response maybe uses the same method.

I’m not supposed to explain everything here, the encrypt/decrypt process is based on these funcs

  1. Swap 2 bytes next together
  2. Xor data with last byte
  3. Xor data with len(data) & 0xff
  4. Xor data with first byte
  5. Reverse data


def swap(st):
  sv = ""
  for i in range(0, len(st)-len(st)%2, 2):
    sv += st[i+1] + st[i]
  if len(st) % 2 == 1:
    sv += st[-1]
  return sv

def xor(st, c):
  sv = ""
  for i in range(len(st)):
    sv += chr(ord(st[i])^ c)
  return sv

dec = "037e6c612e496c67446c3c2e7168746c772e6c6c2e65667a6d686e6c652e2e6c6c772e6f2e6262702e746277576b457840571931746c7b6a686167646d6a7164727a6f62697b626a6b7b73706c776b62726c716c11230b7a".decode("hex")
dec = dec[::-1] #5
dec = dec[0] + xor(dec[1:], ord(dec[0])) #4
dec = xor(dec, len(dec) & 0xff) #3
dec = xor(dec[:-1], ord(dec[-1])) + dec[-1] #2
dec = swap(dec) #1
print dec

VM – counter

How can I explain to how to solve this one 😁. Virtual machine challenges are always the most immotivate thing you have to do.

With the name counter and input number must be 9009131337, we can make a guess it might use multiple loop to annoy us.

Reverse the machine, luckily, there are only 3 opcode (or maybe not the luck we wanted…)

from struct import unpack

code = open('code','rb').read()[4:]

ar = []
for i in xrange(0, len(code), 16):
  ar.append(unpack("<IIII", code[i:i+16]))

for i in xrange(len(ar)):
  b0, b1, b2, b3 = ar[i]
  if b0 == 0:
    print "0x%2x:\treg[%2x]++; jmp 0x%2x" % (i, b1, b2)
  if b0 == 1:
    print "0x%2x:\tif reg[%2d]:\n\t\t\treg[%2d]--; jmp 0x%2x;\n\t\telse: jmp 0x%2x" % (i, b1, b1, b2, b3)
  if b0 == 2:
    print "0x%2x:\tclone reg, process 0x%2x, copy to %2d; jmp 0x%2x" % (i, b2, b1, b3)

Script above generates an pseudo code. After 3 or 4 hours reconstruct these noisy code, I’ve optimized it into

def AAAA(r1):
    r2 = 0
    while True:
        r0 = r1
        r3 = r0
        if r3 < 2: break
        if r1 % 2 == 0:
            r0 = r1 / 2
            r0 = 3*r1 + 1
        r2 += 1
        r1 = r0

    return r2

def BBBB(r0, r1, r2, r3):
  return r1 % r2
  while True:
    if r2>r1:
      r0 = r1
      return r0
    r1 = r1 - r2

def CCCC(r0, r1, r2, r3):
  r0 = r1
  r3 = r0
  r0 = 0
  if r3 == 0: return r0
  r3 -= 1
  r0 += 1
  if r3 == 0: return r0
  r3 -= 1
  if r1 ==0: return r0
  r1 -= 1
  r0 = CCCC(r0, r1, r2, r3)
  r4 = r0
  r0 = 0
  if r1 == 0: return r0
  r1 -= 1
  r0 = CCCC(r0, r1, r2, r3)
  r4 += r0
  r0 = 0
  r1 = r4
  r4 = 0
  r0 = BBBB(r0, r1, r2, r3)
  return r0

def main(inp):
  s = 0
  for i in range(inp+1):
      s += AAAA(i)

  return CCCC(0, inp, s, 0)

number = 30 #9009131337
result = main(number)
print "CTF{%016x}" % result

  • AAAA is the jump step for 3 * n + 1 problem. Link
  • BBBB is just modulo operator
  • CCCC is n(th) fibonacii number
  • So the flag number is CCCC(num) % ( AAAA(0) + AAAA(1) + ... + AAAA(num) )

The last trouble is the big running time for 9009131337

To solve the 3 * n + 1 problem, I used C threading. Credit to @knight9 for 16 cores cpu 🙂

#include <stdio.h>
#include <string.h>
#include <pthread.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>

#define GOAL 9009131337
#define NUM 16

typedef struct {
    int id;
    uint64_t min;
    uint64_t max;
} ManStruct;

uint64_t poolres[NUM];
pthread_t tid[NUM];

uint64_t collatz(uint64_t r1) {
    uint64_t r2 = 0;
    while (1) {
        if (r1<2) break;
        if (r1 %2 == 0)
            r1 = r1 / 2;
            r1 = 3*r1 + 1;
    return r2;

void TaoDauQuaMan(void* args) {
    ManStruct notMan = *(ManStruct*)args;
    int id = notMan.id;
    uint64_t min = notMan.min;
    uint64_t max = notMan.max;
    uint64_t i, res = 0, cur = 0;
    for (i=min; i<=max; i++) {
        cur = collatz(i);
        res += cur;
        if (i % 1000000 == 0)
            printf("%2d - %11ld - %6ld - %20ld\n", id, i, cur, res);
    printf("[%d] DONE! result [%ld - %ld] = %ld\n", id, min, max, res);
    poolres[id] = res;
    return 0;

int main()
    uint64_t jump = GOAL / (NUM-1), myMax;
    ManStruct* man;
    int i = 0;
    for (i=0; i<NUM; ++i) {
        man = (ManStruct*)malloc(sizeof(ManStruct));
        man->id = i;
        man->min = jump*i;
        if ( jump*(i+1) > GOAL )
            man->max = GOAL;
            man->max = jump*(i+1) - 1;
        pthread_create(&(tid[i]), NULL, &TaoDauQuaMan, man);

    uint64_t FinalOfFinal = 0;
    for (i=0; i<NUM; ++i) {
        pthread_join(tid[i], NULL);
        FinalOfFinal += poolres[i];

    printf("ALL DONE\n");
    printf("Result = %ld\n", FinalOfFinal);

    return 0;

And to find final result, we must calculate n(th) fibonacii. Credit to @tinduong for this solution. Link

The last challenge is embed game for Arduino presented in Intel Hex format.

How can they considered it is easy problem? At anytime, I always find it hard no matter how low point it is 🙁 , Without a single clue how to do with this binary after load into IDA by AVR processor and find some special strings. I give up, that’s all…

Haha, if you are still here, so you must be a low-level-system-lover like me 🙂

In the near future, maybe I’ll write more often than, at least not 1 post per year like this =]. Haha

Thanks again, and don’t be shy to ask me anything.