Notice
Recent Posts
Recent Comments
Link
«   2024/12   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30 31
Tags
more
Archives
Today
Total
관리 메뉴

Study

리버싱 핵심원리_20_인라인 패치 실습 본문

Reversing/리버싱 핵심원리

리버싱 핵심원리_20_인라인 패치 실습

마늘부추 2019. 2. 21. 05:23

20. 인라인 패치 실습

 

20.1 인라인 패치

 

- 인라인 코드 패치 : 원하는 코드를 직접 수정하기 어려울 때 간단히 코드 케이브라는 패치 코드를 삽입한 후 실행해 프로그램을 패치시키는 기법
- 대상 프로그램이 실행압축 혹은 암호화되어 있어서 파일을 직접 수정하기 어려울 경우 많이 사용하는 기법

왼쪽의 그림을 보면 EP는 원본 코드가 아닌 암호화된 원본 코드를 디코딩 하는 코드에 존재하고 그곳에서 OEP 코드를 복호화 하고 나면 OEP로 점프한다.

만약 패치하고자 하는 코드가 암호화된 OEP 코드에 존재한다면 EP의 복호화 코드를 실행시킨 후에 패치를 해야 하기 때문에 실행 시 복호화, 패치를 모두 수행할 수 있도록 Code Cave를 추가한다.
그리고 복호화가 끝난 후 점프할 주소를 OEP가 아닌 Code Cave로 수정한다.

 

이렇게 할 경우 프로그램은 매번 실행할 때마다 메모리의 코드를 패치하게 된다.

 

 

20.2 실습

 

unpackme 파일을 이용한다. 실행하면 다음과 같은 메시지 박스와 다이얼로그가 나타난다.

 

 

 

위 두 군데의 문자열을 바꾸면 되는데, 문제는 이 문자열들이 암호화되어있다.

 

 

 

20.3 디버깅 - 코드 흐름 살펴보기

 

 

 

프로그램을 Ollydbg로 열어보면 EP 코드가 보이고, 그 밑에는 암호화된 데이터들이 보인다.
여기서 F7을 눌러 조금 더 들어가 보면 다음과 같은 코드가 나온다.

 

 

이 코드가 바로 복호화 코드이다. 파란색으로 표시된 첫 번째 반복문을 돌리면 EBX 레지스터(4010F5~401248)
위치의 데이터 공간을 복호화 하기 시작한다. (이를 영역 B라고 해두자)

 

 

 

루프를 돌면서 해당 공간에 데이터가 바뀌는 것을 볼 수 있다.

루프를 다 돌고 나면 CALL 004010BD 명령어를 타고 해당 위치로 간다.

 

 

이어서 두 번째 루프가 등장한다. EBX 레지스터에 저장된 데이터를 주소로 이용하여 해당 위치를 복호화 하는 루프인데

 

이때 EBX 레지스터엔 00401007이라는 값이 저장되어 있다.

이는 처음 프로그램을 Ollydbg에 올렸을 때 보였던 암호화 영역이다.

 

(EP화면)

 

 

4010C8 주소의 복호화 루프로 401007~401085 영역이 복호화 된다. (암호화가 풀린 이곳을 영역 A라고 해두자)

 

 

이어서 진행하다 보면 다시 세 번째 루프를 마주하게 된다.

 

그런데 EBX 레지스터의 주소를 보면 아까 전 복호화 했던 4010F5가 저장되어 있다.
이 부분은 한번 복호화 했던 영역을 키값만 다르게 하여 다시 또 복호화 하는 것이다.

 

루프를 다 돌린 후 해당 영역을 살폈더니

 

 

이곳이 바로 우리가 패치해야 할 문자열이 저장된 영역이었다.

 

 

4010BD 함수의 호출이 끝나고 F7을 눌러 CALL 00401039를 타고 해당 함수로 간다.
그런데 생각해보니 401039는 아까 전 복호화 된 영역 A이다.

 

 

이번 함수에서도 역시 반복문이 등장한다. 그런데 기존의 반복문들과 다른 점이 있다. 바로 XOR이 아니라 ADD라는 점이다.
반복문에 진입하기 전 EDX를 0으로 초기화한 후 EBX(4010F5)에 저장된 값을 주소로 하는 영역의 데이터를 4byte씩 읽어서 EDX에 더한다.
이 동작을 ECX(154) 만큼 반복하는데 그럼 결국 4010F5~401248 영역에 저장된 값을 모두 더하는 셈이다.

 

왜 이런 동작이 있는지에 대한 궁금함은 잠시 접어두고 CALL 0040108A 명령어를 따라 해당 주소로 접근한다.


 

 

이곳에서도 반복문이 등장한다. 마찬가지로 EBX 레지스터에 저장된 위치를 복호화 하는데 그 영역은 40124A~40127F이다.

영역의 값이 변경된 뒤 함수는 종료되고, 일단 다시 돌아가 다음 명령어를 수행한다.
(무슨 의미인지는 20.6 참고)

 

 

눈여겨볼 부분은 이곳이다.
이 두 명령어는 아까 전, ADD 명령어를 이용해 EDX에 누산 한 값을 31EB8DB0과 비교하여 같을 경우 401083 주소로 점프시키는 명령어이다.

여기서 비교하는 값은 바로 4010F5~401248 영역의 값이 변조되었는지를 확인하는 Checksum인 것이다.

지금은 따로 변조한 사실이 없기 때문에 바로 점프할 수 있지만 만약 문자열이 변조되었다면 그 아래 명령어들을 통해 Error 메시지 박스가 출력되고 프로그램은 종료된다.

 

(결국 영역 A는 영역 B의 Checksum을 구하기 위한 코드였다!)

 

 

401083 주소엔 JMP 명령어가 존재하고 그를 따라 가면

 

 

이런 화면이 나온다. 분명 복호화를 모두 끝마쳤으므로 OEP로 이동한 것일 텐데 왜 이런 문자열이 나오는 걸까?

이때 당황하지 말고 [Ctrl + a]를 눌러준다.
복호화 한 영역을 Ollydbg가 영역의 값을 1byte씩만 읽어낸 탓에 코드가 아닌 데이터로 인식하여 일어난 일시적인 오류이다.

 

[Ctrl + a]를 누르면 다음과 같이 정상적인 화면이 된다.

 

 

이곳에서 바로 준비된 문자열을 이용하여 메시지 박스를 만들어낸다.
사용하는 API는 DialogBoxParam이다. 이 함수의 정의를 검색해보면

 

위와 같다. 여기서 4번째 파라미터인 lpDialogFunc이 Dialog Box Procedure 주소이다.
다시 코드를 보면 인자로 사용하는 건 40122C 주소에서 PUSH 하는 4010F5 영역임을 알 수 있다.

 

 

해당 위치의 코드를 보면 패치해야 할 문자열이 보인다. 스크롤을 좀 더 아래로 내려보면 이 문자열을 이용하여 다이얼로그와 메시지 박스를 출력하는 함수도 존재한다.

 

 

 

20.4 코드 구조

 

 

그림에서 [A], [B], [C]는 암호화된 코드이며 [EP Code]와 [Decoding Code] 영역에는 암호화를 해제하는 복호화 코드가 존재한다.

[EP Code]는 단순히 [Decoding Code]를 호출하는 역할만 하며, 실제 [Decoding Code]에서 디코딩 작업이 이루어진다.

결국 [B]-[A]-[B] 순서로 디코딩을 하고 암호가 해제된 [A] 영역 코드를 실행한다. [A] 영역 코드 내에서는 [B] 영역의 Checksum을 구하여 [B] 영역의 변경 여부를 판별한다. 그런 다음 [C] 영역을 디코딩 한 다음 마지막으로 OEP(40121E)로 점프한다.

 

 

 

20.5 인라인 패치 실습

 

프로그램 실행에 필요한 영역들을 전부 디코딩 하여 데이터들을 얻었다. 그 후 OEP로 점프하는데 우리는 OEP로 점프하기 전, 코드를 패치 할 Code Cave를 먼저 거치고 점프해야 한다. 그러기 위해선 파일에서 사용되지 않는 공간에 Patch Code를 작성하고 [A] 영역의 JMP OEP 명령을 JMP Patch Code로 수정해야 한다.

 

※ 여기서 잠깐!

[A] 영역은 암호화되어 있다는 점을 고려해서 수정해야 한다.
만약 암호화된 문자열을 바로 "JMP Patch Code"로 수정해버리면 [A] 영역이 복호화 될 때 원래 변경한 명령어와 전혀 다른 명령어로 변하기 때문이다.

 

20.5.1 패치 코드를 어디에 설치할까?

 

파일의 빈 영역에 설치
② 마지막 섹션을 확장한 후 설치
③ 새로운 섹션을 추가한 후 설치

 

보통 패치 코드의 크기가 작은 경우 1번 방법을 사용하고, 나머지 경우 2 또는 3번 방법을 사용하면 된다.

 

PEView로 예제 파일의 첫 번째 섹션(.text) 헤더를 살펴본다.

 

첫 번째 섹션의 파일 사이즈는 400이고 메모리에서의 사이즈는 280이다. 즉 파일에서의 크기는 400이지만 그중 280 크기만 메모리에 로딩한다는 뜻이다. 나머지 영역(680~800)은 쓰이지 않는 영역(NULL-padding영역)이니 바로 이곳에 패치 코드를 설치하면 된다.

참고!

1E4 주소의 Characteristics에 IMAGE_SCN_MEM_WRITE(쓰기 속성)이 추가되어 있다.
복호화 코드와 복호화 대상 코드 모두 .text 영역에 있기 때문에 프로그램 내에서 복호화 작업을 수행하기 위해서는 반드시 섹션 헤더에 쓰기 속성을 추가해서 해당 메모리에 대한 쓰기 권한을 얻어야 한다. 그래야 에러가 나지 않는다.
일반적인 PE 파일은 이 영역에 쓰기 권한이 없는데 이 예제를 포함하여 패커, Crypter 류의 파일들은 코드 섹션에 쓰기 속성이 존재한다.

 

 

 

20.5.2 패치 코드 만들기

 

예제를 다시 실행하여 OEP(40121E) 위치로 간다.
아까와 같이 [Ctrl + a]를 누른 후 401280 위치에 다음과 같이 명령어를 작성한다.

 

 

MOV ECX,0C
MOV ESI,004012A8
MOV EDI,00401123
각 레지스터에 오른쪽의 값들을 저장한다.

 

REP MOVS BYTE PTR ES:[EDI],BYTE PTR DS:[ESI]
REP : ECX 레지스터에 저장된 횟수만큼 반복한다.
MOVS BYTE : 1byte씩 ESI 위치의 내용을 EDI 위치에 복사한다.

 

이 명령어들은 "You must patch this NAG !!!"의 시작 부분부터 "ReverseCore" 문자열을 덮어씌우는 것이다.
그 아래 명령어도 마찬가지로 9번(unpacked.의 길이) 반복하여 EDI 위치에 덮어씌운다.

 

실제 이 부분을 나중에 실행해보면 문자열 영역이 아래와 같이 덮어씌워진다.

 

이 작업이 끝난 후 바로 OEP 코드로 점프하게 된다.

 

코드를 수정한 상태로 Ollydbg의 변경된 내용을 저장한다. ('Copy to executable - All modifications' 명령 -> Save File)

 

 

 

20.5.3 패치 코드 실행하기

 

마지막으로 수정해야 할 코드는 다음과 같다.
401083 위치에 OEP로 가기 직전 [A] 영역에서 OEP로 점프하는 명령어가 있다.

 

 

여기서 JMP 40121E를 JMP 401280으로 바꾸어 OEP에 진입하기 전에 패치 코드를 먼저 실행하도록 Code Cave의 시작 주소로 점프시켜야 한다.

 

그러나 이 영역은 현재 복호화 된 상태이지만 원래 XOR 7로 암호화가 되어있다.
따라서 Ollydbg를 이용하여 패치할 것이 아니라 암호화된 기준으로 HxD를 이용하여 수정해주어야 한다.

 

암호화된 영역은 483~485이고 뒤의 00 00은 암호화 영역이 아니다.
실제로 HxD의 값(EE 91 06)이 복호화 과정을 통해 위 Ollydbg에서의 값(E9 96 01)으로 바뀌는 것을 알 수 있다.

 

변경할 JMP 주소는 401280이다. 이를 명령문의 Instruction으로 바꿔보면 E9 F8010000이다. 앞의 3byte를 각각 나눠 XOR 7을 하면

 

E9 XOR 7 = EE
F8 XOR 7 = FF
01 XOR 7 = 06

 

으로 변환된다. 이를 HxD에 입력한 후 저장한다.

 

 

 

20.5.4 결과 확인

 

 

성공적으로 패치되었다.

 

 

 

20.6 복호화한 40124A~40127F는 무엇이었을까?

 

 

아까 전 이 반복문을 통해 복호화된 영역(40124A~40127F)은

 

 

IAT의 위치를 명시한 간접 호출 영역이었다!!

 

 

 

20.7 Comment

 

복호화 부분은 15장과 비슷한 내용이었다. 그때 이미 노가다 식으로 돌려보며 관찰했기에 이번에 루프를 이해하는데 어렵지 않았다.

알고 나니 특별할 것 없는 파트였던 것 같다. 그렇지만 반복해보면 코드를 보는 눈도 생기고, 패치에 대한 기술도 넓힐 수 있기 때문에 꼼꼼히 해보는 것을 추천한다.

(매번 비슷한 말을 쓰는듯하다...)