Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

How to write a shell from scratch

2025-04-08 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Development >

Share

Shulou(Shulou.com)06/03 Report--

This article introduces the knowledge of "how to write a shell from scratch". In the operation of practical cases, many people will encounter such a dilemma. Next, let the editor lead you to learn how to deal with these situations. I hope you can read it carefully and be able to achieve something!

Principle of adding shells by hand

That is, we add a section to the PE file and set it as the entry point, so that the initial command of the PE file is that we add the section that is the shell instruction, which decrypts the encrypted area, decompresses the compressed area, restores the original EXE file, and then jumps to the entrance of the original program, and the program runs as usual.

First generate an exe file that prints the hello.

# include int main () {printf ("hello");}

What we need to do now is to manually add a shell part to the PE file and set it as the program entry so that it can jump back to the original entry. Then come on.

Open our exe file with 010editor and enable exe template analysis. We first modify its header numverofsection property, which is used to define how many sections exist in the current PE file. Because we want to add a shell section, we add it by 1 to 6

After we reload the template, we will find an empty section table in the section table.

The meaning of the more important fields from top to bottom is\ 1. Name indicates the name of the section 2.VirtualSize indicates the size in memory (generally memory alignment is 0x1000) 3.virtualaddress virtual address, that is, the VirtualAddress of the previous section + the size of the previous section after memory alignment granularity alignment 4.sizeofdata indicates the size in the file (general file alignment is 0x200) the offset of the 5.pointertorawdata file PointerToRawData of one section + SizeOfRawData of the previous section

Then we define the properties of a new section (shell section) by modifying the above values

Just fill in one of the virtualsize here. At this point, we only define the section table, but the section does not exist in the file, so we have to create the section. Then make the section editable by changing the following values to 1

Ctrl+shift+i inserts 0x200-sized space at the offset of the target file. In this way, the shell section is created. Then we need to modify the SizeofImage of the extension header. Change it to the memory address of the last section + memory size

Then remove the random base address option.

Find the DLL attribute field of the extension header, remove the random base address, and change 4081 to 0081

Next, we set the program entry point to the shell section. Use LORDPE to set the entry point to the virtual address of the shell block

Then we open this file with OD

The real shelling process

The manual shell mentioned just now is only the most basic shell prototype, and the real shell also involves operations such as code encryption and decryption.

When you really write a shell, you usually write two things, the shell and the so-called stub shell, which is to create a new section for the shelled file, encrypt the program in some way at the same time, then put the stub into the new section, and set the program entry point to the address of the new section, and then jump back to the original program entrance after the end of the new section. This new section is called the shell section. Then this stub is the first command executed by the program after the shell is added. It executes the decryption algorithm and releases the original program.

Shell programming based on C++

Https://github.com/ConsT27/PackingEXE/tree/master project address

Stub

Stub is the code that is implanted into the PE file, and it typically does the following things.

The process is as follows

0. Merge data,rdata into text 1.PEB dynamic addressing, traverse the export table to find the GetProcAddress function 2. Decryption 3. Modify the entry point to the original entry point

At the same time, stub exists in the form of dll. The reason is that DLL usually comes with its own relocation table, which provides great convenience in the relocation operation during our migration.

Merge data segments

If we want to migrate stub in the past, we certainly need to migrate code segments as well as data segments. Why don't we just merge the data segment into the code segment and transplant it together.

PEB dynamic addressing & deriving the table traversal search function

Why is this technique used to write stub? Because when our stub.dll is implanted into the host program, only .text is implanted in the past, and there is no corresponding import table, so our stub cannot call some API directly. So we need to get all kinds of API dynamically. I use PEB dynamic query to get the GetProcAddress function, and then use the GetProcAddress function to get each API.

So, what is PEB? PEB is a structure that Microsoft has not fully disclosed its function. It is called process environment information block and contains process information. Its structure is as follows

Typedef struct _ PEB {BYTE Reserved1 [2]; BYTE BeingDebugged; / / debugged status BYTE Reserved2 [1]; PVOID Reserved3 [2]; PPEB_LDR_DATA Ldr;PRTL_USER_PROCESS_PARAMETERS ProcessParameters;BYTE Reserved4 [104] PVOID Reserved5 [52]; PPS_POST_PROCESS_INIT_ROUTINE PostProcessInitRoutine;BYTE Reserved6 [128]; PVOID Reserved7 [1]; ULONG SessionId;} PEB, * PPEB; copy code

What we care about is the PPEB_LDR_DATA Ldr; obtained by PEB offset 0c, which is a pointer to a PPEB_LDR_DATA structure that stores information about dynamic link libraries that have been installed by the process.

Typedef struct _ PEB_LDR_DATA {ULONG Length; / / + 0x00BOOLEAN Initialized; / / + 0x04PVOID SsHandle; / / + 0x08LIST_ENTRY InLoadOrderModuleList; / + 0x0cLIST_ENTRY InMemoryOrderModuleList; / + 0x14LIST_ENTRY InInitializationOrderModuleList;// + 0x1c} PEB_LDR_DATA,*PPEB_LDR_DATA; / / + 0x24

PPEB_LDR_DATA offset 1c is a pointer to the LIST_ENTRY InInitializationOrderModuleList structure, which stores the header pointing to the module initialization list and stores the PE load runtime initialization module information in order. generally speaking, the first linked list node is ntdll.dll and the second linked list node is kernel32.dll. We find the information of kernel32.dll, get its PE information, get the export table, and loop through to get the GetProcAddress function. In addition, the PEB address is TEB offset from 0x30. It is fs: [0x30] in assembly language.

The above is the general flow of PEB addressing, and another key point is to traverse the kernel32.dll export table to get GetProcAddress function information. For more information about the export table, see this article, https://blog.csdn.net/evileagle/article/details/12176797.

First of all, an export table structure is as follows

Typedef struct _ IMAGE_EXPORT_DIRECTORY {DWORD Characteristics; / / is generally 0. There is nothing to use DWORD TimeDateSt / / to export the time WORD MajorVersion; / / version generated by the table, and WORD MinorVersion; / / is also useless. The version information is generally 0DWORD Name; / / the module name of the current export table, DWORD NumberOfFunctions; / / the cardinality of the sequence number in the table DWORD NumberOfNames. / / the number of functions exported by name DWORD AddressOfFunctions; / / ordinal table DWORD AddressOfNames; / / name table DWORD AddressOfNameOrdinals; / / address table} IMAGE_EXPORT_DIRECTORY, * PIMAGE_EXPORT_DIRECTORY

Where the starting sequence number of the ordinal table is the value defined by the Base attribute. The following is the relationship between the serial number name and address table of the export table

Our traversal process is to first traverse the name table to find the subscript of GetProcAddress in the name array, then use this subscript to find the ordinal value of the same subscript in the array of ordinal numbers, and then use this ordinal value as the subscript to find the corresponding value in the address array. The value in the address table we found is the function entry.

Now I will release the assembly code of this program. I crammed this code into C++ using inline assembly.

Void GetApis () {HMODULE hKernel32;_asm {pushad;; / / get the load base address of kernel32.dll; mov eax, fs: [0x30]; / get the PEB address mov eax, [eax + 0ch]; / / get the LDR_PEB_DATA address mov eax, [eax + 0ch]; / / get the LIST_ENTRY InLoadOrderModuleList; address mov eax, [eax]; / / get the address mov eax of the next entry in LIST_ENTRY InLoadOrderModuleList, [eax] / get the address mov eax of the LIST_ENTRY InInitializationOrderModuleList we need under LIST_ENTRY InLoadOrderModuleList, [eax + 018h]; / get the kernel32.dll address mov hKernel32, eax;mov ebx, [eax + 03ch]; / / get the VAadd ebx of the kernel32.dll NT header RVAadd ebx, eax; / / NT header, 078h; / / get the section table mov ebx, [ebx]; / / get the export table RVAadd ebx, eax; / / export table VAlea ecx, [ebx + 020h] Mov ecx, [ecx]; / / ecx = > first address of name table (rva); add ecx, eax; / / ecx = > first address of name table (va); xor edx, edx; / / used as index (index). _ WHILE:;mov esi, [ecx + edx * 4]; / / name array entry point rva, name array unit size 4-byte lea esi, [esi + eax]; / / entry point VAcmp dword ptr [esi], 050746547h / / name matching: 050746547h, that is, if the GetPjne _ LOOP;// stored in the small end is not equal, jump into the _ LOOP segment cmp dword ptr [esi + 4], 041636f72h; / / name Chen matching, rocA, followed by ddre,ssjne _ LOOP;cmp dword ptr [esi + 8], 065726464h LOOP;cmp word ptr _ LOOP;cmp word ptr [esi + 0ch], 07373h LOOP;cmp word ptr _ LOOP;mov edi, [ebx + 024 h]; add edi, eax; / get the serial number VAmov di, [edi + edx * 2] / / get the address corresponding to the subscript in the ordinal array, the size of the ordinal array is 2 bytes and edi, 0FFFH; / / raise the di to 32 bits, that is, give the address mov edx, [ebx + 01ch] to the corresponding subscript in the edi serial number table; add edx, eax; / / get the address table mov edi, [edx + edi * 4]; / / get the value corresponding to the serial number in the address array, and the address array unit size is 4 bytes add edi, eax. / / obtain the entry address mov MyGetProcAddress of GetProcAddress, edi; / / assign jmp _ ENDWHILE; / / END_LOOP:;inc edx; / / + + index;jmp _ WHILE;_ENDWHILE:;popad;} decrypt

Decrypt the code segment. This paragraph is easy to write.

Void Decrypt () {unsigned char* pText = (unsigned char*) g_conf.textScnRVA + 0x400000 lock to the text section of the PE file (because the randomization of the base address is removed when the shell is added, so confidently fill in the base address as 0x400000DWORD old = 0MyVirtualProtect (pText, g_conf.textScnSize, PAGE_READWRITE, & old); / / modify the properties of the code snippet, note that we use the dynamically obtained / / decryption code segment for (DWORD I = 0; I).

< g_conf.textScnSize; i++){pText[i] ^= g_conf.key;}//把属性修改回去MyVirtualProtect(pText, g_conf.textScnSize, old, &old);}修改入口点_asm {mov eax, g_conf.srcOep; //入口点是g_conf.srcOepadd eax, 0x400000jmp eax}加壳器 加壳器流程如下 1.打开需要被加壳的PE文件 2.加载stub 3.加密代码段 4.添加新区段 5.stub重定位修复 6.stub移植 7.PE文件入口点修改 8.去随机基址 9.保存文件 以下的各个流程描述中会用到诸多自定义函数,我先贴上来吧。 诸多自定函数&结构体//****************//对齐处理//time:2020/11/5//****************int AlignMent(_In_ int size, _In_ int alignment) {return (size) % (alignment)==0 ? (size) : ((size) / alignment+1) * (alignment);}//***********************//PE信息获取函数簇//time:2020/11/2//***********************PIMAGE_DOS_HEADER GetDosHeader(_In_ char* pBase) {return PIMAGE_DOS_HEADER(pBase);}PIMAGE_NT_HEADERS GetNtHeader(_In_ char* pBase) {return PIMAGE_NT_HEADERS(GetDosHeader(pBase)->

PIMAGE_FILE_HEADER GetFileHeader (_ In_ char* pBase) {return & (GetNtHeader (pBase)-> FileHeader);} PIMAGE_OPTIONAL_HEADER32 GetOptHeader (_ In_ char* pBase) {return & (GetNtHeader (pBase)-> OptionalHeader);} PIMAGE_SECTION_HEADER GetLastSec (_ In_ char* pBase) {DWORD SecNum = GetFileHeader (pBase)-> NumberOfSections;PIMAGE_SECTION_HEADER FirstSec = IMAGE_FIRST_SECTION (GetNtHeader (pBase)); PIMAGE_SECTION_HEADER LastSec = FirstSec + SecNum-1 boot return LastSec } PIMAGE_SECTION_HEADER GetSecByName (_ In_ char* pBase,_In_ const char* name) {DWORD Secnum = GetFileHeader (pBase)-> NumberOfSections;PIMAGE_SECTION_HEADER Section = IMAGE_FIRST_SECTION (GetNtHeader (pBase)); char buf [10] = {0}; for (DWORD I = 0; I

< Secnum; i++) {memcpy_s(buf, 8, (char*)Section[i].Name, 8);if (!strcmp(buf, name)) {return Section + i;}}return nullptr;}typedef struct _StubConf{DWORD srcOep; //入口点DWORD textScnRVA; //代码段RVADWORD textScnSize; //代码段的大小DWORD key; //解密密钥}StubConf;struct StubInfo{char* dllbase; //stub.dll的加载基址DWORD pfnStart; //stub.dll(start)导出函数的地址StubConf* pStubConf; //stub.dll(g_conf)导出全局变量的地址}; 打开PE文件 这里采用的方法是利用CreateFileA函数。同时这个函数还抛出了一个指向PE文件大小的指针 char* GetFileHmoudle(_In_ const char* path,_Out_opt_ DWORD* nFileSize) {//打开一个文件并获得文件句柄HANDLE hFile = CreateFileA(path,GENERIC_READ,FILE_SHARE_READ,NULL,OPEN_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL);//获得文件大小DWORD FileSize = GetFileSize(hFile, NULL);//返回文件大小到变量nFileSizeif(nFileSize)*nFileSize = FileSize;//申请一片大小为FileSize的内存并将指针置于首位char* pFileBuf = new CHAR[FileSize]{ 0 };//给刚刚申请的内存读入数据DWORD dwRead;ReadFile(hFile, pFileBuf, FileSize, &dwRead, NULL);CloseHandle(hFile);return pFileBuf;} 加载STUB void LoadStub(_In_ StubInfo* pstub) {pstub->

Dllbase = (char*) LoadLibraryEx (L "F:\\ stubdll.dll", NULL, DONT_RESOLVE_DLL_REFERENCES); pstub- > pfnStart = (DWORD) GetProcAddress ((HMODULE) pstub- > dllbase, "Start"); / / get the entry function Start of stub (a function pstub- > pStubConf = (StubConf*) GetProcAddress (HMODULE) pstub- > dllbase, "g_conf" defined by yourself in stub) } / / not only loaded stub, but also obtained the global structure thrown by stub for collecting information (g_conf, a structure thrown by stub, the structure is as follows) typedef struct _ StubConf {DWORD srcOep; / / entry point DWORD textScnRVA; / / code snippet RVADWORD textScnSize; / / the size of the code segment DWORD key; / / decryption key} StubConf

Encrypted code snippet

DWORD textRVA = GetSecByName (PeHmoudle, ".text")-> VirtualAddress;DWORD textSize = GetSecByName (PeHmoudle, ".text")-> Misc.VirtualSize;Encry (PeHmoudle,pstub); void Encry (_ In_ char* hpe,_In_ StubInfo pstub) {/ / get the first address of the snippet BYTE* TargetText = GetSecByName (hpe, ".text")-> PointerToRawData + (BYTE*) hpe;// get the snippet size DWORD TargetTextSize = GetSecByName (hpe, ".text")-> Misc.VirtualSize / / encryption snippet for (int I = 0; I

< TargetTextSize; i++) {TargetText[i] ^= 0x15;}pstub.pStubConf->

TextScnRVA = GetSecByName (hpe, ".text")-> VirtualAddress;pstub.pStubConf- > textScnSize = TargetTextSize;pstub.pStubConf- > key = 0x15;} / / encrypt the code segment and give stub some information to add a new section char* AddSec (_ In_ char*& hpe, _ In_ DWORD& filesize, _ In_ const char* secname, _ In_ const int secsize) {GetFileHeader (hpe)-> NumberOfSections++;PIMAGE_SECTION_HEADER pesec = GetLastSec (hpe); / / set section table properties memcpy (pesec- > Name, secname, 8) Pesec- > Misc.VirtualSize = secsize;pesec- > VirtualAddress = (pesec- 1)-> VirtualAddress + AlignMent ((pesec- 1)-> SizeOfRawData,GetOptHeader (hpe)-> SectionAlignment); pesec- > SizeOfRawData = AlignMent (secsize, GetOptHeader (hpe)-> FileAlignment); pesec- > PointerToRawData = AlignMent (filesize,GetOptHeader (hpe)-> FileAlignment); pesec- > Characteristics = 0xE00000E0X / set OPT header image size GetOptHeader (hpe)-> SizeOfImage = pesec- > VirtualAddress + pesec- > SizeOfRawData;// expansion file data int newSize = pesec- > pesec- + PointerToRawData > PointerToRawData Char* nhpe = new char [newSize] {0}; / / enter data memcpy (nhpe, hpe, filesize) into the new buffer; / / cache replacement delete hpe;filesize = newSize;return nhpe;}

Stub relocation

Boy, the slightest carelessness of this thing will pull the whole program off. (why do you need stub repositioning for advice from people who have been there? Because our stub is initially loaded in memory, many of its instructions such as jumping to the address are determined on the basis of memory, but we need to transplant it into the file, so many addresses in its code are wrong, we need to deal with these addresses, that is, relocate, to make them address repair according to the standard of the host program. Maybe what I'm saying is not very clear.

For example, when stub is loaded into memory, there is a jump instruction when jmp 12345678. If we port this instruction into the PE file without processing, then the PE file will jump to 12345678 when it is executed here, and the 12345678 address may no longer be the memory range loaded by the PE file, thus the program will crash. So fix it. Fix it according to stub's relocation table. The relocation table records the data of which addresses need to be repaired, and we can just traverse these addresses to fix them. If the following code looks laborious, you can take a look at the relocation table first.

Void FixStub (DWORD targetDllbase, DWORD stubDllbase,DWORD targetNewScnRva,DWORD stubTextRva) {/ / find stub.dll 's relocation table DWORD dwRelRva = GetOptHeader ((char*) stubDllbase)-> DataDirectory [5] .VirtualAddress; IMAGE_BASE_RELOCATION* pRel = (IMAGE_BASE_RELOCATION*) (dwRelRva + stubDllbase); / / traversal relocation table while (pRel- > SizeOfBlock) {struct TypeOffset {WORD offset: 12 word type: 4;}; TypeOffset* pTypeOffset = (TypeOffset*) (pRel + 1); DWORD dwCount = (pRel- > SizeOfBlock-8) / 2 / / the number of for to be relocated (int I = 0; I

< dwCount; i++){if (pTypeOffset[i].type != 3){continue;}//需要重定位的地址DWORD* pFixAddr = (DWORD*)(pRel->

VirtualAddress + pTypeOffset [I] .offset + stubDllbase); DWORD dwOld;// modifies the attribute to writable VirtualProtect (pFixAddr, 4, PAGE_READWRITE, & dwOld); / / removes the current loading base address of dll * pFixAddr-= stubDllbase;// removes the default segment header RVA*pFixAddr-= stubTextRva;// and replaces the target file loading base address * pFixAddr + = targetDllbase;// plus the new section header RVA*pFixAddr + = targetNewScnRva;// to modify the attribute back to VirtualProtect (pFixAddr, 4, dwOld, & dwOld) } / / switch to the next relocation block pRel = (IMAGE_BASE_RELOCATION*) ((DWORD) pRel + pRel- > SizeOfBlock);}

Stub transplantation

It's simple. There's nothing to say.

Memcpy (GetLastSec (PeNewHmoudle)-> PointerToRawData+ PeNewHmoudle,GetSecByName (pstub.dllbase, ".text")-> VirtualAddress+pstub.dllbase,GetSecByName (pstub.dllbase, ".text")-> Misc.VirtualSize); modify the entry point GetOptHeader (PeNewHmoudle)-> AddressOfEntryPoint = pstub.pfnStart- (DWORD) pstub.dllbase-GetSecByName (pstub.dllbase, ".text")-> VirtualAddress+GetLastSec (PeNewHmoudle)-> VirtualAddress

Remove random base address

If the random base address is not removed, the loading base address is not fixed and is not convenient to operate.

GetOptHeader (PeNewHmoudle)-> DllCharacteristics & = (~ 0x40); save the file void SaveFile (_ In_ const char* path, _ In_ const char* data, _ In_ int FileSize) {HANDLE hFile = CreateFileA (path,GENERIC_WRITE,FILE_SHARE_READ,NULL,CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,NULL); DWORD Buf = 0 CreateFileA WriteFile (hFile, data, FileSize, & Buf,NULL); CloseHandle (hFile) } "how to write a shell from scratch" is introduced here. Thank you for your reading. If you want to know more about the industry, you can follow the website, the editor will output more high-quality practical articles for you!

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Development

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report