코드를 수정하고 싶은데 직접 접근해서 수정하기 어려울 때 사용하는 우회 방법이다. Code Cave라는 패치 코드를 파일에 삽입하고 패치코드를 실행시키게 만드는 방법이다.
타깃 프로그램이 암호화되어있거나 실행압축되어있는 경우에 사용한다.
만약 내가 수정하고 싶은 코드가 OEP가 있는 영역에 위치한다면 어떻게 될까. OEP가 암호화되어 있기에 직접 수정하기가 매우 까다로워진다. 왜냐하면 디코딩하는 과정에서 내가 수정한 코드가 이상하게 복호화되기 때문이다.
그래서 케이브를 마련한다. 케이브에는 패치하길 원하는 코드가 적혀있다. EP 코드에서 복호화를 모두 끝마치고서야 케이브로 가기 때문에 더 이상 암호화는 신경쓸 필요가 없다. 패치된 코드가 실행되고 그제서야 OEP로 이동하게 된다.
일반적인 코드와 인라인 패치기법의 차이를 알려줄게
코드 패치 | 인라인 패치 | |
---|---|---|
대상 | 파일 | 파일 & 메모리 |
횟수 | 1번 | 파일에는 1번, 메모리는 실행될 때마다 |
방법 | 원하는 위치에 직접 패치 | Code Cave 미리 설치해서 메모리에서 원하는 영역이 복호화되었을 때 패치됨 |
인라인 패치를 하기 전에 어떻게 파일이 실행되는지 좀 알아보자.
ap0x라는 리버서가 제작한 patchme 예제를 사용한다.
실행하면 이런 메시지박스가 나온다.
확인을 눌러보자. 그 다음에는 이런 문구가 나온다.
이번에도 교재 순서랑 다르게 진행할거다. 좀 더 편한 이해를 위해서 미리 전체 전개도를 한번 볼거다.
1st EP에서 복호화 영역으로 이동
2nd 복호화 영역에서 복호화 진행 (XOR 연산)
3rd 체크썸을 통해서 복호화된 값이랑 정답 문자열 비교
4th OEP로 이동
이 과정을 잘 이해해두자.
디버거로 열어보자.
EP코드가 나오는데 매우 심플하다.
4010E9 를 CALL 하고 RET한다. 그 밑에 있는 것들은 죄다 암호화도니 영역이다.
디버깅을 하는 제일 쉬운 방법 중 하나인 문자열 참조를 이용하자.
문자열들이 모두 암호화 되어있기 때문에 나타나지 않는다.
결국 그냥 call 4010E9를 들어가보자.
4010E9로 들어가면 제일 먼저 CALL 40109B가 보인다. 여기로 가보자.
여기가 복호화 코드가 등장하는 영역의 시작이다.
지금 보이는 부분이 바로 첫번째 복호화 루프이다. 44와 XOR 연산을 진해아는 것이다.
복호화하는 영역은 4010F5부터 401248이라는데 왜 그런진 모른다.
첫번째 복호화가 끝나면 그 밑에 CALL 4010BD를 따라간다.
여기가 바로 두번째 복호화다. 7과 XOR 연산을 진행한다. 복호화되는 영역은 401007~401085이다.
바로 밑에 다음 복호화가 있다.
여기선 11과 XOR 연산을 진행한다. 복호화되는 영역은 4010F5~401248이다.
첫번째와 세번째 복호화에서 복호화되는 영역이 4010F5~401248로 동일하다. 즉 원본이 이중으로 암호화되었기 때문에 두번 복호화를 진행하는 것이다.
복호화 코드의 영역에 시작에 있을 때 복호화 코드인 4010BD를 끝나면 나오는게 CALL 401039이다. 여기로 가자.
여기로 들어오면 바로 Checksum을 계산하는 루프 영역에 들어오게 된 것이다.
근데 여기선 교재의 ollydbg랑 내 x96dbg랑 instruction이 다르게 나와서 교재걸로 보자.
401041에서 EDX를 0으로 초기화
401046에서 4010F5~401248에서 4바이트 단위마다 순차적으로 값을 읽어들여서 EDX 레지스터에 ADD 연산으로 누적시킨다.
누적 루프가 끝나면 EDX에 특정한 값이 저장된다. 이게 바로 Checksum 값이 된다. 이 체크썸 계산 영역은 아까 말한 이중 암호화된 영역이다.
여기가 아마 우리가 패치하려는 문자열들이 담긴 곳이 아니겠는가.
*** 원래 EDX는 4바이트 크기 레지스터잖아? 근데 여기에 4바이트를 계속 더했으면 overflow가 발생하지만 checksum은 보통 이런 overflow 무시하고 마지막에 edx에 남은 값을 사용해.
이렇게 계산한 checksum 값을 31E88DB0과 동일한지 비교한다. 동일하면 401083으로 간다. 이건 여기서 좀만 내려가면 나오는 건데 뭐가 있냐면
얘가 있어. jmp 40121E.
여기가 바로 OEP의 주소이다.
체크썸은 특성 영역 코드나 데이터가 변조되지 않았는지 확인하고 검증하는 용도로 좋다. 한 바이트만 변조되어도 체크섬은 달라지기 때문이다.
즉 이 말은 내가 타깃 코드나 데이터를 수정하고 싶으면 체크섬 비교 구문도 수정해둬야 한다는 것이다.
이렇게 OEP로 가면은 내가 보라색으로 칠해둔 40123E 주소에 CALL user32.DialogBoxParamA()가 보인다. user32.dll에 있는 이 API 구조를 보면
4번째에 DLGPROC lpDialogFunc 이라는 파라미터가 Dialog Box Procedure 주소이다.
아까 파란색으로 칠해둔 명령어보면 4010F5 push하는거 보이지? 이게 그 파라미터로 들어가는 주소다.
즉 DlgProc(4010F5)를 실행하는거지. 코드로 보자.
드디어 우리가 이 파일을 실행할 때 보이던 문자열 string들이 나오네. 그보다 밑에는
그 문자열들을 어떻게 사용하는지 보인다.
이제 어딜 패치해야 하는지를 생각해봐야 한다.
인라인 코드는 어디다 설치해야 하는 지는 코드의 크기에 따라 구별지을 수 있다.
패치 코드 크기 | 위치 |
---|---|
작을 때 | 1. 파일의 빈 영역 (null padding 된 곳) 활용 |
클 때 | 2. 마지막 섹션을 확장한 뒤 설치 |
클 때 | 3. 섹션을 새로 추가해서 설치 |
2번이나 3번 같은 건 섹션 헤더 정보들도 수정해줘야 하니까 더 필요한게 많을거다.
여기선 패치할 게 적으니까 1번 방식대로 할거야.
파일 첫번째 섹션인 .text 헤더를 보자.
데이터의 사이즈는 280인데 size of raw data가 400이다. 즉 파일에서 120만큼의 공간은 남을거다.
메모리에선 section alignment가 1000이니까 720만큼 남겠지 이런게 다 null로 패딩되는거다.
pointer to raw가 400이니까 400부터 67F까진 데이터가 있을거고 680~800은 비는 거다. 진짜 딱 비어있지 않니. 여기다가 Code Cave를 심어둘거다.
이때 중요하게 알아둬야 할 것이 있다.
아까 .text 섹션의 섹션 헤더를 보면 Characteristics에 빨강칠해둔 것처럼 write 권한이 주어져있다. 메모리에 쓸 수 있는 권한이 반드시 주어져야 패치를 할 수 있다.
아까 디버깅하면서 본 OEP까지 가보자. 진짜 귀신같이 1280부터는 아무것도 없는걸 볼 수 있지. 여기에 패치 코드를 쓰는거다. 이런 식으로 써가는데 x32dbg로 수정할 때는 교재처럼 안되고 있다.
그래서 교재 사진으로 대체
파란색으로 칠한 명령들이 보라색으로 칠한 저 문자열들을 가져와사서 패치하게 되는 것이다.
기존 : You must patch this NAG!!! -> 패치 : Reverscore
기존 : You must unpack me!!! -> 패치 : Unpacked
파란색 명령어가 끝나고 나오는 부분 4012A2를 보자.
이제 OEP로 점프하라고 한다.
패치코드 실행 -> OEP로 점프하는 이 단계가 완성된 것이다.
이제 해야할 건 기존코드에서 OEP로 점프하는 명령을 이 패치코드로 점프하도록 만드는 것이다. 그럼
복호화 끝->체크썸 끝 -> 패치코드로 점프->OEP로 점프까지가 완성되는 것이다.
아까 체크썸 영역의 마지막이 바로 OEP로 점프하는 것이다.
우리는 JMP OEP를 JMP Code Cave(401280)로 바꾸고 싶단 것이다.
어떻게 바꾸느냐.
E9 96 01 00 00이 있는 오프셋 가서 수정해야지.
401083은 RAW로 바꾸자.
imagebase가 400000인거 확인 rva가 1000이고 pointer to raw가 400인 것도 확인
401083 - 401000 + 400 = 483
가보자. 오프셋 483
483, 484, 485 오프셋이 바로 OEP로 가는 명령어가 있는 오프셋이다. 이걸 바꿔야 하는데 중요한 주의사항이 있다.
오프셋에는 EE 91 06이라고 되어있는데 디버거에선 E9 96 01 이라고 되어있다.
체크썸 영역에서 본 E9 96 01 00 00은 실제 파일에선 암호화되어 있기 때문이다!!
그럼 우리가 패치해서 집어넣을 때도 암호화시켜서 집어넣어야 한다.
401007~401085을 복호화하는 건 복호화2가 담당했었다. XOR 7이었지.
복호화 과정을 떠올려보자.
EE 91 06 => XOR 7 => E9 96 01이었다.
즉 우리가 원하는 명령어 JMP Code Cave는 E9 F8 01이다
E9 F8 01 -> XOR 7 -> EE FF 06으로 바꿔서 넣어야 한다.
이제 실행시켜보자.
좋아