
DotNet Framework 대상 파일용 자동 언패커 및 로거
DotNet Framework 대상 파일을 위한 자동 언패커 및 로거입니다! 이 도구는 Black Hat USA 2022에서 공개되었습니다. Black Hat Asia 2023에서는 DotDumperGUI와 DotDumperNative, 그리고 DotDumper 버전 1.1-stable이 함께 공개되었습니다. 이 세 가지 도구는 함께 사용하도록 설계되었으며, DotDumper 1.1-stable은 DotDumperNative의 DLL이 필요하고 DotDumperGUI는 DotDumper 실행 후 생성된 JSON 출력을 열고 필터링하기 위한 그래픽 사용자 인터페이스 역할을 합니다.
특정 파일을 신뢰할 수 있는 방식으로 자동 감지 및 분류하는 것은 종종 멀웨어 분석의 성배로 간주됩니다. 이를 달성하기 위한 시행착오는 많기 때문에, 그러한 시스템의 구축은 높은 평가를 받습니다. DotNet 대상 바이너리의 경우, 새로운 오픈 소스 도구인 DotDumper는 그 과정에서 중요한 여러 단계, 즉 (메모리 내) 활동 로깅, 흥미로운 메모리 세그먼트 덤프, 주어진 샘플에서 특성 추출을 지원하는 것을 목표로 합니다.
간단히 말해, 수동 언패킹은 분석가에게 불균형적으로 많은 시간을 소모하는 지루한 과정입니다. 난독화된 바이너리는 주어진 파일을 언패킹하는 데 필요한 시간을 더욱 증가시킵니다. 이를 확장할 때 조직은 매일 멀웨어를 분석하는 수많은 분석가가 필요하며, 확장 가능한 샌드박스와 결합되어야 할 가능성이 높습니다. 낭비된 소중한 시간은 널리 퍼져 있는 평범한 일반 멀웨어 대신 새로운 위협을 발견하기 위해 흥미로운 캠페인이나 샘플을 파고드는 데 사용될 수 있습니다. 결국 분석가는 건초 더미에서 몇 개의 바늘을 찾는 것입니다.
그렇다면 DotDumper는 어떤 차이를 만들어낼까요? DotDumper를 통해 DotNet 기반 멀웨어 샘플을 실행하면 중요하고 맥락을 제공하는 일반적인 함수 호출의 로그 파일을 세 가지 형식(사람이 읽을 수 있는 일반 텍스트, JSON, XML)으로 제공하며, 유용한 메모리 내 세그먼트의 복사본도 제공합니다. 따라서 분석가는 함수 호출 로그를 훑어볼 수 있습니다. 또한 덤프된 파일을 스캔하여 분류할 수 있으므로, 멀웨어 샘플과 그 안에 포함된 데이터에 대한 추가적인 통찰력을 제공합니다. 이는 트라이징 및 사고 대응 프로세스에 중요한 시간을 단축하고, SOC 분석가와 연구원의 시간을 더 정교한 분석 작업에 사용할 수 있게 해줍니다.
맥락을 제공하는 함수 호출과 그 결과를 로깅하고 덤프하기 위해 DotDumper는 순수 C#으로 작성된 리플렉션과 관리형 후크의 혼합을 사용합니다. 아래에서는 주요 기능을 강조하고 설명하며, 패킹된 AgentTesla 스틸러 샘플(해시는 아래와 같음)에 대한 DotDumper 결과의 일부를 함께 제시합니다.
| 해시 유형 | 해시 값 |
|---|---|
| SHA-256 | b7512e6b8e9517024afdecc9e97121319e7dad2539eb21a79428257401e5558d |
| SHA-1 | c10e48ee1f802f730f41f3d11ae9d7bcc649080c |
| MD-5 | 23541daadb154f1f59119952e7232d6b |
DotDumper는 다양한 인수와 함께 명령줄 인터페이스를 통해 사용할 수 있습니다. 아래 이미지는 도움말 메뉴를 보여줍니다. 모든 인수가 설명되는 것은 아니며, 가장 많이 사용되는 인수에 대해 설명합니다.

주어진 샘플을 실행하기 위한 최소 요구 사항은 "-file" 인수와 파일 이름 또는 파일 경로를 제공하는 것입니다. 전체 경로가 제공되면 해당 경로가 사용됩니다. 파일 이름만 제공되면 현재 작업 디렉터리와 DotDumper 실행 파일 위치의 폴더가 확인됩니다.
디렉터리 이름이 제공되지 않는 한 "-log" 폴더 이름은 확장자를 제외한 샘플의 파일 이름과 동일하게 설정됩니다. 폴더는 DotDumper가 있는 동일한 폴더에 위치하며, 로그와 덤프된 파일이 저장됩니다.
라이브러리 또는 바이너리의 대체 진입점의 경우, "-overrideEntry true"를 사용하여 진입점을 재정의해야 합니다. 또한 "-fqcn My.NameSpace.MyClass"를 사용하여 네임스페이스를 포함한 정규화된 클래스를 제공해야 합니다. 이는 DotDumper가 선택할 클래스를 알려주며, 제공된 함수 이름("-functionName MyFunction" 사용)이 검색되는 곳입니다.
선택한 함수에 인수가 필요한 경우, "-argc"와 필요한 인수 개수를 제공해야 합니다. 인수 유형과 값은 "string|myValue int|9" 형식으로 제공됩니다. 값에 공백이 사용되는 경우 명령줄 인터페이스의 인수를 따옴표로 묶어 단일 인수로 전달되도록 해야 합니다.
"-raceTime" 또는 "-deprecated"와 같이 덜 자주 사용되는 옵션은 기본 설정에서 안전하지만, 향후 DotNet Framework의 변경으로 인해 조정이 필요할 수 있습니다. 현재는 명령줄 인터페이스에 노출되어 필요시 쉽게 변경할 수 있도록 되어 있습니다.
로깅과 덤프는 DotDumper의 두 가지 핵심 기능입니다. 분석 시간을 최소화하기 위해 로깅은 분석가에게 맥락을 제공해야 합니다. 이를 위해 각 로깅된 함수 호출에 대해 분석가에게 다음 정보를 제공합니다.
덤프된 각 파일에 대해 파일 이름은 파일의 SHA-256 해시와 동일합니다.
위 내용을 명확히 하기 위해 로그의 일부를 아래에 제시합니다. 이 발췌문은 앞서 언급한 AgentTesla 샘플의 세부 정보를 보여주며, DotNet의 Assembly.Load 함수를 사용하여 두 번째 스테이지를 로드합니다.
![가로챈 Assembly.Load(byte[] rawAssembly) 함수 호출에 대한 로그](images/assembly_load.png)
첫째, 로컬 시스템 시간과 함께 원래 함수의 반환 유형, 이름 및 인수가 제공됩니다. 둘째, 스택 추적이 제공되어 샘플의 메인 함수가 생성자로 이어지고, 구성 요소를 초기화하며, 두 개의 사용자 정의 함수를 호출함을 보여줍니다. Assembly.Load 함수는 "NavigationLib.TaskEightBestOil.GGGGGGGGGGGGGGGGGGGG(String str)" 내에서 호출되었습니다. 이는 분석가가 이 호출 주변의 코드를 찾아야 할 경우 맥락을 제공합니다.
그런 다음 어셈블리 호출 순서에 대한 정보가 제공됩니다. 더 많은 스테이지가 로드될수록 호출이 어떤 스테이지를 통해 발생했는지 파악하기가 더 복잡해집니다. 일반적으로 한 스테이지가 다음 스테이지를 로드할 것으로 예상되지만, 경우에 따라 이후 스테이지가 비선형 순서로 이전 스테이지를 활용합니다. 또한 시작 어셈블리에 대한 정보가 제공되어 분석가의 데이터를 더욱 풍부하게 합니다.
다음으로 부모 해시가 제공됩니다. 스테이지의 부모는 이전 스테이지이며, 이 예에서는 아직 존재하지 않습니다. 새로 로드된 스테이지는 이 스테이지를 부모로 갖게 됩니다. 이를 통해 분석가가 이벤트를 더 쉽게 연관시킬 수 있습니다.
마지막으로 함수의 반환 유형과 값이 저장되며, 후크된 함수에 전달된 각 인수의 유형, 이름 및 값이 저장됩니다. 변수의 크기가 100바이트를 초과하는 경우 디스크에 저장됩니다. 그런 다음 값 대신 파일을 참조하는 참조가 로그에 삽입됩니다. 일부 배열은 수천 개의 인덱스 크기이므로 로그 출력의 지연을 방지하기 위해 임계값이 설정되었습니다.
Microsoft 문서에 따르면 리플렉션은 "[…] 어셈블리, 모듈 및 유형을 캡슐화하는 개체를 제공합니다"로 가장 잘 요약됩니다. 간단히 말해, 멀웨어 샘플에서 DotNet 클래스와 함수를 동적으로 생성하고 호출할 수 있습니다. DotDumper에는 분석가가 DotNet Framework 기반인 한 실행 파일과 라이브러리를 모두 로드하고 분석할 수 있는 리플렉션 로더가 포함되어 있습니다.
로더를 사용하려면 명령줄 인터페이스에서 진입점을 재정의하고, 주어진 파일 내에서 클래스(네임스페이스 포함)와 함수 이름을 지정해야 합니다. 선택적으로 지정된 함수에 인수를 제공할 수 있으며, 모든 기본 유형 및 해당 배열에 대해 가능합니다. 기본 유형의 예로는 int, string, char 및 int[], string[], char[]와 같은 배열이 있습니다. 모든 인수는 명령줄 인터페이스를 통해 제공되어야 하며, 유형과 값을 모두 지정해야 합니다.
진입점을 재정의하지 않으면 기본 진입점이 사용됩니다. 기본적으로 빈 문자열 배열이 샘플의 메인 함수에 전달되며, 이는 샘플이 인수 없이 실행된 것처럼 처리됩니다.
또한 로더는 다음 스테이지의 특정 클래스에서 특정 함수를 호출하기 위해 리플렉션을 자주 사용합니다. 때로는 인수도 함께 전달되며, 나중에 리소스를 해독하는 데 사용됩니다. 앞서 언급한 AgentTesla 샘플에서 이 정확한 시나리오가 발생합니다. DotDumper의 호출 관련 후크는 이러한 발생을 로깅하며, 아래에서 확인할 수 있습니다.

첫 번째 줄의 함수 이름은 DotNet Framework의 내부 함수가 아니라 두 번째 스테이지의 특정 함수에 대한 호출입니다. 함수 시그니처에는 세 인수의 유형과 이름이 나열됩니다. 해당 값은 함수 인수 정보 섹션에서 찾을 수 있습니다. 이를 통해 분석가는 주어진 인수 값으로 사용자 지정 로더에서 두 번째 스테이지를 로드하거나, 이전에 덤프된 스테이지를 로드하고 인수를 제공하여 DotDumper를 사용하여 이를 수행할 수도 있습니다.
관리형 후크에 대해 알아보기 전에 후크가 어떻게 작동하는지 이해해야 합니다. 여기에는 두 가지 주요 변수가 있습니다: 대상 함수와 후크라고 하는 제어된 함수입니다. 간단히 말해, 대상 함수(예: Assembly.Load)의 메모리가 수정되어 대신 후크로 점프합니다. 이로 인해 프로그램의 실행 흐름이 전환됩니다. 그런 다음 후크는 임의의 작업을 수행하고, 선택적으로 원래 함수를 호출한 후 필요에 따라 반환 값과 함께 실행을 호출자에게 반환합니다. 아래 다이어그램은 이 과정을 보여줍니다.

후크가 무엇인지 아는 것은 관리형 후크가 무엇인지 이해하는 데 필수적입니다. 관리형 코드는 DotNet 런타임이나 Java 가상 머신과 같은 가상 및 관리 환경에서 실행됩니다. 관리형 함수가 있는 메모리 주소를 얻는 것은 C와 같은 비관리형 언어와 다릅니다. 두 함수 모두에 대한 올바른 메모리 주소를 얻은 후에는 안전하지 않은 C#을 사용하여 메모리에 직접 액세스하고, DotNet의 상호 운용성 서비스를 사용하여 네이티브 Windows API 기능을 호출함으로써 후크를 설정할 수 있습니다.
DotDumper v1.1-stable부터 DotDumper는 비관리형(또는 네이티브) 함수도 후킹할 수 있습니다. 함수 리디렉션은 관리형 및 비관리형 함수 간의 모든 조합이 가능하지만, 한 가지 중요한 제약이 있습니다. 관리형 후크를 사용하는 비관리형 함수는 스택의 값을 제대로 읽을 수 없습니다. 따라서 네이티브 구성 요소(DotDumperNative라고 함)가 필요합니다. 이 구성 요소는 명명된 파이프를 통해 DotDumper 자체와 통신하여 중앙 집중식 로깅 시스템을 사용하면서 스택에서 찾은 함수 인수에 액세스하여 비관리형 호출을 가로챕니다.
DotDumper는 외부 종속성 없이 순수 C#으로 작성되었으므로 Visual Studio를 사용하여 프레임워크를 쉽게 확장할 수 있습니다. 코드는 이 블로그, GitHub, 클래스, 함수 및 소스 코드의 인라인에 문서화되어 있습니다. 이는 명확한 명명 체계와 결합되어 누구나 도구를 원하는 대로 수정할 수 있도록 하며, 도구를 이해하는 데 필요한 시간과 노력을 최소화합니다. 대신 개발자와 분석가 모두 도구 개선에 노력을 집중할 수 있습니다.
샌드박스 타임아웃 지원 기능은 변경되거나 수정되지 않았지만, 이전에 문서화되지 않았습니다. DotDumper는 주어진 파일을 실행하므로, 샘플이 종료될 때까지 실행이 계속됩니다. 멀웨어는 종종 "대기" 상태에 들어가 특정 조건이 충족되어야 다시 활성화되거나, 다른 프로세스에서 실행을 계속합니다(프로세스 인젝션을 통해). 예를 들어, 할로윈된 프로세스에서 새로 생성된 스레드는 완료될 때만 반환됩니다.
지연을 방지하기 위해 DotDumper에는 정체 처리기가 있습니다. 후크의 결과가 로깅될 때마다 카운터가 증가합니다. 이 카운터가 20초 간격으로 3번 연속으로 변경되지 않으면 DotDumper는 정체 상태가 발생했다고 가정합니다. 이 경우 시스템 시간을 2200년 12월 30일 12:00으로 설정하여 샌드박스가 타임아웃되도록 강제한 후, 로거를 통해 분석가에게 알리고 자체 종료됩니다. 샌드박스 타임아웃은 현재 날짜부터 새로 설정된 날짜까지의 경과 시간이 일반적인 5~10분을 초과할 때 발생합니다. 샌드박스 실행 시간이 시간 또는 일 단위로 설정되어 있더라도 초과됩니다.
분석을 조기에 중단하는 이유는 시간과 리소스를 절약하기 위해서입니다. 10분 타임아웃을 가진 제출이 실제로 실행되는 데 2분밖에 걸리지 않을 수 있기 때문에, 정체 처리기를 사용하면 동일한 시간 범위 내에 여러 샘플을 실행할 수 있습니다. 정체 처리기의 로직 개요는 아래와 같습니다.

DotDumper의 목표와 기능이 명확해졌으므로, ILSpy, dnSpyEx, de4dot 또는 pe-sieve와 같은 공개적으로 알려진 도구와 중복되는 것처럼 보일 수 있습니다. 한 도구가 다른 도구보다 더 낫다고 주장할 의도는 없으며, 오히려 도구가 어떻게 다른지 설명하고자 합니다.
DotDumper의 목표는 DotNet 대상 샘플에서 중요하고 맥락을 제공하는 일반적인 함수 호출을 로깅하고 덤프하는 것입니다. ILSpy는 DotNet 디스어셈블러 및 디컴파일러이지만 파일 실행을 허용하지 않습니다. dnSpyEx(및 그 전신인 dnSpy)는 ILSpy를 디스어셈블러 및 디컴파일러 구성 요소로 사용하면서 디버거를 추가합니다. 이를 통해 메모리를 수동으로 검사하고 조작할 수 있습니다. de4dot는 DotNet 바이너리를 난독 해제하여 코드의 가독성을 향상시키는 데만 사용됩니다. 이 비교에서 마지막 도구인 pe-sieve는 실행 중인 프로세스에서 멀웨어를 감지하고 덤프하기 위한 것이며, 사용된 프로그래밍 언어는 고려하지 않습니다. 아래 표는 위에서 언급한 도구의 그래픽 개요를 제공합니다.

DotDumper는 지속적으로 검토 및 개발 중이며, 모든 작업은 버그 수정과 새로운 기능 추가라는 두 가지 주요 관심 영역에 초점을 맞추고 있습니다. 개발 중에 코드가 테스트되었지만, 변경될 수 있는 DotNet Framework의 함수에 후크를 삽입하기 때문에 코드에 버그가 있을 가능성이 매우 높습니다. 버그를 발견한 사람은 GitHub 저장소에 이슈를 열어 주시기 바랍니다. 그러면 검토할 것입니다. 새로운 기능 제안도 GitHub 저장소를 통해 가능합니다. GitHub 계정이 있거나 공개적으로 상호작용하고 싶지 않은 경우, 제 트위터로 개인 메시지를 보내 주셔도 좋습니다.
말할 필요도 없이, 분석 중에 DotDumper를 사용했거나 창의적인 방식으로 사용했다면 공개적으로든 개인적으로든 연락해 주세요! 직접 만든 도구의 사용에 대해 듣는 것보다 더 좋은 것은 없습니다!
DotDumper에는 더 많은 것이 준비되어 있으며, 업데이트가 가능해지면 커뮤니티에 알려드리겠습니다!