[ARACTF Qual 2022] – OWL 2077

Diberikan sebuah file .exe bernama OWL_2077 yang ketika dijalankan, ternyata adalah sebuah permainan game dimana kita berperan sebagai burung hantu yang nampaknya kita harus menang dalam setiap level untuk mendapat satu per satu karakter flag sampai ke level tertinggi.

Pada folder, icon dari program tersebut adalah icon python exe

Dan ketika distrings, terlihat juga kata-kata python, .py, dll. Sehingga semakin pasti bahwa exe ini merupakan python executable.

Maka dari itu, saya menggunakan tools pyinstxtractor (https://github.com/extremecoders-re/pyinstxtractor) untuk mengekstrak exe yang digenerate pyinstaller. 

Setelah menelusuri file-file yang ada di situ, kami menemukan hal yang menarik pada file cfg.py yang memiliki array SECRET,

import os
import sys

if getattr(sys, ‘frozen’, False) and hasattr(sys, ‘_MEIPASS’):
    os.chdir(sys._MEIPASS)
   
FONTPATH = os.path.join(‘resources/font/font.ttf’)
SECRET = [159, 218, 179, 244, 202, 141, 247, 39, 119, 27, 143, 153, 205, 115, 162, 2, 133, 13, 190, 36, 118, 189, 111, 194, 141, 3, 34, 117, 152, 207, 28, 113, 144, 25, 102, 36, 99, 29, 227, 224, 97, 122, 164, 39, 209, 246, 4, 14, 40, 187, 103, 18, 165, 250, 10, 107, 98, 43]
BULLET_IMAGE_PATHS = {
    ‘up’: os.path.join(‘resources/images/bullet/bullet_up.png’),
    ‘down’: os.path.join(‘resources/images/bullet/bullet_down.png’),
    ‘left’: os.path.join(‘resources/images/bullet/bullet_left.png’),
    ‘right’: os.path.join(‘resources/images/bullet/bullet_right.png’)
}
ENEMY_TANK_IMAGE_PATHS = {
    ‘1’: [
        os.path.join(‘resources/images/enemyTank/enemy_1_0.png’),
        os.path.join(‘resources/images/enemyTank/enemy_1_1.png’),
        os.path.join(‘resources/images/enemyTank/enemy_1_2.png’),
        os.path.join(‘resources/images/enemyTank/enemy_1_3.png’)
    ],
    ‘2’: [
        os.path.join(‘resources/images/enemyTank/enemy_2_0.png’),
        os.path.join(‘resources/images/enemyTank/enemy_2_1.png’),
        os.path.join(‘resources/images/enemyTank/enemy_2_2.png’),
        os.path.join(‘resources/images/enemyTank/enemy_2_3.png’)
    ],
    ‘3’: [
        os.path.join(‘resources/images/enemyTank/enemy_3_0.png’),
        os.path.join(‘resources/images/enemyTank/enemy_3_1.png’),
        os.path.join(‘resources/images/enemyTank/enemy_3_2.png’),
        os.path.join(‘resources/images/enemyTank/enemy_3_3.png’)
    ],
    ‘4’: [
        os.path.join(‘resources/images/enemyTank/enemy_4_0.png’),
        os.path.join(‘resources/images/enemyTank/enemy_4_1.png’),
        os.path.join(‘resources/images/enemyTank/enemy_4_2.png’),
        os.path.join(‘resources/images/enemyTank/enemy_4_3.png’)
    ]
}
PLAYER_TANK_IMAGE_PATHS = {
    ‘player1’: [
        os.path.join(‘resources/images/playerTank/tank_T1_0.png’),
        os.path.join(‘resources/images/playerTank/tank_T1_1.png’),
        os.path.join(‘resources/images/playerTank/tank_T1_2.png’)
    ],
    ‘player2’: [
        os.path.join(‘resources/images/playerTank/tank_T2_0.png’),
        os.path.join(‘resources/images/playerTank/tank_T2_1.png’),
        os.path.join(‘resources/images/playerTank/tank_T2_2.png’)
    ]
}
FOOD_IMAGE_PATHS = {
    ‘boom’: os.path.join(‘resources/images/food/food_boom.png’),
    ‘clock’: os.path.join(‘resources/images/food/food_clock.png’),
    ‘gun’: os.path.join(‘resources/images/food/food_gun.png’),
    ‘iron’: os.path.join(‘resources/images/food/food_iron.png’),
    ‘protect’: os.path.join(‘resources/images/food/food_protect.png’),
    ‘star’: os.path.join(‘resources/images/food/food_star.png’),
    ‘tank’: os.path.join(‘resources/images/food/food_tank.png’)
}
HOME_IMAGE_PATHS = [
    os.path.join(‘resources/images/home/home1.png’),
    os.path.join(‘resources/images/home/home_destroyed.png’)
]
SCENE_IMAGE_PATHS = {
    ‘brick’: os.path.join(‘resources/images/scene/brick.png’),
    ‘ice’: os.path.join(‘resources/images/scene/ice.png’),
    ‘iron’: os.path.join(‘resources/images/scene/iron.png’),
    ‘river1’: os.path.join(‘resources/images/scene/river1.png’),
    ‘river2’: os.path.join(‘resources/images/scene/river2.png’),
    ‘tree’: os.path.join(‘resources/images/scene/tree.png’)
}
OTHER_IMAGE_PATHS = {
    ‘appear’: os.path.join(‘resources/images/others/appear.png’),
    ‘background’: os.path.join(‘resources/images/others/background.png’),
    ‘boom_dynamic’: os.path.join(‘resources/images/others/boom_dynamic.png’),
    ‘boom_static’: os.path.join(‘resources/images/others/boom_static.png’),
    ‘gameover’: os.path.join(‘resources/images/others/gameover.png’),
    ‘logo’: os.path.join(‘resources/images/others/logo.png’),
    ‘mask’: os.path.join(‘resources/images/others/mask.png’),
    ‘protect’: os.path.join(‘resources/images/others/protect.png’),
    ‘tip’: os.path.join(‘resources/images/others/tip.png’),
    ‘gamebar’: os.path.join(‘resources/images/others/gamebar.png’)
}
AUDIO_PATHS = {
    ‘add’: os.path.join(‘resources/audios/add.wav’),
    ‘bang’: os.path.join(‘resources/audios/bang.wav’),
    ‘blast’: os.path.join(‘resources/audios/blast.wav’),
    ‘fire’: os.path.join(‘resources/audios/fire.wav’),
    ‘Gunfire’: os.path.join(‘resources/audios/Gunfire.wav’),
    ‘hit’: os.path.join(‘resources/audios/hit.wav’),
    ‘start’: os.path.join(‘resources/audios/start.wav’)
}

WIDTH = 630
HEIGHT = 630
BORDER_LEN = 3
GRID_SIZE = 24
PANEL_WIDTH = 150
TITLE = ‘Owly Games’
LEVELFILEDIR = os.path.join(‘modules/levels’)

Lalu /modules/interfaces/switchLevelIterface.py yang memiliki generate class calc, lalu memanggil gen.next() yang dixor dengan SECRET dan ditampilkan hasilnya setelah pergantian level.

import sys
import pygame
import cfg

def switchLevelIterface(screen, cfg, level_next=1):
    background_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get(‘background’))
    color_white = (255, 255, 255)
    color_gray = (192, 192, 192)
    font = pygame.font.Font(cfg.FONTPATH, cfg.WIDTH//20)
    logo_img = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get(‘logo’))
    logo_img = pygame.transform.scale(logo_img, (446, 70))
    logo_rect = logo_img.get_rect()
    logo_rect.centerx, logo_rect.centery = cfg.WIDTH/2, cfg.HEIGHT//4
    path = “modules/levels/{}.lvl”
    path = path.format(level_next-1)
    f = open(path, errors=’ignore’)
    for line in f.readlines():
        line = line.strip(‘\n’)
        if line.startswith(‘#’) or (not line):
            continue
        elif line.startswith(‘%TOTALENEMYNUM’):
            total_enemy_num = int(line.split(‘:’)[-1])
        elif line.startswith(‘%MAXENEMYNUM’):
            max_enemy_num = int(line.split(‘:’)[-1])
        else:
            continue
    gen = calc(total_enemy_num, max_enemy_num, color_white[0])
    if(level_next==1):
        font_render = font.render(‘Loading game data, You will enter Level-%s’ % level_next, True, color_white)
    else:
        font_render = font.render(‘Here is part of flag – %s , You will enter Level- %s’ % (chr(gen.next()^cfg.SECRET[level_next-2]), level_next ), True, color_white)
    font_rect = font_render.get_rect()
    font_rect.centerx, font_rect.centery = cfg.WIDTH/2, cfg.HEIGHT/2
    gamebar = pygame.image.load(cfg.OTHER_IMAGE_PATHS.get(‘gamebar’)).convert_alpha()
    gamebar_rect = gamebar.get_rect()
    gamebar_rect.centerx, gamebar_rect.centery = cfg.WIDTH/2, cfg.HEIGHT/1.4
    tank_cursor = pygame.image.load(cfg.PLAYER_TANK_IMAGE_PATHS.get(‘player1’)[0]).convert_alpha().subsurface((0, 144), (48, 48))
    tank_rect = tank_cursor.get_rect()
    tank_rect.left = gamebar_rect.left
    tank_rect.centery = gamebar_rect.centery
    load_time_left = gamebar_rect.right – tank_rect.right + 8
    clock = pygame.time.Clock()
    while True:
        for event in pygame.event.get():
            if event.type == pygame.QUIT:
                pygame.quit()
                sys.exit()
        if load_time_left <= 0:
            return
        screen.blit(background_img, (0, 0))
        screen.blit(logo_img, logo_rect)
        screen.blit(font_render, font_rect)
        screen.blit(gamebar, gamebar_rect)
        screen.blit(tank_cursor, tank_rect)
        pygame.draw.rect(screen, color_gray, (gamebar_rect.left+8, gamebar_rect.top+8, tank_rect.left-gamebar_rect.left-8, tank_rect.bottom-gamebar_rect.top-16))
        tank_rect.left += 1
        load_time_left -= 1
        pygame.display.update()
        clock.tick(60)

class calc:
    def __init__(self, a, c, m):
        self.seed = 1337
        self.a = a
        self.c = c
        self.m = m

    def next(self):
        self.seed = (self.a * self.seed + self.c) % self.m
        return self.seed

Seperti yang bisa dilihat, untuk bisa mendapatkan nilai gen.next(), pemanggilan class Calc setiap levelnya membutuhkan 3 variabel, yaitu total_enemy_num, max_enemy_num, lalu color_white. Untuk total_enemy_num dan max_enemy_num bisa kita dapatkan dari file /modules/levels/x.lvl dimana x merujuk pada level berapa class tersebut harus digenerate, sedangkan untuk color white ternyata bernilai statis yaitu bernilai 255.

Oleh karena itu, kami menyusun script python yang melakukan looping terhadap seluruh bilangan di SECRET, yang akan di-xor dengan gen.next() dimana class digenerate dengan parameter diambil secara parsing dengan regex dari file level sesuai index loop. Beginilah kira-kira kita membuat script pythonnya:

class calc:
    def __init__(self, a, c, m):
        self.seed = 1337
        self.a = a
        self.c = c
        self.m = m

    def next(self):
        self.seed = (self.a * self.seed + self.c) % self.m
        return self.seed

SECRET = [159, 218, 179, 244, 202, 141, 247, 39, 119, 27, 143, 153, 205, 115, 162, 2, 133, 13, 190, 36, 118, 189, 111, 194, 141, 3, 34, 117, 152, 207, 28, 113, 144, 25, 102, 36, 99, 29, 227, 224, 97, 122, 164, 39, 209, 246, 4, 14, 40, 187, 103, 18, 165, 250, 10, 107, 98, 43]

import re
for i in range(1,59):
    f = open(str(i)+”.lvl”).read()
    tes = re.findall(r”NUM:(\d+)”, f)
    total = int(tes[0])
    maxim = int(tes[1])
    gen = calc(total, maxim, 255)
    print(chr(SECRET[i-1] ^ gen.next()), end=””)
    i += 1

Output:

Flag = ara2022{y0u_4r3_4_g4m3_m4ST3r_ed66350941115995750727c22c6}

Leave a Reply

Your email address will not be published. Required fields are marked *