🚀 I have migrated to a new website. This blog is now archived and will no longer receive updates. Visit 0xtimemachine.com to go back to the future!
Posts Marathon CTF
Post
Cancel

Marathon CTF

Marathon CTF was a great CTF organized by CyberTalents during the whole month (1 Mar. - 31 Mar. 2021) but I participated during only the last 4 days and I got the 28th place (same points ad 25th) as I solved all Reverse challenges but one, and some of Web, Crypto, Forensics challenges and Here’s the writeup for some of these challenges!


Python Art

Python Art Info

Well, This challenge I really liked because It’s more like a reverse challenge or at least for me! :D Python Art website

So this website basically draws “whatever” I typed like this: testing input

Since the challenge name is “Python Art” I thought of Django or Flask, I wanted to see the HTML source using inspect element and there was an interesting comment inside the <pre> tag under the drawn text the comment was <!-- test is not defined --> or something like that so I guessed the comment is a result of eval()! I tired 2+3 as input and the result in the comment was 5 so YUP, that’s eval() function messing with me I KNEW IT!

Because I’m a reverse guy I overthought it so I tried to get the flag variable but it was not defined, I thought it might be in a text file and tried to read flag.txt but didn’t work either, then I tried to execute compile() function inside the eval function but none of that worked but that made me read about code objects in Python so I had fun.

since the URL is http://18.196.160.18:3333/send I tried the input send and the comment had this response <!-- function send at 0xfff..... --> so that’s the address of the function in memory, huh!

so to get the properties and methods of the function send I tried dir(send) and the response was

1
['__call__', '__class__', '__closure__', '__code__', '__defaults__', '__delattr__', '__dict__', '__doc__', '__format__', '__get__', '__getattribute__', '__globals__', '__hash__', '__init__', '__module__', '__name__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'func_closure', 'func_code', 'func_defaults', 'func_dict', 'func_doc', 'func_globals', 'func_name']

And by taking a look at func_globals by typing send.func_globals .. BOOM!:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'DEFAULT_FONT': 'standard',
'art': <function art at 0x7f2ce3b7b4d0>,
'Flask': <class 'flask.app.Flask'>,
'app': <Flask 'app'>,
'aprint': <function aprint at 0x7f2ce3b7b450>,
'DECORATION_COUNTER': 195,
'randart': <function randart at 0x7f2ce3b7b550>,
...
lots of gibberish stuff
...
'__file__': 'app.py',
'art_param': <module 'art.art_param' from '/usr/local/lib/python2.7/site-packages/art/art_param.pyc'>
...
'decor_dic': <module 'art.decor_dic' from '/usr/local/lib/python2.7/site-packages/art/decor_dic.pyc'>, 'request': <Request 'http://18.196.160.18:3333/send' [POST]>, 'ART_VERSION': '5.1', 'ART_COUNTER': 700

So this is a Flask web application written in app.py file and there’s functions like art, aprint, randart, etc.. which belong to the art python library! All makes sense now! :D

Since I have the filename of source code I grabbed it open('app.py','r').read() and styled it a little:

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
26
27
#!/usr/bin/python
from flask import Flask
from flask import render_template
from flask import request
from art import *

app = Flask(__name__, static_url_path='/static')

@app.route('/')
def index():
	return render_template('index.html')


@app.route('/send', methods = ['POST'])
def send():
	text = request.form.get('name')
	art = text2art(str(text),font='block',chr_ignore=True)

	try:
		cmd = eval(text)
	except NameError as e:
	    return render_template('index.html', cmd=e , art= art)
	return render_template('index.html', cmd=cmd , art=art)


if __name__ == '__main__':
	app.run(host='0.0.0.0')

so as I thought the comment is using eval() function and all looks good, Now it’s time to get the flag!

I typed __import__('glob').glob("*") to discover what’s around (which I should’ve done at first BTW xD):

1
<!-- [&#39;app.py&#39;, &#39;flag&#39;, &#39;templates&#39;, &#39;static&#39;] -->

Okay it’s flag file, not flag.txt! then by reading that file open('flag', 'r').read() I’ve got the flag: flag


ASM (4SM)

4SM info

I do love assembly! taking a look at 4SM.asm:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
flag_checker:
        push    rbp
        mov     rbp, rsp
        mov     DWORD PTR [rbp-4], edi
        xor     DWORD PTR [rbp-4], 133337
        sar     DWORD PTR [rbp-4], 3
        add     DWORD PTR [rbp-4], 1337
        sub     DWORD PTR [rbp-4], 137
        mov     edx, DWORD PTR [rbp-4]
        mov     eax, edx
        add     eax, eax
        add     eax, edx
        mov     DWORD PTR [rbp-4], eax
        cmp     DWORD PTR [rbp-4], 1128648
        jne     .L2
        mov     eax, 1
        jmp     .L3
.L2:
        mov     eax, 0
.L3:
        pop     rbp
        ret
.LC0:
        .string "Enter the secret number: "
.LC1:
        .string "%d"
.LC2:
        .string "Correct number :D"
.LC3:
        .string "Wrong number :p"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 16
        mov     edi, OFFSET FLAT:.LC0
        mov     eax, 0
        call    printf
        mov     eax, DWORD PTR [rbp-4]
        mov     esi, eax
        mov     edi, OFFSET FLAT:.LC1
        mov     eax, 0
        call    __isoc99_scanf
        mov     eax, DWORD PTR [rbp-4]
        mov     edi, eax
        call    flag_checker
        mov     DWORD PTR [rbp-8], eax
        cmp     DWORD PTR [rbp-8], 0
        je      .L5
        mov     edi, OFFSET FLAT:.LC2
        call    puts
        jmp     .L6
.L5:
        mov     edi, OFFSET FLAT:.LC3
        call    puts
.L6:
        mov     eax, 0
        leave
        ret

The flow goes as follows:

  • starting from main
  • “Enter the secret number: “ message get printed using printf function
  • getting user’s input through scanf function with %d format
  • flag_checker function is called “to check the flag obviously”
  • doing some math: ((((x ^ 133337) >> 3) + 1337) - 137) * 3 where x is the user’s input
  • comparing so the result should be equal to 1128648

NOTE: the ^ is XOR and >> is shift arithmetic right

I had the equation ((((x ^ 133337) >> 3) + 1337) - 137) * 3 = 1128648 so by solving it:
x = ((((1128648/3) + 137) - 1337) << 3) ^ 133337 = 3133337

The flag: flag{3133337}


Jett

Jett info

I was given an assembly source like the previous challenge:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
.LC0:
        .string "Enter the flag:"
.LC1:
        .string "%25s"
.LC2:
        .string "Wrong Flag :("
.LC3:
        .string "Correct Flag : %s"
main:
        push    rbp
        mov     rbp, rsp
        sub     rsp, 160
        mov     DWORD PTR [rbp-160], 150
        mov     DWORD PTR [rbp-156], 204
        mov     DWORD PTR [rbp-152], 233
        mov     DWORD PTR [rbp-148], 31
        mov     DWORD PTR [rbp-144], 211
        mov     DWORD PTR [rbp-140], 75
        mov     DWORD PTR [rbp-136], 212
        mov     DWORD PTR [rbp-132], 74
        mov     DWORD PTR [rbp-128], 193
        mov     DWORD PTR [rbp-124], 215
        mov     DWORD PTR [rbp-120], 85
        mov     DWORD PTR [rbp-116], 176
        mov     DWORD PTR [rbp-112], 132
        mov     DWORD PTR [rbp-108], 215
        mov     DWORD PTR [rbp-104], 57
        mov     DWORD PTR [rbp-100], 222
        mov     DWORD PTR [rbp-96], 38
        mov     DWORD PTR [rbp-92], 75
        mov     DWORD PTR [rbp-88], 2
        mov     DWORD PTR [rbp-84], 139
        mov     DWORD PTR [rbp-80], 75
        mov     DWORD PTR [rbp-76], 21
        mov     DWORD PTR [rbp-72], 215
        mov     DWORD PTR [rbp-68], 21
        mov     DWORD PTR [rbp-64], 229
        mov     edi, OFFSET FLAT:.LC0
        call    puts
        lea     rax, [rbp-48]
        mov     rsi, rax
        mov     edi, OFFSET FLAT:.LC1
        mov     eax, 0
        call    __isoc99_scanf
        mov     DWORD PTR [rbp-4], 0
        jmp     .L2
.L5:
        mov     eax, DWORD PTR [rbp-4]
        cdqe
        movzx   eax, BYTE PTR [rbp-48+rax]
        movsx   eax, al
        imul    eax, eax, 137
        cdq
        shr     edx, 24
        add     eax, edx
        movzx   eax, al
        sub     eax, edx
        mov     DWORD PTR [rbp-8], eax
        mov     eax, DWORD PTR [rbp-4]
        cdqe
        mov     eax, DWORD PTR [rbp-160+rax*4]
        cmp     DWORD PTR [rbp-8], eax
        je      .L3
        mov     edi, OFFSET FLAT:.LC2
        call    puts
        mov     eax, 0
        jmp     .L6
.L3:
        add     DWORD PTR [rbp-4], 1
.L2:
        cmp     DWORD PTR [rbp-4], 24
        jle     .L5
        lea     rax, [rbp-48]
        mov     rsi, rax
        mov     edi, OFFSET FLAT:.LC3
        mov     eax, 0
        call    printf
        mov     eax, 0
.L6:
        leave
        ret

The execution flow:

  • starting from main
  • allocating the stack and storing some bytes
  • printing “Enter the flag:” message to the screen using puts
  • reading user’s input to the address of [rbp-48] using scanf with %25s format
  • jumping to .L2 label
  • while loop executing while counter [rbp-4] < 25:
    • reading each byte from the user’s input (let’s call it x)
    • doing the math: y[i] = (byte)[((x[i] * 137) >> 24) + (x[i] * 137)]
    • known_values[i] = y[i] - ((x[i] * 137) » 24)
    • increment counter

The full equation is known_bytes[i] = (byte)[(x[i] * 137) >> 24) + (x[i] * 137)] - ((x[i] * 137) >> 24) and since (x[i] * 137 >> 24) for printable characters is always zero it can be reduced to: known_bytes[i] = (byte)(x[i] * 137) = (x[i] * 137) % 256 and I could brute force them and that’s one solution

The other solution is simply using modular inverse to get the flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#!/bin/env python3

from Crypto.Util.number import inverse

known_bytes = [150, 204, 233, 31, 211, 75, 212, 74, 193, 215, 85, 176, 132, 215, 57, 222, 38, 75, 2, 139, 75, 21, 215, 21, 229]

flag = ""

for a in known_bytes:
    inv = inverse(137, 256)

    original = inv * a % 256

    flag += chr(original)

print(flag)

And I got the flag: flag{34zy_m0d_1nv3rs3-_-}


HiKi

HiKi info

I was given an apk file so I decompiled it to jar to see the source code using dex2jar tool and there were two interesting source files MainActivity.class and EncDec.class:

Hiki MainActivity.class Hiki EncDec.class

By reading the code it’s obvious that it’s doing AES Decryption to a pre-encrypted value and the key is the pin entered by the user concatenated with Cyb3rT4l3nt5

Here’s the trick the pin in code should be 6 digits or less and since Cyb3rT4l3nt5 length is 12 and since we’re talking about a small key then It’s 128-bit key which is 16 bytes we know 12 bytes so the pin should be 4 digits! (so the key is on the format xxxxCyb3rT4l3nt5)

The pin has 10k possibilities I wrote a python script to brute force it and simulate the decryption process:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#!/bin/env python3

import base64
from Crypto.Cipher import AES

known_key = "Cyb3rT4l3nt5"

enc = "blJ9s0JObmk7Qi7vfe98RWN3snqSc79GZTC+8IaHuV052R3Q43SH8vXhjQt7E5m3"
b64_enc = base64.b64decode(enc)

for pin in range(0, 10000):

    try:
        key = str(pin).zfill(4) + known_key

        cipher = AES.new(key.encode('utf-8'), AES.MODE_ECB)
        msg = cipher.decrypt(b64_enc).decode('utf-8')

        print("key = {}, flag = {}".format(key, msg))

    except:
        continue

HiKi solver.py


C1ph3r

C1ph3r info

Last but not least! This is the most challenge I enjoyed so far in the Marathon CTF and you will figure out why!

I was given this 32-bit binary and by analyzing it in IDA and taking a look at the strings:

strings

There are a lot of messages that shows the binary is corrupted and need to be fixed also in the API imports IsDebuggerPresent function from KERNEL32 Library so I’m expecting ant-debugging techniques!

I went to "Enter the flag:\n" reference:

strings fake flag function

After of pushing data to the stack it calls some functions one of them XORs these bytes with 0x44 (I named it XOR_44) which result in the string just for your attention, nothing here is real and yeah just another rabbit hole :”D

Another note from the code overview is that to approach a function it calls a function that jumps to the desired function

I headed back to the beginning to the main function and started tracing statically the flow which was full of exceptions and debugger traps int 3 instructions to make breakpoints and another important note is Pseudocode is not working so we’re talking assembly here which is cool tbh ;)

My approach here was:

  1. hitting a breakpoint at the start of try..catch exception
  2. modifying EIP to the call instruction (to get out of there ASAP :D)
  3. repeating 1 and 2 until reaching finding the treasure (a cool function)

Let’s dive in starting from main:

main function main function again

There were 2 intermediate functions and then I reached this function which seems to be cool the exception start was on the right block but I changed the flow to the highest block above where the magic begins:

treasure function

Here’s a closer look to the upper part the first part basically prints “Enter the flag:” and accepts user’s input (32 characters) and I could see clearly 2 loops in there one inside another:

start of magic function

the first loop iterates while count_1 < 32 (count increases by two) and allocates memory using the standard memset function with size 0x1B28 let’s call that buff and the second loop iterates while count_2 < 0xD94 (which is half of 0x1B28):

the two loops

The second loop is doing encryption and after it finishes it’s using memcmp standard function to compare it to a pre-defined memory chunk

encrypt and compare

Putting pieces together the first loop takes two characters from the user’s input at a time, decrypt it then comparing the result with the pre-stored chunks of memory so I dumped those 16 chunks using 3 lines of IDApython:

1
2
3
4
start = 0x00CACB60

for counter in range(16):
	idc.savefile("mem" + str(counter).zfill(2) + ".bin", 0, start + (counter * 0x1B28), 0x1B28)

Now the decryption process:

  • val = two bytes of the user’s input
  • loop from zero to 0xD94:
    • if val % all_bytes[i] != 0 increment counter
    • otherwise buf[i] += 1 and val = val / all_bytes[i]

Where all_bytes are stored constants in memory to see if these constants divides the value of 2 bytes

So again I made a 2 lines IDApython script to get the all_bytes from memory:

1
2
3
all_bytes_addr = 0x00CAB038

idc.savefile("all_bytes.bin", 0, all_bytes_addr, 0x1B28)

Okay enough with the analysis let’s talk code this is the encryption simulation in python for first 2 letters of the flag "fl" = 0x666c which its result was equal to mem00.bin (the first chunk of memory):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# encrpytion simulation
buf = [ 0 for _ in range(0xD94)]

val = 0x666C
count_2 = 0

while count_2 < 0xD94:

    this_byte = all_bytes[count_2]

    if val % this_byte != 0:
        count_2 += 1;

    else:
        buf[2 * count_2] += 1
        val = int(val / this_byte)

for i in buf:
    print(i, ' ', end='')

Output: 2 0 1 0 1 0 0 0 0 0 0 0 0 0 1 0 1 … 0 (which is identical to mem00.bin which holds the first 2 bytes’ factorization of the flag)

Let’s sum up, I had the constants and how many times each constant divides the 2 bytes value from the flag so to get those bytes I should calculate the Greatest Common Divisor (GCD) for the 16 values (32 bytes flag) and this way I should have the flag!! so I made a python script to do that for me:

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
#!/bin/env python3

import struct
from Crypto.Util.number import long_to_bytes

all_bytes = []

# reading all_bytes from the memory dump to array as unsigned short values in little endian
with open("all_bytes.bin", "rb") as f:

    while True:
        buf = f.read(2)

        if not buf:
            break

        all_bytes.append(struct.unpack('<H', buf)[0])



flag_bytes = []

# looping through the 16 memory dumps for the encrypted 2 letters flag
for count_1 in range(16):

    # reading memory dump
    with open("mem/mem" + str(count_1).zfill(2) + ".bin", "rb") as f:
        memdump = f.read()

    # reseting counters
    count_2 = 0
    result = 1

    # getting the divisors from the memory dump
    for ptr in range(0, len(memdump)):

        if memdump[ptr] > 0:

            # how many times the divisor divides the value
            times = memdump[ptr]

            #print("@count_2 = %i -> 0x%x" % (count_2, times))

            # multiplying the divisors to get the GCD (greatest common divisor)
            for _ in range(times):
                result *= all_bytes[count_2]

        elif ptr % 2 == 1:
            count_2 += 1

    flag_bytes.append(result)


# decoding the flag
flag = ""
hex_bytes = ''.join([hex(x)[2:] for x in flag_bytes])
flag = long_to_bytes(int(hex_bytes, 16)).decode('utf-8')
print(flag)

C1ph3r solver.py

This post is licensed under CC BY 4.0 by the author.