통신을 위한 ICALL GADGET 악용
일반 정보: 이것은 Windows 커널에 있으며, 이를 통해 안티치트에 탐지되지 않고 드라이버에서 통신할 수 있습니다
그래서 IDA에서 이것저것 살펴보던 중 이것을 발견했습니다
image
추가로 살펴보면 _guard_dispatch_icall_ptr라는 함수를 호출하는 것을 볼 수 있습니다. 이 icall은 기본적으로 rax로의 jmp이므로, 조금만 생각해보면 이 전체 함수를 수정하여 셸코드를 사용해 우리의 핸들러를 대신 호출하도록 만들 수 있습니다.
우리의 셸코드
우리는 rax를 우리 핸들러 함수의 포인터로 설정하는 셸코드를 만들 것입니다. 그렇게 하면 함수가 호출될 때 우리의 셸코드가 실행되고 우리의 핸들러를 호출하게 됩니다. 셸코드를 만들 때 수정하려는 코드와 정확히 동일한 바이트 크기인지 확인해야 합니다. 다음은 제가 asm으로 사용한 셸코드의 예시이며, 이를 바이트로 변환해야 합니다.
sub rsp,0x38
movabs rax,0xdeadbeef #placeholder for handler
movabs r10,0xab39cfee
inc rax
dec rax
call QWORD PTR [rip+0x720d4] # 0x720f8
jmp 0x29
우리의 코드
cpp에서는 함수의 주소를 가져온 다음 우리의 셸코드를 그 안에 매핑하기만 하면 되지만, 여기서 문제가 생깁니다. 만약 이것을 그대로 단순하게 수행하면 스택이 손상되어 때때로 블루스크린이 발생합니다. 이를 피하려면 스택을 복구해야 합니다. 따라서 우리의 핸들러 내부에서 반환하는 대신, 함수를 수정하지 않았을 때 함수가 수행했을 것과 동일한 작업을 수행해야 합니다. 안티치트를 회피하는 놀라운 방법을 스푼피딩하고 싶지 않기 때문에 이 부분은 독자가 직접 하도록 남겨두겠습니다. 하지만 함수의 원본 asm을 보고 우리가 무엇을 수정하고 있는지 확인하는 것을 잊지 마세요. 참고: 스택 손상을 수정하고 핸들러 내부에서 함수가 원래 의도했던 작업을 수행하면 아무것도 수정되지 않은 것처럼 보일 것입니다! :)
//getting address to function
FunctionAddress = module + 0xD70C;
BYTE shellcode[] = { 0x48, 0x83, 0xEC, 0x38, 0x48, 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x49, 0xBA, 0xEE, 0xCF, 0x39, 0xAB, 0x00, 0x00, 0x00, 0x00, 0x48, 0xFF, 0xC0, 0x48, 0xFF, 0xC8, 0xFF, 0x15, 0xD4, 0x20, 0x07, 0x00, 0xE9, 0x00, 0x00, 0x00, 0x00 };
memcpy(&shellcode[6], &hkfunction, 8);
DisableWriteProtection();
memcpy((PVOID)FunctionAddress, &shellcode, sizeof(shellcode));
EnableWriteProtection();
마무리
이 짧은 글에서 뭔가 얻어갔기를 바랍니다. 더 복잡할수록 더 많은 일을 할 수 있는 다른 가젯들도 찾을 수 있다는 것만 알아두세요.