일단 문제를 보면 Key값이 주어졌고 이를 이용하여 Name을 찾는 문제였다.

 

이번 문제는 너무 어려워서 다른 라이트업을 참고했지만 그것도 완벽히 이해하지는 못했다...

 

 

먼저 주어진 Key 입력하고 Name 아무거나 입력해보았다. Check it을 눌러보았더니

 

 

위와 같이 Key값을 더 입력하라는 메세지가 떴다..

처음에는 Name이 한자리라고 했으니까 다 입력해볼까 생각도 했지만 일단 먼저 디버거로 열어봤다.

 

 

코드를 진행하다가 위와 같은 부분을 발견할 수 있었다.

 

 

 

0045BB12~18 부분을 통해 내가 입력한 문자열을 불러올 것을 예측할 수 있었다.

이때 0045BB24 부분을 보면 CMP EAX,3으로 되어 있는데 문제에서 Name값이 한자리라고 했으므로 CMP EAX,1로 패치를 해주어야 한다.

또한, 아까 봤던 Please Enter More Chars가 있는 것을 확인할 수 있고

밑의 주석 부분은 30글자가 넘으면 나오는 메세지임을 예측할 수 있다.

 

 

또한 좀 더 코드를 살펴보면 위와 같이 성공 메세지 창이 뜨는 부분을 확인할 수 있다.

 

 

일단 위와 같이 CMP EAX, 1로 패치해준 후 코드를 한줄씩 실행했다.

 

 

위와 같이 첫번째 루틴이 나오고 이러한 연산이 몇 번 반복된다.

 

 

그렇게 계속 진행시키다보면 0045BBA9에서 내가 입력한 값이 위와 같이 저장된 것을 확인할 수 있다.

이때 첫번째 루틴이 끝나고 EDX 레지스터에 저장된 값을 확인해보면 어떻게 연산을 해도 항상 4자리가 같다는 것을 확인할 수 있다.

 

이렇게 연산을 다시 진행하여 BEDA-2F56-BC4F4368-8A71-870B이 나오도록 구해주면 된다.

 

이때 코드를 직접 짜서 확인해주는 작업이 필요한데 이 부분은 완벽히 이해하지 못해서 라이트업을 참고했다.

 

 

위와 같이 코드를 짜준 후에 실행 결과를 확인해보면

 

 

위와 같이 F를 연산했을 때 beda가 나오는 것을 확인할 수 있다.

 

 

따라서 재시작 후 Name에 F를 입력해주면 위와 같이 아까 주석으로 본 성공메세지창이 잘 나타나는 것을 확인할 수 있다.

 

또한, 사이트에서는 F의 MD5 해쉬값으로 인증을 해주면 된다.

1. 레지스터란?

- CPU가 요청을 처리하는 데 필요한 데이터를 일시적으로 저장하는 기억장치

- 메모리로 연산의 결과를 보내고 영구적으로 저장할 데이터를 하드디스크에 저장하는 등의 명령 처리

- 연산 속도가 메모리보다 훨씬 빠르며 특정 주소를 가리키거나 값을 읽어올 수 있음

- 컴퓨터의 bit가 클수록 처리속도가 빠름

- 어셈블리에서 레지스터란 CPU가 사용하는 변수를 의미

 

 

2. 레지스터의 종류

1) 범용 레지스터 (General Purpose Register)

 

- 작은 데이터의 임시 저장 공간

- 연산 처리 및 번지 지정을 도와주며 컴퓨터의 장치를 제어함

 

종류 특징
EAX
(Accumulator)
  - 누산기 레지스터로 입출력, 산술연산, 논리연산을 수행
  - 함수의 리턴 값 저장
  - AX는 EAX의 오른쪽 16비트 부분
  - AH는 AX의 왼쪽 8비트 상위부분, AL은 AX의 오른쪽 8비트 하위부분
EBX
(Base address)
  - 주소 지정을 확장하기 위해 인덱스로서 사용하는 레지스터
  - BX는 EBX의 오른쪽 16비트 부분
  - BH는 BX의 왼쪽 8비트 상위부분, BL은 BX의 오른쪽 8비트 하위부분
ECX
(Counter)
  - 반복문을 수행할 때 반복 횟수를 지정하는데 사용
  - CX는 ECX의 오른쪽 16비트 부분
  - CH는 CX의 왼쪽 8비트 상위부분, CL은 CX의 오른쪽 8비트 하위부분
EDX
(Data)
  - 큰 수의 산술연산, 논리연산을 할 때 EAX의 보조적 역할로 사용
  - EAX 레지스터와 같이 쓰임
  - 부호 확장 명령 등에 사용
  - DX는 EDX의 오른쪽 16비트 부분
  - DH는 DX의 왼쪽 8비트 상위부분, DL은 DX의 오른쪽 8비트 하위부분

 

2) 포인터 레지스터 (Pointer Register)

 

- 메모리 스택 영역과 관련된 주소 값을 나타냄

 

종류 특징
ESP (Extended Stack Pointer)   - 스택 영역의 최상단을 가리킴
EBP (Extended Base Pointer)   - 스택 영역의 기준이 되는 주소를 가리킴
EIP (Extended Instruction Pointer)   - 다음 실행할 명령이 들어 있는 메모리의 주소를 가리킴

 

3) 인덱스 레지스터 (Index Register)

 

- 문자열을 복사/비교

- 인덱스 주소지정과 덧셈, 뺄셈에서 사용 가능

 

종류 특징
ESI (Extended Source Index)   - 복사/비교의 대상의 주소를 가리킴
EDI (Extended Destination Index)   - 복사/비교를 할 곳의 주소를 가리킴

 

4) 세그먼트 레지스터 (Segment Register)

 

- 다양한 크기로 구분을 하는 목적으로 사용

- 16비트로 구성

 

종류 특징
CS (Code Segment)   - 함수와 제어문 같은 명령어들이 저장되는 코드 세그먼트
DS (Data Segment)   - 전역, 정적 변수 데이터가 들어있는 데이터 세그먼트
SS (Stack Segment)   - 주소와 데이터를 일시적으로 저장할 목적으로 쓰이는 세그먼트
  - 스택의 주소를 지정하는데 사용
ES (Extra Segment)   - 추가 레지스터로 문자 데이터의 주소를 지정하는데 사용

 

5) 플래그 레지스터 (Extended Flags Register)

 

- 실행 순서를 제어하기 위한 목적으로 사용

- 플래그를 1로 설정하는 것을 SET, 0으로 설정하는 것을 CLEAR 또는 RESET이라고 함

 

종류 특징
상태플래그 CF (Carry Flag)   - 산술 연산 후 상위 비트의 캐리를 포함
  - 자리이동 또는 회전연산 후 마지막의 비트내용을 포함
PF (Parity Flag)   - 연산 결과 1비트들의 개수에 따라 짝수 패리티와 홀수 패리티로 나타냄
OF (Overflow Flag)   - 산술 연산 후 상위비트의 오버플로우를 나타냄
AF (Auxiliary Flag)   - 특수화된 산술에서 사용
  - 비트 3에서 비트 4로의 캐리를 포함
ZF (Zero Flag)   - 산술이나 비교 연산의 결과를 나타냄
SF (Sign Flag)   - 산술 연산의 결과값에 대한 부호를 포함
제어플래그 DF (Direction Flag)   - 스트링 데이터를 이동시키거나 비교할 때 방향 결정
시스템플래그 TF (Trap Flag)   - 단일 단계 모드의 프로세서 연산 허용
IF (Interrupt Flag)   - 키보드 입력과 같은 외부 인터럽트의 처리 여부를 나타냄

 

먼저 문제를 확인해보면 C드라이브의 이름이 CodeEngn일 때 생성되는 시리얼 값이 어떤 것으로 변경되는지 알아보는 것이다.

 

 

먼저 실행을 한 후 아무거나 입력해보면 위와 같은 메세지창이 뜨는 것을 확인할 수 있다.

 

 

먼저 C드라이브 이름이 CodeEngn이라고 했으므로 내컴퓨터 C드라이브 이름을 바꾸어준 후에

Immunity Debugger로 실행 파일을 열어 보았다.

어떻게 시작해야할지 몰라 코드를 확인해보다가 위와 같은 부분을 발견할 수 있었다.

004010FC 부분에서 EAX레지스터의 값과 0을 비교하여 시리얼값 정답 여부를 판별해주는 분기문을 볼 수 있었다.

 

 

코드를 실행하다보면 위와 같이 GetVolumeInformationA라는 함수를 발견할 수 있는데 이는 하드디스크의 볼륨값을 불러오는 함수로 이 함수를 통해 내 컴퓨터의 C드라이브 값을 불러온다는 것을 예측할 수 있다.

 

 

위와 같이 004010AD 부분으로 이동한 후 코드를 확인해봤더니 스택의 값을 1씩 더해주는 것을 두 번 반복하는 코드를 확인할 수 있다.

 

 

위에서 봤던 코드를 한줄씩 실행시켰더니 아까와는 다르게 Code 부분이 Eqfg로 바뀐 것을 확인할 수 있다.

 

 

또한 StringToAdd를 실행시켜주면 해당된 문자열을 연결해준 것이 Serial이라는 것을 예측할 수 있다.

004010ED를 보면 알 수 있듯이 내가 입력한 Serial값인 1234와 문자열을 연결한 Serial 값을 비교할 것이다.

 

 

위 부분까지 실행한 후 레지스터 창을 확인해보면 EAX레지스터에 위와 같은 값이 들어가 있는 것을 확인할 수 있고 이 값을 CMP로 비교하여 아까 봤던 분기문을 판단해주는 것이다.

 

 

따라서 다시 실행한 후 위처럼 Serial 값을 입력해주었더니 성공 메세지창이 뜨는 것을 확인할 수 있었다.

 

또한, 문제에서 인증키는 CodeEngn이 변경된 값이었기 때문에 답은 EqfgEngn이 된다.

 

 

먼저 문제를 보면 Serial 값을 찾는 문제이다.

 

 

 

주어진 파일을 실행시켜보면 위와 같은 창이 뜨는 것을 확인할 수 있다.

 

 

Name이 CodeEngn이라고 했으니 그렇게 입력해주고 Serial 값은 아무거나 입력했더니 위와 같이 Try Again!이라는 메세지창이 뜨는 것을 확인할 수 있었다.

 

 

Immunity Debugger로 파일을 열어준 후 코드를 실행시킨 후 아까 위에서 실행했던 것처럼 Name에는 CodeEngn, Serial에는 1234를 입력한 상태이다.

위의 코드를 보면 00458831 부분에 EAX레지스터의 값과 스택에서 45B844 부분에 저장된 값을 비교하여 정답 여부를 확인해 주는 코드를 볼 수 있다.

여기서 두 값이 같다면 You cracked the ~~ 부분으로 넘어갈 것이고 다르다면 Try Again 부분으로 넘어갈 것이다.

 

 

먼저 EAX 레지스터를 확인해보면 위와 같이 1234라고 입력한 값이 저장된 것을 확인할 수 있다.

 

 

입력한 1234를 16진수로 변환한 수는 4D2이기 때문에 위와 같이 EAX레지스터에 4D2가 저장된 것을 확인할 수 있다.

 

 

또한 아까 EAX 레지스터와 비교하는 주소의 값을 확인하기 위해 Hex Dump 창에서 해당 주소(0045B844)로 이동해봤고 위와 같은 값을 확인할 수 있었다.

이때 덤프주소 값은 little endian 방식으로 읽어주어야 하고 이 방식은 낮은 주소에 데이터의 낮은 바이트부터 저장하는 방식을 의미한다. 이는 평소 숫자를 사용하는 선형 방식과는 반대로 거꾸로 값을 읽어주어야 한다.

또한, 아까 자료형이 DWORD였고 이는 4byte를 의미하므로 위의 값을 읽어보면 00 00 61 60이 된다.

 

 

즉, 위에서 본 값을 10진수로 바꿔준 값을 Serial에 입력하면 EAX레지스터에는 그 값이 16진수로 바뀌어 저장될 것이므로 CMP를 해주었을때 점프하지 않고 바로 다음 코드를 실행시킬 것이다.

위와 같이 계산기로 6160을 10진수로 바꿔주면 24928이라는 것을 확인할 수 있다.

 

 

프로그램을 다시 실행한 후 위와 같이 Serial 값을 입력해주었더니 성공창이 뜨는 것을 확인할 수 있다.

 

 

먼저 문제를 살펴보면 StolenByte를 찾아야한다는 것을 알 수 있다.

여기서 StolenByte란 말그대로 훔친코드로, 패커가 이동시킨 코드의 윗부분(보통 OEP로부터 몇 개의 명령어)이라고 한다.

이는 언패킹을 해도 정상 실행이 되지 않으며 숨겨진 코드를 제자리에 가져도 놓고 제대로 복구를 하면 정상 실행이 된다.

또한, UPX에서는 마지막 JMP 전 POPAD 이후 일정 바이트의 코드를 의미한다.

 

 

먼저 주어진 실행파일을 Immunity Debugger로 실행시켜봤더니 위와 같이 PUSHAD 명령어가 보였다.

저번에 풀었던 문제처럼 언패킹을 해야하나? 라는 생각이 들었다.

 

 

먼저 Exeinfo PE 툴로 확인을 해봤더니 UPX라는 것을 알 수 있었다.

 

 

그래서 저번처럼 cmd 창에서 upx -d 파일이름 명령어를 입력해준 후 언패킹을 해주었다.

 

 

언패킹한 파일을 Immunity Debugger로 열어봤더니 제일 위에 위와 같이 NOP 코드가 써있는 것을 확인할 수 있었다.

 

 

이를 실행시켜보니 위와 같이 알 수 없는 글자들로 깨져 오류가 생기는 것을 볼 수 있었다.

 

 

힌트를 보고 언패킹 전에 패킹된 상태로 실행했던 코드를 잘 살펴봐야겠다는 생각을 했고

다시 처음 다운로드 받았던 파일을 열어봤더니 위와 같이 POPAD를 확인할 수 있었다.

 

패킹된 프로그램을 실행하면 PUSHAD부터 POPAD까지 원본 프로그램의 코드를 복구하는 작업을 할 것이고

POPAD부터 OEP까지의 코드에 어떤 것이 있나 확인해주는 작업이 필요하다.

 

 

위에서 본 POPAD 뒤의 코드를 하나씩 실행시켜보면 위와 같이 스택에 어떤 값들이 저장되는 것을 확인할 수 있다.

 

 

 

다시 언패킹해주었던 실행파일을 열고 NOP코드 부분에 아까 패킹된 파일에서 POPAD 뒤에 값을 PUSH해주었던 부분의 HEX 코드를 위와 같이 입력해주었다.

 

 

그랬더니 위와 같이 NOP으로 있었던 코드들이 딱 맞게 채워지는 것을 확인할 수 있었다.

 

 

이후에 계속 코드를 실행시켜보면 언패킹된 파일에서도 위와 같이 메세지가 깨지지 않고 잘 출력되는 것을 확인할 수 있다.

 

 

따라서 이 문제의 답은 StolenByte인 6A0068002040006812204000이고

구글링했던 StolenByte의 개념과 일치하다는 것을 확인할 수 있었다.

 

먼저 문제를 보면 디버거를 탐지하는 함수의 이름을 찾는 것이 목표였다.

 

 

주어진 파일을 다운받은 후 실행시켜보면 위와 같이 정상이 일정한 시간마다 출력되는 것을 확인할 수 있다.

 

 

이 파일을 Immunity Debugger로 실행시켜보면 위와 같이 디버깅 당함이라는 문자가 출력되는 것을 확인할 수 있다.

 

 

우선 실행 시켰을 때 제일 먼저 나오는 코드 부분이다.

 

 

계속 F8키를 눌러 진행시키다보면 위와 같은 코드를 볼 수 있고 여기서 계속 F8키를 눌러주면 실행창에 뭔가 출력되는 것을 보고 breakpoint를 눌러주었다.

 

 

재시작 후 위에서 발견한 00408454에서 F7키를 누르면 위와 같은 코드로 이동한다.

여기서 IsDebuggerPresent라는 함수를 발견할 수 있다.

 

 

위에서 발견한 의심되는 함수 즉, 0040105E 부분에서 계속 진행시켜보면 위와 같은 코드를 볼 수 있다.

 

 

코드를 진행시켜 보면 EAX 레지스터 값이 1로 설정되는 것을 확인할 수 있다.

TEST 명령어는 두 개의 값을 AND 연산한 결과값을 세팅하여 분기문에 영향을 준다.

 

 

위와 같이 00431024에 값을 PUSH 하는 것을 볼 수 있고 여기서 CALL 명령어를 실행하면 디버깅 당함이라는 값이 출력되는 것을 확인할 수 있다.

 

 

이때 Hex dump 창에서 00431024 부분으로 이동해보면 위와 같은 값을 확인할 수 있다.

 

 

정상 메세지가 출력되도록 하기 위해서는 아까 TEST 명령어로 값을 분기해주는 부분에서 JE를 만났을 때 넘어가도록 해주어야 하기 때문에 위와 같이 EAX 레지스터의 값을 0으로 세팅해주었다.

 

 

그랬더니 위와 같이 분기문에 걸려 0040107E 부분으로 넘어갔고

 

 

위와 같이 CALL 함수를 만나 정상이라는 문구가 뜬 것을 확인할 수 있다.

 

 

해당 주소 부분으로 가보면 위와 같은 아스키코드 값을 확인할 수 있다.

 

 

따라서 이 문제에서 원했던 디버거를 탐지하는 함수의 이름은 IsDebuggerPresent이다.

 

먼저 문제를 보면 손상된 실행파일의 패스워드를 알아내는 문제였다.

 

 

문제 파일을 다운로드 한 후 Immunity Debugger로 열어봤는데 위와 같이 실행할 수 없다는 에러가 뜨는 것을 볼 수 있었다.

 

 

실행파일을 실행하지 않고 분석할 수 있는 툴이 뭐가 있을지 찾아보다가 PEview로 파일을 열어보았더니 위와 같이 exe 파일 헤더(MZ)로 시작하는 데이터들을 확인할 수 있었다.

 

 

데이터가 많지 않길래 스크롤 해보면서 아스키코드 부분을 확인해보다가 위와 같은 부분을 발견할 수 있었다.

비밀번호의 정답 여부를 확인해주는 문구 같았고 혹시 Yeah, you did it! 뒷부분에 JK3FJZh가 정답 아닐까?라는 생각이 들어 인증해봤더니 맞았다.

 

안티 디버깅(Anti-Debugging)

 

1) 정의

 

- 디버깅을 방해하여 분석을 어렵게 하는 기술

- 안티 디버깅을 적용한 프로그램이 실행 중에 있을 때 디버깅을 당한다면 디버깅을 하지 못하도록 에러를 발생시키거나 디버거 프로그램을 종료시키는 등 여러가지 방법을 사용하여 분석을 방해

 

2) 목적

 

- 디버깅을 방지하기 위해 사용

- 각종 안티 디버깅 기법들의 동작 원리를 파악한 후 회피하기 위해 사용

 

 

Static Anti-Debugging

 

1) 정의

 

- 프로그램의 첫 실행에서 디버거를 탐지하는 것

- 안티 디버깅을 수행하는 함수가 호출되는 시점에서만 디버거의 존재를 확인하고 그 뒤에는 탐지하지 않음

- Static 기법이 적용된 파일들은 디버거에서 실행조차 되지 않음

 

2) Static Anti-Debugging의 해제 방법

 

호출되는 API에 후킹을 걸어 원래 API의 루틴이 아닌 다른 루틴을 수행하도록 하는 방법

플러그인을 사용하여 자동으로 해제하는 방법

수동으로 해당 안티디버깅을 풀어내는 방법

 

3) 디버깅 기법

 

PEB(Process Environment Block)

 - 현재 프로세스의 디버깅 여부를 판단하기 위해 이용

NtQueryInformationProcess()

- ntdll!NtQueryInformationProcess() API를 이용하여 프로세스의 디버깅 관련 정보를 얻을 수 있음

NtQuerySystemInformation()

- 디버깅 환경을 체크하는 기법

- 현재 OSDebug Mode로 부팅되었는지 판단

NTQueryObject()

- 시스템에서 어떤 디버거가 다른 프로세스를 디버깅 중일 때 DebugObject타입의 커널 객체가 생성되는데, DebugObject의 존재를 확인하는 것

- ntdll!NtQueryObject() API를 이용하여 시스템의 다양한 종류의 커널 객체 정보를 얻을 수 있음

ZwSetInformationThread()

- ZwSetInformationThread() API를 사용하면 자신을 디버깅하고 있는 디버거를 떼어낼 수 있음

- ZwSetInformationThread() 함수는 스레드에게 정보를 세팅하는 System Native API

TLS 콜백 함수

- 프로그램의 Entry Point 코드보다 먼저 실행되기 때문에 유용하게 사용

- IsDebuggerPresent() 함수로 디버깅 여부를 판별하여 프로그램을 계속 실행할지 결정

 

 

Dynamic Anti-Debugging

 

1) 정의

 

- 프로그램이 실행되는 중간중간 계속하여 디버거를 탐지하는 것

- 프로그램의 실행과 함께 계속하여 실행되며 중간중간 디버거의 유무를 확인하기 때문에 지속적으로 해제가 필요

- 디버거 트레이싱을 방해하여 원본 프로그램의 코드와 데이터를 확인할 수 없게 만드는 것

디버거 트레이싱 : 디버깅 당하는 쪽의 내부 구조를 하나씩(한줄씩) 실행하면서 레지스터, 메모리 등을 실시간으로 확인하는 것

 

2) Dynamic Anti-Debugging의 해제 방법

 

호출되는 API에 후킹을 거는 방법

플러그인을 사용하여 자동으로 해제하는 방법

프로그램을 이용하여 자동적으로 풀어내거나 디버거로 설정하는 방법

수동으로 해당 안티디버깅을 풀어내는 방법

 

3) 디버깅 기법

 

예외(Exception)

- 정상적으로 실행된 프로세스에서 예외가 발생하면 SEH(Structured Exception Handling) 메커니즘에 의해 OS에서 예외를 받아서 프로세스에 등록된 SEH를 호출하지만, 디버깅 당하는 경우에 예외가 발생하면 디버거에서 예외처리를 담당

    정상 실행되는 경우와 디버깅 당하는 경우를 판별하여 서로 다른 동작 수행

Timing Check

- 디버거 트레이싱을 할 때 발생하는 실행 시간의 차이를 측정하여 디버깅 여부를 판별

0xCC Detection

- 일반적으로 프로그램을 디버깅할 때 Software BP(BreakPoing)를 많이 설치하는데, BPx86 Instruction'0xCC’

    → 이 값이 정확히 발견된다면 디버깅 여부를 판별 가능

 

 

 

안티 디버깅 종류

 

출처 : http://www.openrce.org/reference_library/anti_reversing

 

 

+ Recent posts