程序脱壳后都需要修复IAT,否则程序运行会出错,以前都是用工具来修复,工具一般都是新加一个节,然后将导入函数放在新的节里面(这个函数会比实际程序多一些,但不影响程序的运行,但个别定位出问题的多余函数会影响到程序运行),再修复跟函数相关的重定位项,其实有另一种方式可以完美修复IAT,今天就给大家交流一下,希望多多指正。
首先用ASPack给程序加一个壳,然后进行脱壳,脱壳过程很简单,这里就不详细叙述了。
运行到OEP用OllyDump导出当前进程。
注意:把重建输入表前面的对勾去掉,因为我们要手工恢复,所以就不需要这个选项了。
用工具查看输入表信息:
------------------------------------DataDirectory
No. VirtualAddress Size
0 00000000 00000000 EXPORT
1 00067FAC 00000240 IMPORT
2 00061000 00005400 RESOURCE
3 00000000 00000000 EXCEPTION
4 00000000 00000000 SECURITY
5 00067F54 00000008 BASERELOC
6 00000000 00000000 DEBUG
7 00000000 00000000 ARCHITECTURE
8 00000000 00000000 GLOBALPTR
9 00067F3C 00000018 TLS
10 00000000 00000000 LOAD_CONFIG
11 00000000 00000000 BOUND_IMPORT
12 00000000 00000000 IAT
13 00000000 00000000 DELAY_IMPORT
14 00000000 00000000 COM_DESCRIPTOR
15 00000000 00100000 ...
输入表的地址是:67FAC,查看一下这个地址的数据。可以看出下面的数据是壳的导入表,并不是程序自身的。
下面我们查找程序自己的导入表,很简单,搜索Kernel32.dll这个字符串,会找到几个,搜索字符串后面是乱码的跳过,最后我们定位的信息如下:
这是程序自己的导入表,但有些数据被覆盖了,所以程序运行会出错。
从这个地址向前查找会找到完整导入表信息,这里的界限很明显,非全零的那一行就是导入信息的开始,地址是:56000,但数据已经乱了,我们需要的就是重建这部分结构。
如何重建,这里我们要先介绍一下导入表的结构:
typedef struct _IMAGE_IMPORT_DESCRIPTOR {
union {
DWORD Characteristics;// 0 for terminating null import descriptor
DWORD OriginalFirstThunk; // RVA to original unbound IAT (PIMAGE_THUNK_DATA)
} DUMMYUNIONNAME;
DWORD TimeDateStamp;// 0 if not bound,
// -1 if bound, and real date\time stamp
// in IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT (new BIND)
// O.W. date/time stamp of DLL bound to (Old BIND)
DWORD ForwarderChain; // -1 if no forwarders
DWORD Name;
DWORD FirstThunk; // RVA to IAT (if bound this IAT has actual addresses)
} IMAGE_IMPORT_DESCRIPTOR;
typedef IMAGE_IMPORT_DESCRIPTOR UNALIGNED *PIMAGE_IMPORT_DESCRIPTOR;
上面结构中的的FirstThunk指向下一个结构:
typedef struct _IMAGE_THUNK_DATA32 {
union {
DWORD ForwarderString;// PBYTE
DWORD Function; // PDWORD
DWORD Ordinal;
DWORD AddressOfData;// PIMAGE_IMPORT_BY_NAME
} u1;
} IMAGE_THUNK_DATA32;
typedef IMAGE_THUNK_DATA32 * PIMAGE_THUNK_DATA32;
上面结构中的数据指向了我们刚才查找是内容,那里的内容结构如下:
typedef struct _IMAGE_IMPORT_BY_NAME {
WORD Hint;
CHAR Name[1];
} IMAGE_IMPORT_BY_NAME, *PIMAGE_IMPORT_BY_NAME;
我们现在要修复的就是前两个结构的数据,第三组结构的数据是正常的,没有被打乱,我们就是根据第三组结构的数据来还原前两组结构,这个修复是完美的修复,修复后倒入表信息和加壳之前的完全一样。
下面我们就先来看一组数据:
其中地址A中的数据指向DLL的字符串,地址B中的数据指向函数名的地址,地址C中的数据指向函数名,可以看到地址A和B中的数据都已经乱了,指向不正确。
地址C中的数据已经被函数的实际地址替代,C中的地址是程序加载的时候由系统填写的,但现在因为有外壳加密,所以这部分数据是由壳程序来填写的。
我们只要恢复A、B、C中的数据即可恢复导入表的信息。第一个红框中的数据除了A和B其他都可以用00来填充。
知道了原理手工修复就简单了,但是这个工作量有些大,因此就写了一个程序恢复这些信息。
代码如下:
procedure IAT;
var
Txt:File of Char;
Buf:Array of Char;
Len:integer;
PDosHead:PImageDosHeader;
PNtHead:PImageNtHeaders;
PSectionHead:PImageSectionHeader;
PSec:PImageSectionHeader;
ls:String;
i,j,n:integer;
p:PChar;
ImpAddr,ExpAddr,vAddr,pAddr:Cardinal;
pImp:PIMAGE_IMPORT_DESCRIPTOR;
pImpname:PIMAGE_IMPORT_BY_NAME;
pExp:PImageExportDirectory;
pNameAddr:Cardinal;
pd:PDWORD;
begin
Memo1.SetFocus;
FileMode:=fmOpenRead;
AssignFile(Txt,Edit1.Text);
Reset(Txt);
Len:=Filesize(Txt);
Setlength(Buf,Len);
BlockRead(Txt,Buf[0],Len);
CloseFile(Txt);
n:=Integer(@buf[0]);
pImp:=@Buf[$56000];
p:=@Buf[$56724];
pImp.Name:=integer(p)-n;
pd:=@Buf[$56118];
pImp.FirstThunk:=$56118;
pImp.OriginalFirstThunk:=0;
pImp.TimeDateStamp:=0;
pImp.ForwarderChain:=0;
While True do
begin
Inc(p,Length(p));
for i:=1 to 10 do
begin
Inc(p);
if Pos('.dll',p)<>0 then
begin
ShowMessage('--'+p);
pd^:=0;
inc(pd);
Inc(pImp);
pImp.Name:=integer(p)-n;
pImp.FirstThunk:=integer(pd)-n;
pImp.OriginalFirstThunk:=0;
pImp.TimeDateStamp:=0;
pImp.ForwarderChain:=0;
Inc(p,Length(p));
Continue;
//Exit;
end;
if p[0]<>#0 then
begin
//ShowMessage(#9+p);
break;
end;
end;
if i=11 then
break;
//dec(p,2);
pd^:=(integer(p)-n-2);
Inc(pd);
ShowMessage(' '+IntToHex(pImp.FirstThunk,8)+' '+p);
end;
AssignFile(Txt,'1.exe');
Rewrite(Txt);
BlockWrite(Txt,Buf[0],Len);
CloseFile(Txt);
end;
我是用PAS写的代码,大家也可以用其他语言来写,修改完IAT表以后,还有最后一步,就是要把导入表的地址改为:56000,以前的导入表地址是:067FAC,查找二进制的字符串:AC 7F 06改为新地址即可。
附件中是这次演示的文件,大家可以尝试手工修复一下。
(点击文末“原文链接”即可获取附件~)
- End -
原文作者:lrtlrt
原文链接:https://bbs.pediy.com/thread-248111.htm
转载请注明转自看雪学院
更多阅读:
网友评论