데이터 압축은 파일의 크기를 줄일 때 이용한다.
한번 압축된 파일을 완전히 원래대로 복원 가능
우리가 흔히 아는 알집, 반디집을 사용하는 경우가 바로 비손실 압축이다. 이런 비손실 압축 알고리즘은 Run-Length, Lempel-Ziv, Huffman 등이 있다.
한번 압축된 파일을 원래대로 복원 불가
파일에 손상을 내는 대신 압축률을 높이는 것이다. 음악, 동영상, 사진 같은 멀티미디어 파일을 압축할 때 쓰는 것인데 이 손실이 우리 인간이 사용하는데 있어서 알아차릴 수 없다.
가령 mp3파일을 압축하는 경우에 사람의 가청주파수인 20~20000Hz를 벗어나는 주파수의 소리 데이터들은 다 날려버리고 압축해도 되는 것이다.
PE 파일을 대상으로 하는 압축 기술이다. 일반적인 압축과 달리 압축된 파일 내에 압축해제 코드가 포함되어 있어서, 파일을 실행할 때, 압축해제 코드가 실행되고 메모리에서 압축을 헤제해서 파일을 실행시킨다.
일반적인 압축 파일은 압축된 후에 .zip, ,7z 같은 확장자를 가지게 되지만 실행 압축의 결과물은 그대로 .exe나 .dll, .sys를 가지게 된다.
항목 | 일반 압축 | 실행 압축 |
---|---|---|
대상 | 모든 파일 | PE 파일(exe, dll, sys) |
압축 결과 | 압축 파일(zip, rar) | PE 파일(exe, dll, sys) |
압축 해제 방식 | 전용 압축해제 툴 사용 | 내부의 decoding 루틴 존재 |
파일 실행 여부 | 자체 실행 불가 | 자체 실행 가능 |
장점 | 모든 파일을 높은 압축률로 압축 가능 | 별도의 해제 프로그램 없이 바로 실행 가능 |
단점 | 전용 해제 프로그램 필요 | 실행때마다 디코딩 루틴이 호출되어야 하므로 미세하게 실행시간이 느려짐 |
뚜렷한 차이는 바로 자체 실행이 가능하다는 것이다. 그냥 일반 PE를 실행시키듯이 딸깍 클릭이 가능ㅎ다.
PE파일을 압축하는 전문 압축기를 Run Time 패커, 혹은 그냥 패커라고 한다.
패커를 쓰는 목적이 있지.
지금은 패커가 널리 쓰이지만 예전엔 디코딩 루틴을 호출하면 실행시간이 무지막지하게 느려져서 패커를 안 썼다.
진짜 위와 같은 순수한 의도의 패커들도 있고, 패킹한답시고 심하게 파일을 훼손하는 불순한 의도의 패커들이 있다. 불순하다는 것은 Virus total에서 진단이 양성으로 나왔다는 것을 의미한다.
순수한 패커 : UPX, ASPACK 등
불순한 패커 : UPACK, PESpin, NSanti 등
패커 중에서 PE파일을 진짜 제대로 보호한다는 목적 하에 나온 유틸리티가 프로텍터다.
그냥 실행압축만 하는 것이 아니라 리버싱되는 것을 방지하겠다는 목표로 Anti-Debugging, Anti-Emulatin, Code Obfuscating, Polymorpihc code, Garbage code, Debugger Detection 등등을 추가한 것이다.
복잡한 기법들이 추가되기 때문에 압축이라곤 하나 원본 파일보다 크기가 더 커질 수도 있다. 대신 디버깅하기 어ㅓㅓㅓㅓㅓㅓ엄 청 어렵다.
프로텍터를 사용하는 이점이야 언급했다시피
가 될 것이다. 그래서 게임 같은 프로그램에선 프로텍터가 사용되어서 핵을 못 쓰게 하는 것이다.
프로텍터도 종류가 다양한데 공개용으로 풀린 것과 상업용으로 쓰이는 것이 있다. 그 외 악성코드에서만 쓰이는 놈들도 있고.
공개용 : UltraProtect, Morphine
상용 : ASProtect, Themida, SVKP
UPX라는 패커를 이용합니다.
윈도우 버전에 맞는 upx를 다운받아주고 윈도우 커맨드에서 실행시켜 줍니다.
upx를 압축해제 한 디렉토리로 변경하여 커맨드를 실행시켜서 설치를 확인해봅니다.
upx가 존재하는 폴더에 notepad.exe를 옮겨주고 패킹합니다.
패킹 전이다.
패킹 후) 용량이 줄어들었어!
패킹이 작동한 방식을 보여주는 그림으로 알아보자.
특징을 살펴본다면
PE View로 한번 들여다보자.
분명히 size of raw data는 0인데 Virtual Size는 완전 크다. 즉 압축이 풀리고 파일이 실행될 때 압축된 코드를 바로 이 첫번째 섹션에 풀어버리겠단 것이다. 그래서 메모리에 할당할 이 커다란 virtual size가 필요한 것이다.
압축된 원본코드는 두번째 섹션에 존재한다.
내 로컬 pc에 있는 notepad.exe의 복사본을 실행 압축해서 x96dbg로 디버깅 하려고 하면 교재랑 차이가 생긴다. 64비트 아키텍처의 실행 파일을 하는 안 배웠지 뭐니. 그래서 실습 환경과 똑같은 32비트 notepad.exe로 다시 시작하자.
우선 원본 notepad.exe의 EP 코드를 살펴보자.
10073b2 주소에서 EDI를 CALL 했다. 그 위에 보면 EDI엔 GetModuleHandle() API 호출주소가 담겨 있다. 그래서 여기서 GetModuleHandle() API 를 호츨해서 notepad.exe 프로세스의 ImageBase를 구하게 된다.
더 아래로 내려가면 10073B4 주소에서 문자열과 5A4D를 비교한다. 이건 MZ 시그니처이다. 그 밑에 4550과 CMP하는 구문에선 PE 시그니처와 비교한다.
이제 실행 압축된 notepad를 열어보자.
진입 주소가 1015330이다. 여긴 어딜까. 두번째 섹션의 끝부분이다. 원본 notepad 코드는 이 ep주소보다 위에 존재한다.
한줄씩 해석해보자.
PUSHAD : EAX부터 EDI 레지스터 값으 스택에 PUSH해서 저장하자.
MOV ESI : ESI를 1011000 주소로 세팅한다. 이 주소는 두번째 섹션의 시작주소이다.
LEA EDI : EDI 값을 ESI-10000한 주소로 세팅한다. 그럼 EDI는 1001000이 될 것이다. 여긴 첫번째 섹션 시작 주소다.
즉, ESI(Source)에서부터 데이터를 읽어와서 압축을 해제하는 EDI(Destination)에 저장하는 일련의 과정이다.
코드를 하나하나 실행하며 쫓아가는 것을 트레이스라고 한다. 현업에선 일일히 트레이싱하지 않고 노하우와 자동화를 하면서 원본 파일의 entry point로 가겠지만 난 배우는 단계니까 그냥 하자.
트레이싱에서 전해져 내려오는 명심해야 할 원칙
"루프...루프를 만나면 살펴만 보고 탈출해...탈ㅊ..지지직"
뭐 이런 감성 정도인듯
이유를 말하자면 압축해제가 워낙 많은 루프를 가지고 있어서 적절히 탈출해야 한세월 걸리지 않을 수 있다.
여기서 우리가 찾아야 하는 건 압축해제(decoding) 루프이다.
ESI는 두번째 섹션(UPX1 섹션)을 가리키고 있고 EDI는 첫번째 섹션(UPX0 섹션)을 가리키고 있다.
디코딩 루프는 두번째 섹션의 주소에서 차례대로 값을 읽어서 적절한 연산을 거쳐서 첫번째 섹션의 주소에 값을 써주는 것이다.
봐봐 AL 레지스터의 값을 edi에 가져오지.
여기도 그렇지.
근데 왜 다 EAX(AL) 레지스터에서 가져오는 걸까. 교재에선 AL에 압축해제된 데이터가 들어있다고 한다.
하 근데 왜 x32dbg에선 트레이싱이 안되지. 루프가 막막 돌고 커서가 정신없이 바뀌어야 한다는데 그런게 없다...
교재에 있는 세번째 루프로 그냥 찾아가자.
여기는 뭐하는 곳이냐면 원본 코드에도 call이나 jmp가 있었겠지? 그런 명령어들은 destination 주소가 필요할 건데 그 주소를 복원시켜주는 코드이다.
이런 루프를 탈출하는 방법은 루프 다음 주소에 bp를 설정하고 선택한 곳까지 실행을 클릭해서 나가면 된다.
마지막으로 IAT 설정이다.
바로 밑에 다음 루프를 보자.
이게 IAT를 세팅하는 루프이다. 맨 처음 시작주소에서 EDI는 두번째 섹션(UPX1) 섹션 영역의 주소인 1014000으로 설정된다. (ESI 1001000 + 13000 = 1014000)
여긴 원본 notepad.exe에서 사용되는 API들의 이름 문자열들이 저장되어있다.
notepad.exe가 UPX에서 실행 압축될 때, 원본 파일의 IAT를 분석해서 이 소프트웨어에서 사용되는 API의 이름 목록들을 추출해놓게 된다.
이걸 갖고 밑에 보면 1015467주소에서 GetProcAddress()를 호출하는 것이다. 이걸 호출해서 API 시작 주소를 얻는다.
원본의 IAT 영역을 지금 EBX가 가리키고 있는데 이 영역에 얻어낸 시작 주소를 입력한다. 모든 API이름 문자열들에 이 작업을 모두 수행해주면 notepad.exe의 IAT 복원 과정이 끝난다.
트레이싱 개빡세다. 그치. 그래서 이런 OEP를 빨리 찾는 팁이 있다.
아까 실습 처음 할 때 우린 EP 코드가 PUSHAD/POPAD 명령어로 둘러싸인 걸 볼 수 있었다. 이건 UPX 패커의 특징이다.
그리고 OEP로 가는 JMP 명령어가 POPAD 다음에 바로 나타나는 것도 주목했다면, 이 JMP에 BP를 설치하고 실행하기를 한다면 곧장 OEP로 가게 된다.
계속 AD가 나오는데 이건 EAX부터 EDI까지 8개의 범용 레지스터의 값을 말한다.
PUSHAD/POPAD를 이용하는 또 다른 방식이다.
PUSHAD 때문에 EAX부터 EDI가 스택에 잘 쌓여있을 것이다. 스택 최상단 주소에 하드웨어 BP를 걸어준다.
CPU에서 지원해주는 브레이크 포인트이다. 그냥 BP와 뭐가 다르냐면 BP가 설치된 명령어까지 실행된 이후에 실행이 멈추게 된다는 것이다. 이 하드웨어 BP는 4개까지 설치할 수 있다.
그래서 이렇게 스택주소에 하드웨어 BP를 설치해주고 실행하면 어떻게 될까
압축이 해제되고, 그럼 코드가 실행되고, POPAD가 호출되는 순간이 올 것이다.
그때 하드웨어 BP가 설치된 스택 최상단 주소에 액세스하고서 제어가 멈춘다.
그 밑에 OEP로 가는 JMP가 있게 되는 것이다.