# -*- coding:utf-8 -*-

'''
Date - 2017.10.23
'''

import struct

#Check Bit
def checkBit(value):
if value == "e000":
print "\tSize Of Optional Header = 32bit"
elif value == "f000":
print "\tSize Of Optional Header = 64bit"
else:
print "\tI Don't know what is this"


#Check Machine
def mCheck(hex):
if hex == "6486":
print "\tMachine = " + "AMD64"
elif hex == "4c01":
print "\tMachine = " + "Intel386"

#Print Section Infomation
def secinfo(f):
print "\tName = ", f.read(8).encode('ascii')
print "\tVirtualSize = " + "0x" + f.read(4)[::-1].encode('hex').upper()
print "\tVirtualAddress = " + "0x" + f.read(4)[::-1].encode('hex').upper()
f.seek(24, 1)
space()


#Line Feed
def space():
print "\n",


def parser(filepath):
with open(filepath, 'rb') as f:
space()
print "[+] Only 32Bit Program Can Parse"
print "[+] Parsing Start = ", filepath
space()

#[IMAGE_DOS_HEADER]
print "[IMAGE_DOS_HEADER]"
e_magic = f.read(2)
print "\tHeader Signature = " + e_magic
f.seek(60)
e_lfanew = f.read(4)
print "\tOffset PE = " + "0x" + e_lfanew[::-1].encode('hex')
space()

#[IMAGE_FILE_HEADER]
print "[IMAGE_FILE_HEADER]"
startpe = struct.unpack("<L", e_lfanew)[0]
f.seek(startpe)
print "\tSignature = " + f.read(4)
machine = f.read(2).encode('hex')
mCheck(machine)
nSection = f.read(2)[::-1].encode("hex")
if nSection == "000a":
nSection = 10
else:
print "\tNumber Of Sections = ", int(nSection)
f.seek(12, 1)
bit = f.read(2).encode('hex')
checkBit(bit) # 32 | 64 bit
space()

#[IMAGE_OPTIONAL_HEADER]
print "[IMAGE_OPTIONAL_HEADER]"
f.seek(18, 1)
oep = f.read(4)[::-1].encode('hex')
print "\tAddress Of Entry Point = ", "0x" + oep.upper()
f.seek(8, 1)
imgbase = f.read(4)[::-1].encode('hex')
print "\tImageBase = " + "0x" + imgbase
space()

#[SectionTable]
print "[SectionTable]"
f.seek(192, 1)
for i in range(int(nSection)):
secinfo(f)

print "PATH :",
path = raw_input('')
parser(path)



[그림 1] - 경로 입력


위 코드를 실행하게 되면 위와 같이 PE 구조의 분석을 원하는 프로그램의 경로를 입력을 받으며, 입력 시 아래와 같이 파싱 결과를 출력한다


[그림 2] - PE 구조 파싱




리버싱에 필요한 PE 구조를 분석에 필요한 부분만 파싱하는 도구를 만들어 봄으로써 PE 구조에 대한 이해도를 높일 수 있었고, 


완벽하게 만들진 않았기에 위 코드가 혹.시. 필요하다면 참조를 통해 더 필요한 부분의 추가나 수정을 통해 사용하길 바랍니다



참조

http://blog.eairship.kr/270

'Coding' 카테고리의 다른 글

Crawling  (0) 2018.07.27
[Pcap Parser]  (0) 2018.07.22
[ Crawler ] Kakao Donation Automation Scripts (카카오 기부 자동화 스크립트)  (1) 2018.02.21
원탁의 기사  (0) 2018.01.05
100 계단 오르기  (0) 2018.01.04

#About


Author : Cruhead / MiB



Korean :

이 프로그램은 Key파일을 필요로 하는 프로그램이다.

위 문구가 출력되도록 하려면 crackme3.key 파일안의 데이터는 무엇이 되어야 하는가

Ex) 41424344454647


English :

This program needs a key file.

What does the data in the file crackme3.key have to be to make it print the above message.

Ex) 41424344454647

(This problem has multiple answers, so post your answer on the messageboard in a private thread and we will verify it for you.)




#Contents


XOR 연산의 이해를 통해 CRACKME3.KEY 파일안의 데이터를 입력해줌으로써 CodeEngn!을 출력하는 것이 목표다




#Solution


[그림 1] - 실행화면


20.exe를 실행하면 위와 같은 UI를 확인할 수 있으며 별 다른 기능은 없어보인다


[그림 2] - MASM


PEiD를 통해 패킹여부를 확인결과, 패킹은 되어 있지 않았고 어셈블리어로 코딩되어 있었다


[그림 3] - 실행 화면


00401066 위까지의 과정을 설명하면 다음과 같다. CreateFileA 함수를 통해 CRACKME3.KEY 파일을 만들고 


ReadFile 함수를 통해 0x12 즉 18바이트를 읽어들인다 이는 먼저 CRACKME3.KEY 파일을 18바이트를 채워 만들어 놓는 


과정을 진행 해야 함을 의미하기에 아래와 같이 18 바이트 크기의 CRACKME3.KEY 파일을 만들어 주었다


[그림 4] - CRACKME3.KEY 파일


위와 같이 파일을 작성하고 ReadFile 함수를 거치게 되면 아래와 같이 00402008~004020019부분에 CRACKME3.KEY 파일의 내용을 읽어 들인다


[그림 5] - ReadFile 함수 실행


위와 같이 CRACKME3.KEY 파일을 읽어 들인 후 


[그림 6] - 00401311 호출


위의 00401074 주소의 명령을 살펴보면, 00401311을 호출하며 해당 호출 주소로 따라가 본 결과는 아래와 같다


[그림 7] - 알고리즘


위 0040133B 까지의 루틴을 해석하면 아래와 같다


00401311

ECX를 0으로 만듦


00401313

EAX를 0으로 만듦


00401315

[ESP+4]의 주소 값을 ESI에 복사 = CRACKME3.KEY 파일의 내용이 적힌 주소 값을 ESI에 복사


00401319

41을 EBX의 최하위 1 바이트에 복사


0040131B

[ESI]의 주소 값에 담긴 최하위 1 바이트를 EAX에 복사


0040131D

EAX와 EBX의 최하위 1 바이트를 XOR 연산 결과를 EAX에 복사


0040131F

EAX의 최하위 1 바이트를 ESI의 최하위 바이트에 복사


00401321

ESI 값을 1증가


00401322

EBX의 최하위 1 바이트 값 증가


00401324

EAX 값을 4020F9에 복사


0040132A

EAX의 최하위 1바이트를 0과 비교


0040132C

0040132A의 명령이 참이라면 00401335로 분기


0040132E

ECX의 최하위 1 바이트값을 1증가


00401330

EBX의 최하위 1바이트와 0x4F 비교


00401333

00401330의 명령이 거짓이라면 0040131B로 분기


00401335

ECX의 값을 402149로 복사


0040133B

리턴


위 루틴은 결국 반복문이 진행되는 동안 문자열 1바이트와 0x41~0x4F까지 XOR한 결과 값인 AL을 4020F9에 계속 더한다


0x41~0x4F의 길이는 14이며 이는, CRACKME3.KEY의 14글자는 XOR 연산에 쓰이지만 나머지 4글자는 쓰이지 않음을 의미하며


이를 다시 설명하면 결국 14글자가 키 값을 생성하는 알고리즘에 사용되는 것이고 나머지 4글자가 키에 해당한다


다시 코드로 돌아온 아래 그림을 살펴보자


[그림 8] - 포인트 루틴


위 빨간 네모박스에 있는 루틴을 간략히 설명하면, 00401074의 XOR 연산 결과 값이 담긴 주소의 값이랑 12345678이랑 XOR 연산을 진행한다


그리고 0040108B에서 CALL 0040133C 명령을 통해 XOR 연산에 쓰이지 않은 나머지 4글자를 가져와 EAX에 반환한다


00401093의 비교 구문을 통해 EAX와 [4020F9]를 비교하며 00401099의 SETE AL명령을 통해 위 조건문(CMP)의 결과가 


참일 경우 1을 반환하며 아닐 시 보수를 AL의 사이즈에 맞게 반환한다


00401093의 비교구문의 거짓을 의미하는 보수 값이 AL에 담기면 0040109F의 명령을 통해 실패로 분기하며


참을 의미하는 값 1이 AL에 담겼다면 004010A6의 CALL 00401346의 명령을 통해 크랙 성공 메시지가 출력된다


[그림 9] - XOR 값


[그림 10] - 인증



'Wargame' 카테고리의 다른 글

[Lord of SQL Injection] Cobolt  (0) 2017.10.25
[Lord of SQL Injection] Gremlin  (0) 2017.10.25
CodeEngn Basic 19  (0) 2017.10.19
CodeEngn Basic 18  (0) 2017.10.17
CodeEngn Basic 17  (0) 2017.10.17

#About


Author : CodeEngn


KO : 이 프로그램은 몇 밀리세컨드 후에 종료 되는가

EN : How many miliseconds does it take for this program to terminate




#Contents


해당 문제는 안티디버깅 함수인 IsDebuggerPresent와 timeGetTime 함수의 이해를 목표로 한다




#Solution


[그림 1] - 실행화면


19.exe의 실행화면이며 약 10초 후 해당 프로그램이 종료된다. 메시지 박스만 띄우고 종료되기에 딱히 프로그램이라 할 것은 없지만 분석을 진행해보자


[그림 2] - UPX 패킹


패킹 여부 확인 결과 UPX로 패킹되어 있었으며 upx.exe를 이용해 아래와 같이 언패킹을 진행했다


[그림 3] - UPX 언패킹


다른 문제 들과 같이 언패킹 진행 후 아스키 값과 함수 목록을 보았지만, 데이터가 많은 탓에 파악하기 힘들었다


그래서 많은 양의 데이터 중 필요한 데이터만 찾아내기 위해 문제에서 주어진 "밀리세컨드"라는 단어를 이용해


시간 관련 API를 검색결과 timeGetTime()라는 함수의 리턴 값의 단위가 "밀리 초"임을 통해 문제와 관련 있음을 유추할 수 있었다


[그림 4] - BreakPoint > timeGetTime()


유추한 것을 토대로 함수 목록에서 timeGetTime()을 찾아 보았더니 여러개를 확인할 수 있었고, 모든 해당 함수에

bp를 걸어준 뒤 실행 시켜보았더니 아래와 같은 메시지가 출력된 후 프로그램이 종료 되었다


[그림 5] - Information MessageBox


무엇 때문에 자꾸 종료되는지 해 있던 찰나, 코드엔진 Basic 4번(http://croas.tistory.com/21)서 확인할 수 있었던 안티 디버깅 함수

  IsDebuggerPresent()이 떠올랐고, 해당 함수의 존재 여부를 확인후 bp를 걸고 아래와 같이 조금의 코드 수정을 해주었다


[그림 6] - 코드 수정


위와 같이 코드 수정을 진행해준 뒤 실행시키다보면 아래와 같이 00444C44에서 멈추는 것을 확인할 수 있다


[그림 7] - 주석(1)


[그림 8] - 주석(2)


[그림 7]과 [그림 8]의 과정은 해당 문제의 핵심이되는 부분이기에 주석을 달아 놓았으며 세부 설명으로는 00444C5F에서

timeGetTime 함수를 재 호출하여, 00444C44에서 받아 왔던 처음 시간 값과 비교하여 크거나 같으면 00444D38 지점으로

분기하여 2번째 timeGetTime 함수의 반환 값에서 1번째 timeGetTime 함수의 반환 값을 뺀다 timeGetTime 함수 반환의 차이값인 

EAX와 EBX+4 주소가 가리키는 값을 비교하여 분기


- 차이값이 작으면 시간을 구하는 부분으로 돌아가 이 과정을 반복

- 차이값이 크면 다른 곳으로 분기 후 프로그램을 종료시킨다

( 인용 : http://l3m0n-tr33.tistory.com/entry/CodeEngn-Basic-RCE-L19 )


따라서 EAX와 비교하는 EBX+4의 주소가 가르키는 값을 확인하면 해당 문제를 해결할 수 있으며 값 확인 결과는 아래와 같다


[그림 9] - EBX+4의 4바이트 값 확인


0x2B70 = 11120ms = 11.12초

'Wargame' 카테고리의 다른 글

[Lord of SQL Injection] Gremlin  (0) 2017.10.25
CodeEngn Basic 20  (0) 2017.10.23
CodeEngn Basic 18  (0) 2017.10.17
CodeEngn Basic 17  (0) 2017.10.17
CodeEngn Basic 16  (0) 2017.10.13

+ Recent posts