I recently participated in the KOSPO competition hosted by Korea Southern Power. The problems were more diverse than I expected and not too difficult, so it was great to be able to try all of them, even though I couldn’t solve many.

scoreboard

  1. Bonus 1

helloworld.apk

When I downloaded the problem, I received a file like this.

To understand how the APK functions, I ran it using Nox.

NOX

This screen appeared, but I couldn’t find any other meaningful information.

I proceeded to analyze the APK further using Jadx.

Jadx.png

During the analysis with Jadx, I found a hint in the BuildConfig file.

public static final String kospo_hint = "CN+C+OU+ST+L+O=Base64";

This value seemed to suggest that the fields needed to be concatenated and then converted to Base64.

After researching with GPT, I found out what this information meant.

In an APK file, “CN+C+OU+ST+L+O=Base64” generally represents the subject information from the certificate used to sign the APK. Each field provides details about the issuer or owner of the certificate, following the X.509 standard.

The fields indicate the following information:

CN (Common Name): Typically refers to the name of the issuer or domain. C (Country Name): Represents the country code (e.g., KR for Korea, US for the United States). OU (Organizational Unit): Refers to the department within the organization. ST (State or Province Name): The name of the state or province. L (Locality Name): The name of the city or locality. O (Organization Name): The name of the organization or company. This string is Base64-encoded, meaning it was converted from its original form to prevent data corruption during transmission. Base64 encoding converts binary data into text.

Thus, “CN+C+OU+ST+L+O=Base64” refers to the certificate’s subject information that has been Base64-encoded.

By examining this information, I identified the following certificate details:

CN=U21hc, OU=5lcmd, O=l9MaWZl, L=dHRlc, ST=5X0Jl, C=nRfRW

These values seemed odd, so I concatenated them in the appropriate order:

U21hcnRfRW5lcmd5X0JldHRlcl9MaWZl

After obtaining this string, I used Cyberchef to decode it from Base64.

Smart_Energy_Better_Life
  1. Bonus 2

This was the first time I got a “first blood” on a challenge during a competition or CTF (I probably won’t get another one in the future.)

I solved this challenge on my laptop, but I accidentally deleted the files, so I’ll briefly explain the situation before getting into the details.

The challenge provided a .py file and a video file.

The Python script was designed to break the video down into frames, but when I played the video, I couldn’t directly see any values that resembled the flag. I figured the solution would be to inspect the frames one by one.

Below is the modified version of the code I was provided with, which ended up being the solution:

import cv2
import os

# Path to the video file (relative path) 
video_path = 'problem.mp4'

# Directory path where the script is executed
current_folder = os.path.dirname(os.path.abspath(__file__))

# Set the save path for images in the current folder
save_path = os.path.join(current_folder, "img")
if not os.path.exists(save_path):
    os.makedirs(save_path)

# Open the video file
vidcap = cv2.VideoCapture(video_path)

count = 0

# Check if the video file was opened successfully
if not vidcap.isOpened():
    print(f"Error: Could not open video file at {video_path}")
else:
    print(f"Video file opened successfully: {video_path}")

# Read the frames while the video is opened
while vidcap.isOpened():
    ret, image = vidcap.read()

    if not ret:
        print("Error: Could not read frame or video finished.")
        break

    try:
        if int(vidcap.get(1)) % 1 == 0:  # Save every frame
            print(f"Saving frame number: {int(vidcap.get(1))}")
            cv2.imwrite(os.path.join(save_path, "%d.jpg" % count), image)
            print(f"Saved frame {count}.jpg")
            count += 1
    except Exception as e:
        print(f"An error occurred during frame processing: {e}")
        break

vidcap.release()
print("Video processing complete.")

When this code is executed, it breaks the video down into individual frames and saves them as images.

After checking all the frames one by one, I found the flag between frames 200 and 300, if I remember correctly.

The video was related to Mala Tang, so the flag was {TangHooR00}.

3.Bonus 4

A problem that really gave me a hard time. (I might never like Charmander again)

When I downloaded the problem file, I got this:

bonus4.zip

It was just a picture of Charmander. The challenge description was to “evolve Charmander!” I initially thought it was a steganography challenge, so I checked for hidden data, but didn’t find anything suspicious.

Next, I decided to open the file with HxD, a hex editor, and examined it thoroughly.

I found three different magic numbers, which indicated that the file was a combination of multiple file types.

1.

hex1

2.

hex2

3.

hex3

In situations like this, there are two main methods you can use to extract the files. Before I introduce the easier method, it’s important to understand the structure of a PNG file.

A PNG file begins with its magic number and ends with an IEND chunk. Keeping this in mind, you can extract the images and view them. Below are the extracted images:

Another method is to use a tool like Binwalk, which helps you find hidden files within the original file. Using Binwalk will show you what’s inside, and you can extract the files using the foremost command. Since I will likely use this method a lot in the future, it’s a good one to remember.

파이리

파이리2

파이리3

After extracting these Charizard images, I thought I had solved the challenge. However, the flag was nowhere to be found.

I tried various steganography techniques, enlarging the image, and more, but nothing worked. At that point, I decided to set it aside and move on to other problems.

After the competition ended, I checked some write-ups from other participants and learned that the trick was to adjust the width and height of the image to reveal the flag.

hexedit

This part in the hex values is where you adjust the width and height. After tweaking these values and checking the image again, I finally managed to solve it.

flag

  1. capcha This challenge didn’t involve other files but was a web-based problem.

The task was to enter the value displayed on a CAPTCHA image as verification. If the value was correct, the solve count would increase by 1. The challenge was to solve a total of 30 CAPTCHAs within 60 seconds.

After the competition, I saw some write-ups from other participants and found it funny and interesting that some people solved this problem manually. 😆 Meanwhile, I solved it programmatically.

Since the server is down while I’m writing this, I can’t provide every detail, but here’s a brief explanation.

The image showed a random combination of 6 uppercase letters (A-Z) and numbers (0-9). Each time you entered the CAPTCHA value, the image would change randomly, so I had to figure out how to solve this.

My solution was to decode the base64-encoded image, restore it, and use an OCR API like Google Vision to extract the text from the image.

One issue with this approach was that sometimes the OCR would confuse characters like ‘0’ and ‘O’, or ‘1’ and ‘I’. So, I let the code run until the problem was solved while I worked on other tasks.

Here is the code I used to solve the challenge:

import base64
import io
import time
from google.cloud import vision
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.options import Options
from PIL import Image
from io import BytesIO

# Google Vision API client setup
client = vision.ImageAnnotatorClient()

def detect_text_google_vision(image_content):
    # Use Google Vision OCR to extract text from the CAPTCHA image
    image = vision.Image(content=image_content)
    response = client.text_detection(image=image)
    texts = response.text_annotations

    if texts:
        return texts[0].description.strip()
    else:
        return None

# Set options to ignore SSL certificate errors
chrome_options = Options()
chrome_options.add_argument('--ignore-certificate-errors')
chrome_options.add_argument('--ignore-ssl-errors')

# Set up the web driver (using Chrome)
driver = webdriver.Chrome(options=chrome_options)

# Navigate to the CAPTCHA page
driver.get("http://hackbox.kospo.co.kr:14447/captcha")

for i in range(30):  # Repeat 30 times
    try:
        # Locate the CAPTCHA image element
        captcha_image_element = driver.find_element(By.XPATH, "//img[@src]")

        # Extract the base64 string from the src attribute
        captcha_base64 = captcha_image_element.get_attribute('src').split(',')[1]

        # Decode the base64 string to an image
        captcha_image_data = base64.b64decode(captcha_base64)

        # Use Google Vision OCR to extract text from the CAPTCHA image
        captcha_text = detect_text_google_vision(captcha_image_data)
        print(f"[{i+1}/30] Extracted CAPTCHA text: {captcha_text}")

        # Enter the CAPTCHA text into the input field
        captcha_input = driver.find_element(By.NAME, "captcha")  # 입력 필드 'name'을 사용
        captcha_input.clear()
        captcha_input.send_keys(captcha_text)

        # Click the submit button
        submit_button = driver.find_element(By.XPATH, "//button[@type='submit']")
        submit_button.click()

        # Wait for a moment
        time.sleep(1)

    except Exception as e:
        print(f"Error on iteration {i+1}: {e}")
        break

# After completing the task, check the page text
try:
    page_text = driver.find_element(By.TAG_NAME, 'body').text
    print(f"Final Page text: {page_text}")
except Exception as e:
    print(f"Error retrieving final page text: {e}")

# Keeping the browser open
# driver.quit() is not called

This code decodes the CAPTCHA image, extracts the text using OCR, and submits it. There were small issues like confusing ‘0’ with ‘O’ and ‘1’ with ‘I’, but I let the script run until the problem was solved while I worked on other challenges.

It was a bit disappointing that I couldn’t win an award, but it was fun to participate in the competition after a long time.