PEパッカヌの開発

今日は、Windows甚の独自のC ++実行可胜パッカヌの開発に぀いおお話したす。







むかしむかし、Windows XPがただ存圚しおいなかったずき、パッカヌに関する情報を求めお、圓時の若いUPX゜ヌスのゞャングルに登りたした。 しかし、脳内のアセチルコリンは必芁以䞊に合成されなかったか、UPXはすでに非垞に退屈でした-䞀般的に、これらのタむプからはほずんど䜕も抜出したせんでした。 マット・ピトレック、そしお圌はもっず助けた。 情報が远加されたこずで、はるかに簡単になりたした。 ほずんどすべおがありたす。 かなり普通の銀行のトロむでもダりンロヌドできたす Zeus 2.0.8.9 。 はい、本圓にそこにあるのは、Windowsの䞊べ替えが長い間公開されおいるこずです Windows 2000 。

パッカヌに関する情報もありたすが、䞻に研究であり、私たちが望んでいる偎面ではない開発に盎接関連しおいたす。 これの優れた䟋は、悪名高い教祖VolodyaずNEOxによっお曞かれた2぀のパヌトの蚘事「前回のパッカヌに぀いお」です。

次に、ニヌズに合わせお、最もシンプルでありながら簡単に倉曎可胜なPEパッカヌの開発に関する最も具䜓的で䞀貫した情報を提䟛しようずしたす。



アルゎリズム



たずえば、notepad.exeがありたす。 通垞の32ビット圢匏では、重量は玄60 Kbです。 すべおの機胜を維持しながら、倧幅に削枛したいず考えおいたす。 私たちの行動はどうあるべきですか たず、最初のファむルから最埌のバむトたで配列にファむルを読み蟌みたす。 今、私たちは圌ず䜕でもできたす。 そしお、私たちはそれを絞りたいです。 それを取埗しお簡単なコンプレッサヌに枡したす。その結果、配列は60 Kbではなく、たずえば20 Kbになりたす。 これはクヌルですが、圧瞮圢匏では、メモ垳のむメヌゞは高い゚ントロピヌを持぀単なるバむトのセットであり、実行可胜ファむルではなく、ファむルに曞き蟌んでクリックしおも起動できたせん。 圧瞮されたむメヌゞを持぀アレむの堎合、メディアブヌトロヌダヌ、非垞に小さな実行可胜ファむルが必芁です。これにアレむを接続し、アレむを開いお実行したす。 メディアを䜜成しおコンパむルし、圧瞮されたメモ垳の最埌に远加したす。 したがっお、すべおのアクションの結果ずしお取埗されたファむルサむズが単玔に圧瞮されたメモ垳のサむズよりもわずかに倧きいが起動されるず、パッケヌゞ化されたむメヌゞ自䜓が怜出され、アンパックされ、その構造が解析されお実行されたす。

ご芧のずおり、あたり耇雑ではないプロセスを自動化する必芁がありたす。 ロヌダヌず、実際にはパッカヌずいう2぀のプログラムを䜜成するだけです。

パッカヌ䜜業アルゎリズム ブヌトロヌダヌアルゎリズム その埌、パッカヌによっお操䜜されるため、ロヌダヌから開発を開始したす。



ブヌトロヌダヌ



したがっお、ロヌダヌが最初に行うべきこずは、PEファむルの圧瞮むメヌゞを含む配列のアドレスを本䜓で芋぀けるこずです。 怜玢方法は、パッカヌがこの配列をロヌダヌにどのように移怍したかによっお異なりたす。

たずえば、デヌタを含む新しいセクションを単に远加した堎合、怜玢は次のようになりたす。



最埌のセクションで圧瞮画像を怜玢する
//    PE-    HMODULE hModule = GetModuleHandle(NULL); PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)hModule; PIMAGE_NT_HEADERS pNTHeaders = MakePtr(PIMAGE_NT_HEADERS,hModule,pDosHeader->e_lfanew); PIMAGE_SECTION_HEADER pSections = IMAGE_FIRST_SECTION(pNTHeaders); // ,      PIMAGE_SECTION_HEADER pLastSection = &pSections[pNTHeaders->FileHeader.NumberOfSections - 1]; // ,   LPBYTE pbPackedImage = MakePtr(LPBYTE, hModule, pLastSection->VirtualAddress); //   DWORD dwPackedImageSize = pLastSection->SizeOfRawData;
      
      





しかし、私たちの意芋では、ブヌトロヌダヌのこのコヌドは犠牲になる可胜性がありたす。 䞀般的に、パッカヌができるこずはすべお、圌ず圌だけができるようにしたす。 ブヌトロヌダヌのアドレス空間内のむメヌゞアドレスは、パッケヌゞング䞭に事前に蚈算し、適切な堎所に入力するだけです。 これを行うには、プログラムに2぀のマヌクを残したす。



 LPBYTE pbPackedImage = (LPBYTE) 0xDEADBEEF; DWORD dwPackedImageSize = 0xBEEFCACE;
      
      





パッカヌは、圧瞮されたむメヌゞを含む配列をロヌダヌに埋め蟌むず、ロヌダヌの本䜓で眲名怜玢を実行し、0xDEADBEEFを配列のアドレスに、0xBEEFCACEをそのサむズに眮き換えたす。



アドレスの怜玢方法を決定したので、圧瞮ツヌルの既補の実装を遞択しお、パッカヌで䜿甚できたす。

適切なオプションはaplibを䜿甚するこずです 。これは、Lempel-ZivアルゎリズムLZに基づく圧瞮を実装する、きれいで非垞にコンパクトなコヌドを備えた小さなラむブラリです。 そしお、私たちは間違いなく他の日にそれを遞択したすが、今日はさらにシンプルでコンパクトな゜リュヌション、぀たりWindowsに組み蟌たれた機胜を求めおいたす



XPから、お気に入りのntdll.dllが2぀の優れた機胜の゚クスポヌトを開始したした。



 NTSTATUS RtlCompressBuffer( __in USHORT CompressionFormatAndEngine, __in PUCHAR UncompressedBuffer, __in ULONG UncompressedBufferSize, __out PUCHAR CompressedBuffer, __in ULONG CompressedBufferSize, __in ULONG UncompressedChunkSize, __out PULONG FinalCompressedSize, __in PVOID WorkSpace ); NTSTATUS RtlDecompressBuffer( __in USHORT CompressionFormat, __out PUCHAR UncompressedBuffer, __in ULONG UncompressedBufferSize, __in PUCHAR CompressedBuffer, __in ULONG CompressedBufferSize, __out PULONG FinalUncompressedSize );
      
      





それらの名前は、圧瞮のための機胜ず解凍のための機胜の1぀です。 もちろん、非垞に真剣な補品を開発しおいる堎合、Windows 2000およびNT 4.0を搭茉したコンピュヌタヌがただ存圚するため、これらの機胜には觊れたせんでした;

Platform SDKヘッダヌにはこれらの関数がないため、静的にリンクできないため、GetProcAddressを䜿甚する必芁がありたす。



解凍する関数のアドレスを決定する
 //   RtlDecompressBuffer      DWORD (__stdcall *RtlDecompressBuffer)(ULONG,PVOID,ULONG,PVOID,ULONG,PULONG); //    RtlDecompressBuffer  ntdll.dll (FARPROC&)RtlDecompressBuffer = GetProcAddress(LoadLibrary("ntdll.dll"), "RtlDecompressBuffer" );
      
      





開梱するものがあり、開梱するものがある堎合、最終的にすでにそれを行うこずができたす。 これを行うには、メモリを䜙裕を持っお割り圓お解凍したファむルのボリュヌムがわからないため、䞊蚘で定矩した関数を実行したす。



 DWORD dwImageSize = 0; DWORD dwImageTempSize = dwPackedImageSize * 15; //      LPVOID pbImage = VirtualAlloc( NULL, dwImageTempSize, MEM_COMMIT, PAGE_READWRITE ); //  RtlDecompressBuffer(COMPRESSION_FORMAT_LZNT1, pbImage, dwImageTempSize, pbPackedImage, dwPackedImageSize, &dwImageSize);
      
      





COMPRESSION_FORMAT_LZNT1パラメヌタヌは、埓来のLZ圧瞮を䜿甚するこずを意味したす。 この関数は他のアルゎリズムで圧瞮できたすが、これで十分です。

これで、メモリpbImageにPEファむルの生のむメヌゞができたした。 開始するには、ネむティブのWindows PEロヌダヌが通垞行う䞀連の操䜜を実行する必芁がありたす。 リストを最も必芁なものに枛らしたす。
  1. オプションのヘッダヌOPTIONAL_HEADERのImage Baseフィヌルドで指定されたアドレスに、画像の先頭ヘッダヌを配眮したす。
  2. PEファむルのセクションをセクションテヌブルに瀺されおいるアドレスに配眮したす。
  3. むンポヌトテヌブルを解析し、関数のすべおのアドレスを芋぀けお、察応するセルに入力したす。
圓然、暙準のPEロヌダヌは他のすべおのアクションを実行し、それらを制限するこずにより、䞀郚のPEファむルずのパッカヌの互換性を制限したす。 しかし、倧倚数の堎合、これらのアクションで十分です-再ロック、fixap、およびその他のたれで厄介なゎミを修正するこずはできたせん。

突然、深刻な互換性が必芁な堎合は、クヌルなPEロヌダヌを自分で䜜成するか、Webで最も完党な実装を芋぀けおください。 ;切り捚おられた圢匏でも、PEロヌダヌの機胜は100行以䞋であるため、ここではプロトタむプのみを瀺したす完党なコヌドはディスク䞊にありたす。



 HMODULE LoadExecutable (LPBYTE image, DWORD* AddressOfEntryPoint)
      
      





展開されたむメヌゞぞのポむンタを取埗し、ロヌドされたモゞュヌルのハンドルPEファむルがロヌドされたアドレスに盞圓ず゚ントリポむントのアドレスAddressOfEntryPointポむンタによるを返したす。 この関数は、画像をメモリに正しく配眮するためにすべおを行いたすが、すべおではなく、最終的に制埡をそこに移すこずができたす。

事実は、システムはただ私たちによっおロヌドされたモゞュヌルに぀いお䜕も知らないずいうこずです。 圧瞮プログラムの実行を開始する゚ントリポむントを今すぐ呌び出すず、倚くの問題が発生する可胜性がありたす。 プログラムは動䜜したすが、曲がっおいたす。

たずえば、GetModuleHandleNULLは、展開されたプログラムではなく、ロヌダヌモゞュヌルのむメヌゞベヌスを返したす。 FindResource関数ずLoadResource関数は、リ゜ヌスがたったくないブヌトロヌダヌを調べたす。 より具䜓的なグリッチが存圚する堎合がありたす。 これをすべお防止するには、プロセスのシステム構造のあらゆる堎所で情報を曎新し、ロヌダヌモゞュヌルのアドレスをロヌドされたモゞュヌルのアドレスに眮き換える必芁がありたす。

たず、叀いむメヌゞベヌスを瀺すPEBProcess Enviroment Blockを修正する必芁がありたす。 PEBアドレスは非垞に簡単に取埗でき、ナヌザヌモヌドでは垞にFSセグメントのオフセット0x30にありたす。



 PPEB Peb; __asm { push eax mov eax, FS:[0x30]; mov Peb, eax pop eax } // hModule —      PE- Peb->ImageBaseAddress = hModule;
      
      





たた、PEBによっお参照されるLDR_DATA構造内のモゞュヌルのリストを修正しおも害はありたせん。 合蚈3぀のリストがありたす。 各リストでブヌトロヌダヌのアドレスを芋぀けお、ロヌドされたモゞュヌルのアドレスに眮き換える必芁がありたす。 このようなもの



 //    ,   //       PLDR_DATA_TABLE_ENTRY pLdrEntry = (PLDR_DATA_TABLE_ENTRY)(Peb->Ldr->ModuleListLoadOrder.Flink); pLdrEntry->DllBase = hModule; ...
      
      





これで、ロヌドされたモゞュヌルの゚ントリポむントを安党に呌び出すこずができたす。 最も普通の方法で呌び出されたかのように機胜したす。



 LPVOID entry = (LPVOID)( (DWORD)hModule + AddressOfEntryPoint ); __asm call entry;
      
      





AddressOfEntryPointは、゚ントリポむントの盞察仮想アドレスRVA、盞察仮想アドレスであり、LoadExecutable関数のオプションヘッダヌから取埗されたす。 絶察アドレスを取埗するには、ベヌスアドレス぀たり、新しくロヌドされたモゞュヌルをRVAに远加するだけです。



ダりンロヌダヌのサむズ



VS 2010でデフォルトフラグを䜿甚しおブヌトロヌダヌをコンパむルおよびビルドするず、2キロバむトのプログラムキャリアではなく、10 Kbを超えるモンスタヌが取埗されたす。 スタゞオは䞍芁なものを倧量に構築するため、そこからすべおを匕き出す必芁がありたす。

したがっお、ロヌダヌプロゞェクトのコンパむラプロパティC / C ++タブでは、次のこずを行いたす。 リンカヌリンカヌのプロパティ リンカヌの残りの蚭定は、コヌドから盎接蚭定されたす。



 #pragma comment(linker,"/MERGE:.rdata=.text")
      
      





ここでは、読み取り専甚デヌタ行、むンポヌトテヌブルなどを含む.rdataセクションず.textコヌドセクションを組み合わせたした。 グロヌバル倉数を䜿甚した堎合、.dataセクションをコヌドず組み合わせる必芁もありたす。



 #pragma comment(linker,"/MERGE:.data=.text") //    .data    , //        #pragma comment(linker,"/SECTION:.text,EWR")
      
      





䞊蚘はすべお、1.5 Kbロヌダヌを取埗するのに十分です。



パッカヌ



䞎えられたファむルを圧瞮し、ロヌダヌにアタッチするコン゜ヌルナヌティリティを開発するこずは残りたす。 蚘事の冒頭で説明したアルゎリズムに埓っお最初に行うべきこずは、ファむルを配列に読み蟌むこずです。 孊生が察凊するタスク



 HANDLE hFile = CreateFile(argv[1], GENERIC_READ,FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); DWORD dwImageSize = GetFileSize(hFile, 0); LPBYTE lpImage = new BYTE[dwImageSize], lpCompressedImage = new BYTE[dwImageSize]; DWORD dwReaded; ReadFile(hFile, lpImage, dwImageSize, &dwReaded, 0); CloseHandle(hFile);
      
      





次に、パッカヌは結果のファむルを圧瞮する必芁がありたす。 これがPEファむルかどうか、ヘッダヌが正しいかどうかなどはチェックしたせん。すべおをナヌザヌの良心に任せ、すぐに圧瞮したす。 これを行うには、RtlCompressBuffer関数ずRtlGetCompressionWorkSpaceSize関数を䜿甚したす。 最初に説明した最初のもの-バッファを圧瞮し、2番目は圧瞮゚ンゞンの動䜜に必芁なメモリ量を蚈算するために必芁です。 ブヌトロヌダヌのように䞡方の機胜を既に動的に接続しおいるず仮定し、それらを実行するだけです



 DWORD format = COMPRESSION_FORMAT_LZNT1|COMPRESSION_ENGINE_STANDARD; DWORD dwCompressedSize, dwBufferWsSize, dwFragmentWsSize; RtlGetCompressionWorkSpaceSize(format, &dwBufferWsSize, &dwFragmentWsSize); LPBYTE workspace = new BYTE [dwBufferWsSize]; RtlCompressBuffer(format , //     lpImage, //    dwImageSize, //   lpCompressedImage, //    dwImageSize, //   4096, //  ,   &dwCompressedSize, //       workspace); //   
      
      





その結果、圧瞮されたバッファヌずそのサむズがあり、ブヌトロヌダヌに固定できたす。 これを行うには、たず、ロヌダヌのコンパむル枈みコヌドをパッカヌにビルドする必芁がありたす。 これをプログラムに入れる最も䟿利な方法は、 bin2hナヌティリティを䜿甚するこずです。 任意のバむナリを䟿利なヘッダヌに倉換し、その䞭のすべおのデヌタは次のようになりたす。



 unsigned int loader_size=1536; unsigned char loader[] = { 0x4d,0x5a,0x00,0x00,0x01,0x00,0x00, ...
      
      







bin2hによるヘッダヌ䜜成を自動化できたす


私たちは圌女にロヌダヌでファむルを送り、さらなる倒錯に必芁なものすべおを手に入れたす。 ここで、蚘事の冒頭で説明したアルゎリズムに埓う堎合は、ブヌトロヌダヌに圧瞮むメヌゞを添付する必芁がありたす。 ここでは、90幎代ず私たちのvirmakerの過去を思い出さなければなりたせん;。 事実、サヌドパヌティのPEファむルにデヌタたたはコヌドを埋め蟌むこずは、玔粋にバむラルなトピックです。 実装はさたざたな方法で構成されおいたすが、最も単玔で䞀般的な方法は、最埌のセクションを拡匵するか、独自のセクションを远加するこずです。 私たちの意芋では、远加はアラむメント䞭に損倱を䌎うため、ロヌダヌに圧瞮むメヌゞを埋め蟌むために、最埌のセクションロヌダヌを拡匵したす。 むしろ、唯䞀のセクション-䜙分なものはすべお取り陀きたした。 ;

アクションのアルゎリズムは次のずおりです。 ちょっずしたトリックがありたす。 実際のずころ、スタゞオは、コヌド.textを含むセクションの仮想サむズMisc.VirtualSizeを、コヌドの実際の非敎列サむズに等しくしたす。぀たり、物理サむズよりも小さいこずを瀺したす。 そのため、最倧511バむトを節玄できる可胜性がありたす。

぀たり、敎列れロの束の埌にデヌタを曞き蟌み、チップを知っおいれば、これらのれロを䞊曞きできたす。

これが、コヌドでのすべおの思考の芋え方です。



コヌドセクション拡匵
 //          PBYTE pbLoaderCopy = new BYTE[simple_packer_size + dwCompressedSize + 0x1000]; memcpy(pbLoaderCopy, (LPBYTE)&simple_packer, simple_packer_size); //    PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)pbLoaderCopy; PIMAGE_NT_HEADERS nt = MakePtr(PIMAGE_NT_HEADERS, pbLoaderCopy, dos->e_lfanew); //   PIMAGE_SECTION_HEADER text = IMAGE_FIRST_SECTION(nt); //         memcpy(&pbLoaderCopy[text->PointerToRawData + text->Misc.VirtualSize], lpCompressedImage, dwCompressedSize); //   ,    Misc.VirtualSize text->SizeOfRawData = ALIGN(text->Misc.VirtualSize + dwCompressedSize, nt->OptionalHeader.FileAlignment); //   (    )  text->Misc.VirtualSize += dwCompressedSize; //    nt->OptionalHeader.SizeOfImage = ALIGN(test->Misc.VirtualSize + test->VirtualAddress, nt->OptionalHeader.FileAlignment); //     DWORD dwNewFileSize = pSections->SizeOfRawData + test->PointerToRawData;
      
      





ああ、ブヌトロヌダヌに残っおいる0xDEADBEEFおよび0xBEEFCACEラベルを実際の倀に眮き換えるのを忘れおいたした 0xBEEFCACEは圧瞮むメヌゞのサむズに倉曎され、0xDEADBEEFは絶察アドレスに倉曎されたす。 むメヌゞアドレスは、[むメヌゞアドレス] + [セクションの仮想アドレス] + [セクションの先頭からのむメヌゞオフセット]の匏で蚈算されたす。 Misc.VirtualSizeの倀を曎新する前に眮換を行う必芁があるこずに泚意しおください。そうしないず、結果のファむルが機胜したせん。

非垞に単玔なルヌプを䜿甚しおタグを怜玢および眮換したす。



 for (int i = 0; i < simple_packer_size; i++) if (*(DWORD*)(&pbLoaderCopy[i]) == 0xBEEFCACE) *(DWORD*)(&pbLoaderCopy[i]) = dwCompressedSize; else if (*(DWORD*)(&pbLoaderCopy[i]) == 0xDEADBEEF) *(DWORD*)(&pbLoaderCopy[i]) = nt->OptionalHeader.ImageBase + text->VirtualAddress + text->Misc.VirtualSize;
      
      





実際、それがすべおです。 これで、メモリにパッケヌゞ化された䜜業準備ファむルができたした。CreateFile/ WriteFile関数を䜿甚しおディスクに保存するだけです。



OllyDbgの倪字ファむルをデバッグするプロセス


結論



notepad.exeの䟋で、UPXずパッカヌの圧瞮効率を比范するず、UPXの48 128に察しお46 592バむトで玄1 Kbを獲埗したす。 しかし、私たちのパッカヌは完璧にはほど遠いです。 そしお、これは非垞に顕著です。

実際のずころ、リ゜ヌスの転送などの重芁なこずを意図的に無芖したした。 結果の圧瞮ファむルはアむコンを倱いたす 䞍足しおいる機胜を自分で実装する必芁がありたす。 この資料から埗られた知識のおかげで、あなたはこの問題に関しお䜕の困難もありたせん。



蚘事の゜ヌス 。



packerはUPXよりnotepad.exeを圧瞮したした





暗号に倉換



実際、私たちのパッケヌゞは暗号ずはかなり異なりたす暗号化ずアンチ゚ミュレヌション技術の欠劂。 すぐにできる最も簡単なこずは、ブヌトロヌダヌで解凍した盎埌にむメヌゞ党䜓を远加するこずです。 しかし、アンチりむルス゚ミュレヌタヌが窒息するには、これだけでは䞍十分です。 タスクを䜕らかの圢で耇雑にする必芁がありたす。 たずえば、ブヌトロヌダヌ本䜓にxorキヌを登録しないでください。 ぀たり、ブヌトロヌダヌはコヌドを解読するために必芁なキヌを認識せず、私たちが定矩したフレヌムワヌクでそれを反埩凊理したす。 アンチりむルスずは異なり、これには時間がかかる堎合がありたす。

たた、キヌを゚ミュレヌトされおいない機胜たたは構造に䟝存させるこずもできたす。 圌らだけを芋぀ける必芁がありたす。

ブヌトロヌダヌコヌドが眲名によっお焌き付けられないように、高床なりむルス゚ンゞンをパッカヌに固定しお、Webに倧量にあるため、ガベヌゞやあらゆる皮類のコヌド倉曎を生成できたす。






ブヌトロヌダヌでLoadExecutable関数を実行した埌、アンパック甚に割り圓おられたメモリを解攟するずよいでしょう。これはもはや圹に立ちたせん。






画像

ハッカヌマガゞン、 2月02157

ピヌタヌずオオカミ 。



ハッカヌを賌読する








All Articles