FLARE ON 2016 문제풀이

Posted by : on

Category : reversing   learning


배경

2025년 8월에 진행한 FLARE ON 2016 스터디 진행중 나온 풀이를 저장하게 되었습니다.

chal1

_

_

_

401260함수는 0x3f를 봐서 base64와 비슷해보이지만, 413000데이터를 참조하고 있었다. 해당 데이터를 보니 zyxabcdefg등으로 알파벳이 일부 옮겨진듯하다

x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q을 위 로직처럼 일부 옮긴 후 base64 디코딩을 하면 된다


import base64

dat = "x2dtJEOmyjacxDemx2eczT5cVS9fVUGvWTuZWjuexjRqy24rV29q"

def decode(c: str) -> str:
    if c.isdigit():
        return c
    UPPER = c.isupper()
    c = c.lower()
    result = ""
    if c == "x":
        result = "c"
    elif c == "y":
        result = "b"
    elif c == "z":
        result = "a"
    else:
        result = chr(ord(c) + 3)
    if UPPER:
        result = result.upper()
    return result

dat = "".join([decode(c) for c in dat])
print(dat)
print(base64.b64decode(dat).decode())
# c2gwMHRpbmdfcGhpc2hfaW5fYV9iYXJyZWxAZmxhcmUtb24uY29t
# sh00ting_phish_in_a_barrel@flare-on.com

chal2

_

윈디펜더에 걸리거나 하는 점들을 보아 랜섬웨어와 암호화된 파일로 보입니다

  • 현재 디렉토리의 Briefcase파일이 있는지 확인

  • 현재 드라이브 시리얼 번호 기반으로 체크

  • 401940에서 프로그램 내장의 시리얼 번호를 이용해 xor시켜 값을 복호화합니다 복호화된 값-thosefilesreallytiedthefoldertogether

  • 401080에서 crypt provider를 생성하고 rsa_aes 키를 401148에서 지정합니다
    • those…문자열을 sha 해시화 후 CryptDeriveKey를 이용해 키로 지정합니다
  • 401300에서 암호화를 시작합니다. Briefcase라는 폴더 내부 파일들을 가져와 암호화를 합니다 401990에서는 파일이 디렉토리인지 검사합니다.

_

  • 401770에서 암호화를 하고 401500에서 저장하는것으로 보입니다

  • 401770

    • 파일명을 md5합니다
    • md5 결과값을 IV로 사용합니다, 패딩값이 있을 경우 AB입니다

iv값은 확인하였으나 CryptDeriveKey 호출 과정에서 부족한 키 데이터가 어떻게 expand되는지 확인하지 못했습니다.

→ 확인해보니 expanded key 계산 필요 없이, crypt메소드만 decrypt메소드로 바꾸면 해결된다고 합니다

chal3

_

_

  • 인자가 있는지 확인합니다

  • 실행프로그램 경로에 R이 있는지 확인합니다

  • r 뒤의 문장 및 인자를 이용해 해시를 생성합니다

  • v22에 flare on등의 문자열을 저장합니다

_

  • 실행프로그램 자신 자체를 읽어 RSDS문자열을 찾은 뒤, 해당 문자열 뒤에 있는 16바이트 데이터를 v23에 저장합니다 _

  • b6 1e 2d d0은 0x12345678과 xor됩니다

_

계산한 해시값에 R 이후의 글자와 xor 이후 일부 데이터를 md5하는것으로 보이나, 정확히 어떤 데이터가 md5되는지 확인하지 못하였습니다

chal4

_

dll 디컴파일한 함수의 반환값들이 특정 숫자를 반환하고 있어서, 매핑 프로그램 작성하였습니다

#include <stdio.h>
#include <stdlib.h> // qsort 사용을 위함
#include <windows.h>

// DLL 함수 포인터 타입 정의
typedef unsigned int(__stdcall* DLL_FUNCTION_PTR)();

// 함수 호출 결과를 저장할 구조체
typedef struct
{
  int          ordinal;
  unsigned int returnValue; // uint4에 해당
  int          isValid;     // 함수 호출이 성공했는지 여부 (0: 실패, 1: 성공)
} FunctionResult;

// qsort를 위한 비교 함수
// 오름차순 정렬 (returnA - returnB)
int
compareFunctionResults(const void* a, const void* b)
{
  FunctionResult* resultA = (FunctionResult*)a;
  FunctionResult* resultB = (FunctionResult*)b;

  // 유효하지 않은 결과는 맨 뒤로 보내거나, 특정 처리를 할 수 있습니다.
  // 여기서는 유효한 결과만 정렬하고, 유효하지 않은 결과는 정렬 대상에서 제외한다고 가정하거나
  // 유효한 결과와 유효하지 않은 결과를 분리하여 처리하는 것이 좋습니다.
  // 간단하게 유효한 결과들만 비교하여 정렬합니다.
  if (resultA->isValid && resultB->isValid) {
    if (resultA->returnValue < resultB->returnValue)
      return -1;
    if (resultA->returnValue > resultB->returnValue)
      return 1;
    return 0;
  }
  // 유효하지 않은 결과는 비교에서 제외하거나 별도 처리
  return 0;
}

// 내림차순 정렬 (returnB - returnA)
int
compareFunctionResultsDesc(const void* a, const void* b)
{
  FunctionResult* resultA = (FunctionResult*)a;
  FunctionResult* resultB = (FunctionResult*)b;

  if (resultA->isValid && resultB->isValid) {
    if (resultA->returnValue > resultB->returnValue)
      return -1;
    if (resultA->returnValue < resultB->returnValue)
      return 1;
    return 0;
  }
  return 0;
}

int
main()
{
  const char* dllPath      = "flareon2016challenge.dll";
  const int   startOrdinal = 1;
  const int   endOrdinal   = 48;
  const int   numFunctions = endOrdinal - startOrdinal + 1;

  HMODULE         hDll    = NULL;
  FunctionResult* results = (FunctionResult*)malloc(sizeof(FunctionResult) * numFunctions);

  if (results == NULL) {
    printf("Error: Memory allocation failed.\n");
    return 1;
  }

  hDll = LoadLibraryA(dllPath);

  if (hDll == NULL) {
    printf("Error: Failed to load DLL '%s'. GetLastError: %lu\n", dllPath, GetLastError());
    free(results);
    return 1;
  }

  printf("DLL '%s' loaded successfully.\n", dllPath);

  int validCount = 0; // 유효한 결과의 개수
  for (int i = 0; i < numFunctions; ++i) {
    int ordinal        = startOrdinal + i;
    results[i].ordinal = ordinal;
    results[i].isValid = 0; // 기본적으로 실패로 설정

    DLL_FUNCTION_PTR pFunc = (DLL_FUNCTION_PTR)GetProcAddress(hDll, (LPCSTR)ordinal);

    if (pFunc != NULL) {
      results[i].returnValue = pFunc();
      results[i].isValid     = 1; // 성공
      printf("Ordinal %d: Returned value = %u\n", ordinal, results[i].returnValue);
      validCount++;
    } else {
      printf("Ordinal %d: Function not found or cannot be called. GetLastError: %lu\n",
             ordinal,
             GetLastError());
    }
  }

  printf("\n--- 오디널 및 반환값 목록 --- (위와 동일)\n");
  for (int i = 0; i < numFunctions; ++i) {
    if (results[i].isValid) {
      printf("Ordinal %d: %u\n", results[i].ordinal, results[i].returnValue);
    } else {
      printf("Ordinal %d: (호출 실패)\n", results[i].ordinal);
    }
  }

  // 유효한 결과만 담을 새 배열 (선택 사항: 메모리 최적화)
  FunctionResult* validResults = (FunctionResult*)malloc(sizeof(FunctionResult) * validCount);
  if (validResults == NULL) {
    printf("Error: Memory allocation failed for valid results.\n");
    FreeLibrary(hDll);
    free(results);
    return 1;
  }

  int currentValidIndex = 0;
  for (int i = 0; i < numFunctions; ++i) {
    if (results[i].isValid) {
      validResults[currentValidIndex++] = results[i];
    }
  }

  printf("\n--- 반환값 기준 정렬 (오름차순) ---\n");
  // qsort를 사용하여 반환값 기준으로 정렬
  // validResults 배열의 유효한 항목들만 정렬합니다.
  qsort(validResults, validCount, sizeof(FunctionResult), compareFunctionResults);

  for (int i = 0; i < validCount; ++i) {
    printf("Ordinal %d: %u\n", validResults[i].ordinal, validResults[i].returnValue);
  }

  printf("\n--- 반환값 기준 정렬 (내림차순) ---\n");
  // 내림차순 정렬을 위한 비교 함수 사용
  qsort(validResults, validCount, sizeof(FunctionResult), compareFunctionResultsDesc);

  for (int i = 0; i < validCount; ++i) {
    printf("Ordinal %d: %u\n", validResults[i].ordinal, validResults[i].returnValue);
  }

  FreeLibrary(hDll);
  free(results);      // 원래 배열 해제
  free(validResults); // 유효한 결과 배열 해제
  return 0;
}
Ordinal 35: 1
Ordinal 22: 2
Ordinal 25: 3
Ordinal 9: 4
Ordinal 45: 5
Ordinal 23: 6
Ordinal 36: 7
Ordinal 2: 8
Ordinal 12: 9
Ordinal 37: 10
Ordinal 14: 11
Ordinal 18: 12
Ordinal 8: 13
Ordinal 46: 14
Ordinal 30: 15
Ordinal 7: 16
Ordinal 4: 17
Ordinal 42: 18
Ordinal 34: 19
Ordinal 47: 20
Ordinal 11: 21
Ordinal 32: 22
Ordinal 27: 23
Ordinal 39: 24
Ordinal 10: 25
Ordinal 3: 26
Ordinal 21: 27
Ordinal 13: 28
Ordinal 16: 29
Ordinal 33: 31
Ordinal 26: 32
Ordinal 17: 33
Ordinal 44: 34
Ordinal 6: 35
Ordinal 20: 36
Ordinal 48: 37
Ordinal 19: 38
Ordinal 29: 39
Ordinal 38: 40
Ordinal 24: 41
Ordinal 15: 42
Ordinal 41: 43
Ordinal 31: 44
Ordinal 28: 45
Ordinal 43: 46
Ordinal 5: 47
Ordinal 40: 48
Ordinal 1: 51

반환값 30 및 49 50이 빕니다

  • 반환값 순서대로 정렬 후 실행하였으나 실패하였습니다.

  • 반환값을 prestep으로 보고 정렬 후 실행하였으나 실패하였습니다. 51, 1, 35,...,15, 30, 50, 50, 50...,

  • 반환값을 nextstep으로 보고 정렬 후 실행하였습니다 30, ..., 1, 51, 50, ...

#include <stdio.h>   // printf를 사용하기 위함
#include <stdlib.h>  // malloc, free 사용을 위함
#include <windows.h> // Windows API 함수들을 사용하기 위함

// DLL 함수 포인터 타입 정의
typedef unsigned int(__stdcall* DLL_FUNCTION_PTR)();

// 함수 호출 결과를 저장할 구조체
typedef struct
{
  int          ordinal;
  unsigned int returnValue; // uint4에 해당
  int          isValid;     // 함수 호출이 성공했는지 여부 (0: 실패, 1: 성공)
} FunctionResult;

int
main()
{
  // 호출할 DLL 파일의 경로를 지정합니다.
  const char* dllPath = "flareon2016challenge.dll";

  // 호출할 오디널 번호들을 원하는 순서대로 배열에 작성합니다.
  int       ordinal_array[] = { 30, 15, 42, 18, 12, 9,  4,  17, 33, 31, 44, 34, 19, 38, 40, 48, 37,
                          10, 25, 3,  26, 32, 22, 2,  8,  13, 28, 45, 5,  47, 20, 36, 7,  16,
                          29, 39, 24, 41, 43, 46, 14, 11, 21, 27, 23, 6,  35, 1,  51, 50, 50,
                          50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 50, 49 };
  const int num_ordinals = sizeof(ordinal_array) / sizeof(ordinal_array[0]);

  // 결과 저장을 위한 동적 배열 할당
  FunctionResult* results = (FunctionResult*)malloc(sizeof(FunctionResult) * num_ordinals);
  if (results == NULL) {
    printf("Error: Memory allocation failed.\n");
    return 1;
  }

  HMODULE hDll = NULL;

  // DLL 로드
  hDll = LoadLibraryA(dllPath);

  if (hDll == NULL) {
    printf("Error: Failed to load DLL '%s'. GetLastError: %lu\n", dllPath, GetLastError());
    free(results);
    return 1;
  }

  printf("DLL '%s' loaded successfully.\n", dllPath);

  // 각 오디널별 함수 호출 (배열 순서대로)
  for (int i = 0; i < num_ordinals; ++i) {
    int ordinal        = ordinal_array[i];
    results[i].ordinal = ordinal;
    results[i].isValid = 0; // 기본적으로 실패로 설정

    printf("Ordinal %d 함수 호출 시도...\n", ordinal);

    // GetProcAddress 함수는 DLL에서 내보낸 함수의 주소를 가져옵니다.
    DLL_FUNCTION_PTR pFunc = (DLL_FUNCTION_PTR)GetProcAddress(hDll, (LPCSTR)ordinal);

    if (pFunc != NULL) {
      results[i].returnValue = pFunc();
      results[i].isValid     = 1; // 성공
      printf("  Ordinal %d: 반환값 = %u\n", ordinal, results[i].returnValue);
    } else {
      printf("  Ordinal %d: 함수를 찾을 수 없거나 호출할 수 없습니다. GetLastError: %lu\n",
             ordinal,
             GetLastError());
    }
  }

  // DLL 언로드 (메모리 해제)
  FreeLibrary(hDll);
  printf("\nDLL '%s' unloaded.\n", dllPath);

  printf("\n--- 호출 순서 및 결과 요약 ---\n");
  for (int i = 0; i < num_ordinals; ++i) {
    if (results[i].isValid) {
      printf("Ordinal %d: %u\n", results[i].ordinal, results[i].returnValue);
    } else {
      printf("Ordinal %d: (호출 실패)\n", results[i].ordinal);
    }
  }

  free(results); // 할당된 메모리 해제
  return 0;      // 성공적으로 프로그램 종료
}

답지를 보니 nextstep으로 보는 것은 맞았지만, 51 ordinal 실행 시 복호화 된 데이터는 실행프로그램이었다고 합니다. 메모리를 덤프해 나온 실행 프로그램을 리버싱하여 50 ordinal을 실행하는 것이라고 합니다

chal5

_

분석 결과, 401610 및 hashNth 및 401880에서 해시를 여러번 반복하며 복호화를 진행하는 루틴이 있습니다

어떤 방법으로 해시화 및 복호화를 하는지는 체크하였지만, 입력 값이 조금이라도 바뀔 경우 해시 값이 많이 바뀌어 어떻게 푸는지는 추측하지 못했습니다

chal6

_

_

cpython이 내장된 C++프로그램으로 보인다

_

py2exe로 생성된 프로그램으로 추정됩니다. 한번에 맞춰도 플래그가 나오지 않으므로 코드를 봐야합니다

unpy2exe를 이용해 해제시 poc.py.pyc가 나옵니다

_

# Visit https://www.lddgo.net/en/string/pyc-compile-decompile for more information
# Version : Python 2.7

import sys
-1
import random
__version__ = 'Flare-On ultra python obfuscater 2000'
-1
target = random.randint(1, 101)
count = 1
-1
error_input = ''
while None:
    if True:
        -1
        print '(Guesses: %d) Pick a number between 1 and 100:' % count,
        input = sys.stdin.readline()
        
        try:
            input = int(input, 0)
        except:
            -1
            error_input = input
            -1
            print 'Invalid input: %s' % error_input
            continue
        

        -1
        if target == input:
            break
        -1
        -1
        count += 1
    if target == input:
        -1
        win_msg = 'Wahoo, you guessed it with %d guesses\n' % count
        -1
        sys.stdout.write(win_msg)
    if count == 1:
        print 'Status: super guesser %d' % count
        -1
        sys.exit(1)
if error_input != '':
    -1
    tmp = ''.join((lambda .0: pass)(error_input)).encode('hex')
    if tmp != '312a232f272e27313162322e372548':
        sys.exit(0)
        -1
    -1
    -1
    -1
    -1
    -1
    stuffs = [
        67,
        139,
        119,
        165,
        232,
        86,
        207,
        61,
        79,
        67,
        45,
        58,
        230,
        190,
        181,
        74,
        65,
        148,
        71,
        243,
        246,
        67,
        142,
        60,
        61,
        92,
        58,
        115,
        240,
        226,
        171]
    import hashlib
    -1
    stuffer = hashlib.md5(win_msg + tmp).digest()
    for x in range(len(stuffs)):
        -1
        -1
        -1
        -1
        -1
        -1
        print chr(stuffs[x] ^ ord(stuffer[x % len(stuffer)])),
    
    print 

gpt로 스크립트를 만들어 실행하면

import hashlib

# 검증 상수 (hex) 및 그에 대응하는 ASCII
expected_tmp_hex = "312a232f272e27313162322e372548"
expected_tmp_ascii = bytes.fromhex(expected_tmp_hex)  # b"1*#/'.'11b2.7%H"

# 사용자가 입력/가공한 결과가 expected_tmp_hex 인지 확인하는 단계가 원래 필요
tmp = expected_tmp_ascii  # 원 코드 의도 반영: 비교를 통과했다고 가정

stuffs = [
    67,
    139,
    119,
    165,
    232,
    86,
    207,
    61,
    79,
    67,
    45,
    58,
    230,
    190,
    181,
    74,
    65,
    148,
    71,
    243,
    246,
    67,
    142,
    60,
    61,
    92,
    58,
    115,
    240,
    226,
    171,
]

for i in range(200):
    win_msg = f"Wahoo, you guessed it with {i} guesses\n".encode()

    # 원 코드에서는 md5(win_msg + tmp_hex_string) 이었을 가능성 있음(파이썬2 바이트 문자열)
    # tmp 를 hex 문자열로 쓸지, ASCII 바이트로 쓸지 모호하여 둘 다 시도해봄
    m1 = hashlib.md5(win_msg + expected_tmp_ascii).digest()
    m2 = hashlib.md5(win_msg + expected_tmp_hex.encode("ascii")).digest()

    def xor_decode(stuffs, key):
        return bytes([stuffs[i] ^ key[i % len(key)] for i in range(len(stuffs))])

    print("Try ASCII tmp :", xor_decode(stuffs, m1))
    print("Try HEX   tmp :", xor_decode(stuffs, m2))

_

chal7

chal8

_

간단한 윈도우 cli프로그램이다

복호화시 this is the wrong password가 비밀번호이며, 2549 조건과 걸려서 fail이 된다


답지를 보니 dos헤더에 실제 프로그램이 있었다고 한다

chal9

chal10


About 영원염원영웅
영원염원영웅

공부하는것을 좋아합니다.

Email : xhve00000@gmail.com

Website : http://eveheeero.com

Useful Links