Derinlemesine Threading..(3)

// 3 Nisan 2010 // Delphi, İşletim Sistemi, Win32

Threading ile ilgili bir önceki makelemizde Event ve WaitableTimer senkronizasyon mekanizmalarını anlatmış ve konuya ışık tutabilmesi adına örnekler paylaşmıştık. Makalemizin sonunda ise; bir thread’i normal yollarla durdurabilmek için(TerminateThread API’sini kullanmadan) bir makale yazacağımdan bahsetmiştim. Bu makalemizin konusu verdiğim söz gereği; bir thread’i sonlandırma seçenekleri ile ilgili olacak.

Ancak, sizlerden ricam bu makalede ilerlemeden önce Threading ile ilgili yazılmış olan diğer makaleleride okumanızdır. İlgili makalelere aşağıdaki linklerden erişebilirsiniz:

Nedir bu Thread’lerden çektiğimiz..!
Derinlemesine Threading..(1)
Derinlemesine Threading..(2)

Şimdi tüm bu makaleleri okuduğunuzu ve threading hakkında fikriniz olduğunu varsayarak, bir thread’i neden durdurmak isteyebileceğimizi ve karşımıza ne gibi sorunların çıkabileceğini biraz izah etmeye çalışalım. Bildiğiniz gibi thread’leri genellikle paralel programlama yapabilmek, iş yükünü dağıtabilmek, ana uygulamamızın kilitlenmesine mani olabilmek adına kullanırız. Ve bazen, thread’lerimizin içindeki kodlar uzun süreli ve hatta kullanıcı ile interaktif çalışıyor da olabilir.

Bazı durumlarda, yazdığımız thread’lerin içinde çalışan kod bloklarını sonlandırmak isteriz. Buna sanırım en güzel örnek, veritabanına bağlanıp büyük bir sonuç seti çekmeye çalıştığımız zamanlarda programımızın kullanıcısının isteği ile rapor alımını durdurmak verilebilir.

Pek çok programcı bu gibi durumlarda, çalışan thread’ini durdurabilmek için TThread sınıfının Terminated özelliğini kontrol eder. Terminated’in true olması durumunda thread çalışma kodundan çıkılmasını sağlar. Buna küçük bir örnek verebiliriz;

procedure TMyThread.Execute;
begin
  inherited;

  while not Terminated do
  begin
     //...
     //...
  end;
end;

Yukarıdaki örnek, sıklıkla kullanılan çok genel bir örnektir. Malumunuz olduğu üzere; TThread sınıfının Terminate metodu bir thread’i durdurma işini yapmaz. Terminate metodu; Terminated isimli property’nin True olarak set edilmesini sağlar. Programımız içerisinde herhangi bir yerde TThread sınıfının Terminate metodunun çağrılması yukarıdaki kod örneği için ilgili thread’in sonlanması anlamını taşır.

Buraya kadar anlattıklarımızda bir sıkıntı ve bu makaleye hayat verecek bir neden de yok gibi görünüyor. Ancak; bizler her zaman thread’lerimiz içinde yukarıdaki kısa kod örneğinde olduğu gibi Terminated property’sinden istifade edemeyiz. Bu hususta da kısa bir örnek verip devam etmek sanırım daha açıklayıcı olacaktır:

procedure TMyThread.Execute;
begin
  inherited;

  with TADOStoredProc.Create(nil) do
  begin
    Connection := myThreadSpecificConnection;
    ProcedureName := 'sp_Stok_Hareket_List';
    Parameters.Refresh;
    Open; // Tüm stokların çekildiğini ve stok hareket tablosunda 10 milyon kayıt olduğunu düşünelim.

    //...
    //...
  end;
end;

Yukarıdaki örneğimiz; gerçek hayatta sıklıkla kullandığımız bir yapıya işaret ediyor. Örneğimizde; bir thread içinden Stok Hareket Raporu almaya çalışıyoruz. Ve Stok Hareket tablomuzdaki kayıt sayısının da 10 milyon olduğunu varsaydık. Bu gibi bir durumda; Open çağrısı en iyi ihtimalle onlarca dakika sürecektir. İşte tam bu noktada, programınızı kullanan kullanıcının; raporun alınmasının uzun sürmesinden mütevellit işlemden sıkılması ve iptal etmesi isteği hasıl olursa ne yapabiliriz ?

Bütün iş yükünü oluşturan kod TADOStoredProc’un Open metodunun içinde olduğu için; Terminated vb. kontrolleri kullanma imkanına sahip değiliz. Bu gibi zamanlarda, karşımıza bu makaleye konu olacak derin hususlar devreye girecektir.

Bir thread’i sonlandırabilmek için işletim sistemi API’lerinde bizlere sunulan bir kaç seçenek vardır. Bunlar; TerminateThread ve ExitThread API’leridir. Bu API’lerin ne işe yaradıklarını anlatmadan önce her zamanki gibi tanımlarına bir göz gezdirelim:

  VOID ExitThread(
    DWORD dwExitCode 	// exit code for this thread 
   );

  BOOL TerminateThread(
    HANDLE hThread,	// handle to the thread 
    DWORD dwExitCode 	// exit code for the thread 
   );

ve Delphi tanımlarımız:

  procedure ExitThread(dwExitCode: DWORD);
  function TerminateThread(hThread: THandle; dwExitCode: DWORD): BOOL;

ExitThread API’si o anda çalışan uygulamanın içindeki thread’lerin hangisinde çağırılıyor ise o thread’den çıkmak için kullanılır. Yukarıdaki örneğimizde; TMyThread sınıfının Execute metodunda Open’dan sonra ExitThread API’sini kullanmak yine bizim istediğimiz neticeyi vermeyecektir.

ExitThread API’sini uygulamamızın main thread’inde çağırmak demek uygulamamızın kapanması anlamına geleceğinden diğer API’mize bakmak durumunda olacağız. TerminateThread ise kendisine geçilen bir thread handle vasıtası ile ilgili thread’i sonlandırabilme yeteneğine sahiptir.

Bizim istediğimiz işi TerminateThread API’si yapabildiğine göre o halde bu makaleye ne gerek vardı diye düşünüyor olabilirsiniz ?

Elbette bunun çok yerinde nedenleri var. TerminateThread API’sinin kullanımı son derece tehlikelidir. Bir thread içinde senkronizasyon nesneleri kullanıyorsanız(Critical Section, Event, Semaphore, Mutex vb.) TerminateThread çağrısından sonra thread’inizin sonlandırılma kodları çağırılamayacağı için, ilgili senkronizasyon nesneleriniz sürekli açık yada kapalı konumlarında kalabilir. Bu da, bu senkronizasyon nesnelerini ortak kullanan diğer thread’leriniz için son derece büyük bir sorun teşkil edebilir. Aynı zamanda; TerminateThread ile yapacağınız sonlandırmalar; thread içinde oluşturduğunuz nesnelerinizin Free edilememesi, ayırdığınız hafıza bloklarının işletim sistemine iade edilememesi gibi ciddi sorunlara neden olabilir.

İşte bu sebeplerden ötürü, gerçekten çok ihtiyacınız yoksa TerminateThread API’sinin kullanılması tavsiye edilmez.! Peki bu durumda, biz thread’lerimizi sonlandıramayacak mıyız ?

Evet, sonlandırabileceğiz. Ancak, biraz karmaşık bir yöntem kullanmamız gerekecek. Bu yöntemi izaha geçmeden önce; diğer threading mekanizmalarında gördüklerinizi yeniden anımsamanız gerekecek. Hatırlayacağınız üzere, işletim sistemi aynı anda sadece bir tane işlem yapabilmektedir. Thread’lerin birbirleri arasındaki geçişlerinin 20 ms. olduğunu ve bu geçişler için Round Robin denilen algoritmanın kullanıldığını sanırım anımsıyorsunuz.

Tam bu noktada; ilgili threading makalelerinde değinmediğimiz bir soru sormamız gerekiyor kendimize. İşletim sistemi thread’ler arasında geçişler yaparken; thread’lere has local değişkenlerin bilgilerini, o anda thread execute kodunun hangi satırında kaldığını, hangi nesnelerle işlem yaptığını ve o nesnelerin hafıza adreslerinin ne olduğunu nereden biliyor olabilir ?

Eğer işletim sistemi thread’lerle alakalı tüm bu bilgilere haiz olmasa idi, thread’ler arasında geçişi sağlayamaz; sağlasa da CPU son işlediği komutu bilemeyeceği için gerçek bir threading’den söz edilemezdi.

Sizlere bu hususu izah edebilmem için az da olsa assembly bilgisine haiz olmanız gerekmekte. Bildiğiniz gibi; tüm programlama dillerinde üretilen çalıştırılabilir dosyalar işlemci assembly kodlarına dönüştürülür ve öyle icra edilir. Olayın derinliklerinde CPU’nun sadece 0(Sıfır) ve 1(Bir) lerden anladığı da bir gerçekken, assembly kodları da makina kodlarına dönüştürülür.

Kabaca; CPU’nun çalışacak kodları işletebilmesi için çeşitli register‘lara ihtiyacı vardır. Hemen hemen hepimiz az da olsa bu register’lara aşinayızdır. acx, ecx, ah, al, eip, cs, ds gibi registerları en azından görmüş yada bunlar hakkında bir genel kültüre sahibizdir.

Delphi programlama ortamında CPU Editoru aşağıdaki gibi görünür. Sizlerinde aşağıdaki resimde gözlemleyebileceğiniz gibi; pek çok register ve assembly kodları görünmekte. Şimdilik sadece EIP register‘ının içindeki değerin(0045187C) sol taraftaki Memo1.Lines.Add kod satırındaki numara ile aynı olduğunu farketmenizi rica ediyorum.

CPU Editor

Örneğimizde ki 0045187C bir hafıza adresidir ve o anda CPU tarafından işletilen kodun nerede olduğunu bulmaya yarar. CPU bu değere her zaman EIP registerı içinden erişir. Satır satır işletilen her kod’un hafıza edresi EIP registerı içerisinde tutulur.

Tüm bu bilgileri vermemizin nedeni; işletim sisteminin thread geçişlerinde yukarıda bir kısmını gözlemleyebildiğiniz pek çok CPU register’ını bir yere saklıyor olmasından ve thread’e geri dönüşte ilgili register’ların içeriklerini sakladığı yerden yeniden okuyup eski haline almasından ötürüdür. İşletim sistemi thread geçişlerinde ihtiyacı olan tüm bilgileri _CONTEXT isimli bir record’un içine doldurur ve bir yerlerde bu record’u saklar. Thread’e geri dönmesi gerektiğinde sakladığı yerdeki bilgileri _CONTEXT recordunun içerisine geri yükler ve bu sayede CPU kaldığı noktadan kodları işletmeye devam edebilir.

İzahatlarımızda daha fazla ilerlemeden evvel, bu recordun yapısını sizlerle paylaşayım:

  _CONTEXT = record
  {$EXTERNALSYM _CONTEXT}

  { The flags values within this flag control the contents of
    a CONTEXT record.

    If the context record is used as an input parameter, then
    for each portion of the context record controlled by a flag
    whose value is set, it is assumed that that portion of the
    context record contains valid context. If the context record
    is being used to modify a threads context, then only that
    portion of the threads context will be modified.

    If the context record is used as an IN OUT parameter to capture
    the context of a thread, then only those portions of the thread's
    context corresponding to set flags will be returned.

    The context record is never used as an OUT only parameter. }

    ContextFlags: DWORD;

  { This section is specified/returned if CONTEXT_DEBUG_REGISTERS is
    set in ContextFlags.  Note that CONTEXT_DEBUG_REGISTERS is NOT
    included in CONTEXT_FULL. }

    Dr0: DWORD;
    Dr1: DWORD;
    Dr2: DWORD;
    Dr3: DWORD;
    Dr6: DWORD;
    Dr7: DWORD;

  { This section is specified/returned if the
    ContextFlags word contians the flag CONTEXT_FLOATING_POINT. }

    FloatSave: TFloatingSaveArea;

  { This section is specified/returned if the
    ContextFlags word contians the flag CONTEXT_SEGMENTS. }

    SegGs: DWORD;
    SegFs: DWORD;
    SegEs: DWORD;
    SegDs: DWORD;

  { This section is specified/returned if the
    ContextFlags word contians the flag CONTEXT_INTEGER. }

    Edi: DWORD;
    Esi: DWORD;
    Ebx: DWORD;
    Edx: DWORD;
    Ecx: DWORD;
    Eax: DWORD;

  { This section is specified/returned if the
    ContextFlags word contians the flag CONTEXT_CONTROL. }

    Ebp: DWORD;
    Eip: DWORD;
    SegCs: DWORD;
    EFlags: DWORD;
    Esp: DWORD;
    SegSs: DWORD;
  end;

Gördüğünüz gibi pek çok üyesi var. Ancak hemen endişeye kapılmayın. Bizler sadece EIP üyesi ile ilgileniyor olacağız. Record’un diğer üyeleri şu an için bizim konumuz dışında olması münasebeti ile bu üyeler hakkındaki kısıtlı bilgilerimi sizlerle paylaşmıyorum.

Tüm bu açıklamalardan sonra; bir thread’i nasıl sonlandırabileceğimiz hakkında hâla kafanızda soru işaretleri olduğunu hissediyorum. Biz bunun için; genellikle Reverse Engineering konularından birisinin kapsamına gireceğiz. Yani bir thread’in o anda işletmekte olduğu kodun adresini bir başka metodun adresi ile değiştireceğiz. Yapacağımız işin sırrı da tam olarak bu noktada.

Kısaca ve kabaca ifade etmek gerekir ise; thread’in zaman alan bir işlemi yürütüyor olduğu sırada EIP registeri içindeki CPU’nun işlettiği kodun adresini bir başka kod adresi ile değiştireceğiz. Böylece CPU’yu kandırmış olacağız ve thread’in kodları içine bir nevi code injection yapmış olacağız. Bu senaryoyu aşağıdaki pseudo kod ile örnekleyebiliriz:

procedure TMyThread.Execute;
begin
  inherited;

  with TADOStoredProc.Create(nil) do
  begin
    Connection := myThreadSpecificConnection;
    ProcedureName := 'sp_Stok_Hareket_List';
    Parameters.Refresh;
    Open; // Tüm stokların çekildiğini ve stok hareket tablosunda 10 milyon kayıt olduğunu düşünelim.

    //...
    //...
  end;
end;

procedure TForm1.Button1Click(Sender : TObject);
begin
  Change_EIP_Register(myThread, @MyProcedure);
end;

procedure MyProcedure;
begin
  raise Exception.Create('Thread dursun artık..!');
end;

procedure Change_EIP_Register(Thread : TThread; ProcedureAddr : Pointer);
begin
  // Thread'i durdur(Suspend)
  // Thread'in Context record bilgilerini al.
  // Context recordunun içindeki EIP üyesinin değerini ProcedureAddr ile değiştir.
  // Yeni context recordunu thread'in eski context'i ile değiştir.
  // Thread'i yeniden çalıştır(Resume)
end;

Yukarıdaki pseudo kodda görebileceğiniz gibi öncelikle thread’imizi durdurmamız icap ediyor. Context bilgilerinin alınabilmesi için bu şart. Ardından Context record’u içindeki EIP registerımızın içinde bulunan o anda çalışmakta olan kod satırının adresini; yeni tanımladığımız MyProcedure ile değiştiriyor ve thread context’imizi güncelleyip thread’imizi yeniden çalıştırıyoruz. Bu aşamada; CPU direkt olarak MyProcedure metodunun adresini EIP registerından okur ve ilgili kodları çalıştırmaya başlar. MyProcedure metodunun içinde bir exception oluşturduğumuz içinde thread’imiz anında sonlanacaktır ve bu vesile ile thread sonlandırma kodlarımız da gerektiği gibi işletilebilecektir(OnTerminate).

Şimdi sıra bize gereken API’lerin tanımlarına geldi:

  DWORD SuspendThread(
    HANDLE hThread 	// handle to the thread 
   );

  BOOL GetThreadContext(
    HANDLE hThread,	// handle of thread with context  
    LPCONTEXT lpContext 	// address of context structure 
   );

  BOOL SetThreadContext(
    HANDLE hThread,	// handle of thread with context 
    CONST CONTEXT *lpContext 	// address of context structure 
   );

  DWORD ResumeThread(
    HANDLE hThread 	// identifies thread to restart 
   );

ve Delphi tanımları:

  function SuspendThread(hThread: THandle): DWORD;
  function GetThreadContext(hThread: THandle; var lpContext: TContext): BOOL;
  function SetThreadContext(hThread: THandle; const lpContext: TContext): BOOL;
  function ResumeThread(hThread: THandle): DWORD;

Tanımlarımızı da sizlerle paylaştığımıza göre; artık örnek kodumuzu yazabiliriz:

var
  mThread : TMyThread;

implementatiton

constructor TMyThread.Create;
begin
  inherited Create(true);
  FreeOnTerminate := true;
end;

procedure TMyThread.Execute;
begin
  inherited;

  with TADOStoredProc.Create(nil) do
  begin
    Connection := myThreadSpecificConnection;
    ProcedureName := 'sp_Stok_Hareket_List';
    Parameters.Refresh;
    Open; // Tüm stokların çekildiğini ve stok hareket tablosunda 10 milyon kayıt olduğunu düşünelim.

    //...
    //...
  end;
end;

procedure TForm1.ThreadTerminated(Sender : TObject);
begin
  Memo1.Lines.Add('Bitiş Zamanı:' + TimeToStr(Time)); // Memo1 TMemo türündedir.
end;

procedure TForm1.StartThreadClick(Sender : TObject); // StartThread TButton türündedir.
begin
  Memo1.Lines.Add('Başlangıç Zamanı:' + TimeToStr(Time)); // Memo1 TMemo türündedir.

  mThread := TMyThread.Create;
  mThread.OnTerminate := ThreadTerminated;
  mThread.Resume;
end;

procedure StopTheThread;
begin
  raise Exception.Create('Thread terminated by using Context Record EIP Register..');
end;

procedure TForm1.StopThreadClick(Sender : TObject); // StopThread TButton türündedir.
var
  ctx : _CONTEXT;
  ThreadHandle : THandle;
begin
  ThreadHandle := mThread.Handle;

  SuspendThread(ThreadHandle); // Thread'i durdur..
  ctx.ContextFlags := CONTEXT_FULL; // Tüm Context bilgisini istedik.
  GetThreadContext(ThreadHandle, ctx);

  ctx.Eip := Cardinal(@StopTheThread);

  SetThreadContext(ThreadHandle, ctx);
  ResumeThread(ThreadHandle); // Thread'i başlat..
end;

Yukarıdaki kodu çalıştırıp denemelerinizi yaptığınızda uzun süren thread’inizin sonlandırıldığını ve sonlandırılma kodlarının da düzgün bir şekilde çalışabildiğini gözlemleyebilirsiniz. Bu yöntem, biraz karmaşık ve uzun olsa da; thread sonlandırmanız gerektiğinde kullanmanız icap eden en önemli yöntemdir. TerminateThread API’sinin dezavantajlarının hiçbirisine sahip olmaması programlarınızın daha güvenli çalışabilmesi adına büyük bir avantajdır.

Elbette bu yöntem kötüye de kullanılabilir, EIP register’ının içindeki değer değiştirilmeden önce bir başka değişkende saklanırsa ve ardından değiştirilir ve thread yeniden çalıştırılırsa; buna mukabil eski EIP değeri yeniden context’e atanırsa bir thread’in içine code injection yapmış olursunuz. Buna da küçük bir örnek vererek makalemi neticelendirmek istiyorum müsaadeniz ile:

procedure StopTheThread;
begin
  Memo1.Lines.Add('Thread durduruldu.!');
end;

procedure TForm1.StopThreadClick(Sender : TObject); // StopThread TButton türündedir.
var
  ctx : _CONTEXT;
  ThreadHandle : THandle;
  OldEIP : Cardinal;
begin
  ThreadHandle := mThread.Handle;

  SuspendThread(ThreadHandle);
  ctx.ContextFlags := CONTEXT_FULL; // Tüm Context bilgisini istedik.
  GetThreadContext(ThreadHandle, ctx);
  OldEIP := ctx.EIP;
  ctx.Eip := Cardinal(@StopTheThread);

  SetThreadContext(ThreadHandle, ctx);
  ResumeThread(ThreadHandle);

  Sleep(5000); // 5 sn. yeni kodumuzun çalışmasını bekleyelim ve eski çalışma şekline geri dönelim.

  SuspendThread(ThreadHandle);
  ctx.ContextFlags := CONTEXT_FULL; // Tüm Context bilgisini istedik.
  GetThreadContext(ThreadHandle, ctx);
  ctx.Eip := OldEIP;

  SetThreadContext(ThreadHandle, ctx);
  ResumeThread(ThreadHandle);
end;

Kötü amaçlarla kullanmamanız dileği ile ;)

Saygılar, sevgiler..

“Derinlemesine Threading..(3)” için 13 Yorum

  1. Olcay DAĞLI diyor ki:

    Thread hakkında herşeyi önceki iki makalede öğrendiğimizi sanmıştım hocam :D
    Meğer bilmedğimiz daha ne çok şey varmış. Akıcı ve öğretici üslubun için teşekkürler hocam…

  2. Veli BOZATLI diyor ki:

    Tekrar bu değerli makaleleri görmek harika.
    Makale de harika ;)

  3. Tuğrul HELVACI diyor ki:

    O sizlerin güzelliği arkadaşlar ;) Derinlere indikçe anlatabilmek daha da bir zorlaşmaya başladı :)

  4. Hasan MANZAK diyor ki:

    Elinize sağlık Tuğrul Bey, her yeni makale biraz daha destansılaşıyor konular derinleştikçe :)

    Thread’i sonlandırmadan kaldığı yerden devam ettirmek ile ilgili ( code injection örneğinizle ilgili ) eklemek istediğim ufak bir şey var. Context verisi içindeki Eip bilgisi, CPU’nun Instruction Pointer bilgisidir ve sizin de belirttiğiniz gibi işlenecek olan bir sonraki emri işaret eder. Sadece bu veriyi değiştirmek, sadece Thread’in sonlanması istendiğinde bir sorun teşkil etmez. Fonksiyonu çağırdığınızda registerlar stack a itilecek, IP ye müdehale edilecek, thread devam ettirilecek, thread sonlanacak, fonksiyondan geri dönülecek, registerlar stacktan çekilecek, thread sonlandığı için, o thread scope u ile ilgili register ların zaten bir manası kalmayacak :) Ama thread sonlanmayıp, code injection gibi bir olay gerçeklemek istemişsek, thread e geri döndüğümüzde, özellikle akümülatör registerlarının içeriklerini geri yerleştirmemiz gerekli ( IP yi değiştirdiğimiz fonksiyondan henüz çıkmadık, dolayısıyla stacktan geri çekme gerçekleşmedi ). Bu durumda thread in devamını sorunsuz sağlayabilmek için sadece IP yi yedeklemek gerekmez, data taşıyan Eax, Edx gibi registerlarını da saklamamız lazım. Çünkü inject ettiğimiz blok bu registerları kullanıp değiştirecektir ;)

    Pratik açıdan henüz test etmiş değilim yalnız teorik açıdan bu şekilde olması gerektiğini düşünüyorum.

    • Tuğrul HELVACI diyor ki:

      Yorumunuz için çok teşekkürler Hasan bey. Dediklerinizde haklısınız , evet pek çok register’ın saklanması gerekir, özellikle de eax’in. Asm konusunda pek fazla malümatım olmadığı için derinleştiremiyorum bu sohbeti ancak, benim code injection benzetmesi yapmamdaki temel neden Context recordunun gereken tüm registerları zaten saklıyor olmasından ötürü idi ;)

      Yoksa code injection ile ilgili de bir makale yazmayı düşünüyorum ama yeterince iyi anlatamamaktan çekindiğim için geri duruyorum. Bir diğer neden de; bu tarz makalelerin genelde kendini hacker zanneden lamer’ler tarafından sıklıkla kullanılabilme ihtimali. Pratikte gerçek code injection için bir processin hafıza bölgesinde memory allocation(VirtualAllocEx) yapmak, ardından ilgili parametreleri ve inject edilecek kodu o memory bölgelerine yazmak(WriteProcessMemory) ve en son aşamada da inject edilecek kodu işletmek(CreateRemoteThread) gerektiğinin bilincindeyim :)

  5. QuAdR diyor ki:

    Elinize sağlık…illede inject yapacaksanız context ile oynayıp tekrar dallandırmanıza gerek yok.inject yapacağınız processin yapısını önceden debug edip inceledikten sonra hangi hareketle hangi kod bölgesinin icra edileceğinin analizini yapıp ona göre writeprocessmemory yapmanız yeterlidir.eğer inject edeceğiniz kod o esnada register değerlerini değiştiriyorsa registerları stack e basıp kod icrasından sonra tekrar pop etmeniz ve kalan yere tekrar dallanmanız yeterli olacaktır.bir diğer metod ise eğer inject olacak processi biz başlatacaksak processin oep noktasından boş bir memory bölgesine sıçrayıp kodu icra ettikten sonra tekrar oep e dallanıp programı devam ettirir iseniz işiniz daha kolay olacaktır.daha geniş açıklanabilir bu konular ama yorum yazıyorum burada bu kadar yeter herlde :)

    • Tuğrul HELVACI diyor ki:

      Yorumunuza çok teşekkürler QuAdR. Ancak makalem sanırım yanlış anlaşılmış. Bu makalenin amacı code-injection değil, bir thread’i TerminateThread gibi prematüre sonlandırmalardan kurtarmaktır. Benzetmem de de bir nevi code injection tabiri kullanmıştım, sanırım bu yanlış anlaşılmaya neden oldu. Elbette code injection; hedef process’in hafıza bölgesinde yeterli rezervasyon yapıldıktan (VirtualAllocEx) ve gereken değişkenler yazıldıktan sonra(WriteProcessMemory) CreateRemoteThread ile çalıştırmaktan geçiyor. Bu konularla ilgili de bir makale yazmayı düşünüyorum ama malum bu tarz konular pek derin konular dolayısı ile derinlemesine anlatmak dikkat ve özveri gerektiriyor.

      Ancak, eğer sizde arzu ederseniz bilgilerinizi burada makale olarak yayınlamaktan memnuniyet duyarım.

  6. QuAdR diyor ki:

    Makaleye yorum yazmadım yanlış anlamışsınız.zaten makaleniz yorumsuz olmuş.yapılan injection yorumuna yorum getirmiştim.Benim yaptığım makaleler genelde video tarzı oluyor bide bir çoğu makaleyi okuyan kişiye göre değişir sakıncalı içerik belirtebilir.yukarıki mesajınızda belirttiğiniz üzere kendini hacker sananlar çıkabilir…ama sizinle context yapısı üzerine başka bir forumda yazışmıştık.bu yapı hakkında bir şeyler yazabilirim ama ne zaman olur bilemem.

  7. Ahmet Yeşilçimen diyor ki:

    Forever Turgut Abi :) Abicim Valla Yemin Ederim En Sevdiğim Türk Yazılımcılarından İlkisin Ya Yok böyle birşey süpersin çok güzel anlatıyorsun gerçekden anlamamak elde değil seni çok seviyoruz abicim başarılar bu araa derinlemesine threading 4 de istiyorz :D :D

  8. Hakan GÜVEN diyor ki:

    iyi günler Tuğrul bey. Yorumlar üstünden bayağı zaman geçmiş ama yine de bi sorum olacaktı. Cevap verirseniz sevinirim. Ben thread dizisi oluşturmak istiyorum ve dizinin boyutu çalışma anında alınan değere göre belirlenecek ve tün threadler aynı fonksiyon üüzerinden işlem yapacak. Bu işlemler ise veritabanı işlemleri. Acaba delphi de böyle bişey mümkün mü??

    • Tuğrul HELVACI diyor ki:

      TThread bir sınıftır sizin de bildiğiniz gibi ve Delphi’de sınıfları tutan liste sınıfları vardır. TThread instance’ları tutan bir generic TList kullanabilirsiniz mesela ama tüm thread’lerin aynı fonksiyon üzerinden işlem yapmasından kastınızı tam olarak anlayamadım. Daha açık ifade edebilirseniz belki başka öneriler de sunabilirim.

      Not: Delphi’de herşey mümkündür :)

  9. Dursun ÇAKIR diyor ki:

    İyi günler Tuğrul Bey;
    Sizlerin bir konuda bilgisine ihtiyacım var. Bir türlü çözemedim sorunu.

    Sorun şu;
    Yazdığım programda (restorant) birden fazla terminal bir ana bilgisayara bağlı. Ayrıca Bu sistemde 4-5 adet yazıcı var. Bu yazıcılara belli gruptaki yazıları yönlendiriyor. Örneğin içecekler bir yazıcıdan, pizza başka yazıcıdan, çorba ayrı yazıcıdan gibi. Bunu yapabilmek için de Ana bilgisayar üzerinde çalışan bir uygulamam var. Bu uygulama veritabanından 2-3 snde bir yazdırılacakyeni sipariş var mı kontrolünü yapıp varsa gerekli yazıcılara gönderiyor. Bunu da 2-3snyede bir çalışan thread içinden yapıyorum. Bu thread kendi connection ve querylerine sahip. Ancak bir sebepten ötürü bir süre sonra (çok yoğun kullanımda) bu thread çalışmayı durduruyor. Hiç bir hata vermeden. Kodlarda ve querylerde hiç bir hata yok. Sebebini bir türlü bulamadım. Benim bilgisayarımda o kadar iş yükü oluşturamadığım için test debug da edemiyorum. Log kullandım ama durmasına sebep olabilecek hiç bir şey göremedim.

    TYaziciServis = class(TThread)
    private
    FDelay: Integer;
    FConnection : TUniConnection;
    FMySql : TMySQLUniProvider;
    FPgSql : TPostgreSQLUniProvider;
    private
    procedure VeriIsle;
    public
    procedure execute; Override;
    constructor Create(Server, Database, UserName, Password: string; port: Integer; Delay: Integer);
    destructor Destroy; override;
    end;

    procedure TYaziciServis.Execute;
    begin
    inherited;
    while not Terminated do
    begin
    VeriIsle;
    Sleep(FDelay);
    end;
    end;

    procedure TfrmPrintServiceMain.YaziciServisiBaslat;
    begin
    if aAyar.Yazici.YaziciServisAktif then
    begin
    if not Assigned(aYaziciServis) then
    begin
    aYaziciServis := TYaziciServis.Create(aServer, aDatabase, aUsername, aPassword, aPort,
    aAyar.Yazici.YaziciServisSure * 1000);
    aYaziciServis.Priority := tpHighest;
    aYaziciServis.start;
    end;

    end
    else
    begin
    YaziciServisiKapat;
    end;
    end;

    Fikri olan var mı?

    Teşekkür ederim.

Yorum Yazın