Post

[FIESTA 2024] 금융보안 위협분석 대회 PlayZone문제

FIESTA 2024 금융보안 위협분석 대회

나는 아직 정식으로 보안에 관해 배운적이 없는 스크립트 키디(script kiddie)로 보안이나 바이러스, 악성코드 분석 등에 관한 내용은 거의 알지 못한다.

하지만 이번 대회는 대학생 혹은 기업 신분인 사람들 누구나 참가비 없이 참여할 수 있기에 한번 참여를 해보았다.

대회에서 몇등을 하겠다라는 목표는 없지만 한 문제라도 한번 도전해보고 싶다.

일정은 다음과 같다.

1
2
  플레이존 : 09.23. (월) 09:00 ~ 10.02 (수) 18:00
  대회 : 10.04. (금) 18:00 ~ 10.06. (일) 24:00 (2박 3일) 

이번 대회는 CTF(Capture The Flags)대회라고 한다.

내가 해본 다른 워게임과의 차이점이라면 서버가 아니라는점? 자세한건 직접 문제를 풀어보면서 기록해보자.

PLAYZONE

이곳에서는 여러 테스트 문제들을 풀어볼 수 있다.

일단 모든 문제를 찍먹해보고 풀 수 있는 것들은 풀 것이다

image

TEST

image

FLAG가 잘 작동되는지 테스트하는 문제이다.

Input Flagfiesta{MIC_TEST}을 입력하고 Submit Flag를 누르면 끝.

문제 푸는데 성공했다면 Correct! 라고 뜰 것이다.

시나리오

APT(Advanced Persistent Threat) 1

image

공격자가 피해자에게 악성코드를 메일로 보냈고 그 파일에서 악성코드를 찾아서 sha256값을 이용하라고 한다.

일단 먼저 파일을 받아보자.

image

이 파일안에는 .eml파일이 있을 것이다. 그리고 이 확장자는 당연하게도 이메일 확장자이다.

image

email0000.eml-email0099.eml까지 100개의 이메일이 존재한다.

여기서 바이러스가 있는 파일을 찾으려면 어떻게 해야할까?

나는 그냥 VIRUSTOTAL에 하나하나 넣어보기로 했다.

분명 virustotal api를 받아서 하거나 한번에 모든 파일을 검사할 수 있는 방법도 있겠지만 나는 모르기에 하나하나 직접 넣었다.

일단 내용이 무엇일지 궁금하니 돌려봤을 때 문제가 없었던 email0000.eml을 한번 열어보자.

image

어…

1
2
3
미스터 옵션 웃음 목록을 포함한 이유일 수 있습니다. 학생들이 재정적 진실을 지지합니다. 진짜 5는 없습니다.
훌륭한 작가가 앞서 선물했습니다. 가을 아들은 평화의 고객을 의미합니다.
[중략]

이라는데 그냥 아무말이나 쓴 듯 하다.

image

일단 email0007.eml을 찾아냈다.

음… 하나하나 하기에는 너무 많아서 그냥 output.zip파일을 돌려보았다.

image

오호 이렇게 한번에 내부 파일을 읽어서 알려줄 거라고는 생각을 못했다.

찾은 결과는 email0007.eml, email0027.eml, email0028.eml, email0047.eml, email0071.eml, email0073.emal로 총 6개이다.

image

일단 이 친구들을 하나의 폴더에 모았다.

그 후에 munpack을 이용해서 첨부 파일들을 추출해보자.

image

1
for file in *.eml; do munpack "$file"; done

를 이용해서 .eml파일 전부를 한번에 추출할 수 있다.

image

이 파일들 중 우리는 .*zip파일에 문제아가 있다는 것을 다시한번 VIRUSTOTAL의 힘을 빌려 알아낼 수 있다.

image

게다가 각 zip파일마다 .exe확장파일이 존재함을 알 수 있다.

image

전부 zip파일을을 풀어보니 확실히 .exe파일이 많다.

image

그리고 또한 .docx파일이 많은데 VIRUSTOTAL에서 문제가 없다고 하니 열어보면 역시 딱히 의미없는 글이다.

image

그렇다면 진짜로 이중에 범인이 있다는 의미인데…

animalxawa4bw0.exe

image

이 친구는 악성 소프트웨어(멀웨어), 트로이의 목마라고 나온다.

becauseq1af2332.exe

image

이번에는 위 결과에 추가적으로 백도어라고 한다.

glasszyamgvv7.exe

image

와 이번에는 구글에서도 감지되었다고 뜨고 뭔가 훨씬 많은 것들이 추가되었다. 이건가?

until1818qf2.exe

image

이번에도 4가지 정도 나온다.

word5f1ew949.exe

image

너도?

마지막으로 내가 까먹고 압축해제를 하지 않은 28번도 있다. 여기서는 poo라는 파일이 나온다. 그리고 그 안에는 somebodyrce_fx8b.exe도 있다.

somebodyrce_fx8b.exe

image

이야 도대체 누가 범인인걸까?

일단 문제에서는 악성코드의 sha256 해쉬값을 얻어내라고 했었다.

일단 한번 전부 다 넣어볼까? (BruteForce)

image

하나하나 넣어보니 bcb10a8e6250ecb142932ba59cbe94e47f2e143564df1886a5838317bc275b40 becauseq1af2332.exe 이녀석이 범인이였다.

혹시 내가 뭔지 알 수 있을까 해서 뜯어봤는데

image

음… 역시나 어느정도만 이해가 되고 뭐하는 작업인지 모르겠다.

FLAG : fiesta{bcb10a8e6250ecb142932ba59cbe94e47f2e143564df1886a5838317bc275b40}

APT(Advanced Persistent Threat) 2

image

이번에는 패킷 로그가 있고 까지 이해했다. 이걸로 악성코드를 분석할 수 있는건가?

일단 한번 파일을 받아보자

image

오케이 패킷 로그파일만 있는 것을 확인했다. 일단 한번 Wireshark로 열어보자.

image

오… 케이…

Command & Control 서버를 이용하는 악성행위 분석

일단 아는 것이 없으니 wireshark 패킷 분석 / 악성코드 추출 / 네트워크 포렌식을 따라가 보자. 모든 시작은 모방인 것이여

일단 가장 간단하게 내가 생각해 볼 수 있는 HTTP부분을 봐보자.

image

HTTP통신의 내용을 보기 위해서는 보고 싶은 패킷 우클릭 - 따라가기로 내용을 볼 수 있다.

image

근데… 이건 그냥 통신 내용이고..

다른 것들은 또 무엇이 있을까 하여 통계 - 종단점으로 프로토콜 별로 모아서 봐보자.

image

뭔가 더 보기 편해졌다.

image

오 블로그와 다르게 TCP부분에서 이상한 포트들을 쓰는 것을 찾아냈다! 그런데 포트가 자꾸 변하기는 하지만…

image

생각해보니 분명 http로 검색해 봤을 때에도 통신하던 것이 바로 이 친구였다.

혹시 너?

image

일단 한번 html을 추출해보자.

파일 - 객체 내보내기 - HTTP

image

여기서 꺼내고 싶은 것을 꺼내면 된다.

일단은 왜인지 모르게 계속 반복되는 Qy0DUwZD.php를 꺼내보자.

혹시 몰라 그냥 다 저장하였다.

아니 바이러스 토탈에 전부 돌려보아도 문제가 없다…

image

그중 유일하게 찾은 것이 VdbNDQYi.php%3ffile=download.ps1파일은 열린다는 것인데…

image

어… 챗 선생께서는 강종 스크립트라고 한다…

요거는 또 아니겠고 그럼…

Wireshark 악성코드 패킷 분석하기_Web Shell블로그를 참고해 진행해보자.

통계 - 프로토콜 계층 구조로 패킷의 통계를 확인해보자.

image

여기서는 TCP보다는 UDP가 현저히 많다는 것을 알 수 있다. 그럼 UDP를 봐야했던건가?

심지어 그중에 QUIC가 많다… QUIC 프로토콜 이해하기

image

그런데 패킷을 잠시 둘러보던 중 뭔가 좀 많이 이상한 부분이 보인다.

image

흐음… 왜 이렇게 여기가 문제인거 같지?

라고 해도… 내가 뭘 알아야말이지… 일단 이 부분은 여기서 정지…

2023 FIESTA 금융보안 위협 분석 대회 #3 시나리오 - APT

작년 자료에서 과정을 찾았다. 와… 이런건 어디서 배우나 싶다. 나도 이렇게 멋있게 풀어보고싶다!!!

악성 앱 2

악성 앱 1은?

image

세상에.. 이번에는 .apk 파일을 준다.

apk도 처음이지만 뭐 처음이 아닌게 어디있으리.

이번에는 JEB을 이용해보자.

어떤 정보가 C2서버에 넘어가는지 일단 먼저 알아보자.

image

이렇게 연 뒤에 C2 서버에 정보를 보내줘야하니 http 통신 내용을 찾아보자.

image

모드를 Strings로 문자열만 볼 수 있게 한 후에 http로 검색해보면 http://13.124.114.239:9999/라는 부분을 찾을 수 있다.

그리고 나의 뇌는 멈췄다…. 다음 문제

침해대응

문제 2

image

아… 잠시 이 문제도 미루는 것으로..

문제 3

image

내가 하기에는 좀 더 공부가 필요해 보이기에 아래 블로그를 첨부한다.

2023 FIESTA 금융보안 위협 분석 대회 #5 침해대응 - 문제 3

문제 5

image

선 공부 후 도전

특별문제

문제 1

image

파일은 1.solvit.bin 풀어라 라는 파일이 있다.

ida를 통해 열어보고 _main함수를 디컴파일링 해봤을 때 다음과 같은 결과가 나온다.

int __cdecl main(int argc, const char **argv, const char **envp)
{
if ( argc == 2 )
{
  if ( strlen(argv[1]) == 9 )
  {
    byte_40437C[0] = sub_401000(*argv[1]);
    byte_40437C[1] = sub_401000(argv[1][1]);
    byte_40437C[2] = sub_401000(argv[1][2]);
    byte_40437C[3] = sub_401000(argv[1][3]);
    byte_40437C[4] = sub_401000(argv[1][4]);
    byte_40437C[5] = 7 * sub_401000(argv[1][5]);
    byte_40437C[6] = sub_401000(argv[1][6]);
    byte_40437C[7] = sub_401000(argv[1][7]);
    byte_40437C[8] = sub_401000(argv[1][8]);
    if ( sub_401030(byte_40437C[0], byte_40437C[1], byte_40437C[2])
      && sub_4010D0(byte_40437C[3], byte_40437C[4], byte_40437C[5]) )
    {
      if ( sub_401160(byte_40437C[6], byte_40437C[7], byte_40437C[8]) )
        MessageBoxW(0, L"Congratulations\nflag: fiesta{md5[key]}", L"WOW", 0);
    }
    return 0;
  }
  else
  {
    puts("INSERT KEY !");
    return 1;
  }
}
else
{
  puts("INSERT KEY !");
  return 1;
}
}

이번 문제는 답을 찾아내기만 하면 된다!

위에서 부터 설명을 해보자면

if ( argc == 2 )

프로그램이 2개의 인수를 받았는지 확인하고 여기서 argv[0]은 C언어에서 프로그램 절대경로 즉 이름이다. 그리고 나머지 argv[1]에는 입력값으로 들어온 9자리의 문자열이 들어온다고 한다.

Buffer Overflow: argv[0]

if ( strlen(argv[1]) == 9 )

argv[1](입력값)의 길이가 9인지 확인하는 조건문이다.

byte_40437C[0] = sub_401000(*argv[1]);
byte_40437C[1] = sub_401000(argv[1][1]);
byte_40437C[2] = sub_401000(argv[1][2]);
byte_40437C[3] = sub_401000(argv[1][3]);
byte_40437C[4] = sub_401000(argv[1][4]);
byte_40437C[5] = 7 * sub_401000(argv[1][5]); //여기는 곱하는거 추가
byte_40437C[6] = sub_401000(argv[1][6]);
byte_40437C[7] = sub_401000(argv[1][7]);
byte_40437C[8] = sub_401000(argv[1][8]);

argv[1]에 들어있는 각 문자열들을 sub_401000함수에 전달해서 숫자로 변환후에 byte_40437C에 저장한다.

if ( sub_401030(byte_40437C[0], byte_40437C[1], byte_40437C[2])
  && sub_4010D0(byte_40437C[3], byte_40437C[4], byte_40437C[5]) )
{
if ( sub_401160(byte_40437C[6], byte_40437C[7], byte_40437C[8]) )

위에서 나온 결과값들을 sub_401030함수에 넣어서 true, false 조건을 검사한다.

MessageBoxW(0, L"Congratulations\nflag: fiesta{md5[key]}", L"WOW", 0);

마지막으로 위의 모든 조건이 만족되면 프래그값을 메시지 박스에 표시해준다.

image

처음에 나오는 sub_401000함수를 찾아보자.

int __cdecl sub_401000(char a1)
{
if ( a1 <= 57 && a1 >= 48 )
  return a1 - 48;
else
  return 10;
}

image

숫자형태의 문자를 ascii 숫자로 변경하는 부분이다!

sub_401030함수의 형태는 다음과 같다.

BOOL __cdecl sub_401030(unsigned __int8 a1, unsigned __int8 a2, unsigned __int8 a3)
{
if ( a1 == 10 || a2 == 10 || a3 == 10 )
  return 0;
return 3 * a2 + 2 * a1 == 27
    && 7 * a1 - 7 * a2 / a3 == 37
    && 7 * a2 - 2 * a1 - a3 == 16
    && 5 * a3 + -3 * a1 - a2 == 12;
}

3개의 인자를 받아서 a1,a2,a3에 저장하고 조건이 충족되면 TRUE를 아니라면 FALSE를 반환한다.

와.. 수학이다.

sub_4010D0함수의 형태는 다음과 같다.

BOOL __cdecl sub_4010D0(unsigned __int8 a1, unsigned __int8 a2, unsigned __int8 a3)
{
if ( a1 == 10 || a2 == 10 || a3 == 10 )
  return 0;
return 7 * a2 - a1 - a3 == 6 && 7 * a3 + 197 * a1 - 11 * a2 == 452 && 229 * a1 - 7 * a2 - 3 * a3 == 26;
}

위와 동일하다.

sub_401160함수는?

BOOL __cdecl sub_401160(unsigned __int8 a1, unsigned __int8 a2, unsigned __int8 a3)
{
if ( a1 == 10 || a2 == 10 || a3 == 10 )
  return 0;
return 15 * a1 / a2 - a3 == 7 && 3 * a1 - a2 - a3 == 2 && 12 * a3 + a1 - 3 * a2 == 12;
}

여기도 또한 수학놀이다.

그럼 이제 한번 코드를 짜보자. 나는 파이썬을 쓸 것이다.

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
def sub_401000(a1):
  if 48 <= ord(a1) <= 57:
      return ord(a1) - 48
  return 10

def sub_401030(a1, a2, a3):
  if a1 == 10 or a2 == 10 or a3 == 10:
      return False
  return (3 * a2 + 2 * a1 == 27 and
          7 * a1 - 7 * a2 / a3 == 37 and
          7 * a2 - 2 * a1 - a3 == 16 and
          5 * a3 - 3 * a1 - a2 == 12)

def sub_4010D0(a1, a2, a3):
  if a1 == 10 or a2 == 10 or a3 == 10:
      return False
  return (7 * a2 - a1 - a3 == 6 and
          7 * a3 + 197 * a1 - 11 * a2 == 452 and
          229 * a1 - 7 * a2 - 3 * a3 == 26)

def sub_401160(a1, a2, a3):
  if a1 == 10 or a2 == 10 or a3 == 10:
      return False
  return (15 * a1 / a2 == 7 and
          3 * a1 - a2 - a3 == 2 and
          12 * a3 + a1 - 3 * a2 == 12)

def find_key():
  digits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ'
  
  # 9자리 키 생성
  for i1 in digits:
      for i2 in digits:
          for i3 in digits:
              for i4 in digits:
                  for i5 in digits:
                      for i6 in digits:
                          for i7 in digits:
                              for i8 in digits:
                                  for i9 in digits:
                                      key = i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8 + i9
                                      byte_40437C = [sub_401000(c) for c in key]

                                      if (sub_401030(byte_40437C[0], byte_40437C[1], byte_40437C[2]) and
                                              sub_4010D0(byte_40437C[3], byte_40437C[4], byte_40437C[5]) and
                                              sub_401160(byte_40437C[6], byte_40437C[7], byte_40437C[8])):
                                          print(f"Congratulations\nflag: fiesta") ## 깃허브 블로그 올릴 때 여기서 문제가 생겨서 원래 /{/{md5[{key}]/}/} 슬래쉬 지우고 들어가야한다.
                                          return key

  print("No valid key found.")

find_key()

그리고 잠 자기 전에 씻고 온 아직까지도 돌아가는 중이다… 경우의 수가 너무 많아요…

image

그렇기에 Z3라는 친구를 이용해 보기로 했습니다.

[PYTHON] Z3 Examples

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
from z3 import *
import hashlib

# 변수 선언
v1, v2, v3, v4, v5, v6, v7, v8, v9 = Ints('v1 v2 v3 v4 v5 v6 v7 v8 v9')

solver = Solver()

# 제약 조건 추가
solver.add(3 * v2 + 2 * v1 == 27)
solver.add(7 * v1 - 7 * v2 / v3 == 37)
solver.add(7 * v2 - 2 * v1 - v3 == 16)
solver.add(5 * v3 - 3 * v1 - v2 == 12)

solver.add(7 * v5 - v4 - (7 * v6) == 6)
solver.add(7 * v6 * 7 + 197 * v4 - 11 * v5 == 452)
solver.add(229 * v4 - 7 * v5 - (3 * 7 * v6) == 26)

solver.add(15 * v7 / v8 - v9 == 7)
solver.add(3 * v7 - v8 - v9 == 2)
solver.add(12 * v9 + v7 - 3 * v8 == 12)

# 추가 제약 조건: 변수 범위
for v in [v1, v2, v3, v4, v5, v6, v7, v8, v9]:
  solver.add(v >= 0, v <= 9)

# v3가 0이 아님을 보장
solver.add(v3 != 0)

# 해가 존재하는지 확인
if solver.check() == sat:
  model = solver.model()
  values = [model[v].as_long() for v in [v1, v2, v3, v4, v5, v6, v7, v8, v9]]
  result = ''.join(map(str, values))  # 모든 값을 문자열로 변환하여 결합
  md5_hash = hashlib.md5(result.encode()).hexdigest()
  
  # 결과 출력
  print("Values:", values)
  print(f'Key: {result}')
  print(f'MD5: {md5_hash}')
else:
  print('no keys.')

image

정말 속도가 미친듯이 빨라졌네요. 혹시 내가 반복문 코드를 잘못 만들었나? 싶을 정도…

정답은 fiesta{c4f3024e7ffc971df554aa0054e28926}

와… 일단 오늘은 여기까지… 머리가… 9/28

CHALLENGE

는 일이있어 까먹고 있다가 1시간 남기고 떠올라서.. 다음 회차에는 공부하고 도전해 보는걸로

이번에는 플레이존이라도 도전해 봤으니 다행이랄까?

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