前言:單純的手動UPX脫殼問題和解決記錄
對於UPX殼的脫殼問題
在win10上對UPX進行手脫殼如下情況,如下情況是OEP的情況
但是你會發現DUMP出來是打不開的,如下圖所示
重新拉入到調試器中進行觀察,如下圖所示,可以看到此時的ESI的地址為76892A40的地址
內存中進行搜索這個地址是無效的
此時你還會發現當前的EIP是在系統的領空,OEP都還沒到程序就已經異常了,往上拉你還會發現相關的特征詞,此時還在加載PE程序的階段
但是你會發現在XP下同樣的UPX脫殼,最后DUMP出來的程序它能正常運行
然后同樣的對比,觀察下一個WIN10下脫的殼,一個XP下脫的殼,一個自己是可以成功識別脫殼后的IAT表的地址,WIN10下的還是原來UPX指定的IAT表的地址
WIN10下的:
XP下的:
UPX殼的脫殼的解決
在WIN10上進行手動修復IAT即可,如下圖所示
可以看到已經成功輸出0了
具體的WIN10和XP的原因這個問題可以參考我之前的文章筆記 https://www.cnblogs.com/zpchcbd/p/15991346.html
對於沒有INT表的程序是否可以正常運行?
我沒有看過具體操作系統是怎么干的,但是我發現一些程序沒有INT表同樣也可以正常運行,比如如下的UPX殼程序
看到別人的回答是優先找INT表然后再找IAT表,因為程序未加載前都是指向的函數名稱
對於UPX殼的重定位問題解決
WIN10或者高版本系統正常的脫完UPX的殼可能還是打不開,原因就是如下圖所示
可以明顯的看到是重定位沒有修復的問題
主要的原因就是ASRL導致的加載基址不固定的原因導致
手動修復UPX重定位表
最簡單的方法就是直接固定加載基址就可以了,如下圖所示,這樣程序就能正常的打開了
那么如果想要手動修復UPX的重定位表的話,那么又需要怎么做?
先來定位重定位表修改的地方,不想找的話可以直接用特征碼,特征碼為31 C0 8A 07 47 09 C0 74 22 3C EF 77 11 01 C3 8B 03 86 C4 C1 C0 10 86 C4 01 F0 89 03 EB E2 24 0F C1 E0 10 66 8B 07 83 C7 02 EB E2
這里提供兩種方法,如下圖所示
我這里先確定哪里是代碼段的解壓,已經知道了OEP的位置
00363FD4 | E8 F2E2FFFF | call upx加殼.3622CB |
00363FD9 | E9 C6FDFFFF | jmp upx加殼.363DA4 |
00363FDE | CC | int3 |
00363FDF | CC | int3 |
00363FE0 | E9 39EAFFFF | jmp upx加殼.362A1E |
00363FE5 | 55 | push ebp |
00363FE6 | 8BEC | mov ebp,esp |
00363FE8 | FF75 18 | push dword ptr ss:[ebp+18] |
00363FEB | FF75 14 | push dword ptr ss:[ebp+14] |
00363FEE | FF75 10 | push dword ptr ss:[ebp+10] |
00363FF1 | FF75 0C | push dword ptr ss:[ebp+C] |
00363FF4 | FF75 08 | push dword ptr ss:[ebp+8] |
00363FF7 | E8 FAE9FFFF | call upx加殼.3629F6 |
那么這里就先給OEP的位置0x363FD4下一個內存寫入硬件斷點,如下圖所示
程序重新運行,此時被斷下的就是代碼段解壓的時候,如下圖所示
在一個大跳轉的下一行下一個斷點,這個位置就是代碼段解壓完畢的時候
00399392 | 5E | pop esi |
在OEP的找一個全局變量的地方,我找的地方是如下
0036399E | A1 0003F580 | mov eax,dword ptr ds:[80F50300] |
然后在這里下一個硬件訪問斷點,接着繼續殼程序F9,接着就會來到如下的循環條件中
這時候0x0036399E的重定位已經被修復了,如下圖所示,那么也可以確定這個循環語句中的就是修復重定位的語句了
0036399E | A1 80053900 | mov eax,dword ptr ds:[390580] | eax:sub_390569+17
到了這里就可以確定重定位的循環位置是如下位置了,如果想要分析UPX是如何重定位的,那么就需要分析下面這一段的匯編執行流程,分析如下
0039940E | 83C7 04 | add edi,4 |
00399411 | 8D5E FC | lea ebx,dword ptr ds:[esi-4] |
00399414 | 31C0 | xor eax,eax | 清空上一個要修復的重定位值
00399416 | 8A07 | mov al,byte ptr ds:[edi] | EDI中存儲的是下一個要修復的地址的偏移值
00399418 | 47 | inc edi | EDI++,下一次循環准備尋找的偏移值
00399419 | 09C0 | or eax,eax | 判斷EAX是否為空,也就是尋找的偏移值還有沒有,空的話就是修復完了
0039941B | 74 22 | je upx加殼.39943F | 如果修復完的話,那么就跳轉
0039941D | 3C EF | cmp al,EF | 判斷偏移是不是為FE
0039941F | 77 11 | ja upx加殼.399432 | 如果是FE的話,那么就跳轉
00399421 | 01C3 | add ebx,eax | EBX = 上一個修復重定位的地址 + EAX偏移
00399423 | 8B03 | mov eax,dword ptr ds:[ebx] | EAX = 獲取要修復的重定位的地址中的值
00399425 | 86C4 | xchg ah,al | EAX中要修復的重定位地址的低2字節交換
00399427 | C1C0 10 | rol eax,10 | EAX要修復的重定位的地址循環左移10位
0039942A | 86C4 | xchg ah,al | EAX中要修復的重定位的地址低2字節交換
0039942C | 01F0 | add eax,esi | EAX = 偏移地址 + UPX0基址,正好是修復完的地址
0039942E | 8903 | mov dword ptr ds:[ebx],eax | 將EBX地址中的值進行重定位修正
00399430 | EB E2 | jmp upx加殼.399414 | 循環修復重定位
00399432 | 24 0F | and al,F | 如果拿到的偏移是大於0xEF的話,那么這個值就 跟0xF 與運算
00399434 | C1E0 10 | shl eax,10 | 接着 << 10
00399437 | 66:8B07 | mov ax,word ptr ds:[edi] | 取低兩個字節
0039943A | 83C7 02 | add edi,2 | edi + 2,往后跳2個字節
0039943D | EB E2 | jmp upx加殼.399421 | 循環修復重定位
分析完了之后整理一下:
-
UPX自己會有一張表,它會記錄里面所有要修復的重定位的偏移值
-
每次修復的時候,都會從該表里面取出值然后加到上一個修復完的地址上,得到的值就是下一次要修復的重定位地址
-
- 如果取出來的值是 < 0xEF的話是一種情況
-
- 如果取出來的值 > 0xEF的話是另一種情況
-
直到偏移取出來是0的時候表示此時重定位表已經修復完成了
// FixReload.cpp : 此文件包含 "main" 函數。程序執行將在此處開始並結束。
//
#include <Windows.h>
#include <iostream>
#include <vector>
using namespace std;
#define ERROR_FILE_NOT_FOUND 2
typedef struct _MyReloadtion {
DWORD virtualAddress;
DWORD sizeOfBlock;
vector<WORD> typeOffset;
}MyReloadtion, *pMyReloadtion;
DWORD getReloadBase();
void test(char** index);
int alignSize(int imageSize, int sectionAlign);
char* getRelateTable();
void printArray(char* array, int arraySize);
void buildReloadtionTable(DWORD baseAddress, char* pRelateTable);
void saveToFile(vector<MyReloadtion> reloadtionTable);
int calSizeofBlock(int size);
void messageShow(const char message[]);
//裝載的內存
char* mImageBase;
int releateTableSize;
char rvaName[_MAX_PATH] = ".\\rva.bin";
char resultName[_MAX_PATH] = ".\\result.bin";
char targetName[_MAX_PATH] = ".\\target.exe";
int main()
{
//1. 獲取第一節區的內存虛擬地址(VA)
DWORD baseAddress = getReloadBase();
if (baseAddress == NULL)
{
messageShow("獲取第一節區的內存虛擬地址失敗");
}
//2. 獲取提取出來的偏移表
char* pRelateTable = getRelateTable();
if (pRelateTable == NULL)
{
messageShow("處理偏移表錯誤");
}
//3. 在內存構建重定位表
//buildReloadtionTable(baseAddress, pRelateTable);
//messageShow("重定位表生成成功");
}
void messageShow(const char message[]) {
cout << message << endl;
system("pause");
}
void buildReloadtionTable(DWORD baseAddress, char* pRelateTable) {
//1. 位移+基值,變化后拿到offset
vector<MyReloadtion> reloadtionTable;
MyReloadtion reloadtion;
int index = 0;
int size = 0;
// 記錄上一個計算出來的重定位表項的虛擬內存地址
DWORD lastVirtualAddress = NULL;
//為第一個區塊內存地址-4
DWORD pReload = baseAddress;
while (true)
{
//1.拿取位移表一個字節
WORD relate = (BYTE)*pRelateTable;
pRelateTable++;
if (relate == 0)
{
reloadtion.sizeOfBlock = calSizeofBlock(size);
reloadtionTable.push_back(reloadtion);
break;
}
//2. 判斷拿到的字節是否大於EF
if (relate > 0xEF)
{
relate = *pRelateTable;
BYTE hightRelate = *(pRelateTable + 1);
BYTE lowRelate = relate;
relate = ((hightRelate << 8) | lowRelate);
pRelateTable += 2;
}
pReload += relate;
//3. 位運算獲取offset
DWORD virtualAddress = pReload & 0xF000;
WORD typeOffset = pReload & 0xFFF | 0x3000;
if (lastVirtualAddress == NULL)
{
reloadtion = MyReloadtion();
reloadtion.virtualAddress = virtualAddress;
}
else if(lastVirtualAddress != virtualAddress)
{
reloadtion.sizeOfBlock = calSizeofBlock(size);
reloadtionTable.push_back(reloadtion);
size = 0;
reloadtion = MyReloadtion();
reloadtion.virtualAddress = virtualAddress;
}
reloadtion.typeOffset.push_back(typeOffset);
size++;
lastVirtualAddress = virtualAddress;
}
//保存到文件
saveToFile(reloadtionTable);
}
// 計算sizeofBlock
int calSizeofBlock(int size) {
return size * 2 + 8;
}
void saveToFile(vector<MyReloadtion> reloadtionTable) {
//文件寫入
HANDLE hfile = CreateFileA(resultName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
int numReloadtion = reloadtionTable.size();
LONG fileStart = 0;
DWORD receive = NULL;
for (int index = 0; index < numReloadtion; index++)
{
MyReloadtion reloadtion = reloadtionTable[index];
SetFilePointer(hfile, fileStart, NULL, FILE_BEGIN);
int virtualAddressSize = sizeof(reloadtion.virtualAddress);
WriteFile(hfile, &(reloadtion.virtualAddress), virtualAddressSize, &receive, NULL);
fileStart += virtualAddressSize;
SetFilePointer(hfile, fileStart, NULL, FILE_BEGIN);
int sizeofBlockSize = sizeof(reloadtion.sizeOfBlock);
WriteFile(hfile, &(reloadtion.sizeOfBlock), sizeofBlockSize, &receive, NULL);
fileStart += sizeofBlockSize;
SetFilePointer(hfile, fileStart, NULL, FILE_BEGIN);
int offsetSize = calSizeofBlock(reloadtion.typeOffset.size());
int itemNum = reloadtion.typeOffset.size();
for (int index = 0; index < itemNum; index++)
{
WriteFile(hfile, &(reloadtion.typeOffset[index]), 2, &receive, NULL);
fileStart += 2;
}
}
CloseHandle(hfile);
}
char* getRelateTable() {
HANDLE hfile = CreateFileA(rvaName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (GetLastError() == ERROR_FILE_NOT_FOUND)
{
messageShow("找不到位移表文件");
}
SetFilePointer(hfile, 0, NULL, FILE_BEGIN);
releateTableSize = GetFileSize(hfile, NULL);
char* pTable = new char[releateTableSize];
DWORD receiveSize = NULL;
BOOL result = ReadFile(hfile, pTable, releateTableSize, &receiveSize, NULL);
if (!result)
{
messageShow("讀取位移表出錯");
}
CloseHandle(hfile);
printArray(pTable, releateTableSize);
return pTable;
}
void printArray(char* array, int arraySize) {
for (int index = 0; index < arraySize; index++)
{
std::cout<< std::hex << (int)*array << " ";
array++;
}
}
DWORD getReloadBase() {
//1.獲取FileHeader和OptionHeader的一些關鍵變量
HANDLE hfile = CreateFileA(targetName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hfile == INVALID_HANDLE_VALUE || GetLastError() == ERROR_FILE_NOT_FOUND)
{
messageShow("找不到加殼后文件");
}
// 讀取dos頭
IMAGE_DOS_HEADER dosHeader;
DWORD dosHeaderSize = sizeof(dosHeader);
DWORD receiveSize = NULL;
BOOL dosReadResult = ReadFile(hfile, &dosHeader, dosHeaderSize, &receiveSize, NULL);
if (!dosReadResult)
{
messageShow("讀取dos頭錯誤");
}
// 讀取NT頭
IMAGE_NT_HEADERS32 ntHeader;
DWORD pointerResult = SetFilePointer(hfile, dosHeader.e_lfanew, NULL, FILE_BEGIN);
if (pointerResult == INVALID_SET_FILE_POINTER)
{
messageShow("設置讀取指針為NT頭時錯誤");
}
BOOL ntHeaderResult = ReadFile(hfile, &ntHeader, sizeof(ntHeader), &receiveSize, NULL);
if (!ntHeaderResult)
{
messageShow("讀取NT頭錯誤");
}
WORD peHeaderSize = ntHeader.OptionalHeader.SizeOfHeaders;
WORD setctionNums = ntHeader.FileHeader.NumberOfSections;
DWORD imageSize = ntHeader.OptionalHeader.SizeOfImage;
DWORD sectionAlign = ntHeader.OptionalHeader.SectionAlignment;
//2. 對齊鏡像並載入PE頭
int mImageSize = alignSize(imageSize, sectionAlign);
mImageBase = new char[mImageSize];
memset(mImageBase, 0, mImageSize);
SetFilePointer(hfile, 0, NULL, FILE_BEGIN);
ReadFile(hfile, mImageBase, peHeaderSize, &receiveSize, NULL); //將文件頭寫入
//3. 計算並獲取區塊表起始地址
PIMAGE_NT_HEADERS mpNtheader = (PIMAGE_NT_HEADERS)((DWORD)mImageBase + dosHeader.e_lfanew);
int mNtHeadersSize = sizeof(ntHeader.FileHeader) + sizeof(ntHeader.Signature) + ntHeader.FileHeader.SizeOfOptionalHeader;
PIMAGE_SECTION_HEADER mpSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)mpNtheader + mNtHeadersSize);
DWORD keyBaseAddress = NULL;
//4. 遍歷區塊表加載區塊
for (int index = 0; index < setctionNums; ++index)
{
DWORD va = mpSectionHeader->VirtualAddress;
if (index == 0)
{
keyBaseAddress = va;
}
DWORD rawSize = mpSectionHeader->SizeOfRawData;
DWORD vaSize = mpSectionHeader->Misc.VirtualSize;
DWORD rawOffset = mpSectionHeader->PointerToRawData;
if (rawSize == 0)
{
continue;
}
else
{
SetFilePointer(hfile, rawOffset, NULL, FILE_BEGIN);
ReadFile(hfile, &mImageBase[va], rawSize, &receiveSize, NULL);
}
mpSectionHeader++;
}
keyBaseAddress -= 4;
CloseHandle(hfile);
return keyBaseAddress;
}
// 對齊鏡像
int alignSize(int imageSize, int sectionAlign) {
return (imageSize + sectionAlign - 1) / sectionAlign * sectionAlign;
}