[MISC]

지난 해킹캠프의 발표 이력들을 술을 마시고 구경하다가 이것을 토대로 문제를 만들면 어떨까? 라는 생각으로 시작되었습니다. 

이번에도 역시 문제 제목으로 접근을 해보겠습니다.

제목이 HWP Malware Custom 입니다.

HWP나 Word에 스크립트를 사용하여 악성코드를 유포하는 행위가 빈번합니다.
이 중 대표적으로 VB(Visual Basic), PS(Post Scripts), PS1(Power Shell), GS(Ghost Scripts)등이 있습니다.

또한, 최근에는 악성코드들을 아주 교묘하게 숨겨두는 방법이 많이 사용됩니다.
예를 들어보면 이렇습니다. 분명 대충 눈으로 보게 되면 단 1페이지만 글이 작성되어 있지만 자세히 들여다보면 페이지는 2로 되어 있는 경우 입니다.
이런 악성코드는 1페이지에 아주 작은 박스 모양으로 스크립트가 삽입되어 있는 경우가 허다합니다.

저는 이런 정보를 토대로 이런 식의 시나리오를 작성해보았습니다.

1. Ghost Scripts를 사용해보자.
2. 첫 페이지에 삽입하지 말고, 제일 마지막 페이지에 삽입을 해보자.

저에게는 서버가 없었기 때문에 어떻게 스크립트를 삽입할까? 이게 큰 고민거리 였습니다.
고민은 곧 해결되었습니다. 이가 없으면 잇몸으로라도 라는 말이 있듯이 "구글 드라이브"를 사용하기로 결정했습니다.

하지만, 우연히도 악성코드를 키워드로 삼아 이전 기사들을 들여다보다가 이러한 글을 발견하게 됩니다.
https://www.ahnlab.com/kr/site/securityinfo/secunews/secuNewsView.do?seq=26179

우연치 않게 즉석으로 생각한 시나리오대로 흘러갔을 뿐인데, 이 시나리오가 실제로 존재했던 기법이었습니다. 이로써 크래커 혹은 악성코드 유포자들은 독특한 생각을 한다는 것을 알게 되었고 이와 더불어, 화이트 해커도 이에 못지 않은 다양한 상상력의 범위를 넓혀가야한다고 생각하게 되었습니다.

본격적으로 문제 자체에 대한 풀이를 진행해봅니다.

우선, 바이너리에서 구글드라이브 경로를 찾았다면 이러한 파일을 획득할 수 있습니다. 파일을 열어보면, 여러가지 문장과 함께 Ghost Scripts를 커스텀한 알 수 없는 문자열들이 박혀있습니다. 실제 악성코드의 경우 이 부분에 XOR 시키는 KEY VALUE와 SHELL CODE가 일반적으로 적용되어 있습니다. 물론 모든 악성코드가 수학 공식처럼 일반화 되어 있는 것은 아니니 이 부분은 함부로 판단해서는 안 됩니다.

다시 문제 설명으로 돌아가보겠습니다.

스크립트로 추정되는 파일을 들여다보면 상기에는 친절한 설명이 수록되어 있습니다.

Hello I am c0nstant In DemonTeam.
This  problem's intend is not a problem with complex operations.
this code can not active.  
But, Nowadays This Scenario is exist.
There is a lot of malicious code using postscript or ghostscript.
But This Scenario is not used postscript 
Now, if you are a real malware, the binary will be doing malicious activity.
Most malicious activity occurs through C&C.

Now read the pseudo code of the file and think about how to solve it.
Cheerup 

그 후 의사코드라고 적혀있는 명령어가 보이게 되는데요.
cmd.exe을 이용하여 매개변수로 PowerShell을 사용하면서 특정 경로로 이동시키는 전형적인 C2서버를 구현해둔 것을 볼 수 있습니다.

이제 이 부분에 접근을 하게 되면 다음과 같습니다.
--

비밀번호를 입력하라고 하네요.
비밀번호에 대한 힌트도 제공해주었습니다.

# Parsing Coding... #
passwd = 'Parsing and add all the numbers in the HWP file you have viewed. Also parse the numbers in the picture.'

글을 읽어보면, HWP 파일에서 모든 숫자를 파싱 한 후 그 숫자를 더하라. 표에 있는 내용까지 모조리 더하라. 라고 되어있음을 볼 수 있습니다.

Parsing Code는 다음과 같습니다.

#Parsed Code#

import sys
import re

if len(sys.argv) != 2:
    exit(1)

file_name = sys.argv[1]

f = open(file_name, "r")

filedata = ''
data = f.read(1024)
while data:
    filedata+=data
    data = f.read(1024)

tmp = re.findall("[0-9]*", filedata)
numbers = []
sumval = 0
for i in tmp:
    try:
        numbers.append(int(i))
        sumval += int(i)
    except:
        continue

print numbers
print sumval

비밀번호를 획득하게 되면, 저희는 낚였음을 알 수 있습니다.
그렇다면, 그 밑에 bind로 되어 있는 부분을 들여다 보아야 합니다.
우선, 상기에 언급했듯 이 문제는 쉘코드를 작성한 문제는 아니기 때문에 TXT파일을 그대로 옮겨 두었다고도 판단해볼 수 있습니다.
저 같은 경우는 이러한 상황에 직면하였을 때 항상 HxD를 사용하여 Strings값을 살펴봅니다. 이 문제 역시 Strings값이 난독화 되어 있지 않기 때문에 구조를 쉽게 볼 수 있습니다.

코드는 다음과 같습니다.

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Windows.UI.Xaml.Controls.Primitives;
using Windows.UI.Xaml.Data;
using Windows.UI.Xaml.Input;
using Windows.UI.Xaml.Media;
using Windows.UI.Xaml.Navigation;

namespace Unknown
{
    public sealed partial class MainPage : Page
    {
        public  string _str;
        public char[] tmp;
        public string information;

        // This code is not Run...
        // However You can GET Flag
        // There are a lot of ways.
        // I believe in your senses.
        // You should create script 
        // It is 3asy very 3asy 
        // Don't give up
        public MainPage()
        {
            information = "When the button is activated, the _str is displayed";

            _str = "KCDMV{Ioqamajp_Frgclet_\\sv_Gofukeit@ieeLSnivhon\`_QhhtLukamanaoanaa}"; 
            int n = _str.Length;
            this.InitializeComponent();
        }

        public void round6()
        {
            for (int i = 0; i < n; i++)
            {
                _str[55] ^= 0x3;
                _str[57] ^= 0x5;
                _str[59] ^= 0x6;
                _str[61] ^= 0x7;
                _str[63] ^= 0x6;
                _str[65] ^= 0x9;
                _str[67] ^= 0x13;
            }
            round5();
        }

        public void round5()
        {
            for (int i = 0; i < n; i++)
            {
                _str[41] ^= 0x3;
                _str[43] ^= 0x5;
                _str[45] ^= 0x6;
                _str[47] ^= 0x7;
                _str[49] ^= 0x6;
                _str[51] ^= 0x9;
                _str[53] ^= 0x13;
            }
        }

        public void round4()
        {
            for (int i = 0; i < n; i++)
            {
                _str[27] ^= 0x3;
                _str[29] ^= 0x5;
                _str[31] ^= 0x6;
                _str[33] ^= 0x7;
                _str[35] ^= 0x6;
                _str[37] ^= 0x9;
                _str[39] ^= 0x13;
            }
            round3();
        }

        public void round3()
        {
            for (int i = 0; i < n; i++)
            {
                _str[13] ^= 0x3;
                _str[15] ^= 0x5;
                _str[17] ^= 0x6;
                _str[19] ^= 0x7;
                _str[21] ^= 0x6;
                _str[23] ^= 0x9;
                _str[25] ^= 0x13;
            }
            round6();
        }

        public void round2()
        {
            for (int i = 0; i < n; i++)
            {
                _str[0] ^= 0x3;
                _str[2] ^= 0x5;
                _str[4] ^= 0x6;
                _str[6] ^= 0x7;
                _str[8] ^= 0x6;
                _str[10] ^= 0x9;
                _str[12] ^= 0x13;
            }
            round4();
        }
	
	public int help(int what)
        {
            for (int i = 0; i < 13555; i++)
            {
                for (int j = 0; j < 50; j++)
                {
                    if (what == 0)
                        break;
                    else
                    {
                        what ^= i;
                        what += 0xAB;
                        what -= what % (j + i);
                        what *= help(what - 199);
                    }
                }
            }
            what = 3;
            return what;
        }

        public void InitString()
        {
            get_flag.Text = ""; // init 
        }
/*
        public void enc()
        {
            for (int i = 0; i < n; i++)
            {
                if (i % 2 == 0)
                {
                    _str[i % 2] ^= 0xDEAD;
                }
                else
                {
                    _str[i] ^= 0xBEEF;
                }
            }
        }

*/        

        public void dec()
        {
            Random what = new Random();
            what.Next(-1000, 1000);
/*            for (int i = 0; i < n; i++)
            {
                if (i % 2 == 0)
                {
                    _str[i % 2] ^= 0xDEAD;
                }
                else
                {
                    _str[i] ^= 0xBEEF;
                }
            }
*/
            
	    USER_INPUT = Console.ReadLine();
            what = help();
            
            switch (what)
            {
                case 1:
                    round4();
                    break;
                case 2:
                    round3();
                    break;
                case 3:
                    round2();
                    break;
                case 4:
                    round5();
                    break;
                case 5:
                    round6();
              	    break;      
            }

		if(String.Compare(_str,USER_INPUT)==0)
		{
			Console.Write("C0ngratulation!");
		}	
        }


        private void button_Click(object sender, RoutedEventArgs e)
        {
            
            InitString();
            
            dec();
            
            get_flag.Text += _str;
        }
        
    }
}

이 문제를 만들기 2일 전쯤 C# 유니버셜 프로그램을 접하게 되었습니다. 그 지식을 조금 이용하여 의사코드를 작성해보았습니다. 그렇기에 주석에도 쓰여 있듯이 실제로 구동되는 코드는 아닙니다. 하지만, 자신이 사용하는 언어로는 충분히 커스텀 할 수 있습니다.

이 부분 중에 유심히 보지 않으면 실수 하는 부분을 언급해보고자 합니다.

public int help(int what)
        {
            for (int i = 0; i < 13555; i++)
            {
                for (int j = 0; j < 50; j++)
                {
                    if (what == 0)
                        break;
                    else
                    {
                        what ^= i;
                        what += 0xAB;
                        what -= what % (j + i);
                        what *= help(what - 199);
                    }
                }
            }
            what = 3;
            return what;
        }

도움말이라는 함수가 있는데 i와 j를 이용하여 2중 반복문을 어마어마하게 수행하고 다양한 연산을 하고 있습니다. 하지만 이 부분은 for문 스코프를 지나고 나면 전혀 필요 없는 garbage code가 되게 됩니다. 제 아무리 what이 발악을 해도 결과적으로 rvalue에 3이 대입되게 되어
mov eax, what
mov eax, 3 이렇게 되게 되고, 이를 반환 값으로 이용하게 됩니다.

그렇기 때문에 switch 문은 반드시 case 3만 됨을 알 수 있습니다.

   switch (what)
            {
                case 1:
                    round4();
                    break;
                case 2:
                    round3();
                    break;
                case 3:
                    round2();
                    break;
                case 4:
                    round5();
                    break;
                case 5:
                    round6();
              	    break;      
            }

하지만 이 부분도 garbage code가 되게 됩니다. 연산 루틴을 보게 되면 모두 일정한 값으로 XOR 처리됩니다.

XOR의 역연산은 고맙게도 XOR 그대로 이용할 수 있습니다. 그렇기 때문에 _str에 선언 되어 있는 값을 그대로 XOR 하게 되면 플래그를 획득할 수 있습니다.