天天看點

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

文章來源;

https://www.freebuf.com/articles/web/258988.html

前言

距離上一篇文章《那些FastJson漏洞不為人知的事情(開發角度)》已經過去三天了,但在我的心中仿佛過了一年。這三天是我在分析Cobaltstrike源碼的一個過程,閱盡代碼冷暖,但我依然要說一句Cobaltstrike牛逼~

場景描述

最早我為了研究MSF的免殺,去看了MSF木馬源碼,其采用大馬傳小馬的方式,運作時首先将自己在記憶體中循環調用三次,也就是為什麼會有spwan這個值的原因。它代表将自己在記憶體中循環調用幾次,随後在最後一次時與服務端建立連接配接,然後服務端将大馬傳回。後來依照這個邏輯,我自己編寫了MSF的用戶端對接服務端做到了VT全過。原本想着按照這個思路可以将Cobaltstrike的木馬也進行相同邏輯,但随後發現Cobaltstrike并不能生成木馬源碼,隻能生成shellcode。後來聽說了加載器的概念,編寫一個可以運作16進制的代碼無論語言。由于我是做Java的,首先就想到使用Java實作,後來查找了相關資料,運用了很多相關途徑都沒有找到Java可以運作shellcode的邏輯。後來想起當時開發時用過的關鍵字Native,并且參考了Python實作。産生了以下思路:python提供了python與c之間的資料類型轉換庫,可以直接與c對接,叫做ctypes。這點Java卻沒有,Java與c對接隻有Native,通過JNI的方式與c對接。但是這其中存在一個問題,通俗點說就是建立c程式去運作shellcode,然後使用java去進行調用。也就是Java是程式的入口喚醒c程式,這樣的實作絲毫沒有任何意義,不如拿c直接去寫。是以果斷放棄了Java,回歸最開始的思路,采用源碼級免殺。但不得不接受的一個現實就是我們并沒有此源碼,是以我找了很多朋友能把shellcode逆向成Java代碼。結果也是不了了之,直到我狠下決心決定分析Cobaltstrike的源碼,查找它生成木馬的邏輯。這期間我有兩種邏輯,也是我的猜測:1.Cobaltstrike中shellcode是寫死的,整個16進制都是相同的,隻有反彈shell的位址和端口發生變化。在生成時隻會替換端口和IP改動所發生位置,然後進行16進制直接替換。是以可能分析整個源碼也會找不到木馬源碼。2.Cobaltstrike中存在源碼,每次使用者選擇不同的端口和IP之後将其代入源碼,然後将源碼編譯進而産生shellcode,這種情況源碼有迹可查。通過上述兩種推理進而産生了我今天的文章,下面是我推理源碼的過程。

環境準備

如果需要分析源碼則必須将Jar檔案反編譯成.java檔案,然後在編輯器中運作,這樣友善調試。像從前一樣我還是拿出了我的反編譯工具Jd-jui

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

沒錯,結果卡在了這個頁面半個多小時。我以為我電腦的問題,找了幾個朋友都是這種情況後來放棄了。猜測可能存在以下原因:1.Cobaltstrike有什麼内部安全措施,不讓進行反編譯。2.檔案太大導緻jd-jui直接卡死,畢竟Cobaltstrike功能實在太多。

最後在公衆号中碰巧在我迷離之際碰到了這個大佬發的

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

依照這篇文章我成功的在自己的idea上跑起來Cobaltstrike

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

關鍵代碼查找

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

此處為生成payload的關鍵代碼,該類提供了三個方法

第一個方法

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

該方法為選擇監聽器所執行的邏輯處理

第二個方法

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變
在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

該方法為生成payload的主要邏輯,通過使用者所選語言生成shellcode

第三個方法

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

該方法為UI界面填值,

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變
在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

接下來我着重分析第一個方法和第二個方法

public void dialogAction(ActionEvent var1, Map var2) {this.options = var2;boolean var3 = DialogUtils.bool(var2, "x64");String var4 = DialogUtils.string(var2, "listener");this.stager = ListenerUtils.getListener(this.client, var4).getPayloadStager(var3 ? "x64" : "x86");if (this.stager.length == 0) {if (var3) {DialogUtils.showError("No x64 stager for listener " + var4);} else {DialogUtils.showError("No x86 stager for listener " + var4);}} else {Map var5 = DialogUtils.toMap("ASPX: aspx, C: c, C#: cs, HTML Application: hta, Java: java, Perl: pl, PowerShell: ps1, PowerShell Command: txt, Python: py, Raw: bin, Ruby: rb, COM Scriptlet: sct, Veil: txt, VBA: vba");String var6 = DialogUtils.string(var2, "format");String var7 = "payload." + var5.get(var6);SafeDialogs.saveFile((JFrame)null, var7, this);}}

此方法首先監聽使用者所選x86或者x64的作業系統然後注入到全局

protected byte[] stager = null;

此全局數組為使用者所選的配置,如監聽的方式,IP端口等。使用者所選的可變操作都會存放到此,簡單來說就是payload生成的可變參數。然後判斷如果配置錯誤則會彈出相應的内容,如果成功則将界面顯示的語言值和程式内部實際要比對的值進行轉化。為下面第二個方法做鋪墊,此時使用者不管是x86,x64還是監聽方式,所選的生成語言,IP端口都已經放到stager中。

第二個方法

public void dialogResult(String var1) {String var2 = DialogUtils.string(this.options, "format");boolean var3 = DialogUtils.bool(this.options, "x64");String var4 = DialogUtils.string(this.options, "listener");if (var2.equals("C")) {this.stager = Transforms.toC(this.stager);} else if (var2.equals("C#")) {this.stager = Transforms.toCSharp(this.stager);} else if (var2.equals("Java")) {this.stager = Transforms.toJava(this.stager);} else if (var2.equals("Perl")) {this.stager = Transforms.toPerl(this.stager);} else if (var2.equals("PowerShell") && var3) {this.stager = (new ResourceUtils(this.client)).buildPowerShell(this.stager, true);} else if (var2.equals("PowerShell") && !var3) {this.stager = (new ResourceUtils(this.client)).buildPowerShell(this.stager);} else if (var2.equals("PowerShell Command") && var3) {this.stager = (new PowerShellUtils(this.client)).buildPowerShellCommand(this.stager, true);} else if (var2.equals("PowerShell Command") && !var3) {this.stager = (new PowerShellUtils(this.client)).buildPowerShellCommand(this.stager, false);} else if (var2.equals("Python")) {this.stager = Transforms.toPython(this.stager);} else if (!var2.equals("Raw")) {if (var2.equals("Ruby")) {this.stager = Transforms.toPython(this.stager);} else if (var2.equals("COM Scriptlet")) {if (var3) {DialogUtils.showError(var2 + " is not compatible with x64 stagers");return;}this.stager = (new ArtifactUtils(this.client)).buildSCT(this.stager);} else if (var2.equals("Veil")) {this.stager = Transforms.toVeil(this.stager);} else if (var2.equals("VBA")) {this.stager = CommonUtils.toBytes("myArray = " + Transforms.toVBA(this.stager));}}CommonUtils.writeToFile(new File(var1), this.stager);DialogUtils.showInfo("Saved " + var2 + " to\n" + var1);}

此方法判斷使用者所選擇的生成語言進入不同的shellcode生成,将第一個方法擷取到的可變參數傳入

進入

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

生成代碼内部我一下子明白了很多,var0是使用者配置參數,最後卻成為了最後生成shellcode的内容?并且參數都是var0,此處應該有一個觀點就是所有語言生成的shellcode其實是同一套東西

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

隻是不同的語言聲明數組的方式不同,然後根據語言不同去湊編碼。然後把内容給Packer對象指派進行下一步處理。

這正驗證了我分析前提出的第一種猜想,架構源碼中根本沒有木馬源碼。是以源碼級免殺的想法到這裡結束了。

思路轉變

沒有源碼隻能又回歸剛開始從shellcode下手,但是java不行。又隻能從python下手,根據網上的例子從加載器上入手做源碼級免殺。

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

把加載器的代碼删删改改,各種方法抽離,各種變量名稱更改,最後

在linux下做源碼免殺,Cobaltstrike免殺從源碼級到落地思維轉變

然後下載下傳了哈勃,360等實戰依然過了。

說明隻要會代碼,免殺随便玩。

總結:

從木馬源碼級免殺到最後走投無路隻能又從加載器源碼免殺入手,其實我想簡單了,但又想複雜了。複雜在于目前國内的殺軟依然停留在靜态清除的地位(個人觀點,可以交流但别噴)隻是更新特征庫真的快,造成這種原因可能是因為病毒和殺軟之間殺軟本身所處的地位不占優勢,也就是被動,先功後防的狀态。簡單在于通過檢視Cobaltstrike源碼,他的邏輯和思路另我這個開發也自愧不如,雖然國内的it水準已經日益見長,但是進步空間真的很大。

謝謝大家,可以繼續關注我,關注我的專輯