RVA to RAW?汇编程序输出PE文件导入/导出表

一、PE文件的映像(Image)和文件

PE文件在磁盘中存储的时候,是作为指令和数据的静态集合,排列相对紧凑。然而当PE文件被加载到内存中时:

①编译器的附加指令等等和原本的节合并,使内存中的节大小不同于文件中

②而节与节之间因为内存的对齐与文件中的节对齐不同,所以节与节之间的距离很大概率下会被拉大

慧编程添加机器学习拓展后训练模型打不开 慧编程作品怎么导出来_masm


二、RVA to RAW

下面简单陈述算法,在正常情况下,节内的数据与节开头的相对地址使不变的,所以,只要我们能够找到该节在文件中的起始位置,用相对的内存位置加上文件的起始位置,就能得到文件中的数据位置

慧编程添加机器学习拓展后训练模型打不开 慧编程作品怎么导出来_masm_02


所以,步骤大概分三步

①找到目标数据属于哪个节(大于该节的VirtualAddress,小于该节的VitualAddress+VirtualSize)

②减去该节的VirtualAddress,加上该节的Pointer of Raw Data

*如果的到的文件偏移在该节外面,说明该节的文件大小和内存大小有较大差距三、IAT,IID,INT

在OPTINONAL_HEADER中有导入表的地址,即第一个IID

IID:导入库的单位,所以对应单个库的函数全存在它的索引内

INT:导入函数的数组,读取INT就可以读到所导入的函数名

慧编程添加机器学习拓展后训练模型打不开 慧编程作品怎么导出来_导出表_03


该图为IID,在偏移量618h的地方为00002040,转换为RAW(2040h-2000h+600h)=640

2066->666处则是库名称

慧编程添加机器学习拓展后训练模型打不开 慧编程作品怎么导出来_导出表_04


666h处可见kernel32.dll

而640h处为00002074h,找到674的地方,发现GetStdHandle的函数名

慧编程添加机器学习拓展后训练模型打不开 慧编程作品怎么导出来_字符串_05


总结来说:找到IID,去拿库的INT,读取库的INT,填到目标IAT

四、程序的读取思路

①输入表读取->需要IID

找IID->需要找到可选头的地址->读取DOS头e_lfanew

IID地址转化为RAW->需要节头表的三个值

找节头表->PE文件头内有OPTINONAL_Header大小(其大小不定)

IID地址转化为RAW->遍历节头表,将其关键信息存在结构体中(RVA,P of ROW,RVA SIZE)

IID地址转化为RAW->找到合适的节,转化

②IID读取到->转化为RAW->遍历读取INT

->每个INT遍历读取其内的所有函数名输出

③输出表读取,直到读取IED同上,但是之后只用遍历Address of Name

五:代码

注:此代码无输出可能为读取文件的缓冲区不够大,读到了未申请内存

本程序经过masm32/bin/msdis109.dll测试,经过了上一次程序的exe测试,输出正常

.386
.model flat, stdcall
option casemap:none
include D:\masm32\include\windows.inc
include D:\masm32\include\kernel32.inc
include D:\masm32\include\masm32.inc
includelib D:\masm32\lib\kernel32.lib
includelib D:\masm32\lib\masm32.lib
include D:\masm32\include\user32.inc 
includelib D:\masm32\lib\user32.lib
ExitProcess PROTO, dwEXITCODE: DWORD

.data
;过程声明们
dw2hex PROTO :DWORD,:DWORD ;masm32.inc中的十六进制值转字符串的过程
rva2raw PROTO :DWORD       ;将RVA变成文件偏移RAW的过程,参数为RVA
readIID PROTO              ;读取IID的过程
readFuncName PROTO         ;读取每个IID对应名字中的函数名的过程
readIED PROTO              ;读取IED的过程(IED仅有一个,所以不需要readFuncName)

;固定字符串们
NoExport BYTE "no EXport TABlE!!!!!!!!!!!!!!!!!!!!",0
banner BYTE "pls input a PE file:",0
strn BYTE 0ah,00h

;文件的路径,句柄,和内存准备接受文件的缓冲区,因为文件较大,所以必须要申请大的缓冲区们,
;当然,也可以改变文件的指针读入特定地点,但是我不太想一直readfile,我懒,所以就算了
path BYTE 20 DUP(0)
filehandle DWORD ?
filebuf BYTE 217502 DUP(0)

;中间地址们(除了RAW说明以外均为RVA)
NT_addr DWORD ?          ;NT头(pe文件头首地址)
OP_addr DWORD ?          ;可选头首地址
SE_addr DWORD ?          ;节区头首地址
EX_addr DWORD ?          ;导出表地址
IM_addr DWORD ?          ;导入表地址
SE_adds DWORD 30h DUP(0) ;储存各个节的RVA起始地址,RAW起始地址,Image大小,RAW大小的结构体(以下解释为什么是结构体)
INT_addr DWORD ?         ;INT的地址
SE_num WORD ?            ;节的数量
var DWORD ?              ;中间变量1
vara DWORD ?             ;中间变量2
EX_namebase DWORD ?      ;导出表名字数组首地址
pointer DWORD ?          ;指向导入表的第i个IID地址
Lastpointer DWORD ?      ;由于导出表名字不以0结尾,所以需要验证所取得地址和(Last)上一个地址是不是递增
.code

readFuncName PROC
INVOKE rva2raw,eax       ;假设已经传入了eax是INTname的地址RVA
ADD eax,OFFSET filebuf   ;加上我们自己程序内存读取PE文件的基地址
MOV vara,eax             ;临时保存,StdOut会修改寄存器
LNextFunc:              
MOV eax,vara             ;回复eax
MOV esi,DWORD PTR [eax]  ;读取名字地址
CMP esi,0h               ;和0比较(INT name数组结束为0)
JE LTail
MOV edi,esi
AND edi,0ff000000h       ;相等返回
CMP edi,80000000h               
JNE LOrdinal
ADD eax,4h
MOV esi,DWORD PTR [eax]
LOrdinal:              
INVOKE rva2raw,esi       ;将读取的地址转化为RAW,加上我们自己程序内存读取PE文件的基地址
ADD eax,OFFSET filebuf   
ADD eax,2h               ;前两字节是ordinal,跳过
MOV var,eax              
INVOKE StdOut,var
INVOKE StdOut,addr strn  
ADD vara,4h              ;更新eax的指向
JMP LNextFunc
LTail:
ret
readFuncName ENDP

readIID PROC             ;假设传入的esi已经是IID的RAW,以下读取INT地址
MOV eax,DWORD PTR [esi]
MOV INT_addr,eax
ADD esi,0ch
MOV eax,DWORD PTR [esi]
INVOKE rva2raw,eax
ADD eax,OFFSET filebuf
INVOKE StdOut,eax
INVOKE StdOut,addr strn
ADD esi,4h
MOV eax,INT_addr
ret
readIID ENDP

readIED PROC            ;假设传入的esi已经是IED的RAW,以下读取ADDRESS OF NAME地址
ADD esi,20h
MOV eax,DWORD PTR [esi]
INVOKE rva2raw,eax      ;address of name的RVA转化成 RAW
MOV EX_namebase,eax     ;保存名字数组的基地址
MOV Lastpointer,eax     ;保存前一个名字指针的地址
LNextOut:
ADD eax,OFFSET filebuf  ;RAW加上我们自己程序内存读取PE文件的基地址
MOV ebx,DWORD PTR [eax] ;读取
MOV edi,ebx             ;将edx放到edi中和lastpointer比较,如果单增说明还有一个要读,若否则说明结束
SUB edi,Lastpointer     
CMP edi,0h
JL LEndEx
INVOKE rva2raw,ebx      ;读到到字符串地址也是RVA,要转到RAW
ADD eax,OFFSET filebuf
INVOKE StdOut,eax       ;输出
INVOKE StdOut,addr strn
ADD EX_namebase,4h      ;下一个地址
MOV eax,EX_namebase     ;恢复eax
JMP LNextOut
LEndEx:
ret
readIED ENDP

rva2raw PROC rva:DWORD  
MOV eax,rva;eax里面存储需要转换的地址
MOV ecx,0;ecx里面来计数
LSec:
SHR ecx,2
CMP cx,SE_num;ecx和节头数比较 
JE LEnd 
SHL ecx,2                            ;如果等于节头数,说明找完了,退出
MOV edx,DWORD PTR SE_adds[ecx*4]     ;把节大小放到edx,地址放到edi
MOV edi,DWORD PTR SE_adds[ecx*4+4h]
Add edx,edi                          ;edx为上限,edi为下限
CMP rva,edi
JL LNext
CMP rva,edx                          ;条件,输入的RVA大于该节的RVA起始地址,小于该节的RVA+VIRTUAL_SIZE
JG LNext
JMP LEnd
LNext:
ADD ecx,4h                           ;没找到则下一个节的信息结构体
JMP LSec
LEnd:
SUB eax,DWORD PTR SE_adds[ecx*4+4h]  ;若找到了减去RVA,加上RAW pointer
ADD eax,DWORD PTR SE_adds[ecx*4+0ch]
ret
rva2raw ENDP


main PROC
INVOKE StdOut,addr banner
INVOKE StdOut,addr strn
INVOKE StdIn,addr path,20
;读入待分析文件
INVOKE CreateFile,addr path,\
                  GENERIC_READ,\
                  FILE_SHARE_READ,\
                  0,\
                  OPEN_EXISTING,\
                  FILE_ATTRIBUTE_ARCHIVE,\
                  0
MOV filehandle,eax
INVOKE SetFilePointer, filehandle,\
                 0,\
                 0,\
                 FILE_BEGIN
INVOKE ReadFile, filehandle,\
                 addr filebuf,\
                 217502,\
                 0,\
                 0
MOV esi,OFFSET filebuf
;至此,文件读入了内存之中,下面开始对内存进行读写

ADD esi,3ch
MOV eax,DWORD PTR [esi]
MOV esi,OFFSET filebuf
ADD esi,eax
MOV ax,WORD PTR [esi+6h]
MOV SE_num,ax
;到此找到了NT头(PE FILE_HEADER)
MOV NT_addr,esi
MOV ebx,NT_addr
ADD ebx,18h
MOV OP_addr,ebx
;找到可选头,为后面遍历可选头做准备

ADD esi,14h
MOV eax,0
MOV ax,WORD PTR [esi]
MOV ebx,OP_addr
ADD ebx,eax
MOV SE_addr,ebx
;找到节头,为了转换RVA和RAW做准备

MOV esi,OP_addr
ADD esi,60h
MOV eax,DWORD PTR [esi]
MOV EX_addr,eax
ADD esi,8h
MOV eax,DWORD PTR [esi]
MOV IM_addr,eax


;开始遍历节头表
MOV esi,SE_addr
MOV ecx,0
LSection:
MOV edx,DWORD PTR [esi]
CMP edx,0;比较有没有节头内容
JE Lfound
ADD esi,8h;
MOV eax,DWORD PTR [esi]  ;虚拟内存大小
MOV SE_adds[ecx],eax
ADD esi,4h
MOV eax,DWORD PTR [esi]  ;虚拟内存基址
MOV SE_adds[ecx+4h],eax  
ADD esi,4h
MOV eax,DWORD PTR [esi]  ;文件节区大小
MOV SE_adds[ecx+8h],eax
ADD esi,4h
MOV eax,DWORD PTR [esi]  ;文件节区基址偏移
MOV SE_adds[ecx+0ch],eax
ADD ecx,10h
ADD esi,14h
JMP LSection

Lfound:
MOV ebx,IM_addr
;提取导入表导出表的偏移和地址,准备转换
;1.遍历节头,找到具体的节
;2.转化成文件偏移,取值,读取

INVOKE rva2raw,ebx       ;找到了导入表的文件偏移(返回值为eax)
MOV esi,OFFSET filebuf
ADD esi,eax
MOV pointer,esi          ;pointer存储IID每个元素的首地址
NextChunk:
MOV esi,pointer          ;将esi设为每个IID首地址(RAW)读取
MOV edi,DWORD PTR [esi+0ch]  ;检测是否读完了
CMP edi,0
JE ReadEnd
INVOKE readIID
CMP INT_addr,0h
JNE LEmptyDll
MOV eax,DWORD PTR [esi]
LEmptyDll:
INVOKE readFuncName            ;读当前IID元素的函数
ADD pointer,14h
JMP NextChunk
ReadEnd:
INVOKE StdOut,addr strn


;下面开始读导出表
MOV ebx,EX_addr
CMP ebx,0h
JE LNO                   ;检查导出表是否为0
INVOKE rva2raw,ebx       ;eax为返回值
MOV esi,OFFSET filebuf
ADD esi,eax
INVOKE readIED           ;将esi置为IED的首地址(RAW)读取
JMP Lend
LNO:
INVOKE StdOut,addr NoExport
Lend:


INVOKE CloseHandle,filehandle
INVOKE ExitProcess,0
main ENDP
END main

其中看似乱码的是基于不同函数调用约定所生成的名称