본문 바로가기

5. 방학 활동/Write UP

[2023.08.06] 워게임 문제풀이(2) - 4

목차

  • 포렌식
  • 리버싱
  • 웹해킹

포렌식

  • 드림핵 - Windows Search
  • 드림핵 - Snowing!

문제 1 : 드림핵 - Windows Search

👾 Do you know “Windows Search” with (windows + s) command?
Find the flag.txt!

 

먼저 문제를 다운로드한다.

 

파일 유형이 EDB 파일인 것을 확인할 수 있다.

 

EDB 파일이 무엇인지 찾아보니 위와 같았다.

  • 파일 확장자가 .edb인 파일은 메일 관련 데이터를 저장하기 위해 Microsoft Exchange Server에서 만든 사서함 데이터베이스다.

이 파일을 분석하기 위해서 WinSearchDBAnalyzer을 이용하겠다.

 

WinSearchDBAnalyzer를 통해 파일을 연다.

 

C:\Users\hunjison\Desktop 안에 flag.txt 파일을 발견할 수 있다.

 

플래그 발견!

 

문제 2 : 드림핵 - Snowing!

👾 드림이 : 우와! 밖에 눈이 많이와요!
드림맘 : 그렇네~
드림이 : 거의 모두 하얀공간뿐이네요.

 

먼저 문제를 다운로드받는다.

 

flag.txt와 Snow.jpeg 파일이 등장했다.

 

jpeg 파일은 위와 같다.

 

flag.txt 파일 안에는 이런 내용이 있다.

 

ctrl a를 눌러 전체 선택을 해보니 이렇게 떴다.

공백부분에 데이터를 은닉하는 스테가노그래피 기법을 사용한 것으로 보인다.

 

HxD 파일에 올려보니 정말 하얀 눈이 내리는 것처럼 보인다.

 

  • white space steganography

→ SNOW 프로그램은 행의 끝에 공백을 추가하여 ASCII 텍스트의 메시지를 숨기는 데 사용됩니다. 텍스트 뷰어에는 일반적으로 공간과 탭이 보이지 않기 때문에 메시지를 임시 관찰자로부터 효과적으로 숨깁니다. 그리고 내장된 암호화를 사용하면 메시지가 탐지되더라도 읽을 수 없습니다.

snow.exe를 이용해 문제를 풀어보겠다.

 

snow.exe -C FILENAME

 

리버싱

  • Dreamhack : rev-basic-3 풀이
  • DreamHack : rev-basic-4 풀이

문제 1 : Dreamhack : rev-basic-3 풀이

rev-basic-3 문제는 “Correct” 문자열을 출력하는 입력 값을 알아내면 된다.

 

처음 프로그램을 실행하면 Input에 값을 입력하도록 되어있고, “Wrong” 문자열이 출력된다.

 

x64dbg를 이용해 코드를 확인해보면, [00007FF79547150F] 주소에서 “Input :” 문자열이 출력되는 것을 확인할 수 있다.

즉, [chall3.7FF795471120] 주소에 “Correct”와 “Wrong” 문자열 또한 같이 존재할 것이다.

 

[00007FF795471120] 주소의 모습이다. “Input :”, “Correct”, “Wrong” 문자열이 존재한다.

 

[00007FF795471173] 주소에서 두 문자열을 비교하므로 [chall3.7FF795471000] 주소에 “Correct”를 출력하는 문자열이 있을 것이다.

 

[00007FF795471000] 주소의 모습이다. 문자열을 비교하는 반복문이 존재하므로 이를 자세히 확인한다.

 

24(16진수 18 =10진수 24)번 반복하며 어떠한 연산이 이루어지는 것을 확인할 수 있다. 어셈블리어를 해석해보면 다음과 같은 내용을 얻을 수 있다.

  • rcx에 [7FF795473000] 주소에 저장된 값을 저장한 뒤, rax와 더한 값을 eax에 저장한다. (이때, rax는 문자열에 접근하기 위한 인덱스 값이다.)
  • rdx에 사용자가 입력한 문자열을 저장한 뒤, rcx와 더한 값을 ecx에 저장한다. (이때, rcx는 문자열에 접근하기 위한 인덱스 값이다.)
  • ecx에 xor 연산을 수행한다.
  • eax에는 flag 값([7FF795473000] 주소의 값)이 저장되고, ecx에는 xor 연산이 이루어진 문자열이 저장된다.
  • eax와 ecx의 값을 비교한다.

즉, 해당 xor 연산을 역연산하여 eax와 같은 값을 갖게 하는 flag 값을 찾아내야 한다.

 

 

먼저, [7FF795473000] 주소의 값을 확인한다.

 

 

[7FF795473000] 주소의 값을 확인해보니 위와 같은 값이 저장되어 있다.

  • 16진수 : 49 60 67 74 63 67 42 66 80 78 69 69 7B 99 6D 88 68 94 9F 8D 4D A5 9D 45
  • 10진수 : I`gtcgBf.xii{.m.h...M¥.E

그다음, xor 연산을 자세히 확인하기 위해 IDA를 사용한다. [sub_120001000(v4)]에 문자열을 처리하는 반복문이 있을 것이다.

 

[sub_120001000(v4)]의 모습이다. byte_140003000에는 “49 60 67 74 63 67 42 66 80 78 69 69 7B 99 6D 88 68 94 9F 8D 4D A5 9D 45” 값이 저장되어 있고, a1에는 사용자가 입력한 문자열이 저장되어 있다.

이때, if문에서 byte_140003000[i] != (i ^ *(unsigned __int8 *)(a1 + i)) + 2 * i 연산이 진행되는 것을 확인할 수 있다. 이를 a1에 대한 식으로 정리하면 아래와 같다.

  • byte_140003000[i] = (i ^ a1[i]) + 2 * i
  • byte_140003000[i] - 2 * i = i ^ a1[i]
  • a1[i] = (byte_140003000[i] - 2 * i) ^ i

정리한 식을 c언어로 작성하면, 다음과 같다.

 

#include <stdio.h>
#include <string.h>

int main(void) {

    char arr[24] = { 0x49, 0x60, 0x67, 0x74, 0x63, 0x67, 0x42, 0x66, 0x80, 0x78, 0x69, 0x69, 0x7b, 0x99, 0x6d, 0x88, 0x68, 0x94, 0x9f, 0x8d, 0x4d, 0xa5, 0x9d, 0x45 };
    char flag[24];

    for (int i = 0; i < 24; i++) {

        flag[i] = ((arr[i] - 2 * i) ^ i);

    }

    printf("%s", flag);

    return 0;

}

 

코드를 실행하면, 위와 같은 문자열이 출력된다.

 

해당 문자열을 입력하니 “Correct”가 정상적으로 출력된다.

 

문제 2 : Dreamhack : rev-basic-4 풀이

 

rev-basic-4 문제는 “Correct” 문자열을 출력하는 입력 값을 알아내면 된다.

 

처음 프로그램을 실행하면 Input에 값을 입력하도록 되어있고, “Wrong” 문자열이 출력된다.

 

x64dbg를 이용해 코드를 확인해보면, [00007FF6DEAD151F] 주소에서 “Input :” 문자열이 출력되는 것을 확인할 수 있다.

즉, [chall4.7FF6DEAD1130] 주소에 “Correct”와 “Wrong” 문자열 또한 같이 존재할 것이다.

 

[00007FF6DEAD1130] 주소의 모습이다. “Input :”, “Correct”, “Wrong” 문자열이 존재한다.

 

[00007FF6DEAD117E] 주소에서 두 문자열을 비교하므로 [chall4.7FF6DEAD1000] 주소에 “Correct”를 출력하는 문자열이 있을 것이다.

[00007FF6DEAD1000] 주소의 모습이다. 문자열을 비교하는 반복문이 존재하므로 이를 자세히 확인한다.

 

28(16진수 1C =10진수 28)번 반복하며 시프트 연산, and 연산, or 연산이 이루어지는 것을 확인할 수 있다. 그다음, [7FF6DEAD3000]주소의 값을 ecx에 저장해 eax와 비교하는 것을 알 수 있다.

 

먼저, [7FF6DEAD3000] 주소에 저장되어있는 값을 확인한다.

 

[7FF6DEAD3000] 주소의 값을 확인해보니 위와 같은 값이 저장되어 있다.

  • 16진수 : 24 27 13 C6 C6 13 16 E6 47 F5 26 96 47 F5 46 27 13 26 26 C6 56 F5 C3 C3 F5 E3 E3
  • 10진수 : $'.ÆÆ..æGõ&.GõF'.&&ÆVõÃÃõãã

x64dbg에서 알아본 연산을 자세히 확인하기 위해 IDA를 사용한다. [sub_140001000(v4)]에 문자열을 처리하는 반복문이 있을 것이다.

 

[sub_140001000(v4)]의 모습이다. byte_140003000에는 “24 27 13 C6 C6 13 16 E6 47 F5 26 96 47 F5 46 27 13 26 26 C6 56 F5 C3 C3 F5 E3 E3” 값이 저장되어 있고, a1에는 사용자가 입력한 문자열이 저장되어 있다.

이때, if문에서 ((unsigned __int8)(16 * *(_BYTE )(a1 + i)) | ((int)(unsigned __int8 *)(a1 + i) >> 4)) != byte_140003000[i] 연산이 진행되는 것을 확인할 수 있다. 이를 정리하면 아래와 같다.

  • (16 * a1[i]) | (a1[i] >> 4 ) != byte_140003000[i]

a1[i]의 값을 16진수 [0xA]라 가정한 뒤, 정리한 식을 해석하면 아래와 같다.

  1. 16 * a1[i]
    • 16(10진수) * 0xA(16진수) = 0xA0
  2. a1[i] >> 4
    • 0xA(16진수) = 0000 1010(2진수)
    • 0000 1010 >> 4 = 0000 0000
  3. (16 * a1[i]) | (a1[i] >> 4 )
  • 1010 0000 | 0000 0000 = 1010 0000

즉, 모든 연산이 끝나면 [0000 1010]에서 [1010 0000]으로 앞 뒤가 바뀌게 된다.

그러므로 바뀐 앞뒤를 다시 바꾸면 “Correct” 값을 출력하게 만드는 문자열을 얻을 수 있을 것이다.

위의 식을 C언어로 코드를 작성하면 다음과 같다.

 

#include <stdio.h>
#include <string.h>

int main(void) {

    int arr[27] = { 0x24, 0x27, 0x13, 0xC6, 0xC6, 0x13, 0x16, 0xE6, 0x47, 0xF5, 0x26, 0x96, 0x47, 0xF5, 0x46, 0x27, 0x13, 0x26, 0x26, 0xC6, 0x56, 0xF5, 0xC3, 0xC3, 0xF5, 0xE3, 0xE3 };
    int flag[27];

    for (int i = 0; i < 27; i++) {

        flag[i] = ((16 * arr[i]) & 0xF0) | (arr[i] >> 4);
        printf("%c", flag[i]);

    }

    return 0;

}

코드를 실행하면, 위와 같은 문자열이 출력된다.

 

해당 문자열을 입력하니 “Correct”가 정상적으로 출력된다.

 

웹해킹

  • LORD OF SQLINJECTION사이트의 cobolt문제
  • webhacking.kr의 old-54번 문제

문제 1 : LORD OF SQLINJECTION사이트의 cobolt문제

 

1. 문제 살펴보기

  • 이전 문제와 매우 비슷한 코드를 가지고 있다.
  • php의 preg_match이라는 함수는 get방식으로 전달되는 id와 pw값에 prob, _,., \등을 적을 수 없다. 맨 뒤의 /i는 대소문자 구별을 하지 않겠다는 의미이다.
  • $query = "select id from prob_cobolt where id='{$_GET[id]}' and pw=md5('{$_GET[pw]}')";
  • 해당 문제에서는 id에 해당하는 pw를 md5라는 것을 이용한다.

문제 해결하기

  • 위에서 언급한 md5는 128비트 암호화 해시 함수이다. md5를 이용해서 pw를 해싱하는 것이다.
  • if($result['id'] == 'admin') solve("cobolt");
  • 위의 코드를 보아 admin으로 접근해야 한다는 것을 알 수 있다.
  • url encoding → space는 %20, 작은따옴표는 %27
  • 따라서, id 파라미터로 admin%27 or 1=1—;을 입력하고자 한다. 코드에 잇던 $query에 입력된 모양은 아래와 같다.
  • $query = "select id from prob_gremlin where id='admin'or''=''and pw='{$_GET[pw]}'";
  • or ‘’=’’이라는 무조건 참이 되는 조건을 넣어 모든 레코드를 가져오도록 지시하였다.

 

문제 2 : webhacking.kr의 old-54번 문제

webhacking.kr의 old-54번 문제를 보니 여러 문자가 화면에 나타나다 최종적으로 ?문자가 뜬다.

 

1. 문제 살펴보기

  • 지나가는 문자와 숫자 중 FLAG라는 문자가 각각 표시되었다 사라지는 것을 보았고 이를 모두 합치면 최종 플래그가 나올 것이라는 것이라고 추측하였다.
  • 따라서 변화하는 문자열 속 FLAG 값이 존재하는 것처럼 연속된 문자열을 알아내면 될 것이다.
  • 간단히, 빠르게 지나가는 글자를 기록하여 인증하는 문제인 것이다.

2. 코드 살펴보기

  • function answer(i) { ... }: 비동기적으로 서버에 데이터를 요청하고, 받은 응답을 화면에 표시하는 함수입니다.
  • **x.open()**을 사용하여 GET 요청을 생성하고, **x.send()**로 요청을 보냅니다. 일정 시간 후에 다시 answer() 함수를 호출하면서 서버로부터 응답이 오면 계속해서 화면에 표시한다.
  • setTimeout("answer(0)",1000);: 웹 페이지가 로드된 후 1초 후에 answer(0) 함수를 호출하여 초기 요청을 시작한다. 이를 통해 초기에 서버로부터 데이터를 요청하고 화면에 표시된다. 시작 후 20ms마다 요청이 반복된다.
  • 코드의 주요 목적은 서버로부터 데이터를 주기적으로 받아와서 화면에 표시하는 것이다. **x.responseText**는 서버로부터 받은 응답의 내용을 나타내며, 이를 통해 웹 페이지에 동적인 내용을 업데이트할 수 있다.
  • aview.innerHTML=x.responseText;: 서버에서 받은 응답 데이터를 화면에 데이터를 표시한다.
  • if(x.responseText=="") aview.innerHTML="?";: 서버 응답이 없을 경우 내용을 물음표(?)로 설정합니다. 이는 데이터가 없을 때 화면에 표시되는 내용입니다.

3. 문제 해결하기

  • 위의 코드 분석을 활용하여 각 문자들이 20ms마다 변경되는 것이 아니라 점점 쌓여가는 +=의 코드로 변형하여 입력해야겠다고 생각했다.
  • aview.innerHTML=x.responseText;
  • 위의 코드는 화면에 띄워진 문자열이 쌓이는 것이 아니라 새로운 값으로 초기화 되는 것이니 = → += 와 같이 코드를 변경하여 문자열들이 쌓이도록 하였다.
  • if(x.responseText=="")aview.innerHTML="?";
  • 또한 서버 응답이 없을 경우 ?를 띄우는 위와 같은 코드는 삭제하여주면 된다.

개발자 도구 console창에서 함수 answer을 재정의하고 answer(0)을 추가로 입력하여 초기요청을 하였다. 그랬더니 이전처럼 문자가 나타났다 사라지는 것이 아니라 계속해서 추가되었다. 해당 플래그를 입력하였더니 문제를 풀 수 있었다.