ArgonWiper 프로파일링: 복호화 취약점 발견

ArgonWiper Profiling: Decryption Vulnerability Found

1. 개요

전 세계적으로 랜섬웨어 관련 사고가 끊이질 않고 있는 가운데 공격자들의 전략 또한 빠르게 변화하고 있다. 공격자들은 이메일을 이용한 피싱과 소프트웨어 취약점, 유출된 계정 정보, 공급망 공격 등 기술적 접근을 통한 침투와 공격이 다수 발생하고 있으며, 여기에 더해 시스템에 설치되어 있는 정상 도구나 원격 관리도구를 악용해 탐지와 행위를 은폐하고 있다.

또한, Malware-as-a-Service (MaaS), Ransomware-as-a-Service (RaaS)와 같은 전략적 선택이 늘어남에 따라 공격에 사용되는 모든 도구를 직접 제작하지 않고 오픈소스, 유출된 빌더 등을 통해 공격을 시도하는 모습이 많이 확인되고 있다.

그 중 최근 확인된 특정 샘플에서 Wiper와 랜섬웨어가 결합된 형태의 특이한 경우가 확인됐다. 전통적인 Wiper는 부트 섹터나 UEFI 펌웨어를 손상시키는 기법, 정상 삭제 도구나 합법적 서명을 악용하는 방식, 그리고 빠른 속도로 다수 시스템을 동시에 파괴하는 와이퍼 등의 형태로 많이 발견되어 왔으며 단순히 데이터 파괴가 아닌, 정치·경제·사회적 목적 달성을 위한 전략적 수단으로 와이퍼가 많이 사용됐다.

이번 보고서에서 다룰 Wiper 악성코드는 시스템이 파괴되지 않는 선에서 모든 파일을 삭제한다. 데이터베이스, 이미지, 로그, 백업 등 확장자 기반으로 일치되는 파일은 무조건 삭제하며, 이외 파일들은 삭제 전 파일을 백업하는 과정을 거친다. 생성되는 백업 파일은 100개 단위로 파일을 tar + Zstandard(Zstd)로 압축 후 AES GCM모드로 암호화되며, 키 생성에 사용되는 값과 암호화에 사용되는 IV 등과 함께 백업 파일에 반복해서 저장된다.

파일 암호화 키는 백업 파일에 존재하는 0x20 바이트 값과 랜섬웨어 내부에 하드코딩된 0x38(또는 0x20) 바이트의 secret 값으로 argon2 해시를 통해 도출되며, 해시 생성시 필요한 parallelism 값은 하드코딩된 secret의 길이에 따라 2 또는 CPU 코어 수가 적용된다. 암호화에 사용된 키 값을 해당 과정으로 유추할 수 있고, 마찬가지로 백업 파일에 포함된 IV 값은 간단한 연산 후 암호화에 사용되기 때문에 복호화가 가능하다.

또한, PowerShell(Microsoft Shell), 정상 소프트웨어인 Process Explorer(프로세스 관리 도구), 오픈소스로 공개된 Sliver(Red team 침투 테스트 도구), Tokenvator(토큰 기반 권한 변경 도구), SharpInjector(Shellcode 인젝션 도구), Donut(메모리 기반 Shellcode 실행 도구) 등을 사용해 탐지를 최소화하려는 모습이 확인됐다.

이번 보고서에는 확인된 Wiper와 연관된 인프라와 특징, 그리고 특정 그룹과 연결되는 약한 연결고리 등 프로파일링 과정에서 확인된 데이터를 제공해 위협을 감소시키고, 공격에 사용된 악성코드들의 상세 분석 내용과 함께 암호화된 백업파일을 복호화할 수 있는 스크립트를 제공해 잠재적인 위협으로부터 피해를 경감시킬 수 있도록 하고자 한다.

2. 프로파일링

1)유포 악성코드

분석 결과, 최종 단계에서는 공격자가 자체적으로 개발한 Wiper 악성코드를 활용했으나, 전반적인 공격 과정에서는 공개된 오픈소스 도구가 다수 사용된 것이 확인되었다. PowerShell 기반 다운로더, .NET 다운로더, Tokenvator(권한 상승), SharpInjector(프로세스 인젝션), Donut Shellcode(메모리 내 실행) 등은 모두 오픈소스 도구를 직접 사용하거나 일부 변형한 형태였다.

또한, .NET 기반 악성코드에는 Confuser, Golang 기반 Wiper에는 garble과 같은 공개된 난독화 도구가 적용되어 있었다. 이는 상용 난독화 솔루션을 사용하지 않고도 분석 지연 효과와 개발 비용 절감을 동시에 달성하기 위한 선택으로 보인다. 특히 Wiper에 garble을 적용한 부분은 탐지 우회보다는 분석 지연을 통한 방어 측 대응 속도 저하를 목적으로 사용한 것으로 판단된다.

이러한 점을 종합해 보면, 공격자는 공격 도구와 난독화 도구를 결합하여 저비용·고효율 전략을 취했다고 볼 수 있다. 이는 독자적인 고급 기술력보다는 공개 생태계를 적극적으로 재활용하는 방식을 통해 충분한 효과를 거두려는 공격자의 의도를 보여준다.

- 오픈소스 악성코드

악성코드 설명
Sliver 기능 Red team 침투 테스트 도구
SRC https://github.com/BishopFox/sliver
Tokenvator 기능 윈도우 토큰을 사용한 권한 상승 도구
SRC https://github.com/0xbadjuju/Tokenvator
SharpInjector 기능 프로세스에 Shellcode를 인젝션하는 도구
SRC https://github.com/Tw1sm/SharpInjector
Donut 기능 메모리에서 Shellcode를 실행하는 도구
SRC https://github.com/TheWover/donut
POSTDump 기능 LSASS 프로세스 미니덤프 도구
SRC https://github.com/YOLOP0wn/POSTDump

Table 1. 오픈소스 악성코드

- LotL(Living off the Land) 악용

구분 악용
Powershell 다운로더 및 로더로 악용됨
Process Explorer 프로세스를 종료하기 위해 악용됨
PSESESVC 원격 관리 도구

Table 2. LotL 악용

- 자체 제작 악성코드

구 분 설 명
Loader 파워쉘 스크립트를 통해 다운로드 후 악성코드 로드
Downloader Sliver를 다운로드 후 추가 악성코드 실행
Wiper 파일 삭제 및 암호화

Table 3. 자체 제작 악성코드

2)특징

파워쉘 스크립트로 작성된 코드를 통해 악성코드를 다운로드 및 로드 후 Sliver를 다운로드 받아 실행하고, Tokenvator를 통해 권한 상승을 시도한다. 또한, SharpInjector로 Donut Shellcode를 주입해 실행하며 최종적으로 Golang으로 작성된 와이퍼 악성코드를 통해 백업 파일 생성 및 파일 삭제 과정을 거친다. 이후 랜섬노트를 통해 연락처와 방법을 안내한다.

샘플을 분석하는 과정 중에 확인된 특이사항은 다음과 같다.

A. 다양한 오픈소스 도구를 사용하며, 대부분 난독화과정을 거친 악성코드를 사용한다.

B. .NET과 Go 언어를 주 언어로 사용했으며, 오픈소스로 공개된 난독화 도구를 사용하기위한 의도로 보인다.

C. 단순한 Wiper를 넘어 랜섬웨어와 결합된 형태를 보이고 있으며, 파일을 암호화 후 시스템에 남겨두는 랜섬웨어와 달리 압축과 암호화 후 파일을 삭제한다.

Figure 1. AES-GCM Tag

D. 삭제 전 생성하는 압축 형태는 tar + Zstandard(Zstd) 형태이며, AES-GCM 모드를 사용해 암호화한다. 이 때 일반적인 GCM 방법은 GCM 태그를 인증해 무결성을 체크하는 과정을 거친다. 하지만, 해당 샘플은 태그 인증에는 실패하지만 태그 인증을 건너뛰면 복호화가 가능한 형태를 띄고있다.

Figure 2. argon2 hash’s parallelism

E. 파일 암호화에 사용되는 키는 랜덤한 0x20 바이트를 생성 후 Wiper에 고정된 0x38(혹은 0x20) 바이트의 데이터와 함께 argon2 해시를 생성해 사용한다. 이 때 고정된 키 값이 0x38인 경우 argon2 해시 생성 시 사용되는 parallelism 값은 2이며, 만약 고정된 키 값이 0x20일 경우에는 parallelism 값은 시스템의 CPU 코어 수가 입력된다.

F. 복호화를 위해서는 *bak 백업 파일에 존재하는 랜덤값과 IV 값이 필요하며, argon2 해시를 생성할 때 필요한 하드코딩된 secret 값과 샘플에 따라 parallelism 값이 필요하다. 또한, 부록으로 제공하는 스크립트는 백업 파일과 랜섬노트가 필요하며 제공된 스크립트를 통해 하드코딩된 secret, parallelism을 결정한다. 현재는 부록에서 제공하는 IoC에 기재된 샘플에 대해서만 추출이 가능하다.

G. 확인된 백업 파일의 이름은 다음과 같은 형태를 띠고 있다.

파일명
[0-9a-f]{8}evp.bak [0-9a-f]{8}dngi.bak
[0-9a-f]{8}sinar.bak [0-9a-f]{8}ya2hsc.bak
[0-9a-f]{8}.{Computer name} {Drive char}_[0-9a-f]{12}_jamwu.bak
{Drive char}_[0-9a-f]{12}_taib.bak {Drive char}_[0-9a-f]{12}_samc.bak
{Drive char}_[0-9a-f]{12}_ucsi.bak {Drive char}_[0-9a-f]{12}_wpl.bak

Table 4. 백업 파일 이름 리스트

H. 확인된 랜섬노트의 파일명은 backup_log.txt, {Computer name}_log.txt 두가지 형태로 나뉘며, 한글과 영어를 사용해 연락 방법을 안내하고 있다. 샘플에 따라 랜섬노트에 기재된 문구 및 연락 방법이 일부 상이하다.

Figure 3. 랜섬노트 #1 (Ver. Korean)

Figure 4. 랜섬노트 #2 (Ver. English)

Figure 5. 랜섬노트 #3 (Ver. English – Sensitive customer)

Figure 6. 랜섬노트 #4 (Ver. English – simplex.chat)

Figure 7. 랜섬노트 #5 (Ver. English – victim name)

Figure 8. 랜섬노트 #6 (Ver.English – Akira)

I. 섬노트에서 확인된 연락처인 이메일과 onion 주소는 다음과 같다.

주소
h.majestic947@passinbox.com dongileng.another679@passinbox.com
alans.help@axelglue.store taib.help@axelglue.store
samc.help@axelglue.store ucsi.help@axelglue.store
rhs.help@axelglue.store wpl.help@axelglue.store
• on2jtree2bck4rux3xq5zbhpx4okfdpxpmergyrsgoronk6uuuo4vaqd.onion

Table 5. 연락처

3)TTPs
Tactics ID Technique Procedure
Resource
Development
T1584.001 Compromise Infrastructure -
Domain
탈취한 취약 도메인에 각종 도구와 악성코드를 업로드해 공격에 활용한다.
T1587.001 Develop Capabilities -
Malware
공격자는 데이터 파괴용 Wiper 와 추가 페이로드 다운로드용 로더를 직접 개발해 사용한다.
T1588.001 Obtain Capabilities -
Malware
공격자는 데이터 파괴용 Wiper 와 추가 페이로드 다운로드용 Loader를 직접 개발하는 한편, Sliver, Donut, SharpInjector, Tokenvator 등 다수의 오픈소스 도구는 공격에 필요한 핵심 기능만 남도록 코드를 수정해 사용했다.
T1588.002 Obtain Capabilities - Tool
T1608.001 Stage Capabilities - Malware
T1608.002 Stage Capabilities - Tool
Execution T1059.001 Command and Scripting
Interpreter - PowerShell
PowerShell Script 를 활용해 추가적인 악성코드를 다운로드 및 로드한다.
T1059.003 Command and Scripting
Interpreter - Windows
Command Shell
net 명령어를 사용해서 관리자 계정 비활성화와 MS-SQL Server 서비스를 중지한다.
Privilege
Escalation
T1134.001 Access Token
Manipulation - Token
Impersonation/Theft
오픈소스 윈도우 권한 상승 도구인 Tokenvator를 이용해 시스템 권한의 프로세스를 만들어 명령을 실행한다.
T1134.002 Access Token
Manipulation - Create
Process with Token
Defense
Evasion
T1140 Deobfuscate/Decode Files
or Information
Confuser로 난독화된 다운로더와 AES로 암호화된 Donut Shellcode를 사용한다.
T1622 Debugger Evasion Wiper에서 다수의 안티 디버깅 기법을 사용해 분석을 방해한다.
Credential
Access
T1003.001 OS Credential Dumping -
LSASS Memory
POSTDump를 활용해 LSASS 프로세스 메모리에 저장된 자격 증명에 접근한다.
Discovery T1057 Process Discovery 프로세스를 종료한다.
T1083 File and Directory
Discovery
시스템 내 파일 및 디렉터리를 열거하여 암호화/삭제 대상 및 공격 경로를 식별한다.
Command
and Control
T1102.003 Web Service - Bidirectional
Communication
Sliver를 이용해 비콘 설치 후 C2 서버와 통신을 시도한다. 총 4개의 서버에 대해서 mTLS 연결을 시도하며, 성공 시 명령 및 제어가 가능하다.
Impact T1485 Data Destruction Wiper는 시스템 운영에 필요한 최소한의 데이터를 제외하고 데이터를 모두 삭제하거나 압축 후 암호화한다.
T1486 Data Encrypted for Impact
T1489 Service Stop Wiper는 MS-SQL Server 서비스를 중단시킨다.

Table 6. TTP

4)Attribution

Figure 9. 공격 벡터

- 랜섬웨어 그룹

연관된 Wiper에서 발견된 랜섬노트 중 첫번째 문장에서 “All of your files are currently encrypted and downloaded by Akira (google us).” Akira에 의해 암호화 및 다운로드 됐다고 설명하고 있다.

Figure 10. ArgonWiper 랜섬노트 중 일부

또한, “google us” 문구는 과거 과시를 위해 Conti, BlackBasta 그룹에서 “just Google it.”, “You can Google us later”와 같이 사용된 점, Conti 그룹에서 사용했던 랜섬노트와 흡사한 모습을 확인할 수 있다. Conti 그룹이 해체된 이후 BlackBasta, Akira 그룹과의 지속성을 미루어볼 때 일부 연관된 접점이 있는 것으로 보여진다.

Figure 11. Conti 랜섬노트

- 악성코드 유포 서버

도메인: employees.medicalcenterclinic.com

IP: 184.178.18.168

해당 서버는 2015년에 출시된 IIS 10.0 웹 서버로 호스팅 중이며, 서버가 탈취되어 악성코드 유포를 위해 중개 서버로 악용되고 있는 것으로 추정된다.

또한, 해당 서버에서 유포 중인 악성코드와 Sliver에서 연결을 시도하는 서버에서도 동일한 유형의 악성코드들이 확인됐다.

Figure 12. 동일 유형 악성코드 유포

- Sliver mTLS 연결 서버

Sliver를 통해서 명령 및 제어를 하기 위해서는 공격자의 C2 서버와 통신을 성공해야 한다. 통신을 위해서 총 4개의 서버에 mTLS 연결을 시도한다.

mtls://hanwha.tafca.co.uk:443 mtls://ba.joe.dj:443
mtls://hanwha.bgsys.co.kr:443 mtls://ba.sv-italia.it:443

악성코드가 유포된 서버(184.178.18.168)와 같은 유형의 악성코드가 유포된 서버와 도메인을 확인할 수 있다.

Figure 13. mTLS 연결 서버

Figure 14. ba.joe.dj 관련 정보

Figure 15. hanwha.tafca.co.uk 관련 정보

Figure 16. hanwha.bgsys.co.kr 관련 정보

Figure 17. ba.sv-italia.it 관련 정보

- 도메인 매핑

Sliver에서 연결을 시도하는 hanwha.bgsys.co.kr 서버에 144.172.95.65 IP에 매핑되는 65.95.172.144.static.cloudzy.com 도메인을 확인할 수 있다.

Figure 18. 도메인 매핑

65.95.172.144.static.cloudzy.com 도메인은 VPS(Virtual Private Server) 와 RDP(Remote Desktop Protocol) 서비스를 호스팅하는 것이 확인됐다. 또한, 과거 Cloudzy는 이란, 중국, 북한, 러시아, 이스라엘 등 국가 배후 해커 조직, Ryuk, BlackCat 랜섬웨어 그룹들도 악용한 사례가 다수 보고된 적 있다. (Halcyon Cloudzy C2P Report)

Figure 19. VPS, RDP 서비스

bgsys.co.kr 도메인과 국가 배후 조직에서 사용한 인프라와 연결된다.

Figure 20. bgsys.co.kr 연관 인프라

144.172.95.65 IP와 동일 대역의 144.172.95.78 IP는 Nova 랜섬웨어 그룹에서 사용한 이력이 확인된다.

Figure 21. Nova Ransomware IP

3. 결론

본 보고서에서 분석한 사례는 최근 위협 환경의 변화와 맞물려 랜섬웨어와 Wiper의 경계가 점차 모호해지고 있음을 보여준다. 전통적으로 랜섬웨어는 파일 암호화를 통해 금전적 이익을 추구하는 데 집중한 반면, Wiper는 복구 불가능한 데이터 파괴를 목적으로 사용되어왔다. 그러나 이번 사례에서처럼 파일을 암호화한 뒤 삭제하는 혼합적 접근법은 단순한 금전 갈취를 넘어, 데이터의 가용성을 근본적으로 위협하는 새로운 형태의 공격 모델을 제시하고 있다.

특히 주목할 점은 공격자가 보여준 전략적 효율성이다. 공격 과정에서 Sliver, Tokenvator, SharpInjector, Donut, POSTDump 등 오픈소스 도구가 직접 사용되거나 일부 변형된 형태로 활용되었으며, 실행 파일에는 Confuser(.NET), garble(Go) 과 같은 공개된 난독화 도구가 적용되었다. 이러한 선택은 상용 솔루션이나 독자적 개발에 의존하지 않고도 저비용·고효율의 효과를 달성할 수 있음을 보여준다. 다시 말해, 공격자는 공개 생태계를 적극적으로 재활용함으로써 빠른 시간 안에 충분히 파괴적이고 정교한 공격 체인을 구성할 수 있었던 것이다.

또한, 본 샘플은 단순히 "파일 삭제형 Wiper"가 아니라, 암호화와 삭제를 병행하는 이중적 메커니즘을 도입하였다. 구체적으로는 파일을 tar + Zstandard(Zstd)로 압축 후 AES-GCM 모드로 암호화하고, 그 결과물을 곧바로 삭제하는 방식이다. 일반적인 AES-GCM 암호화는 무결성 검증을 위한 태그 인증을 거치지만, 해당 샘플은 태그 인증을 건너뛰어 복호화가 가능하도록 구현되어 있었다. 더불어 암호화 키 생성 과정에 하드코딩된 secret 값이 사용되는 등, 의도적으로 복구 가능성을 남긴 설계가 확인된다. 이는 공격자가 단순한 파괴가 아닌, 협박·금전 갈취·심리적 압박을 복합적으로 고려했음을 시사한다.

공격 체인 상에서도 탐지 회피와 행위 은폐를 위한 다양한 시도가 드러난다. PowerShell, Process Explorer, PsExecSvc와 같은 정상 도구(Living-off-the-Land 기법)가 공격에 포함되었으며, mTLS 기반 Sliver C2 통신이 사용되었다. 여기에 Wiper 내부에는 다수의 안티 디버깅 기법이 포함되어 분석을 방해하는 흔적도 관찰되었다.

Attribution 측면에서, 랜섬노트에서는 Akira 그룹의 이름이 일부 확인되며, "google us" 문구는 과거 Conti 그룹이 사용한 랜섬노트와 유사한 형태로 나타나 일부 연관성을 시사한다. 공격에 사용된 유포 서버(employees.medicalcenterclinic.com)는 탈취된 IIS 10.0 서버로 추정되며, Sliver C2 서버 4개(hanwha.tafca.co.uk, ba.joe.dj, hanwha.bgsys.co.kr, ba.sv-italia.it)와의 mTLS 연결 시도에서 공격 체인 전체의 운영 기반이 드러났다. 일부 서버와 IP(144.172.95.65, 144.172.95.78)는 Nova 랜섬웨어 그룹과 Cloudzy 인프라를 통해 VPS, RDP 등을 활용하는 많은 공격자 그룹의 사례와도 연관되어 있어, 공격자가 기존 랜섬웨어 생태계와 인프라를 재활용한 흔적도 보인다.

이를 종합하면, 본 공격은 고도의 독창적 기술력으로 무장한 APT보다는, 기존 생태계에 존재하는 오픈소스와 빌더를 효율적으로 조합해 위협을 극대화한 사례라고 평가할 수 있다. 이는 현대 위협 환경에서 점점 더 보편화되고 있는 Malware-as-a-Service(MaaS), Ransomware-as-a-Service(RaaS) 트렌드와 궤를 같이한다. 공격자는 필수 기능만 선별적으로 채택하고 불필요한 부분을 제거해 맞춤형 공격 체인을 구성했으며, 결과적으로는 방어자에게 분석 지연, 탐지 회피, 복구 지연이라는 복합적 부담을 안겼다.

이번 사례에서 확인할 수 있는 주요 시사점은 다음과 같다.

정상 도구의 무기화 - 오픈소스로 공개된 도구 및 정상 소프트웨어를 공격자가 악용해 손쉽게 무기화될 수 있으며, 최근 공격자들이 많이 사용하는 전략으로 다시금 확인할 수 있다.

혼합형 악성코드의 확산 가능성 - 랜섬웨어와 Wiper가 결합된 새로운 형태의 악성코드와 더불어 오픈소스와 난독화 도구, Go 언어 사용까지 다양한 도구와 악성코드가 결합돼 다양한 전략적 시도록 확인된다.

저비용·고효율 공격 모델 - 기술적 독창성보다도 오픈소스 도구와 공개 난독화 기법을 조합해 충분히 파괴적인 공격 체인을 구성할 수 있음을 보여준다.

특정 그룹 연관성 및 인프라 재활용 - Akira 그룹과 Conti/BlackBasta 유사 흔적, Nova 그룹과 Cloudzy 인프라 활용 사례를 통해 공격자가 기존 생태계를 전략적으로 재활용했음을 확인할 수 있다.

따라서 침해 위협에 대응하기위해 단순 시그니처 기반 탐지에 머물지 않고, 행위 기반 탐지, 오픈소스 도구 사용 흔적 모니터링, 키 및 백업 파일 패턴 분석 등을 포함한 정교한 대응 전략을 마련해야 한다. 본 보고서에서 제시한 복호화 스크립트와 IoC는 실제 피해 완화에 직접 활용될 수 있을 것이다. 또한, 향후 유사 공격에 대비하기 위해 선제적 인텔리전스 공유와 대응 체계 강화를 강조하며 보고서를 맺는다.

4. 상세분석

Stage 0. Loader

- 1.jpg

파워쉘 스크립트로 다른 악성 바이너리를 다운로드 및 로드하는 역할을 수행한다. MD5: E260F41019DA2FA8489B967D65B994B8

파워쉘 스크립트 코드가 난독화되지 않은 상태로 url을 통해 파일을 다운로드 받고 실행하는 코드이다. 이후 소개될 Stage 2, 3, 4와 최종적으로 Wiper가 실행된다.

Figure 22. 스크립트 실행 코드

Stage 2. Downloader & Sliverg

- msc.jpg

.NET으로 작성된 악성코드로 특정 바이너리를 다운로드 후 실행하는 다운로더 MD5: 7A3880F8DA8374DED9EF913CFFC5F184

Figure 23. 샘플 정보

Confuser 난독화 해제 후 cctor 분석을 위해 exe를 통해 로드 후 강제로 진입하도록 구성 후 분석을 진행한다.

Figure 24. dll 로더

cctor 함수에는 VirtualProtect 코드와 특정 함수를 실행해 동작에 필요한 문자열을 복원하는 코드가 포함되어 있고, 다른 실행, 로드 등의 코드는 확인되지 않는다.

Figure 25. 문자열 복원 함수 호출

Figure 26. 문자열 복원 코드

복원된 문자열 코드의 일부이며 전문은 아래 표와 같다.

Figure 27. 복원된 문자열 일부

???.dll
                       ???ntdll.dll??????ZwAllocateVirtualMemory????ZwWriteVirtualMemory???RtlInitUnicodeString
???LdrLoadDll?? ???RtlZeroMemory??????Invalid ProcessInfoClass:
{0}??????NtQueryInformationProcess??????NtAllocateVirtualMemory????NtWriteVirtualMemory???NtProtectVirtualMemory?????NtFreeVirtualMemory????kernel32.dll!???InitializeProcThreadAttributeList??????UpdateProcThreadAttribute??????CreateProcessA?????DeleteProcThreadAttributeList??????ZwProtectVirtualMemory?????ZwOpenThread???ZwQueueApcThread???ZwAlertResumeThread????ZwClose????VirtualProtect?????.text??????Nt?????Ntdll??????4C?????8B?????D1?????B8?????00?????F6?????04?????25?????08?????03?????FE?????7F?????01?????75?????0F?????05?????C3?????CD?????2E?????Sleep???
???wuauclt.exe????SearchIndexer.exe??????SearchProtocolHost.exe?????SearchFilterHost.exe
???msiexec.exe????robocopy.exe
???taskmgr.exe?-???C:\Program Files\Windows Defender\MsMpEng.exe???
???User-Agent??q???Mozilla/5.0 (Windows NTP 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.180??????*u?????*p?????sihost??    ???taskhostw???

이번에는 dll 바이너리의 extern 함수를 분석하기 위해 로더 코드를 일부 수정 후 분석을 진행한다.

전달 파라미터: “*u.http://ba.joe.dj/beac.jpg *p.4DmQNRhH8”

Figure 28. extern 함수 로더 코드

해당 extern 함수는 WebClient 코드를 포함하고 있으며, 문자열 파라미터를 입력 받는다. WebClient 함수가 포함되어 있어 관련 URL을 입력해 분석을 진행한다.

Figure 29. S.M extern S 함수 코드 일부

함수 내부 핵심 코드로 WebClient, Download 기능을 포함하고 있다.

Figure 30. S 함수 핵심 코드

전달받은 파라미터를 dictionary 형태로 변환한다.

Figure 31. 파라미터 dictionary 변환

다음 함수에서 프로세스 모듈을 불러와 함수 리스트를 로드한다.

Figure 32. 프로세스 모듈 로드

Figure 33. 함수 문자열 로드

통신을 위해 User-Agent 헤더 문자열을 생성한다.

Figure 34. User-Agent 문자열

입력 받은 파라미터와 생성한 User-Agent를 통해 다운로드를 시도하며, 정상적으로 다운로드가 되면 생성된 바이트 바이너리를 통해 Invoke or 로드 및 실행할 것으로 보인다.

Figure 35. WebClient 동작

다운로드 시도: ba.joe.dj

Figure 36. 다운로드 시도

Figure 37. DNS 연관 IP

해당 URL은 동작하지 않지만 연관된 C2 서버에 존재하는 동일 파일명을 통해 코드를 추가 분석하면 다음과 같다.

URL: https://employees.medicalcenterclinic.com/_WCM_images/beac.jpg

바이너리를 다운로드 받은 MemoryStream을 바이트 배열로 변환하는데 해당 과정을 스킵하고 Class16 복호화 로직이 포함된 클래스를 직접 호출해 분석을 진행한다.

Figure 38. 바이너리 다운로드 및 복호화 로직

- beac.jpg

AES로 암호화된 바이너리이며, Donut Shellcode를 통해 Sliver를 호출한다. MD5: 86E110ED8F52B6FE756665F5EFB10D12

분석을 위해 로더 작성 후 분석을 진행하면 Class16 생성자를 통해 기존에 입력 받은 ‘4DmQNRhH8’ 문자열을 SHA256 해시로 변환을 수행한다.

Figure 39. 문자열 SHA256 해시 변환

이후 method_1 함수를 호출하고, 파라미터 값으로 다운로드한 바이너리(beac.jpg) 배열을 전달해 분석을 진행한다. 복호화에 사용된 암호화 알고리즘은 AES로 beac.jpg 파일의 첫 16바이트를 키로, SHA256 변환된 해시를 IV로 사용해 복호화를 진행한다.

Figure 40. 복호화된 배열 array3

복호화된 바이너리

Figure 41. 복호화된 바이너리

복호화된 바이너리 자체를 분석해보면 donut chaskey 복호화 로직이 포함되어 있어 donut shellcode를 사용하는 것으로 확인된다.

Figure 42. donut chaskey shellcode 복호화 함수

donut shellcode를 복호화해 보면 다음과 같이 Golang으로 작성된 샘플 확인이 가능하다.

Figure 43. 복호화된 바이너리

해당 샘플은 Red team 침투 테스트 도구인 BishopFox_Sliver로 확인된다. Sliver는 주로 Shell 기능을 수행하거나 Lateral movement를 통해 추가 악성코드를 로드하는데 사용된다.

https://github.com/BishopFox/sliver

Figure 44. BishopFox Sliver 함수

Figure 45. main 함수 코드

beacon 생성을 위한 코드가 실행되며, 오픈 소스로 공개된 코드와 동일한 기능을 수행하는 것으로 확인된다. 해당 샘플은 mTLS 연결을 수행하며 연결을 시도하는 주소는 다음과 같다.

mtls://hanwha.tafca.co.uk:443 mtls://ba.joe.dj:443
mtls://hanwha.bgsys.co.kr:443 mtls://ba.sv-italia.it:443

서버와 연결이 가능하면 다음과 같이 최초 통신을 위한 Protobuf(Protocol Buffers)를 생성하고 명령어 수신을 기다린다.

Figure 46. 서버 최초 연결 시 전송하는 코드

Protobuf 구조를 구분해보면 다음과 같고, 해당하는 값들은 이후 그림에서 확인 가능하다.

Figure 47. Sliver Protobuf 예시

Figure 48. Sliver Protobuf 할당 값

C2 서버와 연결이 가능하다면 다음과 같은 명령어/기능을 수행할 수 있다.

Figure 49. Sliver help 명령어

실제 동작하는 명령어를 살펴보기 위해 Sliver를 설치 후 mTLS 연결을 시도하는 모습으로 연결된 클라이언트를 확인할 수 있다.

Figure 50. mTLS 생성 및 세션 연결

Figure 51. 연결된 클라이언트의 정보 및 shell 실행

사용 가능한 커맨드 명령어는 다음과 같다.

cat, cd, chmod, chown, chtimes, close, download, execute, execute-shellcode, extensions, getgid, getpid, getuid, ifconfig, info,
interactive, kill, ls, memfiles, mkdir, msf, msf-inject, mv, netstat, ping, pivots, portfwd, procdump, ps, pwd, reconfig, rename, rm,
rportfwd, screenshot, shell, shikata-ga-nai, sideload, socks5, ssh, terminate, upload, whoami

Figure 52. 커맨드 명령어

Sliver는 armory 옵션을 통해 3rd-party를 지원한다.

bof-roast, bof-servicemove, c2tc-addmachineaccount, c2tc-askcreds, c2tc-domaininfo, c2tc-kerberoast, c2tc-kerbhash,
c2tc-klist, c2tc-lapsdump, c2tc-petitpotam, c2tc-psc, c2tc-psk, c2tc-psm, c2tc-psw, c2tc-psx, c2tc-smbinfo,
c2tc-spray-ad, c2tc-startwebclient, c2tc-wdtoggle, c2tc-winver, chisel, chromiumkeydump, coff-loader, coff-loader,
credman, delegationbof, find-module, find-proc-handle, go-cookie-monster, handlekatz, hashdump, hollow,
inject-amsi-bypass, inject-clipboard, inject-conhost, inject-createremotethread, inject-ctray, inject-dde,
inject-etw-bypass, inject-kernelcallbacktable, inject-ntcreatethread, inject-ntqueueapcthread, inject-setthreadcontext,
inject-svcctrl, inject-tooltip, inject-uxsubclassinfo, inline-execute-assembly, jump-psexec, jump-wmiexec, kerbrute,
ldapsigncheck, mimikatz, nanodump, nanorobeus, patchit, portbender, raw-keylogger, remote-adcs-request,
remote-adcs_request_on_behalf, remote-adduser, remote-addusertogroup, remote-chrome-key, remote-enable-user,
remote-get_priv, remote-ghost_task, remote-global_unprotect, remote-lastpass, remote-make_token_cert,
remote-office-tokens, remote-procdump, remote-process-destroy, remote-process-list-handles, remote-reg-delete,
remote-reg-save, remote-reg-set, remote-sc-config, remote-sc-create, remote-sc-delete, remote-sc-description,
remote-sc-start, remote-sc-stop, remote-sc_failure, remote-schtasks-delete, remote-schtasks-stop,
remote-schtaskscreate, remote-schtasksrun, remote-setuserpass, remote-shspawnas, remote-slackKey,
remote-slack_cookie, remote-suspendresume, remote-unexpireuser, sa-adcs-enum, sa-adcs-enum-com,
sa-adcs-enum-com2, sa-adv-audit-policies, sa-arp, sa-cacls, sa-dir, sa-driversigs, sa-enum-filter-driver,
sa-enum-local-sessions, sa-env, sa-find-loaded-module, sa-get-netsession, sa-get-netsession2,
sa-get-password-policy, sa-ipconfig, sa-ldapsearch, sa-list_firewall_rules, sa-listdns, sa-listmods,
sa-locale, sa-netgroup, sa-netlocalgroup, sa-netlocalgroup2, sa-netloggedon, sa-netloggedon2, sa-netshares,
sa-netstat, sa-nettime, sa-netuptime, sa-netuser, sa-netuserenum, sa-netview, sa-notepad, sa-nslookup,
sa-probe, sa-reg-query, sa-regsession, sa-routeprint, sa-sc-enum, sa-sc-qc, sa-sc-qdescription, sa-sc-qfailure,
sa-sc-qtriggerinfo, sa-sc-query, sa-schtasksenum, sa-schtasksquery, sa-tasklist, sa-uptime, sa-vssenum, sa-whoami,
sa-windowlist, sa-wmi-query, scshell, secinject, syscalls_shinject, tgtdelegation, threadless-inject, unhook-bof,winrm
certify, krbrelayup, mlokit, nps, rubeus, seatbelt, sharp-hound-3, sharp-hound-4, sharp-smbexec, sharp-wmi, sharpchrome,
sharpdpapi, sharpersist, sharplaps, sharpmapexec, sharprdp, sharpsccm, sharpsecdump, sharpsh, sharpup, sharpview,
sqlrecon

Figure 53. 3rd-party 설치

Figure 54. 3rd-party 확장자 리스트

Figure 55. 3rd-party 매크로 리스트

Stage 3. Tokenvator

- toke.jpg

Tokenvator이며, 윈도우 토큰을 사용한 권한 상승 오픈 소스 도구이다. MD5: A06D733C0E55B4FDB01471877F22F5E4

Figure 56. 샘플 정보

Confuser로 난독화되어 있어 난독화 해제 후 샘플 분석을 진행 해야하며, 난독화 해제 후 확인되는 메인 함수 코드는 다음과 같다.

Figure 57. 최종 바이너리 Main 함수

해당 샘플의 코드에 기재된 사용법은 다음과 같다.

Figure 58. 사용법 출력 코드

메인함수를 살펴보면 전달 받은 인자를 Stream에 전달하고, MainLoop의 Run 함수를 실행한다.

전달 파라미터 형태: [명령어] /Process:[command PID] /command:[process name]

          ➡ Steal_Token /Process:16808 /command:cmd.exe

Figure 59. MainLoop.Run 함수 코드 일부

파라미터로 전달 받은 “pid”를 통해 프로세스와 연결하고 stream에 전달된 명령어에 따른 행위를 수행한다. 만약 “run”이 전달되면 새로운 프로세스를 생성 후 다음 동작을 수행한다. 전달받은 명령어에 따라 해당하는 행위를 수행한다.

Figure 60. 명령어 수행 코드 일부

코드에서 확인되는 명령어는 모두 다음과 같다.

명령어 기능 설명
exit 종료
help 사용법 출력
info 토큰에 대한 정보 출력
runas 자격증명을 사용해 로그인 후 프로세스 실행
whoami 윈도우 계정의 사용자 이름
history 콘솔 로그
getinfo 토큰 정보 출력
tasklist 프로세스 리스트 출력
sessions 세션 정보 출력
terminate 연결된 프로세스 종료
getsystem 시스템 계정 권한 상승 (get_system 명령어와 동일)
bypassuac UAC 우회
add_group 토큰 그룹 추가
get_system 시스템 계정 권한 상승 (getsystem 명령어와 동일)
logon_user 로그온 계정 정보 출력
clone_token 권한 상승: 토큰 복제 후 프로세스 실행
steal_token 권한 상승: 프로세스/쓰레드 토큰 획득 및 프로세스 생성
list_filters 미니 필터 드라이버 리스트 출력
create_token 토큰 생성
reverttoself impersonation token 제거 후 토큰 복원
start_driver 서비스 실행
unload_filter 미니 필터 드라이버 언로드
runpowershell 파워쉘 명령어 실행
delete_driver 서비스 중지 및 삭제
detach_filter 미니 필터 드라이버 리소스 해제
add_privilege 권한 상승: 커널 드라이버를 통해 토큰 추가
install_driver 미니 필터 드라이버 생성 및 실행
unfreeze_token 미니 필터 드라이버 토큰 해제
nuke_privileges 토큰에 할당된 모든 권한 제거
list_privileges 토큰에 할당된 권한 출력
steal_pipe_token pipe를 통해 토큰 획득 후 프로세스 실행
uninstall_driver 미니 필터 드라이버 삭제
remove_privilege 토큰에 할당된 권한 제거
sample_processes 사용자별 프로세스 출력
enable_privilege 토큰에 할당된 권한 활성화
disable_privilege 토큰에 할당된 권한 비활성화
clear_desktop_acl 권한 상승: DACL(Discretionary Access Control List) 설정
find_user_processes 해당 사용자의 프로세스 리스트 출력
gettrustedinstaller Trusted Installer group을 통해 시스템 토큰 획득 및 프로세스 실행
is_critical_process 주요 프로세스 여부 확인
sample_processes_wmi WMI를 이용해 사용자별 프로세스 출력
get_trustedinstaller Trusted Installer group을 통해 시스템 토큰 획득 및 프로세스 실행
set_critical_process 주요 프로세스로 설정
list_filter_instances 미니 필터 드라이버의 attached 리스트
find_user_processes_wmi WMI를 이용해 해당 사용자의 프로세스 리스트 출력

해당 샘플은 Steal_Token 명령어를 사용했으며 전달된 프로세스 및 쓰레드 ID에 따라 할당 및 프로세스 생성을 시도하는 코드가 존재한다. 입력된 파라미터 값은 프로세스의 토큰을 가져와 새로운 프로세스를 생성하는 기능을 수행하며, 콘솔창에 출력되는 실행 결과는 다음과 같다.

Figure 61. 콘솔창 출력

OpenProcessToken 함수를 통해 PID로 토큰을 얻은 뒤 StartProcessAsUser, CreateProcessWithLogonW 함수를 이용해 프로세스를 생성한다.

Figure 62. steal_token 명령어 수행 코드

Figure 63. openProcessToken 코드

Figure 64. StartProcessAsUser 코드

Figure 65. CreateProcessWithLogonW 코드

Steal_Token 명령어 외 다른 기능에 대한 설명과 간략한 코드에 대해서 살펴보면 다음과 같다.

Figure 66. info 명령어 수행 코드

Figure 67. runas 명령어 수행 코드

Figure 68. whoami 명령어 수행 코드

Figure 69. history 명령어 수행 코드

Figure 70. getinfo 명령어 수행 코드

Figure 71. tasklist 명령어 수행 코드

Figure 72. sessions 명령어 수행 코드

Figure 73. terminate 명령어 수행 코드

Figure 74. getsystem 명령어 수행 코드

Figure 75. bypassuac 명령어 수행 코드

Figure 76. add_group 명령어 수행 코드

Figure 77. logon_user 명령어 수행 코드

Figure 78. clone_token 명령어 수행 코드

Figure 79. list_filters/list_filter_instances 명령어 수행 코드

Figure 80. create_token 명령어 수행 코드

Figure 81. reverttoself 명령어 수행 코드

Figure 82. start_driver 명령어 수행 코드

Figure 83. unload_filter 명령어 수행 코드

Figure 84. runpowershell 명령어 수행 코드

Figure 85. delete_driver 명령어 수행 코드

Figure 86. detach_filter 명령어 수행 코드

Figure 87. add_privilege 명령어 수행 코드

Figure 88. install_driver 명령어 수행 코드

Figure 89. unfreeze_token 명령어 수행 코드

Figure 90. nuke_privileges 명령어 수행 코드

Figure 91. list_privileges 명령어 수행 코드

Figure 92. steal_pipe_token 명령어 수행 코드

Figure 93. uninstall_driver 명령어 수행 코드

Figure 94. remove/enable/disable_privilege 명령어 수행 코드

Figure 95. sample_processes 명령어 수행 코드

Figure 96. clear_desktop_acl 명령어 수행 코드

Figure 97. find_user_processes 명령어 수행 코드

Figure 98. gettrustedinstaller 명령어 수행 코드

Figure 99. is_critical_process 명령어 수행 코드

Figure 100. sample_processes_wmi 명령어 수행 코드

Figure 101. set_critical_process 명령어 수행 코드

Figure 102. find_user_processes_wmi 명령어 수행 코드

Stage 4. SharpInjector & Donut Shellcode

- si.jpg

SharpInjector이며, 특정 프로세스에 쉘코드를 인젝션하는 오픈 소스 도구이다. 다운로드, 쓰레드 실행 기능만 존재하고 오픈 소스에 존재하는 다운로드 받은 바이너리 복호화, Fiber 등의 기능은 제거된 버전이다. MD5: 091D06E580FFAAC0FEDED2BE55E2B48A

우선 메인 함수를 살펴보면 다음과 같다.

Figure 103. 메인 함수 코드

DownloadShellcode 함수에서 WebClient 클래스를 통해 하드코딩된 url에서 바이너리를 다운로드 받는다. 이후 VirtualAlloc을 통해 메모리를 할당 후 다운로드한 바이너리를 로드하고 실행이 완료될 때까지 기다리고 종료된다.

해당 코드에서는 WebClient 연결이 정상적으로 이루어지지 않아 bea.jpg 바이너리를 받아오지 못한다.

해당 url의 바이너리 파일이 유효하기 때문에 직접 다운로드 받아 확인을 해보면 다음과 같고, Donut ShellCode로 확인된다.

Figure 104. 다운로드한 bea.jpg 바이너리

- bea.jpg

Donut Shellcode이며, 메모리에서 직접 실행가능하도록 구현된 오픈 소스 도구이다. MD5: B266312F29C7629FD94A26076B3DDCAD

오픈 소스를 통해 복호화를 시도해보면 다음과 같이 Golang으로 작성된 샘플 확인이 가능하며 Wiper 동작을 수행한다.

Figure 105. 복호화된 바이너리

Stage 5. Wiper

- prod.jpg

Golang으로 작성된 악성코드로 Wiper 동작을 수행한다. 특정 파일을 지우기 전에 ‘[0-9a-f]{8}eyp.bak’ 파일에 백업을 수행하며 암호화 키 생성에 사용되는 랜덤값을 저장한 뒤 사이즈, 데이터 사이즈, IV, 암호화된 데이터 형태로 반복되어 저장된다. MD5: 561EDAE73F0021214FA92EE9B67377D8

무조건 삭제 대상 - *.iso, *.ndf, *.egg*, *.tmp*, *.log*, *.bak*, *.ldf*, *.swp*, *.bkf*, *copy.*, *.copy*, *.temp*, *.back*, *_log.*, *log_.*, *.backup*, *.1*, *.2* 삭제 예외 대상 - *eyp.bak, *LocalLow*, *CrashDumps*, *D3dSCache*, *$Recycle.Bin*, C:\PerfLogs, C:\Users\Public, C:\Users\Default,
C:\Program Files (x86), C:\Program Files\Windows, C:\Program Files\Common Files, C:\ProgramData, C:\Recovery,
C:\Windows, C:\WinNT, C:\$Recycle.Bin, *.backup*
- *.dll, *.msi, *.exe, *.sys, *.ldf, backup_log.txt
이외 모든 파일 100개씩 일괄 삭제 [0-9a-f]{8}eyp.bak 백업 파일 생성 컨텍 이메일 - h.majestic947@passinbox.com 랜섬노트 - backup_log.txt

탐지 회피와 분석 방해를 목적으로 Garble 난독화 도구가 사용됐으며, 실행에 필요한 문자열, 명령어 등을 고유한 키와 연산을 통해 동적으로 디코딩 후 사용한다.

Figure 106. 문자열 동적 로드

main 함수를 살펴보면 많은 기능을 포함하고 있지는 않다. 크게 관리자 계정 비활성화, MS-SQL Server 서비스 중지, 그리고 파일 관련 조작을 수행하는 함수로 나누어 볼 수 있다.

Figure 107. 관리자 계정 비활성화

Figure 108. MS-SQL Server 서비스 중지

관리자 계정 비활성화와 서비스 중지 후 파일 관련 동작을 수행하는 함수가 호출된다. 처음으로 수행하는 동작은 8자리의 랜덤 값을 생성 후 ‘[0-9a-f]{8}eyp.bak’(이후 *eyp.bak로 명명) 형태의 파일을 생성한다.

Figure 109. [0-9a-f]{8}eyp.bak 파일 생성

0x20 바이트만큼의 랜덤 키를 생성 후 앞에서 생성한 *eyp.bak 파일에 저장한다.

Figure 110. 랜덤 키 생성 및 저장

생성된 랜덤 키는 고정된 0x38 바이트의 데이터와 함께 argon2 해시 생성에 사용된다. ID 모드를 사용해 argon2id 해시를 생성하고, 생성된 해시는 AES 키로 파일 암호화에 사용된다.

Figure 111. 암호화 키 생성

이후 파일을 삭제하는 동작을 수행하는데 삭제 대상, 예외 대상, 이외 파일들에 조건에 따라 분기 후 삭제를 수행하며 체크하는 드라이브는 A~Z까지 모든 경우의 수를 확인한다.

Figure 112. 파일 삭제를 위해 호출되는 함수

backup_log.txt와 eyp.bak 문자열이 포함된 파일인 경우 삭제 대상에서 제외되며, 폴더명을 체크해 LocalLow, CrashCumps 등이 포함되어 있을 경우와 dll, msi, exe 등 실행 파일 확장자가 확인되는 경우에도 제외된다.

Figure 113. 파일 삭제 예외 대상 확인 코드

구분 대상
예외 파일명 backup_log.txt, *eyp.bak
예외 폴더명 C:\Program Files\Microsoft SQL Server, C:\Program Files (x86)\Microsoft SQL Server\MSSQL\Data,
*LocalLow*, *CrashDumps*, *D3dSCache*, *$Recycle.Bin*, C:\PerfLogs, C:\Users\Public,
C:\Users\Default, C:\Program Files (x86), C:\Program Files\Windows,
C:\Program Files\Common Files, C:\ProgramData, C:\Recovery, C:\Windows,
C:\WinNT, C:\$Recycle.Bin, *.backup*
예외 확장자 .dll, .msi, .exe, .sys, .ldf

Table 7. 예외 대상 리스트

구분 대상
삭제 대상 .copy, .1, .2, .egg, .tmp, .log, .bak, bak., .temp, .backup, .back, .ldf, _log., log_., .swp, .bkf, .iso, .ndf

Table 8. 삭제 대상 리스트

앞서 확인한 예외 대상과 무조건 삭제하는 대상 외 나머지 파일에 대해서는 다음과 같이 100개씩 리스트화 후 일괄 삭제를 진행하며 삭제 전 삭제 대상 파일의 데이터를 읽은 후 tar 포맷으로 압축을 수행한다.

Figure 114. 파일 내용 압축 및 일괄 삭제 함수 호출

tar 파일을 생성하고 압축 후 파일을 삭제하는 로직은 다음과 같다.

Figure 115. 압축 파일 생성 및 파일 삭제 로직

tar 압축을 위해서 file.stat()을 통해 파일 정보를 추출한다.

Figure 116. file.stat 함수 호출

tar 헤더를 생성하기위한 함수를 호출해 PAX 타입의 헤더를 구성한다.

Figure 117. tar 헤더 구성

이후 파일에서 데이터를 읽어와 tar 파일을 구성한다.

Figure 118. tar 압축 구성

tar 파일을 구성 후 삭제하기위해 파일 속성을 변경하고 파일을 삭제한다.

Figure 119. 파일 삭제 로직

삭제 이후 tar 파일은 Zstandard(Zstd)로 추가 압축한다.

Figure 120. Zstandard 압축

압축된 데이터는 0x10000 단위로 AES 암호화 알고리즘의 GCM모드를 사용해 암호화한다.

Figure 121. 0x10000 단위 암호화

키 생성에 사용되는 값은 랜덤하게 생성되어 파일에 저장되며, 실행 시점에 결정되는 IV 값은 고정되어 있지만 그대로 사용하지 않고, 현재 암호화를 진행하는 메시지 블록의 순서 값을 IV의 마지막 4바이트와 XOR 연산해 사용한다.

Figure 122. IV 설정 및 gcm.Seal 호출

모든 압축 데이터를 암호화하며, 각 블록을 *.bak 파일에 순서대로 저장한다.

Figure 123. *.bak 구조

별도의 키 보호 기법을 사용하지 않기 때문에, 랜섬웨어 내부에 저장된 0x38바이트 secret 값과, *.bak에 저장된 랜덤키, IV를 활용해 복호화할 수 있다. .tar.zst 압축 해제에는 특별한 키가 필요하지 않기 때문에 압축 대상이었던 모든 파일을 확인할 수 있다.

Figure 124. 복구된 파일 리스트

모든 과정이 끝나면 랜섬노트를 생성한다.

이메일: h.majestic947@passinbox.com

Figure 125. backup_log.txt

- mod_bea.jpg

Golang으로 작성된 악성코드로 Wiper 동작을 수행한다. 특정 파일을 지우기 전에 ‘[0-9a-f]{8}dngi.bak’ 파일에 백업을 수행하며 암호화 키 생성에 사용되는 랜덤값을 저장한 뒤 사이즈, 데이터 사이즈, IV, 암호화된 데이터 형태로 반복되어 저장된다. MD5: DC96941E830945E4D9D0777230858554

무조건 삭제 대상 - *.iso, *.ndf, *.egg*, *.tmp*, *.log*, *.bak*, *.ldf*, *.swp*, *.bkf*, *copy.*, *.copy*, *.temp*, *.back*, *_log.*, *log_.*, *.backup*, *.1*, *.2* 삭제 예외 대상 - *eyp.bak, *LocalLow*, *CrashDumps*, *D3dSCache*, *$Recycle.Bin*, C:\PerfLogs, C:\Users\Public, C:\Users\Default,
C:\Program Files (x86), C:\Program Files\Windows, C:\Program Files\Common Files, C:\ProgramData, C:\Recovery,
C:\Windows, C:\WinNT, C:\$Recycle.Bin, *.backup*
- *.dll, *.msi, *.exe, *.sys, *.ldf, backup_log.txt
이외 모든 파일 100개씩 일괄 삭제 [0-9a-f]{8}dngi.bak 백업 파일 생성 컨텍 이메일 - dongileng.another679@passinbox.com 랜섬노트 - backup_log.txt

prod.jpg(561EDAE73F0021214FA92EE9B67377D8) 샘플과 기능 및 동작이 상당 부분 유사하며 일부 설정 값만 바꿔 사용한것으로 보인다. prod.jpg와 기능상 동일한 부분은 제외하며, 차이점을 위주로 설명한다.

시간차를 이용한 방법과 디버거와 디버깅 중임을 확인하는 안티 디버깅 기법을 사용하고 있다.

NtProtectVirtualMemory를 통해 BreakPoint 접근을 차단하고, NtQuerySystemTime, NtDelayExecution을 통해 실행시간을 감지하며 시스템 콜을 통해 실행해 후킹을 방지한다.

Figure 126. 안티 디버깅 기법 #1

NtDelayExecution과 특정 연산을 수행하는 함수 실행 후 QueryPerformanceCounter를 통해 평균 시간차를 계산해 종료하는 안티 디버깅 코드

Figure 127. 안티 디버깅 기법 #2

IsDebuggerPresent, NtQueryInformationProcess를 통해 디버깅중임을 감지하는 안티 디버깅 코드를 포함하고 있으며 마찬가지로 시스템 콜을 사용한다.

Figure 128. 안티 디버깅 기법 #3

마지막으로 NtQuerySystemInformation를 통해 디버거를 체크하는 안티 디버깅 코드를 포함하고 있다.

Figure 129. 안티 디버깅 기법 #4

관리자 계정 활성화, MSSQLSERVER, SQLEXPRESS, MYSQL 서비스 중지, 그리고 파일 관련 조작을 수행하는 함수로 나누어 볼 수 있다. [차이점] 관리자 계정 활성화로 변경, SQLEXPRESS, MYSQL 서비스 중지 추가

Figure 130. 관리자 계정

Figure 131. 데이터베이스 관련 서비스 중지

8자리의 랜덤 값을 생성 후 ‘[0-9a-f]{8}dngi.bak’(이후 *dngi.bak로 명명) 형태의 파일을 생성한다. [차이점] eyp.bak -> dngi.bak 이름 변경

Figure 132. [0-9a-f]{8}dngi.bak 파일 생성

backup_log.txt와 dngi.bak 문자열이 포함된 파일인 경우 삭제 대상에서 제외되며, 폴더명을 체크해 LocalLow, CrashCumps 등이 포함되어 있을 경우와 dll, msi, exe 등 실행 파일 확장자가 확인되는 경우에도 제외된다. [차이점] 표 Bold체 참고

구분 대상
예외 파일명 backup_log.txt, *dngi.bak
예외 폴더명 C:\Program Files\Microsoft SQL Server, C:\Program Files (x86)\Microsoft SQL Server\MSSQL\Data, *LocalLow*, *CrashDumps*, *D3dSCache*, *$Recycle.Bin*, C:\PerfLogs, C:\Users\Public, C:\Users\Default, C:\Program Files (x86), C:\Program Files\Windows, C:\Program Files\Common Files, C:\ProgramData, C:\Recovery, C:\Windows, C:\WinNT, C:\$Recycle.Bin, *.backup*
예외 확장자 .dll, .msi, .exe, .sys, .ldf

Table 9. 예외 대상 리스트

iso, ndf, egg, tmp 등 특정 파일 확장자를 체크해 무조건 삭제 대상이 되는 파일을 확인 후 삭제 할 수 있도록 파일 속성 값을 변경하고 대상 파일을 삭제한다.

구분 대상
삭제 대상 .copy, .1, .2, .egg, .tmp, .log, .bak, bak., .temp, .backup, .back, .ldf, _log., log_., .swp, .bkf, .iso, .ndf

Table 10. 삭제 대상 리스트

앞서 확인한 예외 대상과 무조건 삭제하는 대상 외 나머지 파일에 대해서는 다음과 같이 100개씩 리스트화 후 일괄 삭제를 진행하며 삭제 전 삭제 대상 파일의 데이터를 읽은 후 tar 포맷으로 압축을 수행한다.

tar 압축 방식, 암호화 방식, IV를 변형해 사용하는 방법, *.bak 파일에 저장되는 형태 등 모든 방식이 기존 prod.jpg와 동일한 로직을 따른다.

동일하게 키 보호 기법을 사용하지 않기 때문에, 랜섬웨어 내부에 저장된 0x38바이트 secret 값과, *.bak에 저장된 랜덤키, IV를 활용해 복호화할 수 있다. .tar.zst 압축 해제에는 특별한 키가 필요하지 않기 때문에 압축 대상이었던 모든 파일을 확인할 수 있다.

Figure 133. *.bak 구조

Figure 134. 복호화된 데이터

랜섬노트의 문구는 동일하지만 영어로 작성된 것을 확인할 수 있다.

이메일: dongileng.another679@passinbox.com

Figure 135. backup_log.txt 랜섬노트

Related 1. POSTDump

- post.jpg

.NET dll 파일로 LSASS 프로세스 미니덤프 오픈 소스 도구이다. MD5: FC971483F8FC7580A5ED3626B54E8E52

해당 샘플은 공격에 직접적으로 사용된 흔적이 발견되지 않았지만, C2서버 내부에 존재하는 파일로 추가적인 분석을 진행한 Case이다.

.NET Confuser로 난독화되어 있어 난독화 해제 후 샘플 분석을 진행한다.

Figure 136. 샘플 정보

Figure 137. 난독화 해제 결과

복호화 툴을 통해 dll 내부에 포함된 POSTDump 클래스를 확인할 수 있고, 해당 코드는 오픈 소스로 공개된 LSASS 프로세스를 미니덤프 할 수 있는 기능을 포함하고 있다.

Figure 138. 샘플 POSTDump 메인 코드

Figure 139. 오픈소스 PostDump 메인 코드

샘플에 표기된 사용법과 오픈 소스에 공개된 사용법이 상당부분 동일하며, 샘플 코드에는 live, fromfile 옵션이 존재한다.

Figure 140. 샘플 사용법 출력 코드

Figure 141. 오픈 소스 사용법 출력 코드

- 탐지 회피 목적의 옵션 사용

-e, --encrypt: 탐지 회피를 위해 미니 덤프 데이터를 xor 111 연산 후 저장한다.

Figure 142. xor 연산 코드

-s, --signature: 일반적인 minidump 파싱을 방해하기위해 잘못된 서명을 생성하고 사용한다.

Figure 143. invalid signature

--asr: ASR 정책을 우회한다. (signature, encrypt 옵션 비활성화)

Figure 144. asr 옵션 코드

--kill: Process Explorer의 Procexp.sys 드라이버를 통해 프로세스를 종료한다.

Figure 145. kill 옵션 코드

--driver: Process Explorer의 Procexp.sys 드라이버를 통해 PPL(Protected Process Light, 보호된 중요 프로세스) 우회 후 lsass 핸들을 얻고 덤프한다.

Figure 146. driver 옵션 코드

--live: 파일리스 기법으로 lsass 메모리에서 직접 자격 증명을 파싱한다.

Figure 147. live 옵션 코드

--fromfile: 생성된 덤프 파일을 입력 받아 메모리에서 직접 자격 증명을 파싱한다.

Figure 148. fromfile 옵션 코드

Figure 149. live/fromfile 옵션에서 사용되는 메모리 파싱 코드

- 덤프 기법 선택을 위한 옵션

--snap: PssNtCaptureSnapshot API를 통해 lsass 프로세스 메모리의 스냅샷을 생성한다.

Figure 150. snap 옵션 코드

--fork: NtCreateProcessEx API를 통해 fork 후 덤프한다.

Figure 151. fork 옵션 코드

- 권한 상승을 위한 옵션

--duplicate-elevate: 이미 존재하는 lsass 프로세스를 통해 권한을 상승시킨다.

Figure 152. duplicate-elevate 옵션 코드

--elevate-handle: lsass 프로세스의 핸들을 통해 높은 권한의 핸들로 변환한다.

Figure 153. elevate-handle 옵션 코드

5. 부록

1)복호화 코드

복호화가 가능한 python 코드이며, 백업 파일과 랜섬노트 파일을 통해 키를 결정해 복호화를 수행한다. 이때 백업 파일이 생성된 시스템의 CPU 코어 수를 --proc 옵션을 통해 전달해야 하는 경우가 존재한다.

# Required packages:
#  pip install argon2-cffi pycryptodome

import struct
import binascii
import re
import argparse
import os
from argon2.low_level import hash_secret_raw, Type
from Cryptodome.Cipher import AES

OPT_PROC_CNT = 0

def get_contact(file_path: str):
    pattern = r"(http[s]?://[a-zA-Z0-9\.]+\.onion\b|[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})"

    with open(file_path, "r", encoding="utf-8") as f:
        text = f.read()
        contacts = re.findall(pattern, text)
        contacts = list(set(contacts))

        for contact in contacts:
            print(f"  Extract Contact: {contact}")
            return contact

        return None

def get_static_key(bak_file_path: str, note_file_path: str):
    contact = get_contact(note_file_path)

    if contact == "h.majestic947@passinbox.com":
        return "4e346867081521004b394c5f35600e06072601395c5f35005d79694e686c473b795739212e40016d0b79350f2a51280c513b273b521b2c21"
    elif bak_file_path.endswith("sinar.bak"):
        if contact == "on2jtree2bck4rux3xq5zbhpx4okfdpxpmergyrsgoronk6uuuo4vaqd.onion":
            return "4e346867081521004b394c5f35600e06072601395c5f35005d79694e686c473b795739212e40016d0b79350f2a51280c513b273b521b2c21"
    elif bak_file_path.endswith("dngi.bak"):
        if contact == "dongileng.another679@passinbox.com":
            return "4e346867081521004b394c5f35600e06072601395c5f35005d79694e686c473b795739212e40016d0b79350f2a51280c513b273b521b2c21"
    elif bak_file_path.endswith("jamwu.bak"):
        if contact == "alans.help@axelglue.store":
            return "ec3dda175ca978cb9c6a8b36791827a44a9e0553c98fbc67154e3b62d087a1f9"
    elif bak_file_path.endswith("taib.bak"):
        if contact == "taib.help@axelglue.store":
            return "02f415a8e786b93a8bd0a5fc4e193f6772a34489c237e655d4009bcd571122f9"
    elif bak_file_path.endswith("samc.bak"):
        if contact == "samc.help@axelglue.store":
            return "02f415a8e786b93a8bd0a5fc4e193f6772a34489c237e655d4009bcd571122f9"
    elif bak_file_path.endswith("ucsi.bak"):
        if contact == "ucsi.help@axelglue.store":
            return "02f415a8e786b93a8bd0a5fc4e193f6772a34489c237e655d4009bcd571122f9"
    elif bak_file_path.endswith("ya2hsc.bak"):
        if contact == "rhs.help@axelglue.store":
            return "07040707050404003b6c3c2525200726002c01292c4f05702d09193e181c374b092749515e30711d7b09457f5a21587c214b574b226b5c51"
        elif contact == "ucsi.help@axelglue.store":
            return "47747877787571704b597c5f55507e76775671595c3f75005d79694e686c473b795739212e40016d0b79350f2a51280c513b273b521b2c21"
    elif bak_file_path.endswith("wpl.bak"):
        if contact == "wpl.help@axelglue.store":
            return "fb2a70004bbe6fdc02591075c790b623452acd004bbe6fdc5d891244de101070"

    print("  !!! Not matching static key")
    return None

def create_key(static_key, key_data):
    if static_key is None:
        return None
    static_key = binascii.unhexlify(static_key)

    print("  Argon2 static (hex):", static_key.hex())

    parallelism = 2
    if len(static_key) == 32:
        parallelism = os.cpu_count()
    if OPT_PROC_CNT != 0:
        parallelism = OPT_PROC_CNT

    argon2_hash = hash_secret_raw(
        secret=static_key,
        salt=key_data,
        time_cost=1,
        memory_cost=0x8000,
        parallelism=parallelism,
        hash_len=32,
        type=Type.ID
    )

    return argon2_hash

# IV change rules
def increment_iv(iv_tail: bytes, idx: int) -> bytes:
    iv_tail_int = int.from_bytes(iv_tail, byteorder='little') ^ idx
    return iv_tail_int.to_bytes(4, byteorder='little')

def decrypt_aes_gcm_chunks_fixed_key_iv(bak_path: str, out_path: str, note_path: str):
    with open(bak_path, "rb") as f, open(out_path, "wb") as out:
        idx = 0
        key_data = f.read(32)
        idx += 32
        if len(key_data) < 32:
            print("  !!! Too short file length.")
            return

        static_key = get_static_key(bak_path, note_path)
        key_data = create_key(static_key, key_data)
        if key_data is None:
            print("  !!! Create key fail.")
            return
        print(f"  Generated Key (32 Bytes): {key_data.hex()}")

        block_idx = 0
        base_iv = None
        iv_tail = None
        seed_iv = None

        while True:
            key_size_bytes = f.read(2)

            if key_size_bytes == b'':
                print("  -- EOF --")
                break

            idx += 2

            key_size = struct.unpack("<H", key_size_bytes)[0]
            if key_size != 0x20:
                print(f"  !!! Unexpected key size! {key_size}")
                break

            enc_size_bytes = f.read(2)
            idx += 2
            if len(enc_size_bytes) < 2:
                print("  !!! Insufficient ciphertext size information!")
                break
            enc_size_raw = struct.unpack("<H", enc_size_bytes)[0]
            enc_size = 0x10000 if enc_size_raw == 0xFFFF else enc_size_raw + 1
            print(f"  [Block {block_idx}] encrypted_size={hex(enc_size)}")

            seed_iv = f.read(12)
            idx += 12
            if len(seed_iv) < 12:
                print("  !!! IV read fail!")
                break
            base_iv = seed_iv[:8]
            iv_tail = seed_iv[8:]
            new_tail = increment_iv(iv_tail, block_idx)
            iv = base_iv + new_tail
            print(f"  [Block {block_idx}] IV: {iv.hex()}")
            print(f"  [Block position] {hex(idx)}")

            encrypted_with_tag = f.read(enc_size + 16)
            idx += enc_size + 16
            if len(encrypted_with_tag) < enc_size + 16:
                print(f"  !!! Failed to read ciphertext & tag! expected size: {enc_size + 16}, "
                      f"real size: {len(encrypted_with_tag)}")
                break

            try:
                cipher = AES.new(key_data, AES.MODE_GCM, nonce=iv)
                ciphertext = encrypted_with_tag[:-16]
                tag = encrypted_with_tag[-16:]
                decrypted = cipher.decrypt(ciphertext)
                out.write(decrypted)
            except Exception as e:
                print(f"  !!! Decrypt fail: {e}")
                break

            block_idx += 1

        print("  Decrypt success.")

if __name__ == "__main__":
    parser = argparse.ArgumentParser(description="Usage options")
    parser.add_argument("--bak", required=True, help="Backup file path")
    parser.add_argument("--note", required=True, help="Ransomnote file path")
    parser.add_argument("--proc", type=int, help="Infected system's process core count")

    args = parser.parse_args()
    backup_path = args.bak
    output_path = backup_path + ".tar.zst"
    ransom_note_path = args.note
    if args.proc:
        OPT_PROC_CNT = args.proc

    print("** Backup file decrypter **")
    print(f"  Backup file path: {backup_path}")
    print(f"  Decrypt file path: {output_path}")
    print(f"  Ransomnote file path: {ransom_note_path}")

    decrypt_aes_gcm_chunks_fixed_key_iv(backup_path, output_path, ransom_note_path)
        
2)IoC

- 유포 악성코드

Role File HASH(SHA1)
Loader 1.jpg 11C196DA56A522AB2F86AB4C1023B835123A418D
Downloader msc.jpg 39AA0AEB0039823BCFA52D81FA71D668E6BD13ED
Sliver beac.jpg 8AE075B6A2FB437F74B35CF218FCD067539FA27B
Tokenvator toke.jpg 01381A20F03B2C985B123EDCEBEF56B7A53E175D
SharpInjector si.jpg 34F167DE5D69CA8AEF4A59FADFB5D5D16604D138
Donut bea.jpg 5A6DBBE8D7CB529F7BF2CD8F010BF28F4E231ED4
Wiper prod.jpg A0D04AAD2945D5BDF13FAB9FE51D2B030F970607
mod_bea.jpg 849AABE75504890D536D16D23F10F7A958776C33
sysad.exe CDAD898B46A26BF0413F7C258F7D3A07026F8BAC
- 2562C6FADBF27D8703442E67BBF4083795C827DB
sysadc.exe C4C95472B7E991BCDE8D00568F20F9AE7784ADEB
alans.exe 09B12739B7B2B34395AC9035A49C0CCDD088E2BE
$RR6ENZT.exe 5D94692401170B215ECFF7DEAB03A123A1BD4CD8
taib.exe B2EA4F77CF5D4ACF3ADAE1A00F5D7FA2919DC8FD
ran.exe 3F6365E381836C5BFB1CFB070D0EDC5D439FA7E9
- F3B25222F67D65C88BF15894E3608FF20A586367
senai.exe 505D5E4A83087477B957E8EE15BA248F0A7F9B0B
install.exe 85D15689C80BAF5FDDBAEFEA15417F38EA9BFAEE
sinarr.exe CEDBA5F174FAD396280B89CFD8A36DA4E0D762CC
- 6BE237970B2A1402EFD6378F6106992079027863
cola.exe B8463A3422F33C63F979F01A94276A5880C8F3D2
rhs.exe 08FF3B1F6A191F2B6FEAEAFAAF577015FF141E2F
POSTDump post.jpg 33D5E5CF4DD50BF3A96E16DB73AA9F82477D6FEF

- IP

IP 도메인
184.178.18.168 employees.medicalcenterclinic.com
72.5.42.46 ba.joe.dj, hanwha.lafca.co.uk, hanwha.bgsys.co.kr,
144.172.95.65 hanwha.bgsys.co.kr, hanwha.tafca.co.uk, ba.sv-italia.it, ba.joe.dj
144.172.103.233 hanwha.tafca.co.uk, ba.sv-italia.it, ba.joe.dj
107.189.18.50 hanwha.tafca.co.uk, ba.joe.dj
107.189.26.54 hanwha.tafca.co.uk, ba.joe.dj, ba.sv-italia.it
107.189.19.18 hanwha.tafca.co.uk, ba.sv-italia.it, ba.joe.dj
144.172.115.39 ba.sv-italia.it, ba.joe.dj
144.172.108.160 ba.sv-italia.it, ba.joe.dj
96.9.124.230 ba.sv-italia.it, ba.joe.dj
45.61.137.211 ba.sv-italia.it
107.189.22.8 ba.joe.dj
168.100.9.77 ba.joe.dj