
메모리 내 중간자 공격을 수행하는 도구
MemITM(Mem In The Middle) 도구는 Windows 프로세스 메모리에서 "메시지"를 쉽게 가로채기 위해 개발되었습니다. 네트워크 메시지를 암호화 전에 캡처하거나 IPC 메시지를 캡처하고, 이를 검사하거나 퍼징을 위해 변경하기 위해 많은 맞춤형 메모리 가로채기 도구를 개발했습니다. 각 도구는 매우 맞춤형이었고, 일반적이지 않았으며, C/ASM으로 구현되어 사용/유지보수/적응이 쉽지 않았습니다.
MemITM 도구는 이러한 문제를 해결하기 위해 개발되었으며, 다음으로 구성됩니다:
여기서 다운로드할 수 있습니다(소스 + 컴파일됨): https://github.com/Amossys/MemITM
특정 Windows 프로세스에서 파일 쓰기를 가로채고 싶다고 가정해 봅시다(더 간단한 방법이 있지만 예제를 위한 것입니다). 파일 쓰기는 kernelbase.dll에서 내보내는 WriteFile 함수에 의해 수행되며, BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, ...) 형식을 따릅니다. WriteFile은 __fastcall 함수입니다. lpBuffer는 RDX가 가리키고 NumberOfBytesToWrite는 R8이 가리킵니다. 이 버퍼들을 가로채 봅시다.
IDA Pro에서 "kernelbase.dll" 파일을 열고, generate.idapython.py 스크립트를 로드한 후, getHook(LocByName("WriteFile"), "rdx","r8")을 실행한 다음 updateConfig("config.bin")을 실행합니다.
notepad를 실행한 후, python memitm.py notepad.exe config.bin을 실행합니다.
이것으로 끝입니다! WriteFile 함수에 훅이 배치되었고, memitm.py의 "로거" 및 "퍼저" 함수가 버퍼의 복사본을 받습니다.
getHook 함수는 버퍼와 그 크기에 대해 레지스터를 지정할 수 있지만, 어떤 경우에는 그렇지 않을 수 있습니다. NtCreateFile syscall을 가로채서 파일 이름을 얻고 싶다고 가정해 봅시다. NtCreateFile은 NTSTATUS NtCreateFile( OUT PHANDLE FileHandle, IN ACCESS_MASK DesiredAccess, IN POBJECT_ATTRIBUTES ObjectAttributes, ...) 형식을 따르며, 파일 이름은 ObjectAttributes->ObjectName->Buffer(크기는 ObjectAttributes->ObjectName->Length * sizeof(WCHAR))로 얻을 수 있습니다.
getHook은 또한 레지스터 대신 t1customOpcodes 매개변수에 셸코드를 지정할 수 있습니다. 이 셸코드는 다음을 따라야 합니다:
RCX 레지스터에 배치;RDX 레지스터에 배치;NtCreateFile의 경우, 이 ASM 코드를 실행하여 작업을 수행할 수 있습니다:
mov rax, [r8+0x10] ; rax는 이제 ObjectAttributes->ObjectName
mov rcx, [rax + 0x10] ; rcx는 이제 ObjectAttributes->ObjectName->Buffer
mov rdx, [rax] ; rdx는 이제 ObjectAttributes->ObjectName->Length
and rdx, 0xFFFF ; Length는 USHORT
shl rdx, 1 ; 그리고 *2를 해야 함
이것을 어셈블리합시다 (예를 들어 온라인 디스어셈블리 웹사이트 사용): 498B4010488B4810488B104881E2FFFF000048D1E2. 이제 getHook 호출은 getHook(LocByName("NtCreateFile", t1customOpcodes="498B…E2")가 되어야 합니다.
네! config 파일은 여러 가로채기 지점 정의를 포함할 수 있으며, getHook 함수는 파일에 추가됩니다. 여러 모듈에서 가로채기 지점을 설정할 수 있습니다. 예를 들어, ALPC와 IOCTL 메시지를 동시에 가로챌 수 있습니다.
Python 콜백에서 "메시지 ID"로 구분할 수 있습니다.
걱정하지 마세요. 실시간으로 테스트 케이스(및 수정 사항)를 원격 호스트로 보낼 수 있는 간단한 HTTP 서버(logserver.py)와 httpNetSend 함수도 있습니다.
bufferBitFlip 함수를 사용하여 몇 비트를 뒤집을 수 있습니다. 원시 메시지를 로깅하면 자체 디섹터를 작성하고 수동으로 더 나은 퍼저를 구현하는 데 도움이 됩니다. 버퍼 크기는 변경할 수 없다는 점을 기억하세요!
예를 들어, WriteFile 예제에서 다음 fuzzer 함수는 "hello"를 "world"로 대체합니다:
def fuzzer(data, msgID=None, pid = 0):
return data.replace("hello","world")
예를 들어, ALPC 메시지의 경우 다음 memitm.py 설정을 사용할 수 있습니다:
def logger(data, msgID=None, pid=0):
totalLen = 0
dataLen = 0
typ = 0
dataInfoOffset = 0
cid = 0
tid = 0
messageID = 0
clientViewSize = 0
# PORT_MESSAGE 헤더(0x18 바이트) 건너뛰기
if len(data) > 0x18:
totalLen = struct.unpack("<H",data[:2])[0]
dataLen = struct.unpack("<H",data[2:4])[0]
typ = struct.unpack("<H",data[4:6])[0]
dataInfoOffset = struct.unpack("<H",data[6:8])[0]
zeroInit = struct.unpack("<L",data[4:8])[0]
cid = struct.unpack("<L",data[8:0xC])[0]
tid = struct.unpack("<L",data[0xC:0x10])[0]
messageID = struct.unpack("<L",data[0x10:0x14])[0]
clientViewSize = struct.unpack("<L",data[0x14:0x18])[0]
logMessage = "ALPC message : %x:%x - %x:%x - %d:%d - %x - %x" % (totalLen, dataLen, typ, dataInfoOffset, cid, tid, messageID, clientViewSize)
print logMessage
return
IDA Pro 스크립트에는 여러 명령어를 특정 영역으로 이동하는 간단한 "훅킹" 엔진이 포함되어 있습니다(DIMCT 도구에서 파생됨). 메모리 상대 명령어, 교차 참조 등과 같은 많은 경계 사례를 처리해야 하며, 여러 교차 참조가 포함된 정말 작은 기본 블록(x86 바이너리의 경우 <5바이트, x64 바이너리의 경우 <12바이트)을 가로채려고 하면 "여기에 훅을 설치할 수 없습니다"라는 메시지가 많이 표시될 것입니다. 자세한 내용은 소스 코드를 읽어보세요.
config 파일에 다음 정보를 생성합니다:
RCX/RDX에 배치하고 DLL 로그 루틴을 호출;DLL 인젝터는 간단한 DLL 인젝터입니다(CreateRemoteThread/LoadLibraryA 방식).
DLL 자체는 공유 메모리 영역을 초기화하고 config 데이터를 기다립니다(공유 메모리 영역에는 3개의 일반 필드가 있습니다: 공유 메모리 메시지 ID, 메시지 크기, 메시지 버퍼). 데이터를 받으면 메모리에 훅을 구문 분석하고 설치하며, 이후에는 config 업데이트가 허용되지 않습니다(새 훅을 설정하려면 프로세스를 종료해야 함). 배치된 훅은 genericHookFunction DLL 함수로 연결됩니다. 이 함수는 메모리 영역(크리티컬 섹션으로 보호됨)을 버퍼 데이터, 크기 및 메시지 ID(공유 메모리 메시지 ID 필드의 상위 비트에 설정됨)로 채웁니다. Python 프로세스에 알리기 위해 두 개의 이벤트가 사용됩니다: 새 메시지를 알리기 위해 한 개, 프로세스가 패치할 때까지 기다리기 위해 다른 한 개(타임아웃 2초). 버퍼가 업데이트되면 원래 버퍼도 수정됩니다.
memitm.py 스크립트는 인젝터 프로세스를 실행하고 공유 메모리가 생성될 때까지 기다립니다. 그런 다음 구성을 보내고 메시지 이벤트를 기다립니다. 메시지가 수신되면 공유 메모리를 읽고 버퍼, 프로세스 ID 및 메시지 ID와 함께 logger 및 fuzzer 함수가 호출됩니다. logger 함수가 원래 버퍼와 다른 버퍼를 반환하고 크기가 같으면 공유 메모리 영역에 기록됩니다. 그런 다음 "ACK" 이벤트가 시그널링되고 스크립트는 새 메시지를 기다립니다.
이 도구가 유용하기를 바라며, 기여/리뷰는 언제나 환영합니다! 또한, 프랑스어를 사용하고 렌을 좋아한다면 저희는 채용 중입니다 :)