(Windows) injection & hooking
Dll injection
#1
OpenProcess()
로 HANDLE을 얻는다.VirtualAllocEx()
로 타겟 프로세스에 injection할 dll의 path를 쓸 공간을 할당한다.WriteProcessMemory()
로 step 2의 주소에 path를 쓴다.GetProcAddress()
로 LoadLibraryA의 주소를 얻는다.CreateRemoteThread()
의 네번째 인자로 실행할 함수( LoadLibraryA )의 주소를, 다섯번째 인자로 그 함수가 실행할 인자(path)를 전달한다. 결과적으로 remote process가LoadLibraryA(path)
를 실행하게 되어 dll을 load하게 되고,Dllmain()
이 실행된다. 1) pid를 모른다면 CreateToolhelp32Snapshot, Process32First/Next로 pid를 먼저 구한다.
2) VirtualAllocEx에 인자로 넘기는 HANDLE은 PROCESS\_VM_OPERATION
접근 권한을 갖고 있어야 한다. 즉, 프로세스가 이 권한을 넘기지 않으면 막을 수 있지 않을까 싶다.
4, 5) LoadLibraryA는 kernel32.dll에 있는 API이고, kernel32.dll은 windows 핵심 dll로 ImageBase가 따로 설정되어 있어 relocation이 일어나지 않고 프로세스마다 같은 위치에 매핑된다. 따라서 특별한 경우를 제외하면 모든 프로세스의 LoadLibraryA 주소는 동일하다는 점을 이용한다.
#2
레지스트리 AppInit_Dlls
는 부팅시 모든 프로세스에 dll을 자동으로 매핑해준다.
#3
PE를 직접 수정해서 자체적으로 dll import하도록 만든다.
이 때 IDT는 위치를 빈공간으로 지정해서 IID구조체를 새로 만드는 것이 가능하지만 IAT의 경우 되는지 모르겠다. 일단 IAT 크기가 들어갈 빈 공간이 없고, 위치를 옮겨도 실행이 되는건지 의문. 아무튼, 그래서 남는 IAT에 injection할 dll 정보를 넣어야 되는데 IAT가 다 차있어 남는 공간이 없을 경우 쓸모없어보이는 dll을 삭제하고 거기 넣는 방식으로 해결 가능하다. IAT 크기를 늘려서 끝에 추가하는 것도 해봤는데, IAT 밑에 뭐가 있는건지 값 수정하면 안열린다. IAT를 당기는 것도 안된다.
Dll injection이라고 Global하게 동작하는게 아니다.
copy-on-write 정책이기 때문에. 공유하고 있다가 쓰는 순간 독립된 영역이 만들어진다. global하게 적용하려면 kernel단에서 해야함.
Code injection
dll injection에서는, WriteProcessMemory()
에 injection할 dll의 path를 넣고 이를 CreateRemoteThread()
의 인자로 전달해서 실행할 Thread의 인자로 넘긴다. 따라서 실행되는 Thread는 LoadLibrary다.
반면 code injection은 WriteProcessMemory()
를 Thread 인자를 위해 한 번, Thread 코드를 위해 한 번. 총 두 번 사용한다. CreateRemoteThread()
에 이 두 주소를 인자로 전달한다. dll injection에서 LoadLibrary 주소를 미리 구해서 전달하는 것과 차이가 있다.
결과적으로 remote process가 직접 정의한 Thread를 실행하게 되어 ThreadProc가 실행된다.
hooking
Dll(code) injection을 이용해 hooking하려면, dll에 다음 두 가지 함수가 있어야 한다.
- hooking을 수행하는 함수(hooking_func)
- hooked_func가
jmp
하게 될 fake 함수(hookfunc)
hooking에는 두 가지 방법이 있는데, #1을 API hooking, #2를 code hooking이라고 정의하기도 하지만 엄격히 분류하지는 않는 듯.
code flow #1 ( restore )
1
2
3
4
5
precedence\_func { ... call hooked\_func ... }
→ hooked\_func { ... jmp hookfunc ... }
→ hookfunc { ... restore hooked\_func's original code ...
call hooked\_func ...
return } // → precedence\_func
이 경우 hooking point를 어디로 잡든, 원래의 함수가 실행되기 때문에 프로그램이 정상적으로 동작하게 된다.
code flow #2 ( go-ahead )
1
2
3
4
5
precedence\_func { ... call hooked\_func ... }
→ hooked\_func { ... jmp hookfunc ... }
→ hookfunc { save context ...
... restore context
jmp next(jmp hookfunc) } // → hooked\_func
hookfunc에서 원래 함수를 복원하지 않고 일단 실행한 다음, hooked_func으로 되돌아가 이어서 실행하는 방법. context를 적절히 저장해두고 복원해야 crash나지 않는다. 이 경우 원래 함수로 복원하지 않기 때문에, 주로 함수에 크게 영향을 주지 않는jmp
계열 명령어가 있는 곳을 hooking point로 잡는다.
참고
보안 프로그램에서 dll을 사용할 때는 compile time에 명시하는 방법과 LoadLibrary 중 후자를 사용한다고 한다. 컴파일 타임에 올리는 방식은 dll을 변조해버리면 exe 파일이 아예 실행이 안된다. 그래서 LoadLibrary로 올리고 예외처리를 하는 편이라고 한다.