지난번에 했던 API 코드 패치는 오리지널 API의 첫 시작 5바이트를 수정하는 것이다. 그리고 인젝션된 DLL의 후킹함수로 흐름이 넘어오면 이런 과정이 필요했다.
- 다시 언훅 (무한루프 방지)
- 다시 오리지널 API 호출
- ~~뭐 원하는 거 하겠지? 오리지널 API 흐름
- 다시 후킹
결국 오리지널 api 호출할 때마다 언훅/훅을 반복해야 하니까 cpu 성능도 저하될 것이고 멀티 스레드에서 에러가 생긴다.
한 스레드에서 훅을 하고 있어서 원본 API 5바이트를 패치했다.
이때 다른 스레드에서 원본 api를 실행하려고 하면 충돌한다.
그래서 생긴 방식이 7바이트 패치라고도 하는 핫패치다.
여러 api들 예를 들어 kernel32.CreateProcessA( ), kernel32.LoadLibraryA( ), user32.MessageBoxA( ), gdi32.TextOutW( ) 같은 것들 첫 시작을 보자.
두가지 특징이 있다.
시작 코드가 MOV EDI, EDI라는 쓰잘데기 없는 2바이트 코드(0x8B FF)코드가 있다.
그 위에 NOP(Operation 없음)이 5바이트나 차지하고 있다.
즉, API의 시작부분과 그 위에 아무짝에도 안 중요한 7바이트 공간이 있는 것이다. 이게 바로 핫패치하라고 남겨둔 윈도우 시스템 라이브러리의 공간이다.'
Hot Patch또는 Hot Fix는 실행 중인 프로세스 상태에서 라이브러리를 메모리에서 일시적으로 수정하는 것이다.
핫패치의 핵심은 2단 점프다.
API의 시작 주소 명령어에서 첫번째 도약을 한다.
첫번째는 SHORT JMP인데 왜 SHORT냐면 코드가 2바이트짜리거든.
이 2바이트 점프로 어딜 갈 수 있냐면 API 시작 주소 명령어 바로 위의 명령어로 갈 수 있다.
후킹함수로 가기 위한 도움닫기 정도라고 생각해.
숏 점프는 EIP에서 -128~+127 범위에서만 이동할 수 있어.
API 시작 주소 명령어 바로 위 부분은 사실 아까 말한 NOP 5개 들어있던 5바이트 공간이다. 여기에다가 far jump 명령어를 덮어쓴다.
JMP XXXXXXXX(후킹함수 주소)를 적어두는 거지. 그럼 첫번째 도움닫기로 여기와서 이제 크게 점프하는 것이다.
7코드 패치 장점은 바로 후킹함수에서 언훅/훅이 필요 없다.
그냥 후킹함수에서 그대로 오리지널 API를 호출해도 된다.
지금 오리지널 API에서 패치된 부분은 원래도 쓸데없는 부분이었다. 그럼 API 시작주소+2 부터 오리지널 API를 실행하면 기존 API랑 완전 똑같이 동작하는 것이다.
그래서 후킹함수에서 오리지널 API를 기존 시작주소+2부터 호출하면 되는 것이다.
그럼 멀티스레드에서도 충돌없이 후킹이 가능해진다.
뭐 그냥 커맨드 돌리면 된다.
결국 핫패치를 해주는 건 인젝션하는 DLL에서 구현되어 있다.
핫패치 관련 부분만 자세히 보자.

이거는 패치순서가 중요하대.
우선 API 시작 주소 위의 NOP 5개를 JMP 명령어로 바꿔야 한다.
이때도 JMP에 들어갈 상대주소가 중요하겠지? 아까랑 구하는 원리는 똑같은데 JMP의 위치가 다르다 보니 눈에 보이는 결과가 다르다.
상대주소 = (목적지) - (점프 명령어 주소 + 점프 명령어 길이(5))
점프 명령어 주소 = API 시작 주소보다 다섯칸 위
= API 시작 주소 - 5
따라서 상대주소 = (목적지) - (API 시작 주소 - 5 + 점프 명령어길이(5) ) = (목적지) - (API 시작 주소)
=pfnNew - pFunc

이렇게 pfnNew - pFunc 구했으면 memcpu로 명령어 덮어쓰는 것이다.
그 다음에 short jmp 명령어를 api 시작 주소에 넣어준다.
어차피 시작 주소 바로 위 명령어로 가는 것은 점프 뛰는 값이 0xF9로 고정되어 있다. 그래서 F9로 하드코딩한 버퍼를 그대로 불러오면 된다.

언훅은 어떠헥 하느냐.
이건 하드코딩으로 쉽게 한다.
우선 NOP 다섯개는 0x90을 다섯개를 때려넣은 버퍼로 복원해주면 된다.
그리고 MOV EDI,EDI도 OP 코드인 0x8B랑 0xFF 때려넣은 버퍼로 복원해준다.


이제 우리가 도약하는 후킹함수만을 살펴보자.

잘 보면 더 이상 언훅/훅 같은 게 없는게 기존과 차이점이며 그리고 오리지널 api를 부를 때 실행 시작하는 주소가 다르다.

api 시작주소+2 를 해서 부른다.
jmp short가 있는 부분을 건너뛰어야 하니까.
단점이자 고려해야 할 사항이다.
어떤 api는 그 7바이트가 NOP 5개 & MOV EDI, EDI가 아니라 유의미한 명령어들일 수도 있다.
예를 들어 kernel32.GetStartInfoA( )는 막 바꾸면 안될것처럼 보인다.
그리고 ntdll.dll에서 제공하는 api들도 안된다.
그래서 이렇게 안되는 것들은 기존 5바이트 패치를 써야 한다.