readme.txt

readme.txt 파일을 읽어보면, file 을 복호화하는 것이 목표이며 그 암호화된 파일은 EXE 인 것 같습니다.

 

unpack UPX

암호화하는 프로그램으로 추정되는 run.exe 의 정보를 확인해 보면, upx 로 압축되어 있습니다.

따라서 이를 먼저 풀어줍니다.

 

anti Hexray

run.exe 프로그램을 보면 중간중간 분석을 방해하기 위해서 특정한 목적없이 무의미한 명령어를 반복하는 부분이 있는데, 이로 인하여 디컴파일 도구에서 제대로 분석하지 못하도록 방해하고 있습니다. 따라서 이것들을 먼저 제거해 줍니다.

 

바이트 코드를 확인해 보면 60 61 90 50 58 53 5B 가 반복이 되는데, 이를 010 editor 같은 도구를 이용하여 90 * 7 으로 치환합니다.

 

90 치환 시작 주소 90 치환 끝 주소 offset 실제 offset
0x406 0x129CD 0x125C7 0x125C4
0x129E9 0x24FB0 0x125C7 0x125C4
0x24FD7 0x3759E 0x125C7 0x125C4
0x375AD 0x49B74 0x125C7 0x125C4

이제 90으로 채워져서 NOP 으로 채워져 있지만, 디컴파일에 시간을 잡아먹기 때문에 이 NOP 공간을 점프하는 명령어를 넣습니다.

JMP 할 공간의 offset 은 0x125C7 로 동일하며, 명령어에 사용되는 5바이트를 빼주고 90 의 끝에서 다음 주소를 가리켜야 하기 때문에 1을 더해줍니다.

따라서 E9 C3 25 01 00 을 90 공간의 맨 앞부분에 넣어줍니다.

 

그러면 위와 같이 중간에 NOP 코드를 생략하는 것을 확인할 수 있습니다. 근데 위의 코드도 자세히 보면 레지스터 값을 넣고, 그대로 빼는 의미없는 함수입니다.

 

실제로 이 함수를 여러번 호출하는데, 이를 통해 디컴파일을 방해하려는 목적인 것 같습니다.

 

복호화 루틴

sub_41355e0 함수를 보면, 이 함수에서 복호화가 진행되는 것을 알 수 있습니다.

 

복호화에 사용할 키를 0x44d370 에 입력받습니다.

 

입력으로 들어온 Key 의 길이를 구합니다.

 

그 후 file 이라는 파일을 열고,

 

파일의 크기를 구하고, 파일 포인터를 처음으로 돌려 놓습니다.

 

그 후 파일의 내용을 받아서, dword [ebp - 0x8] + 0x5415b8 영역에 올립니다.

 

그 후 key 와 파일의 내용을 1바이트 씩 가져와서 xor 연산을 수행한 후, 그 값에 0xff 를 xor 즉 ~ 연산을 수행합니다.

 

그 후 그 내용을 file 에 다시 씁니다.

 

복호화 하기

파일의 마지막 부분을 보면, 평소 0으로 채워져 있어야 할 영역을 확인할 수 있습니다.

특정 문자열이 13개씩 반복되는 것으로 보아, 키는 13자리인 것을 추측할 수 있습니다.

또한 exe 라면 dos stub 에 위치한 This program cannot be run in DOS mode 를 이용하여 키를 구해보겠습니다.

 

위에서 찾은 키를 이용하여, 암호화된 파일을 복호화하겠습니다.

 

복호화 key 는 letsplaychess 이며, 그 키를 이용하여 file 을 복호화하였습니다.

 

복호화가 된 파일을 확인해 보면, upx 로 압축된 pe32 파일임을 알 수 있습니다.

 

Flag

복호화한 EXE 에서 플래그를 찾아보겠습니다.

 

먼저 UPX로 압축이 되어 있기 때문에, 이를 먼저 풀어줍니다.

 

그 후 출력하는 부분을 확인해 보면, Key -> Colle System 라는 문자열을 출력합니다.

이 부분외에는 출력하는 부분이 없는 것으로 보아, Colle System 이 플래그인 것 같습니다.

 

인증

'0x00 Reversing > 0x01 Reversing.kr' 카테고리의 다른 글

Reversing.kr :: Easy_ELF  (0) 2019.10.05
Reversing.kr :: Easy_Unpack  (0) 2019.03.01
Reversing.kr :: Easy_Keygen  (0) 2019.02.26
Reversing.kr :: Easy_Crack  (0) 2019.02.24

_start 함수

_start 함수를 보면 main 함수를 호출한다.

 

main 함수

main 함수를 보면 eax 값이 1이 아니면 Wrong 이라는 문자열로 보아 sub_804851 의 return 값이 1이 되어야 한다.

 

sub_8048451 함수

함수의 return 값이 1이 되어야 하는데, 위에서 return 을 할 수 있는 루틴은 맨 왼쪽으로 진행되는 루틴 이외에는 존재하지 않는다. 따라서 해당 루틴으로 갈 수 있는 조건들을 확인해 본다.

 

data_804a021 == 0x31
data_804a024 == 0x7c
data_804a025 == 0x00
data_804a022 == 0x7c
data_804a020 == 0x78
data_804a023 == 0xdd

위에서 부터 위의 조건들을 모두 성립해야 return 1을 한다.

data_804a020 == 0x78
data_804a021 == 0x31
data_804a022 == 0x7c
data_804a023 == 0xdd
data_804a024 == 0x7c
data_804a025 == 0x00

이를 순서대로 정리해 보면 아래와 같이 된다.

하지만 중간에 위치한 xor 도 있기 때문에 이를 적용해 본다.

data_804a020 ^= 0x34
data_804a020 == 0x78
data_804a021 == 0x31
data_804a022 ^= 0x32
data_804a022 == 0x7c
data_804a023 ^= -0x78
data_804a023 == 0xdd
data_804a024 == 0x7c
data_804a025 == 0x00

이를 최종적으로 정리해 보면 아래와 같은 조건들을 만족해야 한다.

data_804a020 == 0x78 ^ 0x34        // L
data_804a021 == 0x31                    // 1
data_804a022 == 0x7c ^ 0x32        // N
data_804a023 == 0xdd ^ -0x78        // U
data_804a024 == 0x7c                    // X
data_804a025 == 0x00                    // End of String

그러면 data_804a020 부터 있는 문자열은 어디서 오는지 확인을 해본다.

 

data_804a020 데이터

아까 main 함수에서 sub_8048451 함수를 호출하기 전에 sub_8048434 함수를 호출한다.

이를 확인해 보면, scanf 함수를 호출하는데 입력받은 값을 data_804a020 에 넣는다.

따라서 data_804a020 는 사용자가 입력한 값이며, 이 값이 위의 조건문을 성립할 경우에만 Correct! 라는 문자열을 확인할 수 있다.

 

정답

'0x00 Reversing > 0x01 Reversing.kr' 카테고리의 다른 글

Reversing.kr :: Ransomware  (0) 2019.12.09
Reversing.kr :: Easy_Unpack  (0) 2019.03.01
Reversing.kr :: Easy_Keygen  (0) 2019.02.26
Reversing.kr :: Easy_Crack  (0) 2019.02.24

0x0 Packer

PE 파일을 unpack 하기 전에 packer 에 대하여 먼저 알아본다.
packer은 사용 용도에 따라 크게 2가지로 나눌 수 있다.

0x01 Compression

  • PE 파일의 용량을 줄이기 위한 용도로 활용

0x02 Protector

  • 리버싱을 방해하기 위하여 사용
  • ex) 카카오톡, 악성코드 등

0x1 Manual Unpacking

_start 함수를 따라가다 보면, 여러 반복문들을 확인할 수 있다.

처음에 kernel32.dll 을 로드하고, 0x409000 에서 0x4994ee 까지 xor 을 하여 복호화를 진행한다.

그 후 VirtualProtect 함수를 이용하여 메모리 영역에서의 실행 권한을 변경한다. 즉 0x405000 부터 0x1000 만큼 변경이 된다.

그리고 0x401000 에서 0x405000 까지 xor 을 하여 복호화를 진행한다.

마지막으로는 0x406000 에서 0x409000 까지 xor 을 하여 복호화를 진행한다.
전반적인 복호화가 끝나고, 0x401150 으로 넘어가게 되고, packing 이 풀어진 코드를 실행한다.

0x2 PEiD

PE 에 대한 정보를 보여주는 프로그램 PEiD 를 이용하여 unpacking 을 해본다.
PEiD 에서 다운로드 받을 수 있다.

PEiD 에서 지원하는 플러그인 중에서 Generic OEP Finder 라는 이름의 플러그인이 존재한다.

해당 플러그인을 이용하면 손쉽게 OEP 를 구할 수 있다.

플러그인 분석

PEiD 의 설치 폴더에 가보면 위와 같이 플러그인을 확인할 수 있다.
이 중에서 GenOEP.dll 라이브러리를 분석해 본다.
[추후 추가 예정]

0x3 Reference

'0x00 Reversing > 0x01 Reversing.kr' 카테고리의 다른 글

Reversing.kr :: Ransomware  (0) 2019.12.09
Reversing.kr :: Easy_ELF  (0) 2019.10.05
Reversing.kr :: Easy_Keygen  (0) 2019.02.26
Reversing.kr :: Easy_Crack  (0) 2019.02.24

루틴 분석


_start 의 마지막 루틴 부분에 보면, __return_addr, var_4, var_8_2 를 인자로 하여 sub_401000 함수를 호출한다.

sub_401000

sub_401000 함수는 크게 4가지로 나눌 수 있다.


Input Name이라는 문자열을 출력하며, 이름을 입력 받는다.


그 후 이름을 이용하여, 어떠한 문자열들을 생성한다.


그리고 Input Serial: 라는 문자열을 출력하고, 시리얼을 입력 받는다.


마지막으로는 이름을 기반으로 생성한 문자열과 시리얼을 비교하고, 이에 대한 결과를 출력한다.
따라서 2번째 과정인 이름을 기반으로 시리얼을 생성하는 과정을 분석해 본다.

시리얼 생성


생성 루틴의 처음을 보면, esi 와 0x3 을 비교한 후 작을 경우에 xor을 생략한다.
처음 루틴에 들어왔을 경우에는 esi 가 0으로 초기화되었기 때문에, xor 루틴을 생략된다. 그리고 esi 는 0 이기에 esp+0xc 를 ecx에 넣는다.


여기서 esp+0xc 에는 0x10이 들어가 있다. 이 값은 esi 의 증가에 따라서 0x10, 0x20, 0x30 으로 변화할 것이다.
그 다음으로는 esp+ebp+0x10의 바이트를 edx에 넣는다. 마찬가지로 ebp 는 0으로 초기화 되어있기에 esp+0x10 의 바이트가 들어갈 것이고, esp+0x10에는 입력받은 문자열의 시작 주소를 가리키고 있다.
따라서 ebp 가 증가함에 따라 edx 에는 입력받은 문자열에서 순서대로 하나씩 문자가 들어갈 것이다.
def name2serial(name):
    xorKey = ["10", "20", "30"]
    serial = []
    for i in range(len(name)):
        ecx = int(xorKey[i%3],16)
        edx = int(name[i].encode('hex'),16)
        serial.append(format(ecx ^ edx,'x'))
    return "".join(serial)
분석한 생성 과정을 바탕으로, 이름에서 시리얼을 생성하는 파이썬 코드는 위와 같다.

이름 복구

생성 방식이 단순히 xor 만을 이용하기에, 시리얼에서 이름을 구하는 코드도 비슷하다.
def serial2name(serial):
    xorKey = ["10", "20", "30"]
    name = []
    for i in range(0,len(serial)/2):
        ecx = int(xorKey[i%3],16)
        edx = int(serial[i*2:i*2+2],16)
        name.append(chr(ecx ^ edx))
    return "".join(name)
시리얼을 문자열 형태로 받고, 이를 이용하여 이름을 복구(?!)한다.


이를 이용하여 이름을 구하면 "K3yg3nm3" 라는 이름으로 나온다.


확인 결과 이름과 시리얼이 일치하는 것을 확인할 수 있다.




'0x00 Reversing > 0x01 Reversing.kr' 카테고리의 다른 글

Reversing.kr :: Ransomware  (0) 2019.12.09
Reversing.kr :: Easy_ELF  (0) 2019.10.05
Reversing.kr :: Easy_Unpack  (0) 2019.03.01
Reversing.kr :: Easy_Crack  (0) 2019.02.24

문자열 확인

본격적인 분석에 앞서 문자열을 확인해 본다.

00406030에 비밀번호가 틀렸을 경우에 나올 문자열이, 00406044에 비밀번호가 맞을 경우에 나올 문자열로 추정되는 문자열이 보인다. 해당 문자열의 Xref를 보면 sub_401080에서 사용이 된다.

루틴 분석

위의 경우에는 비밀번호를 확인하고 그 결과에 따라, Congratulation !! 이라는 메시지박스를 출력 또는 Incorrect Password 라는 메시지박스를 출력합니다.

401080 함수의 시작 부분을 보면, 로컬 스택으로 0x64 만큼 사용한다. 그 후 GetDlgItemText 함수를 호출한다.
UINT GetDlgItemText(
  HWND hDlg, // handle
  int nIDDlgItem, // 컨트롤 ID
  LPTSTR lpString, // 문자열 버퍼
  int nMaxCount // 버퍼의 길이
);
각 인자에 대한 설명은 다음과 같다. 핸들과 컨트롤 ID 를 넘겨주고, 그에 대한 문자열이 반환값이다.
var_64 즉 call 이 끝난 후 cmp 부분에서 esp+0x4는 lpString의 문자열이 들어있다. 따라서 esp+0x5은 lpString[1] 이라는 것을 알 수 있고, 0x61 과 비교를 한다.
[*] password: { _ a }

그 후 스택에 0x2 를 넣고, ecx 에 esp+0x0a 를 넣는다. 여기서 esp+0x0a 에는 lpString[2] 의 값이 들어있다. 왜냐하면 위에서 esp+0x4 에 lpString[0]이였고, 그후 0x2를 넣었기 때문에 esp+0x8 이 lpString[0]이 되었다.

0x406078 을 스택에 넣는데, 해당 주소에는 5y 라는 문자열이 들어있다. 그 후 ecx (lpString[2])를 스택에 넣고, sub_401150 함수를 호출한다. 즉 sub_401150(lpString[2], 0x406078, 2) 이며, 2인자의 주소는 포인터로 보인다.
sub_401150 함수 안에 들어가 보면, 해당 함수는 strncmp 함수 인 것을 알 수 있다. 따라서 lpString[2] 와 5y 2자리를 비교한다.
[*] password: { _ a 5 y }

그 다음으로는 ebx, esi 를 순차적으로 스택에 넣고, esi 에는 R3versing 의 주소 값을 넣는다. 그리고 esp+0x10 을 eax 에 넣는다. 앞에서 push 2번을 하였기에, esp+0x10 에는 lpString[4] 가 들어간다.

그 후에 dl 에 lpString 을, bl 에 R3versing 을 한 글자씩 넣고 이 둘을 비교한다. 그리고 test cl,cl 을 통해 문자열의 끝인지도 확인을 한다.
[*] password: { _ a 5 y R 3 v e r s i n g }

마지막으로 문자열(lpString) 의 처음이 0x45("E") 인지 확인을 한다.
[*] password: { E a 5 y R 3 v e r s i n g }

Reference

'0x00 Reversing > 0x01 Reversing.kr' 카테고리의 다른 글

Reversing.kr :: Ransomware  (0) 2019.12.09
Reversing.kr :: Easy_ELF  (0) 2019.10.05
Reversing.kr :: Easy_Unpack  (0) 2019.03.01
Reversing.kr :: Easy_Keygen  (0) 2019.02.26

+ Recent posts