본문 바로가기

리버싱/x64dbg 디버거를 활용한 리버싱과 시스템 해킹의 원리

ch05 - 코드 분석과 패치

728x90

01 문자열 패치

02 코드 패치

03 실행 파일의 코드 분석 연습

 

1. 문자열 패치

패치 : 프로그램의 파일이나 실행 중인 프로세스의 메모리 내용을 변경하는 작업을 말한다.

 - 패치가 필요한 경우

프로그램의 일부 기능에 오류가 있거나, 새로운 모듈이 개발되어 그 모듈과의 연결이 필요하거나, 프로그램에 보안 취약점이 존재하여 그 취약점을 삭제하는 등, 프로그램의 일부 코드를 수정하여 문제점을 해결하고자 할 때 패치가 사용된다.

 

크랙(crack) : 패치와 비슷한 개념이지만 그 의도가 비합법적이고 비도덕적인 경우를 말한다.

ex) 유료 소프트웨어를 불법적으로 사용하기 위해서 소프트웨어 인증 과정을 우회하는 경우, 특정 프로그램에 악의적인 코드를 심거나 특정 행위를 수행하지 못하게 코드를 수정하는 경우

 

1.1 문자열 패치 방법

 - HelloWorld.exe를 열어 다음과 같이 실행하면 문자열을 볼 수 있다.

 

#welcome message를 다른 문자열로 바꿔보자

바꾸기 전 실행

 

바꾼 후 실행

Ctrl + E = 해당 문자열을 드래그해서 오른쪽 클릭 -> 바이너리 편집기 -> 편집을 들어가면 데이터를 편집할 수 있다.

00 문자열 구분 인자를 맞춰서 바꿔야 한다.

 

 - 파일에서 파일 패치를 들어가면 바뀐 문자열들이 저장되어 있다.

   파일 패치를 클릭하면 원본을 훼손하지 않고 새로운 파일로 만들 수 있다.

 

1.2 원본보다 더 긴 문자열 패치

 -단점 :  문자열을 직접 수정하는 경우, 기존 문자열 버퍼 크기 이상의 문자를 입력하기 어렵다. 크기를 넘칠 경우에 다른 공간의 데이터를 변조하거나, 다른 주소 공간의 데이터가 타당하지 않게 됨에 따라 예기치 않은 결과를 초래할 수 있다.

 - 하지만 더 긴 문자열을 써야 할 경우가 있는데 다른 비어있는 메모리 영역에 새로운 문자열을 생성하고, 문자열 참조 주소를 변경하는 방식으로 패치할 수 있다.

데이터 공간에서 사용하지 않은 영역이라고 생각하는 곳에 원하는 데이터를 추가하고 그 주소를 가져오는데 주소가 초기에는 사용되지 않다가 나중에 사용될 수 있고 주소의 크기가 클 경우 코드 내에 추가하기 어려울 수 있다.

 

빈 메모리를 찾아서 문자열 메시지를 저장했다.

 

 - 이제 주소만 빈 메모리의 주소로 바꿔주면 문자열 자체가 바뀐다.

문자열에 오른쪽 클릭 -> 복사 -> 주소를 한 후에 바꿀 위치를 선택해서 스페이스키를 누르면 다음과 같이 나온다.

여기에 복사한 주소만 바꾸면 된다.

 

1.3 메모리 변경 내용을 파일로 보관

 - 파일 패치하는 것으로 Ctrl + P를 눌러 간단히 열 수 있다.

 

2. 코드 패치

2.1 제어 흐름 찾아가기

 - 프로그램 내에서 제어 구조를 분석하여 흐름을 변경할 수 있다. 일반적으로 비교 구문은 'cmp'나 'test' 명령어로 수행하고 'jmp'명령어로 분기가 이뤄진다.

 

2.2 제어 흐름의 변경

#include  <stdio.h>

int hidden_value = 0xdeadf00 d;

 

int main(){

 int a, b, c, value;

 char buf [100];

 printf("Input password: ");

 gets(buf);

 if(strcmp(buf, "reversing", 9)!= 0){ //분기가 일어나는 곳!

  printf("Invalid password");

 return -1;

printf("Hidden value: 0x%x\n", hidden_value); //16진수로

return 0;

}

 

  • 분기점에서 다른 부분으로 진행시키는 방법

 1) 플래그(flag) 직접 수정 : 디버거의 레지스터 창에 관계된 플래그 값을 클릭하여 플래그 값을 변경한다.

 2) 레지스터 값을 직접 변경 : EAX의 경우 함수 리턴 값을 저장하므로, EAX 값을 수정하면 함수 결과에 따른 분기              루틴을 변경할 수 있다.

 3) JMP 문 변경 : jmp의 조건이나 주소를 직접 변경한다.

 4) Nop 처리 : 기존의 jmp문이나 실행시키지 않을 코드를 nop으로 만든다.

 

비밀번호가 맞지 않았을 때는 다음과 같이 분기점에 의해서 invalid password가 출력된다.

 

# 1) 을 이용해서 플래그 변경

 

- 분기점(je)에서 zf가 0으로 되어있는 것을 볼 수 있다. 이 값을 1로 변경하면 Hidden value가 출력되는 것을 볼 수 있다.

플래그 수정

 

 

비밀번호가 맞았을 때

이때는 문제없이 Hidden value 출력.

 

# 2) 레지스터 값을 직접 변경 : EAX값 변경

분기점에 들어가기전에 test eax의 값을 0으로 변경하면 그대로 분기점을 건너뛰어 hidden value가 출력한다.

원래 EAX값
바꾼 EAX값

 

 

# 3) JMP 문 변경 : jmp의 조건이나 주소를 직접 변경한다.

 -> 분기점의 Je를 jmp로 바꿔서 바로 hidden value가 나오게 할 수 있다.

 

 

# 4) Nop 처리 : 기존의 jmp문이나 실행시키지 않을 코드를 nop으로 만든다.

아래와 같은 분기점 부분을 스페이스키로 nop로 바꾸면 분기점이 없어 바로 hiddenvalue가 출력

 

2.3 패치 공간의 부족

 - 일반적으로 nop나 남는 공간을 사용해서 주소만 연결해주면 된다.

#include <stdio.h>

#include <stdlib.h>

int main(int argc, char * argv []){

int i, n;

char name [15] = "Hacker Kim";

printf("programmer : % s\n", name);

printf("Please enter your desired integer from 1 through 100.\n");

scanf("% d", &n);

if(n < 1 || > 100){ //분기

 printf("Invalid number!");

getch();

return -1;

}

printf("\nThis program finds multiples of % d.\n", n);

for(i=1;i <=1000;i++){

if(i % n ==0)

  printf("% d", i);

}

getch();

return 0;

}

 

 - 위 프로그램의 실행 값을 바꿔보자

-> 문자열 덤프에서 탐색 후 길이만큼 맞춰서 바꿔주면 된다.

 

 - 중요한 부분은 다음인데 cmp로 비교하고 64를 10진수로 하면 100이다. 하지만 값을 수정하면 바이트 수가 넘어서 다른 곳에 추가하고 주소를 정하는 방식으로 바꿔줘야 한다.

 - 위의 명령을 실행하면 1000으로 바뀌어서 주소이동을 통해서 건너뛰어야 한다.

1~100을 넣는 scanf
1~100의 값에서 esp+28에 30을 넣음
eax에 들어온 30

 

공간 부족 해결

1) 비어있는 공간을 찾는다.

 

2) 해당 부분에 코드를 작성 3) 주소 값을 바꾼다.

 -> 분기점에 따라서 위의 주소를 변경해줘야 한다.

 

 

3. 실행 파일의 코드 분석 연습

 - 리버싱 문제로 제시되는 것이 패스워드나 시리얼 번호를 알아내는 것

 

1. 먼저 그냥 실행하여 어떤 순서로 동작되는지를 확인, 패스워드를 요구하는 위치와 관련된 메시지를 확인

   시리얼 번호를 요구하는 위치도 마찬가지로 확인하고, 추가적인 정보를 요구하는 것도 확인

 

2. 패스워드나 시리얼 번호와 관련된 입력값을 요구하는 위치로 이동한다. 이때에는 실행할 때 나왔던 메시지를 확인했다면, 문자열 검색하기로 찾아간다. 입력 창이 보이거나 입력 값을 받는 API를 알 수 있다면, API 검색으로 원하는 위치를 찾을 수 있다.

 

3. 이제 찾아간 위치에서 패스워드를 비교하거나 시리얼 번호를 비교하는 위치를 찾는다. 그리고 패스워드나 시리얼 번호의 생성 방법을 분석한다. 분석 과정에 주요 정보가 스택을 통해 전달되거나 저장될 수 있기 때문에, 스택을 자주 확인한다. 저장된 데이터를 활용하는 경우, 특별한 인코딩 방법이 적용될 수 있으므로 , 그 과정을 하나하나 분석하고 메모한다.

 

4. 비교 과정의 분석이 완료되면, 메모한 내용을 가지고 패스워드나 시리얼 번호의 생성 방법을 재현해 본다.

 

5. 재현 과정에서 계산이 제대로 이뤄졌다면, 주어진 조건의 패스워드나 시리얼 번호를 생성하여 입력한다.

 

3.1 패스워드 찾기

Easy_CrackMe.exe 프로그램

 

(1) 실행 과정 살펴보기

 - 무작정 실행해서 흐름 보기

 

(2) 원하는 위치 찾아가기 (3) 패스워드 비교 코드 분석하기

 

 

 - 처음 메세지 박스를 통해 적은 값이 ebp+10에 저장

 

 - "congratulation"이라는 성공 메시지를 찾아간다.

 GetDlgitemText() API가 다이얼로그 상자에 입력한 내용을 가져오는 함수로 이곳과 분기점에 중단점을 설정하고 본다.

 

첫 번째 비교는 다이얼로그 상자에서 입력을 받고 "cmp byte ptr [esp+5], 61"와 a(61)을 비교

두 번째는 esp+a주소와 상수 5y를 넣고 0x401150을 호출한다.

push 2는 2글자를 비교해서 4만큼 늘어난다.

 

 - ESI에 "R3versing"의 주소가 저장되어 있고, EAX에 내가 적었던 값이 저장되어 두 값을 비교

 

 - 마지막으로 첫번째 값이 E와 값인지 비교

 

 

(4) 패스워드 재현하기

 

 

3.2 시리얼 번호 찾기

Easy_keygenMe.exe 프로그램

(1) 실행 과정 살펴보기

우선 실행해본다.

 

(2) 원하는 위치 찾아가기 (3) 시리얼 생성 코드 분석하기

-> 최종적으로 비교해서 맞으면 correct를 틀리면 wrong을 출력한다.

-> 이름은 choi jai sung을 하고 두 번째 입력인 Input Serial 부분의 값을 확인

 

 

 -> 이 부분에서 XOR 연산을 수행하고 [esp + esi + c] = 0x19FE04이고, 후자는 0x19FE08이다.

 

 

 -> 반복을 통해서 시리얼 값을 만들어 낸다.

 

 

 -> 만들어진 시리얼 값

 

 

- 반복하면서 시리얼 번호가 적은 숫자와 맞는지 비교

 

 

(4) 시리얼 생성 코드 재현하기