
리플렉티브 로더가 적용한 IAT 훅을 사용하여 Cobalt Strike용 맞춤형 C2 채널을 개발하기 위한 템플릿.
이것은 리플렉티브 로더가 적용한 IAT 후크를 사용하여 Cobalt Strike용 사용자 지정 C2 채널을 개발하기 위한 간단한 PoC 및 템플릿입니다.
블로그 게시물: https://codex-7.gitbook.io/codexs-terminal-window/red-team/cobalt-strike/building-custom-c2-channels-by-hooking-wininet
TCP 채널 데모 gif

customCallback.h, hook.c, hash.h 및 hook.h 파일은 대략적인 템플릿이지만, Crystal kit에 넣어 그대로 PoC로 사용할 수 있습니다. udrl/src 폴더에 넣고 기존 파일을 교체하세요. tcg.h는 변경되지 않았습니다 (Raphael Mudge의 tradecraft garden에서 가져온 것).
examples/ 폴더의 예제는 그대로 사용할 수 있지만, 예제일 뿐이며 작성 시 운영상의 고려 사항이 반영되지 않았습니다. 주의해서 사용하세요.
현재 예제는 다음과 같습니다:
Crystal kit의 원래 회피 기능(Draugr 구현, sleep masking 등)은 이 코드베이스를 깔끔하고 이식 가능하게 유지하기 위해 hook.c에서 제거되었습니다. 해당 기능을 유지하려면 Crystal kit의 원본 파일에서 다시 추가할 수 있습니다.
beacon dll을 생성할 때 사용할 http 라이브러리로 winhttp/wininet 중 적절한 것을 선택해야 합니다.
이 템플릿이 테스트된 malleable profile은 저장소에 제공됩니다. 이론상 (대부분의) 다른 malleable profile도 작동해야 하지만, 이 템플릿은 이 프로필(GraphStrike에서 가져옴)로 테스트되었습니다.
공식 ExternalC2 인터페이스는 사용하기 매우 불편합니다. externalc2 채널을 통해 SMB beacon을 스테이징해야 하며, (4.10 이전에는) 먼저 externalc2 에이전트를 통해 스테이징하지 않고 미리 만들어진 SMB beacon과 통신하는 것을 지원하지 않았습니다. 현재 상태에서도 여전히 다음 아키텍처에 묶여 있습니다:
smb beacon --named pipe--> externalc2 agent --custom channel--> externalc2 handler --> teamserver
네, Cobalt Strike 4.12에 UDC2가 추가된 것을 알고 있습니다. 어쨌든 이것은 UDRL에서 해당 기능을 재현해 보는 재미있는 실험일 뿐입니다.
개념은 간단합니다. 콜백에 사용되는 WinAPI를 후킹할 수 있다면 이미 콜백에 필요한 모든 데이터를 갖게 된 것입니다. 이 사용자 지정 C2 채널 구현 방식은 GraphStrike에서 볼 수 있듯 이미 이전에 수행된 적이 있습니다.
그러나 GraphStrike의 구현은 여전히 HTTP 프로토콜 자체에 상당히 묶여 있습니다. 이 저장소의 목표는 HTTP든 아니든 어떤 사용자 지정 채널이든 주변 코드를 거의 수정하지 않고 구현할 수 있는 사용, 수정, 확장이 쉬운 템플릿을 제공하는 것입니다.
이 템플릿은 당연히 기본 상태로 사용하라고 만들어진 것이 아니므로, 원하는 C2 채널을 구현하려면 다음을 수정해야 합니다:
customCallback 함수가 다음 작업을 수행하도록 수정하세요:
인자를 통해 http 요청의 원래 host와 port(Cobalt Strike에서 설정한 값)를 사용할 수 있습니다:
const char *host
INTERNET_PORT port
그리고 broker.py의 handleCallback() 함수를 다음 작업을 수행하도록 수정하세요:
그게 전부입니다.
예를 들어, customCallback.h의 customCallback() 함수에 있는 PoC는 현재 다음 작업을 수행합니다:
broker.py의 handleCallback 함수에 있는 PoC는 현재 다음 작업을 수행합니다:
process_encoded_request(encoded_request) 호출usage: broker.py [-h] --host HOST --port PORT
여기서 host와 port는 팀서버의 http 리스너를 가리킵니다. 브로커는 http 요청을 파싱한 다음 실제 리스너로 보냅니다.
이것은 아마도 제가 한 방식 중 가장 '게으른' 방법이면서도 가장 안정적인 방법 중 하나일 것입니다. 기본적으로 전체 HTTP 요청을 감싸서 원하는 방식으로 브로커에 전달하고, 브로커는 이를 실제로 팀서버에 보내 HTTP beacon으로 실행되게 하며, 팀서버에는 완전히 투명합니다.
제가 시도한 (기술적으로) 더 깔끔한 구현은 최소한의 Malleable C2 프로필(실제로 graphstrike의 것을 사용했습니다)을 사용하고 wininet 후크 내부에서 malleable 스펙에 따라 다양한 beacon 데이터 구성 요소(id, metadata, output 등)를 파싱하는 것이었습니다. 그렇게 하면 콜백 blob 크기가 약간 더 작아졌겠지만, 요청 파싱으로 인해 코드베이스가 불필요하게 지저분해지고 특정 프로필을 사용해야 했기 때문에(그렇게 큰 문제는 아니지만 매우 hacky합니다) 그 구현은 포기했습니다.
이 구현이 더 낫다고 느낀 이유는 hacky한 malleable profile에 덜 의존하고(기술적으로는 여전히 beacon 응답 출력이 응답 본문에 전송되어야 하지만, 그게 유일한 요구 사항입니다) 코드를 10배 더 읽기 쉽게 만들었기 때문입니다.
또한 http 요청 json에는 base64 인코딩 외에 난독화나 암호화가 없다는 점을 알아두세요. 직접 추가해도 되지만, Beacon 콜백은 이미 암호화되어 있으므로 기술적으로 해독될 위험이 있는 데이터는 없습니다. 최악의 경우 탈취된 base64 blob이 HTTP 요청으로 식별될 수 있다는 것입니다. 그 정보를 어떻게 사용할지는 여러분의 몫입니다.
네, 일부 코드와 주석을 작성할 때 LLM을 사용했습니다. 아무도 문서 작성이나 C로 보일러플레이트 json 파싱을 처음부터 쓰는 걸 좋아하지 않으니까요. 나를 고소하세요.
일반적인 면책 조항: 이 형편없는 코드를 사용하여 저지를 수 있는 반인류적 범죄나 일으킬 수 있는 핵 참사에 대해 저는 책임지지 않습니다.