본문 바로가기

My Study/Programming&Theory

SYSENTER Hooking

이번에 해볼 내용은 SYSENTER를 후킹해보도록 하겠습니다.
SYSENTER를 후킹하는건 간단한데 이것을 사용해 무엇을 구현해 보려니 그 부분이 쫌 시간이 걸렸습니다.
먼저 SYSENTER에 대해서 간략하게 설명해보도록 하겠습니다.

SSDT후킹 글 올렸을 때 그렸던 그림 일부를 가져왔습니다.

잘 보시면 DeleteFileW는 내부적으로 ZwOpenFile함수를 호출합니다.
(ZwQueryInformationFile, ZwSetInformationFile 등.. 여러함수를 호출합니다.)
그리고 ZwOpenFileKiFastSystemCall함수를 호출하는데 그 함수 내부에 바로 SYSENTER라는 명령어가 존재합니다.
해당 명령어의 기능은 단순합니다. 커널로 들어가게 되는 것이지요.

당연히 그냥 커널로 들어가는게 아니라 커널로 들어가면서 esp도 바뀌고 CS,SS 값도 바뀌게 됩니다.
이제부턴 커널에서 동작하기 때문이죠.

그리고 그냥 커널로 들어가는게 아니라 실행되어질 커널의 주소는 바로 msr레지스터의 176번째 레지스터 내부에 있는 값으로 이동을 하게 됩니다. 해당 주소는 KiFastCallEntry라는 함수이지요.

SYSENTER라는 명령어가 하는 일입니다. 

이제 그렇게 커널에 들어왔으면 KiFastCallEntry는 eax인덱스 값을 가지고 SSDT에서 Nt함수 주소를 가져오고 해당 함수를 호출하겠지요. 저번 문서에서는 SSDT에 있는 함수 주소를 가로채면서 후킹하는 방법을 보여주었다면 이번에는 
SYSENTER를 후킹하면서 바로 KiFastCallEntry로 오는게 아니라 도중에 가로채서 조작 후 KiFastCallEntry 함수를 호출하는 방법을 보여드리겠습니다.

간략히 제가 해볼 행동에 대하여 그림으로 봐보시겠습니다.

이와 같이 보통은 sysenter를 하면 바로 KiFastCallEntry로 가야하는데 제가 만든 함수를 거친 후 KiFastCallEntry로 가게 되는 것입니다. 그렇게 가게 되는 이유는 msr 0x176레지스터 안에 있는 값을 바꿔주었기 때문이죠.

원리는 이게 전부 입니다.
소스 코드를 보시기 전에 가장 중요한 점을 말씀드리겠습니다.
후킹을 하게 되면 KiFastCallEntry함수를 호출하기까지 여러 과정을 거치게 되는데 이때 레지스터 값과 스택 값은 절대 변경해선 안됩니다. SSDT후킹 때는 단순히 하나의 함수만 후킹하는 것이기 때문에 이런 걱정은 하지 않았는데 이번에는 SYSENTER를 호출하는 모든 경우를 전부 후킹하기 때문에 잘못 레지 값이나 스택 값을 건들였다간 바로 블루스크린이 뜨게 됩니다.
이 코드를 짜면서 이 문제 때문에 가장 많이 블루스크린이 떴습니다 ㅠㅠ

제가 구현해본 내용은 SSDT후킹했을 때 구현한 것과 같습니다.
특정 파일을 삭제하려고 했는데 삭제를 못하도록 막는 것입니다.

이제 코드를 보겠습니다. 전역으로 선언한 변수들 입니다.


먼저 DriverEntry를 보시겠습니다.
GetServiceNum함수는 SYSENTER후킹해서 조작해볼 함수 서비스 넘버를 구해올 함수입니다.
제가 조작할 함수는 ZwOpenFile이므로 Windows XP라면 0x74라는 값을 가져올 것입니다. ( 하드코딩 )

그리고 그 아래 인라인 어셈으로 코드를 구현해 놨는데 이 부분이 msr 0x176레지스터 안에 있는 주소 값을 
변경하는 코드입니다.
인터럽트에 의해 cpu를 뺏기는 것을 막기위해 cli, sti를 사용해주었습니다.
rdmsr은 ecx에 들어있는 msr 레지스터 값을 가져오는 명령어입니다. msr 레지스터 들은 64bit로 이루어져 있기 때문에
리턴되는 값은 edx:eax에 들어가게 됩니다. 하지만 주소값은 32bit이므로 eax값만 사용하면 됩니다.
eax에는 MyKiFastCallEntry함수 주소를 넣어주고 다시 wrmsr명령어로 msr 0x176레지스터에 edx:eax값을 넣어줍니다.

이제 msr 0x176에는 MyKiFastCallEntry가 들어있습니다. Windbg로 확인해 보겠습니다.

msr 0x176레지스터에 MyKiFastCallEntry함수가 있군요.

이제 MyKiFastCallEntry함수를 보겠습니다.
일단 naked함수로 해논 이유는 일반 함수로 만들면 컴파일 시 함수 프롤로그가 자동으로 들어가게 됩니다.
그래서 스택에 레지스터를 push해버리는데 이러면서 스택 위치가 틀어지게 됩니다. 이렇게 틀어진 상태로 KiFastCallEntry를 호출하게 되면 블루스크린이 뜨게 되겠지요.
그것을 막기위해 아에 naked함수로 만들어 버린 것입니다.

그리고 후킹하는 코드까지 어셈블리로 구현하기 그래서 HookFunc라는 함수를 하나 또 만들어 놓고 아에 호출을 해버렸습니다. 어짜피 HookFunc에서는 함수 프롤로그 에필로그 둘다 적용되기 때문에 스택 위치에 영향을 받지 않습니다.

그 위에 
mov ServiceNum,eax 
이런 코드가 있는데 이건 현재 호출된 함수의 서비스 넘버 값을 가져오는 것입니다.
유저영역에서 ZwOpenFile함수를 호출하면 eax에 함수 서비스 넘버를 넣는데 그 값 입니다.

이 값은 HookFunc에서 ZwOpenFile함수를 호출한 경우인지 아닌지 판단할 때 사용합니다.

이제 HookFunc를 보겠습니다.
일단 모든 코드를 pushad, popad로 묶은 이유는 KiFastCallEntry함수를 호출하기 전까지 절대로 레지 값을 변경하지 않기 위해서 입니다.

어셈코드에서 먼저 edx에는 유저영역에서의 esp값을 가지고 있습니다. 그렇기 때문에 호출되었던 인자들을 가지고 오기 위해선 esp값이 아닌 edx값으로 접근해서 인자를 가져와야합니다. 유저 -> 커널로 오면서 esp값은 바뀌었지요.

그리고 edx의 메모리 값을 보면 바로 해당 함수의 첫번째 인자가 있는게 아니라 두개의 값이 push가 된 후 첫번째 인자가 있었습니다. 그래서 접근을 +0xC와 0x10으로 하였습니다.

이제 두개의 인자를 가져왔고 if문으로 해당 함수가 ZwOpenFile이고 상태가 DELETE상태일 때만 안으로 들어오게 했습니다.
SYSENTER가 호출되면 무조건 후킹된 곳으로 오기 때문에 특정 일이 아닌이상 작업을 최소화 해주어야 합니다.
무리하게 코드를 넣었다간 컴퓨터가 엄청 느려질 수도 있습니다.

if문 내부적으로는 ZwOpenFile로 전달된 삭제할 파일명이 "C:\test.txt"면 그 메모리 영역을 0으로 채워버리는 것입니다.
마지막으로 popad로 레지를 복구해주고 이제 KiFastCallEntry로 점프하면 됩니다.

이제 ZwOpenFile에서는 해당 파일의 핸들 값을 얻어오는 함수인데 해당 파일의 경로가 잘못되어 있으므로
핸들 값을 얻어오질 못할 것입니다. 그러므로 파일 삭제는 이루어지지 않고 오류를 뱉을 것입니다.



IDT후킹으로 할 수 있는건 다양한것 같지만 일단 제가 생각해서 직접 해본 IDT후킹이었습니다.