
나쁜 사람들이 만든 나쁜 것들
# HiveV5 키스트림 복호화 PoC ## 소개 이 문서에서 분석 및 참조된 Hive 샘플은 [@rivitna](https://twitter.com/rivitna2)님이 작성한 [이 목록](https://github.com/rivitna/Malware/blob/main/Hive/Hive_samples.txt)에서 무작위로 선택되었으며, 이 자리에서 가장 따뜻한 감사를 드립니다. 아티팩트는 VirusTotal 플랫폼에서 확인할 수 있습니다. 이 문서에서는 a0h2uih3d2.exe 파일을 참조로 사용했습니다. MD5: 15CF5E0DA094ACDD751A513402A8C941 SHA-1: 72E15AC4473903C814E65E3C06F54EB0399580AA SHA-256: 335D2E4A743D059955760ECF2EC25EE86D36AA60B096C9180E860C64EF78EE55 이 랜섬웨어의 복잡성을 파악하려면 Microsoft Threat Intelligence Center(MSTIC)가 발행한 [이 분석](https://www.microsoft.com/security/blog/2022/07/05/hive-ransomware-gets-upgrades-in-rust/)을 살펴보시기 바랍니다. 코드를 사용하기 전에 문서 전체를 주의 깊게 읽으시기 바랍니다! ## Hive v5 간략 개요 최근 몇 달 동안 저는 Hive v5 암호화 알고리즘의 연구와 리버스 엔지니어링에 대부분의 힘을 쏟았습니다. 과거에 Hive의 이전 버전을 분석하고 그 암호화 메커니즘에 관한 코드와 PoC를 공개한 훌륭한 악성코드 분석가이자 리버스 엔지니어인 [@rivitna](https://twitter.com/rivitna2)님과 협업할 수 있는 기쁨을 누렸습니다. 그분은 RUST로 작성되어 분석이 더욱 어려워진 Hive v5의 암호화 작업에 관여하는 구성 요소를 식별하는 데 (적지 않게) 기여했습니다. 저는 2021년 6월에 소스가 공개된 또 다른 매우 중요한 랜섬웨어인 Babuk과 공통점을 발견했습니다: - 키 교환 알고리즘; - 1256개의 암호화 스레드를 시작하기 전에 종료해야 할 프로세스 목록. 피해자 시스템에서 실행되는 Hive 랜섬웨어 v5는 아래 증거의 알고리즘을 사용하여 *QueryPerformanceCounter* 및 *QueryPerformanceFrequency* Windows API를 기반으로 두 개의 평문 키(cleartext key)를 생성합니다. *QueryPerformanceCounter* API에 대한 자세한 내용은 [이](https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter) Microsoft 페이지를, *QueryPerformanceFrequency*에 대한 내용은 [여기](https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancefrequency)를 참조하시기 바랍니다. *QueryPerformanceCounter*는 매우 정확한 시간 카운터입니다. 호출되면 PC가 마지막으로 켜진 이후 경과된 시간을 반환합니다. *QueryPerformanceFrequency*는 성능 카운터의 값(주파수)을 반환합니다. 고정 값은 0x989680입니다. 즉, *QueryPerformanceCounter* 값은 초당 0x989680회, 즉 10,000,000회 업데이트된다는 뜻입니다. 두 개의 평문 키는 크기가 0xCFFF00바이트이며 한 번에 한 바이트씩 생성됩니다. 아래는 Hive가 피해자 PC의 파일을 암호화하는 데 사용하는 소위 **평문 키(cleartext key)** 의 가장 큰 부분인 0xA00000바이트 배열을 생성하는 스니펫입니다.  키의 각 바이트는 AL 레지스터의 값을 취하여 얻습니다. EAX 레지스터에는 *createByte*라는 라벨로 이름이 변경된 0044ADE0 함수의 결과가 포함되어 있으며, 이 함수는 *call_to_QueryPerformanceCounter*라는 라벨로 이름이 변경된 함수 0044A850의 첫 번째 호출 시 계산된 초기 시드 값과 현재 시점의 차이를 구현합니다. 다음은 평문 키를 생성하기 위해 C++로 작성된 코드입니다:  알고리즘은 매우 간단하지만, 0044ADE0 함수 내부에는 평문 키 생성 중 코드 실행 시간을 지연시키기 위해 중복 연산과 다양한 조건부 점프를 수행하는 명령어가 삽입되어 있습니다:  **HiveRansomwareV5_custom_keygen_PoC** 폴더에는 분석된 Hive v5 샘플에서 리버스한 원시 코드가 있습니다. 컴파일된 버전의 코드 한 줄도 놓치지 않아야 했기 때문에 악성코드에서 발견되는 것과 같은 최적화된 코드가 아닙니다. **HiveRansomwareV5_custom_keygen_PoC-optimized** 폴더에는 위에서 언급한 원시 코드에서 파생된 최적화된 코드가 있습니다. 이 버전에서는 구현된 기능을 이해할 수 있도록 코드가 원시 코드보다 훨씬 읽기 쉽습니다. **두 버전 모두 실행 전에 사용자 이름으로 커스터마이즈해야 생성된 평문 키를 바탕 화면에 저장할 수 있습니다.** 두 평문 키 모두 동일한 알고리즘을 사용하여 생성됩니다. 평문 키는 0xA00000 ~~안전하게 무작위 생성된~~ 바이트로 구성됩니다. 그런 다음 처음 0x2FFF00바이트를 끝에 복사하여 최종 0xCFFF00바이트 평문 키를 생성합니다.  그런 다음 Hive는 생성된 두 개의 키를 사용하여 파일을 암호화하지만, 먼저 Hive 랜섬웨어 v5는 생성된 키를 사용자 지정 구조체(이하 **키스트림(keystreams)** 이라고 함)로 암호화하여 .key 확장자를 사용해 암호화하는 각 드라이브의 루트에 배치합니다. 예를 들어 시스템에 C와 D 드라이브가 모두 설치되어 있다면 암호화된 키스트림이 각 드라이브의 루트에 존재하게 됩니다.  Hive 랜섬웨어 v5는 생성된 평문 키를 사용하여 XOR 명령어로 파일을 암호화하므로, 최신 x86/x64 CPU에서 매우 빠른 대칭 암호화에 해당합니다. ## Hive v5가 자신을 보호하는 방법, 평문 키가 키스트림이 되는 방법 Hive 랜섬웨어 v5는 생성된 평문 키를 두 번 암호화하여 보호해야 하며, 이후 이를 **라운드(rounds)** 라고 부르겠습니다. 최종 키스트림을 얻으려면 두 번의 암호화 라운드가 필요합니다. 이를 위해 각 라운드에서 다음 단계가 수행됩니다: 1. 키의 각 바이트를 생성하는 것과 동일한 알고리즘을 사용하여 32바이트 개인 키 생성; 2. Diffie-Hellman 키 교환을 위해 Curve25519 타원 곡선 알고리즘을 사용하여, Hive는 방금 생성된 개인 키에서 공개 키를 파생; 3. Curve25519를 다시 사용하여, Hive는 방금 생성된 개인 키와 Hive 제휴 파트너(affiliate)의 공개 키(모든 Hive v5 아티팩트마다 변경됨)로부터 공유 키를 생성; 4. 개인 키 및 평문 키와 동일한 알고리즘을 사용하여 일종의 IV와 같은 24바이트 논스(nonce) 생성; 5. HChaCha20 알고리즘을 사용하여 생성된 평문 키를 암호화할 키를 파생; 6. 5단계에서 생성된 키와 4단계에서 생성된 논스를 사용하여, Hive는 XChaCha20 알고리즘으로 평문 키를 암호화합니다. 이 작업은 암호화 프로세스의 무결성을 보장하기 위해 16바이트 MAC(Message Authentication Code)도 생성합니다. 3단계는 암호화 중 Hive가 생성한 개인 키 쌍과 Hive 제휴 파트너가 우리를 위해 랜섬웨어를 컴파일할 때 생성한 개인 키 쌍, 두 쌍의 개인 키로 열 수 있는 키스트림의 생성을 보장합니다.  ## 브루트포스의 배경 아이디어 이 설명의 끝에서 한 가지 특정 지점이 분명해집니다: 두 라운드의 암호화에 사용되는 평문 키, 개인 키 및 논스는 모두 위의 동일한 함수(0044ADE0, 일명 *createByte*)에 의해 생성됩니다. **함수 0044ADE0은 CPU가 for 루프 내에서 호출되는 코드를 실행하는 데 걸리는 시간에 의해 조건이 결정됩니다.** 두 라운드의 암호화 이후 키스트림 구조를 강조한 위 그림을 살펴보면, 우리가 자유롭게 접근할 수 있는 것은 **논스**뿐이라는 것이 분명합니다 (그렇지 않으면 Hive 제휴 파트너는 파일을 해독하는 방법을 알 수 없을 것입니다). 그러니 24바이트 길이의 논스에 초점을 맞춰 보겠습니다: NONCE: 40 A4 08 6C D0 D0 34 98 FC 60 C4 28 8C F0 F0 54 B8 1C 80 E4 48 AC AC 10 **논스의 한 바이트와 다음 바이트 사이의 차이(절대값)는 한 반복과 다음 반복 사이에 경과된 시간을 나타냅니다. 이 정의로 핑거프린트(fingerprint)라는 개념을 소개합니다.** NONCE FINGERPRINT: 64 9c 64 64 00 9c 64 64 9c 64 9c 64 64 00 9c 64 9c 64 64 9c 64 00 9c 얻은 값을 분석하면 코드의 실행 시간이 거의 동일하며, 특히 사용 중인 프로세서의 기술로 인한 약간의 변동만 있음을 알 수 있습니다. (제 테스트에서는 10세대 i7 프로세서와 5세대 i5를 사용했으며, 다른 시스템에서는 이 핑거프린트가 다를 수 있습니다.) 논스가 평문 키, 그리고 무엇보다 개인 키를 생성하는 동일한 함수에 의해 생성된다는 점을 고려하면 이 발견은 매우 중요합니다. 언급된 이러한 값들도 이 원칙을 따르기 때문에, 즉 논스의 개별 바이트 간 차이는 예측 가능하므로 개인 키와 평문 키 값도 예측 가능할 것입니다. 그러나 분석 결과 Hive가 계산한 원래 평문 키와 동일한 바이트를 얻기 위해 0xA00000 문자 배열을 생성하는 것은 매우 어렵다는 것이 입증되었습니다: CPU 및 메모리 부하의 변동은 코드 실행 속도에 영향을 미치며, Hive PE가 계산한 원래 평문 키는 우리가 계산한 키와 (단 몇 바이트라도) 다른 경우가 많습니다. 우리는 이 논스 핑거프린트를 0xA00000바이트 길이의 가능한 사전 생성에서 얻은 핑거프린트와 비교하는 데 사용합니다(이 숫자는 경험적으로 고정되었으며, 일련의 테스트 후 통계적으로 이 바이트 수 안에 두 라운드의 암호화에 필요한 각각 32바이트인 두 개의 개인 키가 있다는 것이 확인되었습니다). 논스 핑거프린트가 사전 핑거프린트에 포함되어 있다면 두 개인 키를 모두 브루트포스하기 위한 올바른 사전을 찾은 것입니다. 여기서 끝나지 않습니다. 논스, 평문 키 및 개인 키의 생성에 대해 수행된 동적 분석에서 첫 번째 바이트는 거의 균일한 값을 가진 다른 모든 바이트와 비교하여 두 번째 바이트와 평균 거리가 다르다는 것이 검증되었기 때문입니다. 자세히 살펴보겠습니다:  보시다시피 첫 번째 이후의 핑거프린트 값은 최소한의 변동을 겪습니다. 즉, 개인 키의 첫 번째 바이트와 두 번째 바이트 사이의 절대값 거리는 대부분의 경우 나머지 핑거프린트에 존재하는 값 범위를 벗어납니다. 아마도 이는 CPU에 존재하는 일부 최적화 알고리즘 때문이며, for 루프의 첫 번째 반복 이후 코드 실행 속도를 높이는 것과 관련이 있습니다. ## 가능한 해결책 제안된 코드는 각 키스트림 암호화 라운드의 논스를 읽고 핑거프린트를 결정하며 가능한 개인 키를 포함하는 가능한 키 사전 목록을 생성합니다. 항상 키의 나머지 부분과 다른 핑거프린트의 첫 번째 바이트와 관련된 문제를 해결하기 위해 다음과 같은 방법을 생각했습니다: 1. 생성된 사전의 첫 0x110바이트의 고유 값을 취하여 가능한 선행 바이트 사전을 생성합니다; 2. 생성된 사전의 두 번째 바이트부터 시작하는 모든 가능한 조합을 취하여 31바이트 목록을 생성합니다; 3. 첫 번째 바이트와 나머지 생성된 31바이트의 조합을 만들어 가능한 32바이트 개인 키 조합을 생성하고, 여기서 공개 키를 파생하여 키스트림에 존재하는 우리가 보유한 공개 키와 비교합니다. 두 공개 키가 일치하면 두 번째(마지막) 암호화 라운드가 암호화된 개인 키를 찾은 것입니다. 지금까지 설명한 작업을 다시 반복하면 첫 번째 라운드의 암호화된 키스트림을 해독할 개인 키를 얻을 수 있으며, 마지막으로 원래 평문 키를 추출할 수 있습니다. ## 사용법 **HiveRansomwareV5-keystream_decryptor** 폴더에는 VS 2017 sln과 [monocypher](https://monocypher.org/) 맞춤형 라이브러리가 있습니다. 프로그램에서 수행할 작업을 선택할 수 있습니다.  옵션 "1"은 PC 프로세서에 맞춤화된 바이트 사전을 만들 수 있으므로 가장 먼저 선택해야 합니다. 따라서 암호화된 머신에서 수행해야 합니다. 해당 키스트림에 존재하는 값과 동일한 값을 얻을 가능성이 훨씬 높기 때문입니다.  또는 첫 번째 옵션이 작동하지 않으면 (이미 감염된 동일한 PC에서) 디버거에서 악성코드를 평문 키 생성이 끝날 때까지(for 루프 바로 밖) 실행하여 평문 키가 포함된 메모리 내용을 저장하여 자신만의 사전을 생성하십시오. 이 경우 옵션 "3"을 사용하여 사전을 검증할 수 있습니다:  키스트림에 대한 올바른 사전을 확보하면 옵션 "2"는 개인 키 바이트 값에 어떤 식으로든 영향을 주지 않으면서 바이트 조합을 브루트포스하는 데 필요한 시간을 줄일 수 있는 더 강력한 컴퓨터에서도 수행할 수 있습니다.    옵션 2의 기능을 제대로 수행하려면 공개 키를 추출해야 합니다. 모든 Hive 샘플이 동일하게 생성되는 것은 아니므로 범용 공개 키 추출기를 만드는 것은 그리 쉽지 않습니다. 그러나 작동하는 것으로 알려진 한 가지 방법은 논스 생성 후 디스어셈블된 이 부분에 중단점(breakpoint)을 설정하는 것입니다. 아래 증거에서 공개 키는 curve25519를 사용하여 공유 키를 파생하기 위해 함수 44E3D8에 전달됩니다. 악성코드 실행 전체에서 공개 키가 노출되는 유일한 곳입니다.  Visual Studio 옵션을 다룰 때 주의하시기 바랍니다. 디버그에서 릴리스로 또는 그 반대로 전환하면 사전 생성 바이트와 같은 프로그램 출력이 변경될 수 있습니다. 브루트포스 절차의 속도를 높이려면 코드에서 *dictionary_dimension* 값을 편집할 수 있지만, 사전 크기를 줄이면 개인 키를 찾을 확률도 낮아질 수 있다는 점에 유의하시기 바랍니다. 또한 Hive를 실행하고 메모리에서 덤프하여 생성된 평문 바이트 배열을 사용하기로 결정한 경우 *dictionary_dimension* 변수에 덤프 크기를 설정하는 것을 잊지 마십시오. 복호화 도구를 테스트하기 위해 **dummy_data_PoC** 폴더에 사용할 파일 몇 개를 제공했습니다: - *BumAU1Ky.key* (첫 번째 키스트림) - *UMvObens.key* (두 번째 키스트림) - *a0h2uih3d2_01058000.bin* (사전으로 사용할 Hive 실행 중 덤프된 평문 키) - *dictionary.bin* (샘플 사전) - *public_key.txt* (Hive 제휴 파트너의 공개 키 포함) 행운을 빕니다! ## 참고 자료 <https://github.com/rivitna/Malware/blob/main/Hive/Hive_samples.txt> <https://www.virustotal.com/gui/file/335d2e4a743d059955760ecf2ec25ee86d36aa60b096c9180e860c64ef78ee55> <https://www.microsoft.com/security/blog/2022/07/05/hive-ransomware-gets-upgrades-in-rust/> <https://docs.microsoft.com/en-us/windows/win32/sysinfo/acquiring-high-resolution-time-stamps> <https://monocypher.org/manual/x25519> <https://monocypher.org/manual/advanced/poly1305> <https://monocypher.org/manual/advanced/chacha20> <https://monocypher.org/manual/aead>