sábado, 18 de octubre de 2008

Memory,File CRC(Cyclic Redundancy Check) bypassing?

CRC 체크를 우회하는 방법에 대해 질문하는 분들이 종종 있더군요.
-_-; 너무 엉터리라서 저에게 핵토파스칼 킥을 날릴분들이 많을지도 모르겠지만,
간단히 한번 말해보도록 하겠습니다.

재가 처음에 CRC 체크를 우회하는 것에 대해서 생각을 해볼 때,
첫번쨰 생각한 것은 Shadow Walker Protect 였습니다.
-_-;; 해당 프로젝트가 무엇인지에 대한 설명은 넘어가도록 하겠습니다.

그러나 Shadow Walker Project는 소요되는 시간과 코드량에 비해
위험성이 다소 있기 때문에 결국 쓰지 않았죠. (절떄 귀찮아서가 아닐껍니다.)

두번째 생각한 것은 CRC체크 루틴을 찾아서 수정한다 였는데,
-_- 음.. 얼마 안되서 인터넷에 나오더군요. (그걸 구현하다니 성실한 녀석들)

그렇다면 CRC 체크 루틴은 어떻게 찾을 수 있는 걸까요?

이 대답은 두가지로 나뉘어 집니다.
왜냐하면 CRC체크 루틴은 해당 프로그램 내에서 이루어질 수도 있고,
외부 프로그램에 의해서 이루어질수도 있기 떄문입니다.

먼저 내부 프로그램에 의한 CRC체크 루틴을 찾는 방법에 대해서 말해보자면,
프로그램 내에서 CRC체크 루틴은 대략 어떤 모습을 가지고 있을까요? -_-?
정말 간단하게 생각해보자면 다음과 같다고 볼 수 있겠죠.

for(int i = EntryPoint; i <= SizeOfImage; i++)
{
...
checksum += memory[i];
....
}

-_-;; 재밌는게 보이죠?
모든 메모리의 변조여부를 체크하기 위해서는 모든 메모리에 접근해서 읽어와야
합니다. 즉 Read가 발생하죠... (여기서 감이 오죠)

우리에겐 INTEL이 주신 축복인.. Hardware BreakPoint라는 친구가 있죠.
이 친구가 도와주면 우리는 특정한 메모리 주소에 Write or Read|Write or Excute가
발생했을때와 그 읽기 또는 읽기쓰기 또는 실행을 시도한 주소가 어딘지 알 수 있죠.
-_-; 읽기또는쓰기를 시도한 메모리 주소가 바로 CRC체크루틴이 있는 곳이겠죠..?
(Haredware BreakPoint에 대해선 다음글에서 말하고 있네요 : 링크)

체크루틴을 찾았으면 다음에 할일은 검사에 걸리지 않도록 하는 일입니다.
이것도 역시 아주 간단한데요.
-_-; 음.. 위에서 간단히 설명을 위해 사용했던 코드는 다음과 같이도 표시됩니다.

for(int i = EntryPoint; i <= SizeOfImage; i++)
{
...
checksum += *(BaseAddress + i);
....
}

결국 memory라는 BaseAddress를 기반으로 i값만큼 떨어진 오프셋에 있는 값을
한개씩 확인하는 거죠. 이걸 기반으로 CRC 체크 루틴을 우회할 수 있는 간단한
아이디어를 생각해 낼 수 있는 겁니다.

CRC체크 루틴이 원하는 Original Memory들을 덤프하여 둔 후,
해당 프로세스 내에 다시 메모리를 할당하여 대상 메모리에 덤프해둔 메모리를 복사하면,
기존의 공간과 똑같은 메모리 구조를 가지고 있게 됩니다.
그 후 찾아낸 CRC체크 루틴에서 사용하는 BaseAddress를 할당한 메모리 주소로
바꿔주게 되면 우리가 본래 내용을 변조하여도 언제나 덤프해둔 메모리의 내용만
검사할 것이기 때문에 감지를 우회할 수 있게 됩니다.
이를 간단하게 그림으로 표시해봤습니다.


위는 기존의 메모리 상태이고, 체크루틴의 BaseAddress를 바꾼 후는 : 정말 쉽죠..? (이런말 하다가 죽은 미술가가 한명 있죠..)


그렇다면 외부 프로그램에 의해 검사되는 방식은 어떨까요? 그것도 정말 쉬운데요.
외부 프로세스에서 타 프로세스의 메모리로 접근하기 위해서는 유저레벨에서는
ReadProcessMemory() 라는 API를 사용합니다.
음.. 아마도 인자값이 ReadProcessMemory(hProcess,BaseAddress,buffer,len,&ret);
식으로 썻었던거 같은데요..
커널단에서 ReadProcessMemory()라는 API는 ZwReadVirtualMemory()라는 함수로
연결이 되고, ZwReadVritualMemory함수를 후킹하는 다음과 같은 형식의 코드를
작성함으로써 앞에서 사용했던 방법과 동일하게 돌파가 가능하겠죠.

if((BaseAddress => CRCStartAddress) && (BaseAddress <= CRCEndAddress))

{

..........

BaseAddress = DumpedMemory;

..........

}
내부에서의 검사루틴 돌파법을 이해했다면 두번쨰 방법의 이해는 누워서 호떡먹기보다
쉬울겁니다. -_-/

No hay comentarios: