通过Hash查找API函数地址

 

由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失,均由使用者本人负责,文章作者不为此承担任何责任。(本文仅用于交流学习)

该方法最早由 Stephen Fewer 在2009年提出,顾名思义,其是通过Hash来条用函数,替代传统的直接使用函数的名称。

我们先来简单的了解一下PE结构

PE文件

PE文件是由许许多多的结构体组成,其从上到下依次是Dos头、Nt头、节表、节区和调试信息(可选)。

 

IMAGE_DOS_HEADER

IMAGE_DOS_HEADER的结构如下

typedef struct _IMAGE_DOS_HEADER {      // DOS .EXE header
    WORD   e_magic;                     // Magic number
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // File address of new exe header
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER;

其中最最要的就是e_magic、e_lfanew

e_magic:用十六进制表示就是4D 5A,其是Mark Zbikowski(MZ)的姓名缩写,他是最初的MS-DOS设计者之一

e_lfanew:它保存着IMAGE_NT_HEADERS32这个结构体在PE文件中的偏移地址,PE文件运行时只有通过该文件才能定位到PE签名。

IMAGE_DOS_STUB

在文件的第一个字节之后,将启动一个dos存根。内存中的这个区域大部分都是零

IMAGE_NT_HEADERS

可以看见其分为32位、64位,结构都差不多一样的

typedef struct _IMAGE_NT_HEADERS64 {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER64 OptionalHeader;
} IMAGE_NT_HEADERS64, *PIMAGE_NT_HEADERS64;

typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32, *PIMAGE_NT_HEADERS32;

Signature:PE的签名,相对该结构的偏移0x00

FileHeader:结构体,相对该结构的偏移0x04

OptionalHeader:结构体,相对该结构的偏移0x18

Signature

其DOS头的MZ一样,都是PE文件的标准特征

IMAGE_FILE_HEADER

我们看看IMAGE_FILE_HEADER这个结构体,32位、64位中该结构体都是一样的

typedef struct _IMAGE_FILE_HEADER {
    WORD    Machine;
    WORD    NumberOfSections;
    DWORD   TimeDateStamp;
    DWORD   PointerToSymbolTable;
    DWORD   NumberOfSymbols;
    WORD    SizeOfOptionalHeader;
    WORD    Characteristics;
} IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
Machine

指定该PE文件能够在32位上运行,还是在64位上执行

NumberOfSections

表示当前PE文件节区的数量

TimeDateStamp

该文件创建的时间,时间从1970年1月1日00:00开始计算

PointerToSymbolTable

指向COFF符号表偏移的指针,由于其已经被Debug格式所替代,因此COFF符号表在现今的PE文件中已较少应用

NumberOfSymbols

符号表中符号的数量,由于COFF符号表是一个大小固定的结构,因此只有通过这个字段才能计算出COFF符号表结构的结尾。

SizeOfOptionalHeader

存储该PE文件的可选PE头的大小,在32位的系统中是0x00E0,而在64位系统下则为0x00F0

Characteristics

该值描述PE文件的一些属性信息,比如是否可执行、是否是一个动态连接库等。该值可以是一个也可以是多个值的和

常用的文件属性值如下

比如我们这的和是22,分别是2、20分别对应上面的IMAGE_FILE_EXECUTABLE_IMAGE、IMAGE_FILE_LARGE_ADDRESS_AWARE

IMAGE_OPTIONAL_HEADER

接下来我们看看IMAGE_OPTIONAL_HEADER,其同样也分32位、64位,由于其成员较多因此这里只列出32位且只介绍重要的成员

IMAGE_OPTIONAL_HEADER32

typedef struct _IMAGE_OPTIONAL_HEADER {
   	WORD    Magic;
    BYTE    MajorLinkerVersion;
    BYTE    MinorLinkerVersion;
    DWORD   SizeOfCode;
    DWORD   SizeOfInitializedData;
    DWORD   SizeOfUninitializedData;
    DWORD   AddressOfEntryPoint;
    DWORD   BaseOfCode;
    DWORD   BaseOfData;
	DWORD   ImageBase;
    DWORD   SectionAlignment;
    DWORD   FileAlignment;
    WORD    MajorOperatingSystemVersion;
    WORD    MinorOperatingSystemVersion;
    WORD    MajorImageVersion;
    WORD    MinorImageVersion;
    WORD    MajorSubsystemVersion;
    WORD    MinorSubsystemVersion;
    DWORD   Win32VersionValue;
    DWORD   SizeOfImage;
    DWORD   SizeOfHeaders;
    DWORD   CheckSum;
    WORD    Subsystem;
    WORD    DllCharacteristics;
    DWORD   SizeOfStackReserve;
    DWORD   SizeOfStackCommit;
    DWORD   SizeOfHeapReserve;
    DWORD   SizeOfHeapCommit;
    DWORD   LoaderFlags;
    DWORD   NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[IMAGE_NUMBEROF_DIRECTORY_ENTRIES];
} IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
Magic

文件类型标识

  • 普通可执行映像0x010B

  • ROM镜像为0x0170

  • PE32+为0x020B

AddressOfEntryPoint

程序执行入口的RAV,在大多数可执行文件中,入口点(AddressOfEntryPoint)并不指向main()、winmaim()或dllmain()等函数的入口,而是指向运行时库代码,再由其调用这些函数。

ImageBase

文件在内存中的首选装入地址(对于DLL文件来说,即使其未能在此地址装入,也可以将其实际装入地址称为ImageBase)。如果该地址被占用,则会选用其它地址,但是如果文件被载入其它地址,那么就必须要通过重定位表对其进行资源的重定位,这就会导致文件的载入速度变慢

SectionAlignment

映像文件在被装入内存时的区段对齐大小(该成员的默认大小为系统的页面大小)

FileAlignment

映像文件在磁盘上的区段对齐大小

SizeOfImage

映像文件装入内存后的总大小(从ImageBase到最后一个区段的总大小)

SizeOfHeaders

MS-DOS头、PE头、区块表的尺寸之和

NumberOfRvaAndSizes

数据目录成员的数量,一般为0x00000010

IMAGE_DATA_DIRECTORY

其结构如下

typedef struct _IMAGE_DATA_DIRECTORY {
    DWORD   VirtualAddress;
    DWORD   Size;
} IMAGE_DATA_DIRECTORY, *PIMAGE_DATA_DIRECTORY;

VirtualAddress:指向某个数据的相对虚拟地址RAV

Size:某个数据块的大小

这两个成员就是定位各种表的关键信息,以下表格就是它的对应关系,其不同成员的信息如下

#define IMAGE_DIRECTORY_ENTRY_EXPORT          0   // Export Directory
#define IMAGE_DIRECTORY_ENTRY_IMPORT          1   // Import Directory
#define IMAGE_DIRECTORY_ENTRY_RESOURCE        2   // Resource Directory
#define IMAGE_DIRECTORY_ENTRY_EXCEPTION       3   // Exception Directory
#define IMAGE_DIRECTORY_ENTRY_SECURITY        4   // Security Directory
#define IMAGE_DIRECTORY_ENTRY_BASERELOC       5   // Base Relocation Table
#define IMAGE_DIRECTORY_ENTRY_DEBUG           6   // Debug Directory
//      IMAGE_DIRECTORY_ENTRY_COPYRIGHT       7   // (X86 usage)
#define IMAGE_DIRECTORY_ENTRY_ARCHITECTURE    7   // Architecture Specific Data
#define IMAGE_DIRECTORY_ENTRY_GLOBALPTR       8   // RVA of GP
#define IMAGE_DIRECTORY_ENTRY_TLS             9   // TLS Directory
#define IMAGE_DIRECTORY_ENTRY_LOAD_CONFIG    10   // Load Configuration Directory
#define IMAGE_DIRECTORY_ENTRY_BOUND_IMPORT   11   // Bound Import Directory in headers
#define IMAGE_DIRECTORY_ENTRY_IAT            12   // Import Address Table
#define IMAGE_DIRECTORY_ENTRY_DELAY_IMPORT   13   // Delay Load Import Descriptors
#define IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR 14   // COM Runtime descriptor

由于很多,这里只介绍后面要用到的IMAGE_EXPORT_DIRECTORY(导出表),其结构体的结构如下,同样只介绍 重要的部分

typedef struct _IMAGE_EXPORT_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Name;
    DWORD   Base;
    DWORD   NumberOfFunctions;
    DWORD   NumberOfNames;
    DWORD   AddressOfFunctions;     // RVA from base of image
    DWORD   AddressOfNames;         // RVA from base of image
    DWORD   AddressOfNameOrdinals;  // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
NumberOfFunctions

实际导出的函数个数,这个值并不是真的函数数量,他是通过函数序号表中最大的序号减去最小的序号再加上一得到的,例如:一共导出了3个函数,序号分别是:0、2、4,NumberOfFunctions = 4 - 0 + 1 = 5个

NumberOfNames

导出的函数中具名的函数个数

AddressOfFunctions

导出函数地址数组(数组元素个数=NumberOfFuntions),其可以用来定位导出表中所有函数的地址表,其长度由NumberOfFunctions进行限定,地址表中的成员也是一个RVA地址,在内存中加上ImageBase后才是函数真正的地址。

AddressOfNames

函数名称地址数组(数组元素个数=NumberOfNames),其可以用来定位导出表中所有函数的名称表,它的长度由NumberOfNames进行限定,名称表的成员也是一个RVA地址,在FIleBuffer状态下需要进行RVA到FOA的转换才能真正找到函数名称。

AddressOfNameOrdinals

Ordinal 地址数组(数组元素个数=NumberOfNames),可以用来定位导出表中所有函数的序号表,它的长度由NumberOfNames进行限定,名称表的成员是一个函数序号,该序号用于通过名称获取函数地址。

IMAGE_SECTION_HEADER这个本篇文章用不到,读者感兴趣的话可以去看看参考文章

GetProcAddress() 的原理

  • 利用AddressOfName成员转到"函数名称地址数组"(IMAGE_EXPORT_DIRECTORY.AddressOfNames)

  • 该地址处存储着此模块的所有的导出名称字符串,通过比较字符串(strcmp),找到指定的函数名称。此时数组的索引记为i

  • 利用AddressOfNameOrdinals成员,转到ordinal数组(IMAGE_EXPORT_DIRECTORY.AddressOfNameOrdinals)

  • 在ordinal数组中通过i查找相应的值(AddressOfNameOrdinals[i],即ordinal[i])

  • 查找并跳转到导出函数地址数组所在的地址(IMAGE_EXPORT_DIRECTORY.AddressOfFunctions)

  • 将刚刚得到的ordinal的值作为数组索引,得到最终的函数起始地址,类似(AddressOfFunctions[ordinal[i]])

代码实现

我们用c++实现,去查找MessageBoxW,同时打印出利用GetProcAddress函数获取的地址

#include <windows.h>
#include <stdio.h>
#include <iostream>

using namespace std;

int main(){
    HMODULE hand = LoadLibraryW(L"user32.dll");

    PIMAGE_DOS_HEADER Dos_Header = (PIMAGE_DOS_HEADER)hand;
    PIMAGE_NT_HEADERS Nt_Headers = (PIMAGE_NT_HEADERS)((LPBYTE)hand + Dos_Header->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY Export_Directory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hand + Nt_Headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD fAddr = (PDWORD)((LPBYTE)hand + Export_Directory->AddressOfFunctions);
    PDWORD fNames = (PDWORD)((LPBYTE)hand + Export_Directory->AddressOfNames);
    PWORD  ordinal = (PWORD)((LPBYTE)hand + Export_Directory->AddressOfNameOrdinals);

    for (DWORD i = 0; i < Export_Directory->AddressOfFunctions; i++) {
        LPSTR pFuncName = (LPSTR)((LPBYTE)hand + fNames[i]);

        if (strcmp(pFuncName,"MessageBoxW") == 0) {
            printf("%s\n",pFuncName);
            cout << "Export_Directory value is: " << (LPVOID)((LPBYTE)hand + fAddr[ordinal[i]]) << endl;
            cout << "GetProcAddress value is: " << GetProcAddress(hand, "MessageBoxW") << endl;
        }
    }

}

我们使用python实现简单的Hash加密

import sys

hash = 0x35
if(len(sys.argv) != 2):
    print("usage:\npython3 Hash.py MessageBoxW")
else:
    data = sys.argv[1]
    for i in range(0, len(data)):
        hash += ord(data[i]) + (hash << 1)
    print (hash)

c++中逻辑也相同的

DWORD HashEncode(char* data) {
    DWORD hash = 0x35;
    for (int i = 0; i < strlen(data); i++) {
        hash += data[i] + (hash << 1);
    }
    return hash;
}

实现如下

#include <windows.h>
#include <iostream>

using namespace std;

typedef int (WINAPI* _MessageBoxW)(
    HWND    hWnd,
    LPCWSTR lpText,
    LPCWSTR lpCaption,
    UINT    uType
    );

DWORD HashEncode(char* data) {
    DWORD hash = 0x35;
    for (int i = 0; i < strlen(data); i++) {
        hash += data[i] + (hash << 1);
    }
    return hash;
}

LPVOID FindHash(HANDLE hand) {

    PIMAGE_DOS_HEADER Dos_Header = (PIMAGE_DOS_HEADER)hand;
    PIMAGE_NT_HEADERS Nt_Headers = (PIMAGE_NT_HEADERS)((LPBYTE)hand + Dos_Header->e_lfanew);
    PIMAGE_EXPORT_DIRECTORY Export_Directory = (PIMAGE_EXPORT_DIRECTORY)((LPBYTE)hand + Nt_Headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    PDWORD fAddr = (PDWORD)((LPBYTE)hand + Export_Directory->AddressOfFunctions);
    PDWORD fNames = (PDWORD)((LPBYTE)hand + Export_Directory->AddressOfNames);
    PWORD  ordinal = (PWORD)((LPBYTE)hand + Export_Directory->AddressOfNameOrdinals);

    for (DWORD i = 0; i < Export_Directory->AddressOfFunctions; i++) {
        LPSTR pFuncName = (LPSTR)((LPBYTE)hand + fNames[i]);

        if (HashEncode(pFuncName) == 17036718) {
            cout << "Success Found: " << pFuncName << endl;

            return (LPVOID)((LPBYTE)hand + fAddr[ordinal[i]]);
        }
    }

}

int main() {
    HMODULE hand = LoadLibraryW(L"user32.dll");
    _MessageBoxW lMessageBoxW = (_MessageBoxW)FindHash(hand);
    lMessageBoxW(NULL, L"Hello", L"Hello", MB_OK);
}

题外话

初入计算机行业的人或者大学计算机相关专业毕业生,很多因缺少实战经验,就业处处碰壁。下面我们来看两组数据:

  • 2023届全国高校毕业生预计达到1158万人,就业形势严峻;
  • 国家网络安全宣传周公布的数据显示,到2027年我国网络安全人员缺口将达327万。

一方面是每年应届毕业生就业形势严峻,一方面是网络安全人才百万缺口。

6月9日,麦可思研究2023年版就业蓝皮书(包括《2023年中国本科生就业报告》《2023年中国高职生就业报告》)正式发布。

2022届大学毕业生月收入较高的前10个专业

本科计算机类、高职自动化类专业月收入较高。2022届本科计算机类、高职自动化类专业月收入分别为6863元、5339元。其中,本科计算机类专业起薪与2021届基本持平,高职自动化类月收入增长明显,2022届反超铁道运输类专业(5295元)排在第一位。

具体看专业,2022届本科月收入较高的专业是信息安全(7579元)。对比2018届,电子科学与技术、自动化等与人工智能相关的本科专业表现不俗,较五年前起薪涨幅均达到了19%。数据科学与大数据技术虽是近年新增专业但表现亮眼,已跻身2022届本科毕业生毕业半年后月收入较高专业前三。五年前唯一进入本科高薪榜前10的人文社科类专业——法语已退出前10之列。

 “没有网络安全就没有国家安全”。当前,网络安全已被提升到国家战略的高度,成为影响国家安全、社会稳定至关重要的因素之一。 

网络安全行业特点

1、就业薪资非常高,涨薪快 2021年猎聘网发布网络安全行业就业薪资行业最高人均33.77万!

 2、人才缺口大,就业机会多

2019年9月18日《中华人民共和国中央人民政府》官方网站发表:我国网络空间安全人才 需求140万人,而全国各大学校每年培养的人员不到1.5W人。猎聘网《2021年上半年网络安全报告》预测2027年网安人才需求300W,现在从事网络安全行业的从业人员只有10W人。

 行业发展空间大,岗位非常多

网络安全行业产业以来,随即新增加了几十个网络安全行业岗位︰网络安全专家、网络安全分析师、安全咨询师、网络安全工程师、安全架构师、安全运维工程师、渗透工程师、信息安全管理员、数据安全工程师、网络安全运营工程师、网络安全应急响应工程师、数据鉴定师、网络安全产品经理、网络安全服务工程师、网络安全培训师、网络安全审计员、威胁情报分析工程师、灾难恢复专业人员、实战攻防专业人员…

职业增值潜力大

网络安全专业具有很强的技术特性,尤其是掌握工作中的核心网络架构、安全技术,在职业发展上具有不可替代的竞争优势。

随着个人能力的不断提升,所从事工作的职业价值也会随着自身经验的丰富以及项目运作的成熟,升值空间一路看涨,这也是为什么受大家欢迎的主要原因。

从某种程度来讲,在网络安全领域,跟医生职业一样,越老越吃香,因为技术愈加成熟,自然工作会受到重视,升职加薪则是水到渠成之事。

黑客&网络安全如何学习

今天只要你给我的文章点赞,我私藏的网安学习资料一样免费共享给你们,来看看有哪些东西。

 1.学习路线图 

 攻击和防守要学的东西也不少,具体要学的东西我都写在了上面的路线图,如果你能学完它们,你去就业和接私活完全没有问题。

2.视频教程

网上虽然也有很多的学习资源,但基本上都残缺不全的,这是我自己录的网安视频教程,上面路线图的每一个知识点,我都有配套的视频讲解。

内容涵盖了网络安全法学习、网络安全运营等保测评、渗透测试基础、漏洞详解、计算机基础知识等,都是网络安全入门必知必会的学习内容。

 

 (都打包成一块的了,不能一一展开,总共300多集)

因篇幅有限,仅展示部分资料,需要保存下方图片,微信扫码即可前往获取

3.技术文档和电子书

技术文档也是我自己整理的,包括我参加大型网安行动、CTF和挖SRC漏洞的经验和技术要点,电子书也有200多本,由于内容的敏感性,我就不一一展示了。 

 因篇幅有限,仅展示部分资料,需要保存下方图片,微信扫码即可前往获取

4.工具包、面试题和源码

“工欲善其事必先利其器”我为大家总结出了最受欢迎的几十款款黑客工具。涉及范围主要集中在 信息收集、Android黑客工具、自动化工具、网络钓鱼等,感兴趣的同学不容错过。 

 还有我视频里讲的案例源码和对应的工具包,需要的话也可以拿走。

因篇幅有限,仅展示部分资料,需要保存下方图片,微信扫码即可前往获取

 最后就是我这几年整理的网安方面的面试题,如果你是要找网安方面的工作,它们绝对能帮你大忙。

这些题目都是大家在面试深信服、奇安信、腾讯或者其它大厂面试时经常遇到的,如果大家有好的题目或者好的见解欢迎分享。

参考解析:深信服官网、奇安信官网、Freebuf、csdn等

内容特点:条理清晰,含图像化表示更加易懂。

内容概要:包括 内网、操作系统、协议、渗透测试、安服、漏洞、注入、XSS、CSRF、SSRF、文件上传、文件下载、文件包含、XXE、逻辑漏洞、工具、SQLmap、NMAP、BP、MSF…

 因篇幅有限,仅展示部分资料,需要保存下方图片,微信扫码即可前往获取 

  

  • 34
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值