Skip to content
KitploitKITPLOIT
도구블로그
제출
도구블로그
제출

해킹, 침투 테스트 및 사이버 보안 도구를 당신의 보안 무기고에!

Kitploit은 해킹, 사이버 보안 및 침투 테스트 도구 디렉토리입니다. 최신 프로젝트 업데이트를 발견하여 취약점을 찾고, 시스템을 분석하고, 테스트를 자동화하고, 보안을 강화하세요.

··피드·문의·개인정보·© 2026 Kitploit

도구 디렉토리

카테고리

모든 카테고리 보기
Loading categories
Inline-Execute-PE — CobaltStrike Beacons에서 관리되지 않는 Windows 실행 파일 실행 | Kitploit
도구/GitHubGitHub/octoberfest7/inline-execute-pe
Privilege Escalation
GitHuboctoberfest7/inline-execute-pe

Inline-Execute-PE

CobaltStrike Beacons에서 관리되지 않는 Windows 실행 파일 실행

저장소 보기
7231033년 전Kitploit 검토 완료

인기

모두 보기 →

커뮤니티에서 가장 많이 사용되는 도구를 찾아보세요.

모든 도구 탐색

도구 컬렉션을 둘러보세요

모든 도구 보기 →
공유

Inline-Execute-PE

면책 조항:

이 프로젝트는 복잡하며, 작동 방식을 이해하고 적절히 테스트하지 않으면 Beacon이 충돌하여 접근 권한을 상실할 수 있습니다!

"설계 고려 사항 및 해설" 섹션까지의 모든 문서를 읽을 것을 강력히 권장합니다!

소개

Inline-Execute-PE는 CobaltStrike용 Beacon Object File(BOF) 모음과 Aggressor 스크립트로 구성된 제품군입니다. 이를 통해 운영자는 관리되지 않는 Windows 실행 파일을 Beacon 메모리에 로드하고 실행하여 출력을 검색하고 Beacon 콘솔에 표시할 수 있습니다.

이를 통해 운영자는 Mimikatz, Dsquery, Sysinternals 도구 등 많은 타사 도구를 디스크에 저장하거나, Donut과 같은 도구를 사용하여 위치 독립 코드로 변환하거나, 새 프로세스를 생성하여 실행할 필요 없이 사용할 수 있습니다.

이러한 실행 파일은 Beacon 메모리에 매핑되어 네트워크를 통해 전송하거나 새 메모리를 할당하거나 매번 새 conhost.exe 프로세스를 생성할 필요 없이 반복 실행할 수 있습니다.

Beacon에 로드된 실행 파일은 CobaltStrike 팀 서버에 연결된 모든 CobaltStrike 클라이언트에서 접근 및 실행할 수 있습니다.

Inline-Execute-PE는 x64 Beacon과 Mingw 또는 Visual Studio로 컴파일된 x64 Windows C 또는 C++ 실행 파일을 대상으로 설계되었습니다. 이 프로젝트는 x86 실행 파일이나 다른 언어로 작성되거나 다른 컴파일러로 컴파일된 x64 실행 파일을 지원하지 않습니다.

설정

저장소를 복제하고 선택적으로 make를 실행하여 BOF를 다시 컴파일합니다.

CobaltStrike 클라이언트에 Inline-Execute-PE.cna를 로드합니다. CobaltStrike가 실행 중인 디렉터리가 사용자가 쓰기 가능한지 확인하십시오. Inline-Execute-PE는 해당 디렉터리에 petable.txt 파일을 생성하여 Inline-Execute-PE가 작동하는 데 필요한 데이터의 가용성을 보장합니다.

명령어

Inline-Execute-PE는 BOF를 실행하는 3개의 대상 노출 명령과 프로젝트 데이터 구조를 조작하는 3개의 내부 명령으로 구성됩니다.

대상 노출:

  1. peload
  2. perun
  3. peunload

내부 데이터 구조:

  1. petable
  2. peconfig
  3. pebroadcast

peload

peload는 Inline-Execute-PE의 시작점입니다. 이 명령은 PE를 Beacon 메모리에 로드하는 데 사용됩니다. 다음 주요 작업을 수행합니다.

  1. 지정된 PE를 네트워크를 통해 Beacon으로 전송하거나 대상 머신의 디스크에서 읽을 PE 이름을 전송합니다.
  2. Inline-Execute-PE의 수명 주기 동안 필요한 다양한 포인터와 핸들을 보관하는 구조체를 Beacon 메모리에 생성합니다.
  3. Beacon에 메모리를 할당하고 RW 보호로 PE를 기록합니다.
  4. 사용자 지정 키를 사용하여 PE를 메모리에서 XOR 암호화합니다.
  5. 다른 메모리 청크를 할당하고 XOR 암호화된 PE를 복사합니다. 이는 후속 실행을 위해 PE를 "되돌릴" 수 있도록 하는 데 필요합니다.
  6. Beacon 아래에 conhost.exe 자식 프로세스를 생성하여 stdin/stdout/stderr를 초기화합니다.
  7. stdout과 stderr를 익명 파이프로 리디렉션하여 PE 출력을 캡처할 수 있도록 합니다.

perun

perun은 Inline-Execute-PE의 두 번째 단계입니다. 다음 주요 작업을 수행합니다.

  1. 명령줄 인수를 네트워크를 통해 Beacon으로 전송합니다.
  2. PE를 메모리에서 XOR 복호화합니다.
  3. PE의 Import Address Table을 수정하여 명령줄 인수 및 프로세스 종료와 관련된 특정 API를 후킹합니다.
  4. PE 메모리 보호를 RWX로 변경합니다.
  5. 자체 스레드에서 PE를 실행합니다.
  6. PE의 출력을 캡처하여 CobaltStrike로 반환합니다.
  7. PE 메모리 보호를 RW로 되돌립니다.
  8. peload 중에 생성된 XOR 복사본으로 PE를 메모리에서 덮어씁니다.

peunload

peunload는 운영자가 PE 사용을 완료했거나 다른 PE를 로드하려는 경우 Beacon 메모리에서 PE를 제거하기 위해 호출됩니다. 다음 주요 작업을 수행합니다.

  1. peload 중에 생성된 핸들 및 파일 포인터를 닫습니다.
  2. peload 중에 생성된 conhost.exe 프로세스를 종료합니다.
  3. PE의 두 복사본을 메모리에서 0으로 채운 후 해제합니다.
  4. PE가 Beacon 프로세스에 로드한 DLL을 언로드하려고 시도합니다(선택 사항).

petable

petable은 현재 Beacon에 로드된 모든 PE에 대한 정보를 표시하는 데 사용됩니다.

각 CobaltStrike 클라이언트는 자체 petable을 가지고 있습니다. Inline-Execute-PE는 모든 연결된 CobaltStrike 클라이언트 간의 데이터 동기화를 보장하기 위해 많은 노력을 기울입니다. 자세한 내용은 "설계 고려 사항 및 해설"을 참조하십시오.

image

peconfig

peconfig는 Inline-Execute-PE 작동 방식과 관련된 옵션을 구성하는 데 사용됩니다. 현재 변경할 수 있는 두 가지 옵션은 다음과 같습니다.

  1. Timeout. perun이 PE 실행 완료를 기다리는 시간을 지정합니다. PE에 잘못된 인수가 제공되어 PE가 절대 반환/실행 완료되지 않는 경우를 대비한 안전 장치로 존재합니다. 기본값은 60초이며, 더 오래 실행되는 PE를 수용하기 위해 수정할 수 있습니다.
  2. UnloadLibraries. 이 옵션은 peunload가 PE에 의해 로드된 DLL을 Beacon 프로세스에서 해제하려고 시도할지 여부를 제어합니다. 기본값은 TRUE입니다. 일부 PE에서는 DLL이 Beacon 프로세스에서 언로드될 때 문제가 발생하여 Beacon이 충돌할 수 있습니다. 이 경우 PE에 의해 로드된 모든 DLL을 Beacon 프로세스에 그대로 두는 것이 좋습니다. 이는 powershell.exe를 사용할 때 관찰되었습니다(아마도 .Net CLR을 Beacon 프로세스에 로드하기 때문일 수 있음).

pebroadcast

pebroadcast는 클라이언트의 petable 내용을 다른 모든 연결된 CobaltStrike 클라이언트에 수동으로 브로드캐스트하는 데 사용할 수 있습니다.

다른 모든 CobaltStrike 클라이언트는 브로드캐스트된 데이터로 petable을 업데이트합니다. 이 기능은 실제로 필요하지 않을 수 있지만, 혹시 모를 경우를 위해 존재합니다.

사용법

peload를 사용하여 PE를 Beacon 메모리에 로드합니다. image

또는 대상 머신에 있는 PE를 새 프로세스를 생성하지 않고 사용하려면 경로와 --local 스위치를 제공합니다. image

perun을 호출하고 로드된 PE에 인수를 전달합니다. image

인수에서 큰따옴표는 백슬래시를 사용하여 이스케이프해야 합니다. image

언로드 중에 DLL 해제 시 문제를 일으키는 PE를 확인한 경우 peconfig를 사용하여 unloadlibraries를 false로 설정합니다. image

PE 사용이 완료되면 peunload를 호출하여 Beacon에서 정리합니다. image

이제 다른 PE를 Beacon에 로드할 수 있습니다. image

perun 시간 초과

PE에 전달하는 명령줄 인수에 주의해야 합니다. 일부 PE는 잘못된 인수가 제공되면 바로 충돌하는 반면, 다른 PE는 무한히 실행되어 프로세스가 계속 실행 중임에도 Beacon이 콜백하지 못하게 할 수 있습니다.

이는 Mimikatz.exe에서 인수 목록 끝에 'exit'을 지정하지 않을 때 확인할 수 있습니다. image

...

image

Inline-Execute-PE는 지정된 시간 초과 값에 도달하면 실행 중인 PE의 스레드를 종료합니다. 이를 통해 Beacon이 정상 통신을 재개할 수 있습니다(perun BOF가 실행을 완료할 때까지 Beacon은 콜백하지 않습니다). 이 Beacon에서 일반 CobaltStrike 명령과 다른 BOF는 여전히 사용할 수 있지만, Inline-Execute-PE는 비활성화됩니다. 이러한 방식으로 실행 중인 PE가 종료되면 Beacon 프로세스에서 stdout 및 stderr가 손상되는 것으로 보이며, 이후 로드된 PE가 제대로 작동하지 않습니다.

PE는 여전히 (그리고 반드시) Beacon 메모리에서 언로드되어야 하지만, petable을 보면 이 Beacon에 더 이상 추가 PE를 로드할 수 없음을 나타낼 수 있습니다. image

Inline-Execute-PE로 실행하려는 PE를 테스트하고 perun에 명령줄 인수를 제공할 때 주의를 기울이는 것이 중요합니다. 일부 PE는 다른 PE보다 더 관대합니다.

팁, 요령 및 관찰 사항

다음은 테스트 및 개발 중에 사용자가 Beacon에 로드하려는 특정 PE에 대해 관찰된 사항을 특별한 순서 없이 나열한 것입니다.

  1. Powershell.exe에 peunload를 사용하면 UnloadLibraries가 TRUE인 경우 일반적으로 Beacon이 충돌합니다. 이는 Powershell.exe가 CLR을 로드하기 때문인 것으로 생각됩니다.
  2. Cmd.exe는 첫 번째 인수로 '/c'를 사용하지 않으면 Beacon이 충돌합니다. 예: 'perun /c cd'는 정상 작동하지만, 'perun cd'는 그렇지 않습니다.
  3. Mimikatz.exe는 로드, 사용, 언로드된 후 UnloadLibraries가 첫 번째 peunload 중에 TRUE였던 경우 다시 로드되면 Beacon이 충돌합니다.
  4. 일부 PE는 종료 시 도움말 메뉴를 출력하도록 프로그래밍되어 있습니다. 이러한 메시지는 ExitProcess 및 exit() 등의 호출이 후킹되어 ExitThread로 리디렉션되기 때문에 표시되지 않습니다. 이는 PE가 Beacon 프로세스를 종료하지 않도록 하기 위함입니다.
  5. 일부 PE는 메모리 해제를 잘 처리하지 못하며 프로세스 종료 시 해제되는 메모리에 의존합니다. PE가 Beacon 프로세스 내에서 실행되므로(따라서 PE가 완료되어도 프로세스가 종료되지 않음), 더 많은 PE가 로드되고 실행됨에 따라 Beacon이 비대해질 수 있습니다. Process Explorer와 같은 도구를 사용하여 테스트 중에 이를 관찰하고 작업 중에 염두에 두십시오.
  6. Sysinternals의 PsExec는 작동하지 않는 것으로 보입니다. 실행은 되지만 원격 머신에 대한 핸들이 유효하지 않다고 불평합니다. 실제로 psexec와 같은 것을 사용하려면 CobaltStrike의 socks 프록시와 공격 박스 버전의 psexec를 사용하는 것이 더 나을 것입니다.
  7. Inline-Execute-PE를 사용하기 위해 새 비콘을 생성하는 것은 나쁜 생각이 아닙니다. 특히 다양한 PE가 프레임워크 내에서 상호 작용하고 작동하는 방식에 익숙해지는 과정에서 더욱 그렇습니다. 하나는 없음과 같고 둘은 하나와 같습니다.
  8. 새 프로세스를 생성할 때의 텔레메트리를 피하면서 LOLBIN을 사용하려면 peload와 함께 --local 스위치를 사용하여 대상 시스템의 디스크에서 읽어오십시오. 이는 버전 문제를 피하는 데도 유용할 수 있습니다.

IOC 및 AV/EDR

Inline-Execute-PE와 관련된 IOC는 다음에 국한되지 않습니다.

  1. VirtualAlloc을 사용한 메모리 할당
  2. 할당된 메모리의 보호를 RW와 RWX 사이에서 변경
  3. 자식 conhost.exe 프로세스 생성
  4. 매핑된 PE에 필요한 DLL 로드
  5. 실제 PE가 수행하는 모든 작업(예: Mimikatz가 LSASS에 접근하는 등)

AV/EDR

개발 중에 EDR에 대한 완전한 테스트를 수행하지 않았습니다. 부분적으로는 게으름과 부분적으로는 테스트 환경의 부재 때문입니다. 그러나 최신 패치가 적용된 Windows Defender(제 경험상 상당히 좋은 AV 제품)에 대해서는 테스트되었습니다.

Mimikatz.exe는 아마도 Inline-Execute-PE와 함께 사용하기에 가장 의심스럽고 잘 알려진 PE일 것입니다. Windows Defender가 Inline-Execute-PE를 사용하여 실행 중인 Mimikatz를 탐지하는 능력은 Beacon이 실행 중인 프로세스에 따라 달라진다는 것을 발견했습니다.

독립 실행형 실행 파일(예: artifact kit를 사용하여 Defender를 통과하여 정상적으로 실행할 수 있는 beacon.exe)에서 실행되는 Beacon은 Inline-Execute-PE로 Mimikatz.exe를 사용할 때 탐지됩니다.

Windows 프로세스(Explorer.exe, notepad.exe 등에 주입되거나 DLL이 합법적인 프로세스에 사이드로딩된 경우)에서 실행되는 Beacon은 Inline-Execute-PE로 Mimikatz.exe를 사용할 때 탐지되지 않습니다.

사용자랜드 후킹을 수행하는 EDR에 관해서는 테스트하지 않았지만 다음과 같은 일반적인 생각이 있습니다.

PE가 이미 언후킹/리프레시된 NTDLL이 있는 Beacon 프로세스 내에서 실행되므로, PE에 의한 API 호출이 플래그 지정되는 데 큰 문제가 없을 것으로 생각됩니다. PE가 실제로 수행하는 작업(프로세스 접근, 레지스트리 키 변경 등)에 대한 동일한 문제는 여전히 적용됩니다.

설계 고려 사항 및 해설

몇 달 전 RunPE-In-Memory를 발견하고 이를 CobaltStrike용 BOF로 변환하는 것을 시도해 보려고 생각했습니다. 그 후의 여정은 예상보다 훨씬 더 복잡하고 오래 걸렸습니다. 이 프로젝트는 자체적으로 독립 실행형 도구가 아니라 다른 도구를 실행하는 도구이기 때문에 특히 어려웠습니다. 이는 광범위한 PE와 이러한 PE가 동일한 작업(인수 가져오기, 종료 등)을 수행하는 다양한 방식과의 호환성을 위해 많은 유연성과 노력이 필요합니다.

처음에 Inline-Execute-PE는 PE를 로드, 실행 및 해제하는 올인원 BOF로 구상되었습니다. 프로젝트 시작 약 3주 후, POC가 약 75% 완료되었을 때, 약 1.5년 전에 출시되어 이미 제가 하려는 거의 모든 작업을 수행하는 Pezor를 발견했습니다. 주요 차이점은 Pezor가 내부적으로 Donut을 호출하여 PE를 셸코드로 변환하는 반면, 제 방식은 원본 PE를 수동으로 메모리에 매핑한다는 점이었습니다.

이 발견은 한편으로는 반가웠고 다른 한편으로는 실망스러웠습니다. 성숙한 프로젝트에서 영감을 얻고 코드의 몇 가지 어려운 부분을 극복하는 데 도움을 받을 수 있었던 것은 훌륭했지만, 알지 못하는 사이에 사실상 바퀴를 재발명하고 있었다는 점은 낙담스러웠습니다. Pezor에 대해 읽고 그 설계, 일부 트레이드크래프트 관련 사항 및 제 조직의 운영 요구 사항을 고려한 후 Inline-Execute-PE의 방향을 현재 보이는 형태로 변경했습니다. 이 결정은 여러 요인에 의해 주도되었으며, 아래에서 논의할 것입니다. 또한 여기까지 읽으신 분들께 의문을 제기할 수 있는 몇 가지 더 독특한 설계 선택 사항에 대해서도 논의하겠습니다.

Inline-Execute-PE vs Pezor

운영 경험을 검토하면서 도구를 반복적으로 실행해야 하는 여러 인스턴스와 도구를 발견했습니다. Pezor를 사용할 경우 운영자는 PE를 네트워크를 통해 반복적으로 전송하고, conhost.exe를 생성하고, Beacon에 새 메모리를 할당하는 등의 작업을 수행해야 하며, 이는 AV/EDR 측면에서 바람직하지 않을 수 있습니다. 이러한 사고 방식은 PE를 Beacon에 '로드'하는 아이디어로 이어졌습니다. 이는 .PS1을 반복 사용을 위해 Beacon에 로드할 수 있는 것과 유사합니다. conhost.exe는 PE가 처음 로드될 때 생성되고 PE가 메모리에 로드되어 있는 동안 유지됩니다. 마찬가지로 PE를 처음 로드할 때 한 번 메모리가 할당되며, PE를 사용할 때마다 네트워크를 통해 전송할 필요가 없습니다. Inline-Execute-PE가 채택한 모델에도 단점이 있으며, 이를 다양한 성공 정도로 해결하려고 노력했습니다.

PE의 두 복사본

Inline-Execute-PE가 PE를 Beacon에 두 번 매핑한다는 사실은 눈에 띄는 설계 선택입니다. 이는 분명 바람직하지 않거나 기꺼이 선택한 것은 아니지만, 필요에 의해 탄생했습니다. 앞서 언급했듯이 Inline-Execute-PE는 PE에서 명령줄 인수와 관련된 여러 함수를 후킹해야 합니다. 매핑된 PE가 Beacon 프로세스 내에서 실행되므로 PE는 PEB의 PROCESS_PARAMETERS 섹션에 지정된 명령줄 인수를 사용하려고 시도합니다. 이를 해결하기 위해 PE가 명령줄 인수를 검색하는 다양한 함수 중 하나를 호출할 때 PE를 자체 정의 함수로 리디렉션하여 CobaltStrike에서 perun을 사용하여 전달된 의도된 인수를 제공할 수 있도록 해야 합니다.

이는 잘 작동하지만, 개발 중에 여러 다른 PE에서 이상한 현상을 발견했습니다. PE가 처음 실행될 때는 PE의 IAT에 제공된 사용자 정의 함수가 제대로 호출되었지만, PE가 다른 인수로 다시 실행될 때는 사용자 정의 함수를 호출하지 않아 CobaltStrike에서 전달된 인수를 받지 못했습니다. 내부적으로 실제로 어떤 일이 발생하는지 확실하지 않지만, PE가 한 번 실행된 후 명령줄 인수를 메모리의 어딘가에 복사하고, 이후 실행에서는 후킹된 함수를 호출하기 전에 먼저 해당 메모리 위치를 참조한다고 생각됩니다. 이 이론은 인수에 대한 포인터 배열을 가리키는 또 다른 포인터에 대한 포인터가 있는 메모리 위치를 검색하고, 각 실행 시 이 메모리 위치를 수동으로 수정하여 올바른 포인터를 포함하도록 함으로써 입증되었습니다. 이 방법은 __getmainargs 및 __wgetmainargs 함수에는 효과가 있었지만, __p___argv 및 __p___argc와 같은 대체 함수를 호출하는 다른 PE에서는 작동하지 않았습니다.

인수를 가져오기 위해 PE가 실제로 후킹된 함수를 호출하는 상태로 PE를 "리셋"할 수 있도록 하기 위해, peload 중에 PE의 두 번째 복사본을 메모리에 만들기로 결정했습니다. 이 복사본도 XOR 암호화되어 Inline-Execute-PE의 전체 수명 주기 동안 RX 보호로 유지되며, 단순히 perun을 사용하여 실제로 실행되는 PE의 복사본을 덮어쓰는 데 사용됩니다. 완벽한 해결책은 아니지만, 다양한 PE와 그들이 사용하는 다양한 API에 대한 해결책을 찾느라 혼란에 빠지지 않고 모든 PE를 포괄하는 포괄적인 해결책입니다.

Conhost.exe

Inline-Execute-PE의 주요 판매 포인트 중 하나가 새 프로세스를 생성하지 않고 도구를 실행할 수 있다는 점이라는 점을 감안할 때, 이를 위해 새 프로세스(conhost.exe)를 생성해야 한다는 것은 큰 타격입니다. 이 요구 사항은 콘솔이 없는 경우 Windows 프로그램에서 표준 스트림(stdin/stdout/stderr)이 초기화되지 않는다는 사실에서 비롯됩니다. 우리의 경우 콘솔은 전혀 필요하지 않습니다. 표준 스트림은 익명 파이프로 리디렉션되어 캡처되지만, conhost가 없으면 스트림이 초기화되지 않아 리디렉션할 수 없습니다.

Inline-Execute-Pe는 Pezor와 동일한 방식으로 conhost 문제에 접근합니다. AllocConsole을 호출한 직후 ShowWindow를 사용하여 콘솔을 숨깁니다. 8GB RAM의 Windows 11 VM에서는 콘솔 창이 깜박인 후 사라지는 것을 본 적이 없지만, 대상 시스템에 따라 결과가 다를 수 있습니다.

최근에 기본적으로 동등한 (훨씬 더 발전된) Inline-Execute-PE 버전을 출시한 고급 상용 C2에서 일하는 개발자와 이야기를 나누었습니다. 그는 "Windows를 콘솔이 있는 것처럼 속이는" 방법으로 conhost.exe 생성을 피할 수 있었다고 말했습니다. 이 정보를 가지고 일주일 동안 Windows 프로그램이 conhost와 상호 작용하는 방식에 대한 문서를 인터넷에서 샅샅이 뒤지고, WinDBG에서 쓰기 함수와 콘솔과 관련된 API 호출을 추적하고, 심지어 놀랍게도 Github에서 사용 가능한 Windows Terminal 소스 코드를 조사했습니다. PEB 및 표준 스트림 관련 사항에 대해 많은 것을 배웠지만, 이 문제에 대한 해결책을 찾지 못했습니다. 해결 방법은 kernel32의 특정 콘솔 관련 함수를 패치하는 것과 관련될 수 있다고 생각하지만 잘 모르겠습니다. 솔직히 해결책을 찾지 못한 것에 대해 상당히 실망했지만, 독학으로 몇 년 동안 경력을 쌓은 사람으로서 어쩌면 예상된 결과일 수도 있습니다.### PE 타임아웃 및 복구 BOF를 작성해 본 사람이라면 알겠지만, BOF의 모든 장점에도 불구하고 BOF에서 오류나 충돌이 발생하면 Beacon이 죽을 수 있다는 큰 위험이 있습니다. 이 프로젝트에서는 사용자가 Inline-Execute-PE에 전달하는 데이터를 얼마나 많이 제어할 수 있는지, 그리고 개발자인 제가 쉽고 안정적으로 마련할 수 있는 안전 장치가 거의 없다는 특성으로 인해 위험이 더욱 커집니다. 예를 들어 사용자는 x86 PE를 x64 Beacon에 로드하거나, 앞서 언급한 대로 매핑된 PE에 잘못된 인수를 전달하여 Beacon을 충돌시킬 수 있습니다. 사용자가 PE에 잘못된 인수를 전달하여 Beacon을 충돌시키는 것을 막을 수는 없지만, (Mimikatz에서 'exit'를 지정하지 않은 경우처럼) PE가 무한정 실행되는 상황에서 Beacon을 구출할 수는 있습니다.

이상적으로는 PE의 실행을 중지하여 Beacon이 정상 기능을 재개하도록 한 후, 사용자가 이번에는 (바라건대) 올바른 인수로 다시 시도할 수 있도록 하고 싶었습니다. 실제로 PE를 종료하면 stdout/stderr와 관련된 FILE*가 손상되는 것으로 나타났으며, PE를 완전히 언로드한 후 다시 새로 로드해도 이 문제가 해결되지 않았습니다. 프로세스 전체에 걸쳐 손상된 상태입니다.

'timeout' 옵션을 초과하여 계속 실행되는 PE를 종료하기 위해 CreateThread에서 반환된 핸들에 대해 TerminateThread가 호출됩니다. 이렇게 하면 스레드가 정상적으로 종료될 수 없으므로 일부 기능이 손상될 수 있습니다. 저는 스레드 하이재킹을 구현하여 이 문제를 완화하려고 시도했습니다. 목표는 PE 스레드를 일시 중단하고 실행을 ExitThread() API로 리디렉션하는 것이었습니다. 여기서 기대한 것은 스레드가 (외부에서 강제로 종료되는 대신) 종료 절차를 시작한 것이라면 stdout/stderr가 계속 기능할 수 있지 않을까 하는 점이었지만, 결국 같은 문제가 발생했습니다 (게다가 Mimikatz의 경우 PE 스레드를 일시 중단할 수 없었습니다).

이 문제를 완화할 수 없어서 사용자가 영향을 받은 Beacon에서 PE를 계속 실행하거나 추가 PE를 로드하는 것을 방지하는 방법을 선택했습니다 (그렇게 하면 충돌이 발생할 수 있습니다). 이는 Inline-Execute-PE가 제가 원하는 수준에 미치지 못하는 또 다른 사례이지만, 적어도 운영자는 Beacon을 계속 사용하여 정상적인 기능을 수행할 수 있다는 사실에 만족했습니다.

Inline-Execute-PE 데이터 구조

이 프로젝트의 까다로운 부분 중 하나는 Beacon에 로드된 PE를 Team Server에 연결된 모든 CobaltStrike Client가 사용할 수 있도록 보장하는 것이었습니다. Inline-Execute-PE의 데이터는 Inline-Execute-PE.cna가 생성하는 구조체에 저장되며, 이 구조체는 도구를 사용하려는 각 Client에 로드되어야 합니다. 결과적으로 이러한 데이터 구조는 Team Server가 아닌 각 Client 내에 존재합니다. 이 데이터가 단일 중앙 위치(TS)에 있었다면 각 Client에서 데이터를 검색하는 것이 간단했을 것이며, 이 문제는 전혀 문제가 되지 않았을 것입니다. CobaltStrike 팀이 Inline-Execute-PE와 같은 기능을 CobaltStrike에 공식적으로 통합한다면 분명히 이런 방향으로 갈 것이라고 확신합니다. 하지만 이것은 커뮤니티 애드온이기 때문에 주어진 환경에서 최선을 다해야 합니다.

Beacon에 로드된 PE에 관한 최신 정확한 데이터를 각 CobaltStrike Client가 보유하도록 보장하는 데 있어 몇 가지 시나리오를 고려해야 합니다.

  1. 새 Client가 TS에 연결되어 현재 petable이 필요한 경우
  2. 단일 Client만 TS에 연결되어 있고 CobaltStrike를 다시 시작하여 (Client 메모리에 저장된 petable을 잃어버리는) 경우
  3. Client A가 Inline-Execute-PE 데이터를 변경하여 이를 Client B에 전달해야 하는 경우

이러한 시나리오를 해결하기 위해 다각적인 접근 방식이 채택되었습니다. 단일 CobaltStrike Client만 TS에 연결되어 있는 경우 (따라서 petable 데이터를 보유한 유일한 주체인 경우)를 처리하기 위해 Client가 petable을 변경할 때마다 (peload, peconfig, peunload 등) petable의 내용을 CobaltStrike 디렉터리의 로컬 텍스트 파일에 기록합니다. Client가 종료/재시작되거나 Inline-Execute-PE.cna가 다시 로드되면 먼저 로컬 petable.txt 파일을 읽어 메모리 내 petable을 채웁니다.

여러 Client가 TS에 연결되어 있고 새 Client가 접속할 때 (이벤트 로그 기준), 각 Client는 TS에 연결된 모든 사용자 목록을 가져와 알파벳순으로 정렬합니다. 목록에서 첫 번째에 있는 Client가 "브로드캐스트" Client로 선택되며, 5초를 기다린 후 (새 Client가 초기화되고 로컬 petable.txt를 읽을 수 있도록) petable의 각 항목에 대해 이벤트 로그에 메시지(Action)를 보냅니다. 모든 Client (브로드캐스트 Client 제외)는 이러한 메시지를 읽고 브로드캐스트 정보를 사용하여 자신의 petable을 업데이트합니다. 여기에는 기존 항목 업데이트 및 각 petable에 없는 추가 항목 추가가 포함됩니다.

Inline-Execute-PE와 관련된 일반 작업도 이벤트 로그 메시지 전송에 의존합니다. Client A가 peload를 실행하면 모든 관련 petable 정보를 포함하는 메시지가 브로드캐스트됩니다. 모든 Client는 "on Event_Action" 후크를 사용하여 이러한 브로드캐스트된 이벤트 로그 메시지를 구문 분석하여 각자의 petable을 업데이트합니다. 또한 peload 및 peunload가 BOF 실행을 마칠 때 Inline-Execute-PE 데이터에 변경 사항이 발생합니다. 이러한 변경 사항은 Beacon에 의해 다시 전달되며 (예: peload 실행 후 Beacon은 pMemAddrs 구조체의 메모리 위치로 콜백), 따라서 연결된 모든 Client에서 볼 수 있으며, 각 Client는 "on Beacon_Output" 후크를 사용하여 각자의 petable을 업데이트합니다.

이러한 개별적인 노력이 결합되어 Inline-Execute-PE는 여러 Client 간에 중요한 데이터를 효율적이고 안정적으로 동기화할 수 있습니다.

크레딧

이 프로젝트는 다음 프로젝트와 리소스 없이는 불가능했을 것입니다. 이 프로젝트는 이들을 많이 참조했으며 핵심 부분이 여기서 비롯되었습니다. 저자의 코드와 비전에 큰 감사를 드립니다.

  1. RunPE-In-Memory
  2. Pezor
  3. 많은 StackOverflow
도구 다운로드