6. Barrel-Shot
Barrel Shot
코드
https://github.com/ChoiSG/Operation-Triple-Barrel/tree/main/barrel-shot
데모
개요
HookShot은 오퍼레이션 더블 배럴 보고서 13~14, 35페이지에 등장하는 RDP/SSH 터널링 도구다. 내부 네트워크의 RDP, SSH 등의 서비스에 외부에서 접근하기 위한 도구로, Chisel이나 Ligolo-ng과 비슷한 역할을 한다. 보고서 35페이지에서 DLL Sideloading으로 실행했다고 기술되어 있는데, 이 점을 반영해 처음부터 DLL Sideloading이 가능하도록 만들었다.
아마 C나 C++로 만들어진 악성코드 같은데, 시간이 너무 없어 (3일) Barrel shot은 golang으로 제작했다. AI를 활용해 만들다보니, 사실상 코드 베이스는 chisel과 ligolo-ng의 것을 가져다 사용했다고 봐도 무방하다.
Go로 작성했고, SSH-over-WebSocket 구조를 사용한다.
구조
오퍼레이터 타겟 네트워크
+-------------------+ +-------------------+
| barrel-shot | | barrel-shot |
| server |<--[WSS/SSH]-------+ agent |
| | | |
| - HTTPS listener | | - reverse conn |
| - tunnel binder | | - SOCKS5 proxy |
| | | - reconnect loop |
+-------------------+ +-------------------+
| |
v v
RDP client target:3389 (RDP)
SSH client target:22 (SSH)
proxychains target:* (SOCKS5)에이전트가 타겟 호스트에서 오퍼레이터 서버로 WebSocket 연결을 맺고, 그 안에서 SSH 세션을 만든다. SSH가 암호화와 멀티플렉싱을 다 해주기 때문에 별도의 커스텀 암호화가 필요 없다. 오퍼레이터 쪽에서는 로컬 포트가 열리고, 해당 포트에 RDP/SSH 클라이언트를 연결하면 트래픽이 SSH 채널을 타고 타겟 네트워크 안으로 들어간다.
DLL Sideloading
보고서에서 HookShot이 DLL Sideloading으로 실행됐다고 명시되어 있어서, 빌드 파이프라인에 이를 포함시켰다.
Barrel Shot의 DLL 빌드는 프록시 DLL 패턴을 사용한다. 타겟 DLL(예: python315.dll)의 PE export table을 파싱해서, 모든 export 함수를 원본 DLL로 포워딩하는 C 코드를 자동 생성한다. 이 프록시 코드와 Barrel Shot 에이전트를 함께 컴파일하면, 정상 프로그램이 해당 DLL을 로드할 때 원래 기능은 그대로 동작하면서 에이전트가 백그라운드에서 실행된다.
FUNCTION 파라미터가 중요하다. 에이전트가 시작되는 트리거를 DLL 로드 시점(DllMain, 기본값)이 아니라 특정 export 함수가 호출되는 시점으로 지정할 수 있다. 위 예시에서는 호스트 프로그램(pythonw.exe)이 Py_Main을 호출하는 순간 에이전트가 시작된다.
트리거 모드: DllMain vs. Export Function
에이전트 시작 시점을 두 가지 모드로 제어할 수 있다.
1. FUNCTION=DllMain (기본값): DLL이 로드되는 즉시 Go 런타임의 init()에서 에이전트를 시작한다.
2. FUNCTION=Py_Main (export 트리거): DLL 로드 시에는 아무것도 안하고, 호스트 프로그램이 해당 export 함수를 호출할 때 에이전트를 시작한다. proxygen이 생성하는 C 코드에서 해당 함수의 stub에 GoNow() 호출이 삽입된다.
Export 트리거가 유용한 이유는, DLL이 로드되는 시점에는 프로세스 초기화가 아직 진행 중일 수 있기 때문이다. 예를 들어 Loader Lock이 잡혀 있는 상태에서 네트워크 연결을 시도하면 데드락이 걸릴 수 있다. Export 트리거를 사용하면 프로세스 초기화가 끝난 뒤 안전하게 에이전트를 시작할 수 있다.
GoNow()는 Go 쪽에서 export된 CGO 함수로, RunDLL()을 goroutine으로 실행한다. sync.Once로 감싸져 있어 여러 번 호출돼도 에이전트는 한 번만 시작된다.
RunAgent와 ServiceMain은 blocking 호출이라, rundll32.exe python315.dll,RunAgent 같은 방식으로도 실행할 수 있다.
Proxy 코드 생성
proxygen이 타겟 DLL의 PE export table을 직접 파싱해서 모든 named export를 추출한다. 이미 forwarded된 export (RVA가 export directory 범위 안에 있는 경우)는 건너뛴다.
추출된 export 목록으로 proxy_gen.c를 생성한다. 원본 DLL은 python315_.dll(언더스코어 추가)로 이름을 바꿔 같은 디렉토리에 놓고, 프록시 DLL이 원본 이름(python315.dll)을 차지한다.
version.dll, winmm.dll, dbghelp.dll 등 아무 DLL이나 사용 가능하다. proxygen이 PE export table을 직접 파싱하기 때문에 함수 목록을 하드코딩할 필요가 없다.
Version Info Cloning
빌드 파이프라인에는 versioninfo.py도 포함되어 있다. 원본 DLL의 PE 리소스에서 VS_FIXEDFILEINFO와 StringFileInfo를 추출해 .rc 파일을 만들고, windres로 컴파일해서 .syso로 Go 빌드에 자동 링크한다. 결과물인 프록시 DLL이 원본과 동일한 파일 버전, 제품명, 저작권 정보를 가지게 된다.
임베디드 설정
에이전트 설정(서버 주소, 인증 정보, 터널 정의, 트리거 함수)은 빌드 타임에 Go의 -ldflags -X로 바이너리에 박아넣는다.
설정이 임베디드되어 있으면 인자 없이 실행해도 바로 연결된다. DLL Sideloading 시나리오에서는 CLI 인자를 넘길 방법이 없기 때문에 필수적인 기능이다.
그 외
Reconnect: 연결이 끊기면 exponential backoff + jitter (1초~5분)로 재접속한다
SOCKS5:
R:socks터널을 정의하면 에이전트 측에 SOCKS5 프록시가 열려,proxychains를 통해 내부 네트워크 어디든 접근 가능하다Backend proxy: 서버의
--backend옵션으로 비에이전트 HTTP 요청을 정상 웹서버로 리버스 프록시할 수 있다. 누군가 서버 URL에 브라우저로 접속하면 정상 웹사이트가 보인다TLS: 인증서가 없으면 self-signed를 자동 생성한다. 실제 오퍼레이션에서는 CDN 뒤에 놓기 때문에 CDN이 TLS termination을 해준다
Last updated