2021 RaRCTF - Writeup

Mon, Aug 9, 2021 7-minute read

題目:verybabyrev, Dotty, RaRPG, Infinite Free Trial, Very TriVial ReVersing, Jammy's Old Infra, boring flag checker, Down The Rabbit Hole

Score:NCtfU - 3rd


verybabyrev

Challenge Info

fun fact: verybabyrev backwards is verybabyrev

file:verybabyrev - ad2b44

Solution

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
enc = [0x45, 0x48, 0x1D, 0x12, 0x17, 0x11, 0x13, 0x13][::-1] + \
    [0x09, 0x5F, 0x42, 0x2C, 0x26, 0x0B, 0x41, 0x45][::-1] + \
    [0x54, 0x1B, 0x56, 0x56, 0x3D, 0x6C, 0x5F, 0x0B][::-1] + \
    [0x58, 0x5C, 0x0B, 0x3C, 0x29, 0x45, 0x41, 0x5F][::-1] + \
    [0x40, 0x2A, 0x6C, 0x54, 0x09, 0x5D, 0x5F, 0x00][::-1] + \
    [0x4B, 0x5F, 0x42, 0x48, 0x27, 0x6A, 0x06, 0x06][::-1] + \
    [0x6C, 0x5E, 0x5D, 0x43, 0x2C, 0x2D, 0x42, 0x56][::-1] + \
    [0x6B, 0x31, 0x5E, 0x43, 0x47, 0x07, 0x41, 0x2D][::-1] + \
    [0x5E, 0x54, 0x49, 0x1C, 0x6E, 0x3B, 0x0A, 0x5A][::-1] + \
    [0x28, 0x28, 0x47, 0x5E, 0x05, 0x34, 0x2B, 0x1A][::-1] + \
    [0x06, 0x04, 0x50, 0x07, 0x3B, 0x26, 0x11, 0x1F][::-1] + \
    [0x0A, 0x77, 0x48, 0x03, 0x05, 0x0B, 0x0D, 0x04][::-1]

flag = [ord('r')]

for i, v in enumerate(enc):
    flag.append(flag[-1] ^ v)
    print(chr(flag[-2]), end='')

Flag

rarctf{3v3ry_s1ngl3_b4by-r3v_ch4ll3ng3_u535_x0r-f0r_s0m3_r34s0n_4nd_1-d0nt_kn0w_why_dc37158365}


Dotty

Challenge Info

My new program will keep your secrets safe using military grade encryption!

file:Dotty.exe - 81a733

Solution

It’s a .Net assembly, so simply decode with DNSpy.

A morse code string can be found in Check().

1
2
3
4
5
6
7
// Dotty.Check
// Token: 0x06000007 RID: 7 RVA: 0x000022EC File Offset: 0x000004EC
// Note: this type is marked as 'beforefieldinit'.
static Check()
{
	Check.check = "-|....|.|/|..-.|.-..|.-|--.|/|..|...|/|---|.---|--.-|-..-|.|-.--|...--|..-|--|--..|.....|.--|..|--|.-..|.|.-..|.....|....-|-|.-|.....|-.-|--...|---|.-|--..|-|--.|..---|..---|--...|--.|-...|--..|..-.|-....|-.|.-..|--.-|.--.|.|--...|-|-....|.--.|--..|--...|.-..|.....|-|--.|-.-.|-.|-..|-...|--|--|...--|-..|.-|-.|.-..|.....|/|-...|.-|...|.|...--|..---";
}

Decode the string with | as letter and / as word delimeter, and get

THE FLAG IS OJQXEY3UMZ5WIMLEL54TA5K7OAZTG227GBZF6NLQPE7T6PZ7L5TGCNDBMM3DANL5 BASE32

Decode from Base32 and get the flag.

Flag

rarctf{d1d_y0u_p33k_0r_5py????_fa4ac605}


RaRPG

Challenge Info

Challenge instance ready at 193.57.159.27:60415 I’ve been building a brand new Massively(?) Multiplayer(?) Online Role-Playing(?) Game(?) - try it out! Just don’t try and visit the secret dev room…

If you do not have the linked libraries, download rarpg-libs.zip and extract the libraries into your working directory. Then use

LD_LIBRARY_PATH=$(pwd) ./client <ip> <port>

file:client - edcfd5, rarpg-libs.zip - e89a64, rarpg-libs.zip - e89a64

Solution

It’s a simple game. We can control * to go into portals (X), and any other characters are refrred to as the wall.

Apparently, the goal is to go into the portal in the east.

I found the code which can modify character’s corodinate by pressing arrow keys, so I patched the moving distance from 1 to 2 when the up arrow key is pressed.

Then just play the game and get the flag.

Flag

rarctf{g4m3_h4ck1ng_f0r_n00b5!-75f8b0}


Infinite Free Trial

Challenge Info

We’ve decided to make an app specially for flag hoarding, can you make sure no one can crack it?

NOTE: The flag is a valid registration key

file:ift.zip - 12b533

Solution

It’s a serial key checker. Analyze statically in IDA.

In func(), there is a 2-step verification (do_crc_check() and do_xor_check()).

However, the crc check is useless as it is simple to reverse the xor check and get flag.

1
2
3
4
5
6
7
8
9
xor_check = b'\t\x16\x17\x0f\x17V\x16D:\x18So\x14\x03*\x06o1\x1cG*\x06-_Q\x1b\x00FJ\x00\x04UfP\x01L'
flag = [ord(el) for el in 'rarctf']

print('rarctf', end='')
for i in range(6):
    for j in range(6):
        idx = i*6 + j
        flag[j] ^= xor_check[idx]
        print(chr(flag[j]), end='')

Flag

rarctf{welc0m3_t0_y0ur_new_tr14l_281099b9}


Very TriVial ReVersing

Challenge Info

Hey, at least it’s not Haskell!

file:vtr-alternate - ade66a, vtr - 71cb11

Solution

A flag checker. In main__check() shows that it encode user input and check if it’s the same as the encoded flag.

Below is how the encoding mechanism works.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
encoded_flag = [152, 105, 152, 103, 158, 100, 159, 119, 173, 101, 118, 118, 178, 105, 158, 115, 169, 87,
                180, 35, 158, 119, 179, 146, 169, 88, 174, 45, 89, 101, 168, 21, 89, 33, 173, 102, 165]
key = [0x13, 0x37]


def enc(key, data):
    for el in data:
        e = (ord(el) ^ key[0]) + key[1]
        key = key[::-1]
        print(e, end=', ')
    print()


def dec(key, data):
    for el in data:
        p = (el - key[1]) ^ key[0]
        key = key[::-1]
        print(chr(p), end='')
    print()


dec(key, encoded_flag)
enc(key, 'rarctf{See,ThatWasn\'tSoHard-1eb519ed}')

Flag

rarctf{See,ThatWasn'tSoHard-1eb519ed}


Jammy’s Old Infra

Challenge Info

Jammy asked me to build some login infrastructure for his website. Well, here it is.

file:jammys_old_infra.apk - 7d03b5, jammys_old_infra_4_4.apk - 8f56dd

Solution

After playing app in Android Studio and statically reverse the apk file with apktool and dex2jar (APKLab is also useful)

It came out that the app is to open a login webpage (http://ec2-18-130-135-40.eu-west-2.compute.amazonaws.com:8090).

After pressing the login button, both username and password are checked locally by the function checkCreds() which is inside libnative-lib.so.

checkUsername() simply check if the input username is the same as "SQ]X9]A>" ^ "bc79T08j", so the username is 12jammyT.

checkPasswords() check if the input password is the same as the real password decrypted by AES-CBC. The part that decrypt AES key is quite complicate, so dynamically fetch the real password in memory is a better choice.

Setting the debug environment is the hardest part of the challenge though. Here is my steps in short😢:

  1. Install one rootable VM in Android Studio.
  2. Root the VM with the help of SuperSU.
  3. Enter the shell (adb -e shell)
  4. Open the app and find its pid. (ps | grep jammy)
  5. Setup port forwarding (adb forward tcp:23946 tcp:23946)
  6. Start the gdbserver (gdbserver localhost:23946 --attach <pid>)
  7. Open IDA and remote attach to the gdbserver.

After the catastrophic suffering, just set a breakpoint right after AES decryption is over, and the password in on the stack. (stove:spill2:drivable:1)

Lastly, input the right username and password to get flag.

Flag

rarctf{pl34s3_d0nt_s0lv3_st4tic4lly_3829103890}


boring flag checker

Challenge Info

Why do all rev challenges have to be boring flag checkers these days?

Note: This challenge has the same binary as boring-flag-runner

file:boring-flag-checker.zip - 4dc9bc

Solution

It’s a brainfuck vm challenge.

My script is not good enough, so I will add the writeup in the future days.

Flag

rarctf{1_h0p3_y0u-3njoy3d_my-Br41nF$&k_r3v!_d387171751}


Down The Rabbit Hole

Challenge Info

Good luck

file:loader - ef0b0a

Solution

The program will call sub_401196(i) to load 0x8000 bytes shellcode into a RWX space 0xabcdef000, then execute the shellcode.

This is how the decoding works:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
key_data = open('dump_key', 'rb').read()    # 0x8000*49 bytes stored in the memory
src = open('init_src', 'rb').read()         # 0x8000 bytes initially stored in 0xabcdef000
prev_i = 0


def decode(key, size=0x8000):
    global src
    ret = b''
    for i in range(size):
        curr_key = key[i]
        curr_src = src[i]
        ret += bytes([curr_key ^ curr_src ^ 0x90])
    src = ret
    return ret


def sub_401196(i):
    global prev_i
    pos = 0x8000 * (i + 7 * prev_i)
    prev_i = i
    return decode(key_data[pos:pos+0x8000])


open('decode_0.bin', 'wb').write(sub_401196(0))
open('decode_2.bin', 'wb').write(sub_401196(2))

In short, there are two options, and both options will fork(). The parent process will call the child process to do something and get the result back.

red pill

  1. input at most 16 bytes as password
  2. parent passes the input to the child process to calculate hash (md5(md5(input()) + md5('https://i.imgur.com/gub160B.png\x00')))
  3. parent receives the hash
  4. if the hash is the same as 'cbbefea802bf596615e073d3419262f0', print out half the flag

So I attached to the parent process and modify the receiving hash sotred in the memory, and got the 1/2 flag.

blue pill

  1. come up with a very simple shell
  2. if the cat command is used, pass the argument (file path) to the child process
  3. the child process will first add X padding to the end of the argument till string length is the multiply of 4
  4. then xor each 4 bytes to get a 4 bytes hash value
  5. again, if the hash value is matched, print out another half of the flag.

Similarly to the red pill, I just dynamically changed the zero flag to pass the check, and got the last part of the flag.

Flag

rarctf{D1d-y0u-t4k3-th3-r3d-p1ll-0r-m4yb3-th3-blu3-p177-539c8a}