openssl3.2 - exp - calc PE file checksum and SHA3-512
概述
想在程序中, 对自身的PE内容算校验和和HASH, 然后送给服务端判断PE文件是否被修改了.
前几天, 看了一个资料, 里面有算PE校验和的实现. 迁移到自己工程.
但是没有算HASH, 正好已经将openssl官方demo过了一遍, 有个官方demo正好是对buffer算hash(openssl3.2 - 官方demo学习 - encode - rsa_encode.c), 也迁移到自己工程.
程序中释放openssl资源时, 开始判断条件写错了. 如下:
c
if (NULL == _ossl_lib_ctx)
{
OSSL_LIB_CTX_free(_ossl_lib_ctx);
_ossl_lib_ctx = NULL;
}
导致有内存泄漏.
但是我前几天做了openssl3.2检测内存泄漏的实验(openssl3.2 - crypto-mdebug被弃用后, 内存泄漏检查的替代方法), 非常好使.
现在我的工程模板中都加入了内存检测的实现, 程序跑完, 退出时, 如果有内存泄漏, 直接断言, 然后用代码块注释法, 可以很快的定位修复内存泄漏问题.
c
void* my_CRYPTO_malloc(size_t num, const char* file, int line)
{
void* p = NULL;
It_mem_hook it;
CMemHookRec* rec = NULL;
p = malloc(num);
if (NULL != p)
{
it = g_mem_hook_map.find((uint64_t)p);
if (it != g_mem_hook_map.end()) {
// printf("find key\n");
assert(false);
}
else {
// printf("not find key\n");
rec = new CMemHookRec();
if (NULL != rec)
{
rec->rec_sn = ++g_u64_malloc_cnt_all;
// 观察UI上显示的内存分配记录的序号, 然后在具体序号上下断点, 然后跟进库里面, 再跟出到自己的应用代码处, 就基本知道是啥没释放:)
if (2232 == rec->rec_sn)
{
rec->rec_sn = rec->rec_sn; // 如果哪里泄漏了, 可以单步到openssl库代码中
// 如果是自己没调用释放函数, 用代码块的注释排除法, 很快能确定问题.
}
俺居然预判了可能会发生内存泄漏时如何检测的场景, 提前将检测措施搞定了. 真机智啊:P
笔记
main.cpp
c
/*!
* \file main.cpp
* \note openssl3.2 - exp - calc PE file checksum and SHA3-512
*/
#include "my_openSSL_lib.h"
#include <openssl/crypto.h>
#include <openssl/bio.h>
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include "CMemHookRec.h"
#include <imagehlp.h>
#pragma comment(lib, "imagehlp.lib")
#include "CPeFileCheck.h"
void my_openssl_app();
const char* psz_argv0 = NULL;
int main(int argc, char** argv)
{
psz_argv0 = argv[0];
setvbuf(stdout, NULL, _IONBF, 0); // 清掉stdout缓存, 防止调用printf时阻塞
mem_hook();
my_openssl_app();
mem_unhook();
return 0;
/*! run result
PE calc ok
PE checksum old = 0x0
PE checksum calc = 0x23776
SHA3 512 len = 64
SHA3 512 data below:
0C F4 16 D4 4E 2A 9B 20
6A 9C B4 22 4F A8 3D 19
25 D8 6E 7F 7C 45 20 6B
70 78 77 4B FF A6 94 B5
D1 EE 16 EB 6D 0A B3 97
19 0B 1D 7D EE 63 0A D6
1D 01 F9 02 1D 93 2C 91
F5 00 39 CC 82 9D 65 92
END
free map, g_mem_hook_map.size() = 0
*/
}
void my_openssl_app()
{
CPeFileCheck* pe = NULL;
DWORD dwPeCheckSum = 0;
bool b_rc = false;
DWORD dwPeCheckSumOrg = 0;
DWORD dwPeCheckSumCalc = 0;
uint8_t* pHash = NULL;
int HashLen = 0;
int i = 0;
int col_cnt = 0;
do {
pe = new CPeFileCheck();
if (NULL == pe)
{
break;
}
// 必须是全路径名称
b_rc = pe->PE_calc(psz_argv0);
if (!b_rc)
{
printf("error\n");
}
else {
printf("PE calc ok\n");
if (pe->get_PE_checkSum(dwPeCheckSumOrg, dwPeCheckSumCalc))
{
printf("PE checksum old = 0x%X\n", dwPeCheckSumOrg);
printf("PE checksum calc = 0x%X\n", dwPeCheckSumCalc);
}
if (pe->get_PE_Hash_SHA3_512(&pHash, HashLen))
{
printf("SHA3 512 len = %d\n", HashLen);
printf("SHA3 512 data below:\n");
for (i = 0; i < HashLen; i++)
{
printf("%2.2X", pHash[i]);
if (8 == ++col_cnt)
{
col_cnt = 0;
printf("\n");
}
else {
printf(" ");
}
}
}
}
} while (false);
if (NULL != pe)
{
delete pe;
pe = NULL;
}
printf("\nEND\n");
}
CPeFileCheck.h
c
/*!
\file CPeFileCheck.h
*/
#ifndef __CPEFILECHECK_H__
#define __CPEFILECHECK_H__
#include "my_openSSL_lib.h"
#include <cstdint>
#ifndef BYTE_ORDER
#define LITTLE_ENDIAN 1234
#define BIG_ENDIAN 4321
#define BYTE_ORDER LITTLE_ENDIAN
#endif /* BYTE_ORDER */
#if BYTE_ORDER == BIG_ENDIAN
#define LE_UINT16(x) ((((x) >> 8) & 0x00FF) | \
(((x) << 8) & 0xFF00))
#define LE_UINT32(x) (((x) >> 24) | \
(((x) & 0x00FF0000) >> 8) | \
(((x) & 0x0000FF00) << 8) | \
((x) << 24))
#else
#define LE_UINT16(x) (x)
#define LE_UINT32(x) (x)
#endif /* BYTE_ORDER == BIG_ENDIAN */
#define SIZE_64K 65536 /* 2^16 */
#define SIZE_16M 16777216 /* 2^24 */
#define GET_UINT8_LE(p) ((const u_char *)(p))[0]
#define GET_UINT16_LE(p) (uint16_t)(((const u_char *)(p))[0] | \
(((const u_char *)(p))[1] << 8))
#define GET_UINT32_LE(p) (uint32_t)(((const u_char *)(p))[0] | \
(((const u_char *)(p))[1] << 8) | \
(((const u_char *)(p))[2] << 16) | \
(((const u_char *)(p))[3] << 24))
#define PUT_UINT8_LE(i, p) ((u_char *)(p))[0] = (u_char)((i) & 0xff);
#define PUT_UINT16_LE(i,p) ((u_char *)(p))[0] = (u_char)((i) & 0xff); \
((u_char *)(p))[1] = (u_char)(((i) >> 8) & 0xff)
#define PUT_UINT32_LE(i,p) ((u_char *)(p))[0] = (u_char)((i) & 0xff); \
((u_char *)(p))[1] = (u_char)(((i) >> 8) & 0xff); \
((u_char *)(p))[2] = (u_char)(((i) >> 16) & 0xff); \
((u_char *)(p))[3] = (u_char)(((i) >> 24) & 0xff)
class CPeFileCheck
{
public:
CPeFileCheck();
virtual ~CPeFileCheck();
public:
bool PE_calc(const char* pszFilePathName);
bool get_PE_checkSum(DWORD& dwPECheckSumOrg, DWORD& dwPECheckSumCalc);
bool get_PE_Hash_SHA3_512(uint8_t** ppdata, int& len);
private:
uint32_t get_file_size(const char* infile);
uint8_t* map_file(const char* infile, const size_t size);
void unmap_file(uint8_t* indata);
bool is_PE_format_ok();
bool calc_PE_checksum(DWORD& dwPeCheckSumCalc);
bool calc_hash_SHA3_512();
private:
uint32_t m_u32_file_size;
uint8_t* m_pu8_map_file;
DWORD m_dwPeCheckSumOrg;
DWORD m_dwPeCheckSumCalc;
uint32_t m_header_size;
uint32_t m_pe32plus;
uint32_t m_magic;
uint32_t m_nrvas;
uint32_t m_sigpos;
uint32_t m_siglen;
unsigned int m_digest_length;
uint8_t m_ary_digest_value[1024];
bool m_b_calc_ok;
};
#endif // #ifndef __CPEFILECHECK_H__
CPeFileCheck.cpp
c
//! \file CPeFileCheck.cpp
#include "CPeFileCheck.h"
#include <sys/stat.h>
#include "my_openSSL_lib.h"
#include <openssl/crypto.h>
#include <openssl/bio.h>
#include <openssl/err.h>
#include <openssl/evp.h>
#include <cassert>
CPeFileCheck::CPeFileCheck()
{
m_u32_file_size = 0;
m_pu8_map_file = NULL;
m_dwPeCheckSumOrg = 0;
m_dwPeCheckSumCalc = 0;
m_header_size = 0;
m_pe32plus = 0;
m_magic = 0;
m_nrvas = 0;
m_sigpos = 0;
m_siglen = 0;
m_digest_length = 0;
memset(m_ary_digest_value, 0, sizeof(m_ary_digest_value));
m_b_calc_ok = false;
}
CPeFileCheck::~CPeFileCheck()
{
unmap_file(this->m_pu8_map_file);
}
bool CPeFileCheck::get_PE_checkSum(DWORD& dwPECheckSumOrg, DWORD& dwPECheckSumCalc)
{
dwPECheckSumOrg = this->m_dwPeCheckSumOrg;
dwPECheckSumCalc = this->m_dwPeCheckSumCalc;
return m_b_calc_ok;
}
bool CPeFileCheck::get_PE_Hash_SHA3_512(uint8_t** ppdata, int& len)
{
bool b_rc = false;
do {
if (NULL == ppdata)
{
break;
}
*ppdata = this->m_ary_digest_value;
len = this->m_digest_length;
b_rc = m_b_calc_ok;
} while (false);
return b_rc;
}
bool CPeFileCheck::PE_calc(const char* pszFilePathName)
{
bool b_rc = false;
do {
if (NULL == pszFilePathName)
{
break;
}
m_b_calc_ok = false;
if (NULL != m_pu8_map_file)
{
unmap_file(m_pu8_map_file);
}
m_u32_file_size = get_file_size(pszFilePathName);
m_pu8_map_file = map_file(pszFilePathName, m_u32_file_size);
if (NULL == m_pu8_map_file)
{
break;
}
if (!is_PE_format_ok())
{
break;
}
if (!calc_PE_checksum(m_dwPeCheckSumCalc))
{
break;
}
if (!calc_hash_SHA3_512())
{
break;
}
m_b_calc_ok = true;
b_rc = true;
} while (false);
return b_rc;
}
bool CPeFileCheck::calc_hash_SHA3_512()
{
//! \ref https://blog.csdn.net/LostSpeed/article/details/135581192
bool b_rc = false;
OSSL_LIB_CTX* _ossl_lib_ctx;
int ret = 0;
const char* _psz_option_properties = NULL;
EVP_MD* _evp_md = NULL;
EVP_MD_CTX* _evp_md_ctx = NULL;
unsigned int digest_length;
uint8_t* _p_digest_value = NULL;
do {
_ossl_lib_ctx = OSSL_LIB_CTX_new();
if (NULL == _ossl_lib_ctx) {
// fprintf(stderr, "OSSL_LIB_CTX_new() returned NULL\n");
break;
}
/*
* Fetch a message digest by name
* The algorithm name is case insensitive.
* See providers(7) for details about algorithm fetching
*/
_evp_md = EVP_MD_fetch(_ossl_lib_ctx,"SHA3-512", _psz_option_properties);
if (NULL == _evp_md) {
// fprintf(stderr, "EVP_MD_fetch could not find SHA3-512.");
break;
}
/* Determine the length of the fetched digest type */
digest_length = EVP_MD_get_size(_evp_md);
if (digest_length <= 0) {
// fprintf(stderr, "EVP_MD_get_size returned invalid size.\n");
break;
}
_p_digest_value = (uint8_t*)OPENSSL_malloc(digest_length);
if (_p_digest_value == NULL) {
// fprintf(stderr, "No memory.\n");
break;
}
/*
* Make a message digest context to hold temporary state
* during digest creation
*/
_evp_md_ctx = EVP_MD_CTX_new();
if (NULL == _evp_md_ctx) {
// fprintf(stderr, "EVP_MD_CTX_new failed.\n");
break;
}
/*
* Initialize the message digest context to use the fetched
* digest provider
*/
if (EVP_DigestInit(_evp_md_ctx, _evp_md) != 1) {
// fprintf(stderr, "EVP_DigestInit failed.\n");
break;
}
/* Digest parts one and two of the soliloqy */
if (EVP_DigestUpdate(_evp_md_ctx, this->m_pu8_map_file, this->m_u32_file_size) != 1) {
// fprintf(stderr, "EVP_DigestUpdate(hamlet_1) failed.\n");
break;
}
if (EVP_DigestFinal(_evp_md_ctx, _p_digest_value, &digest_length) != 1) {
// fprintf(stderr, "EVP_DigestFinal() failed.\n");
break;
}
// hash in _p_digest_value[], len = digest_length
if (digest_length > sizeof(m_ary_digest_value))
{
assert(false);
break;
}
m_digest_length = digest_length;
memcpy(m_ary_digest_value, _p_digest_value, digest_length);
b_rc = true;
} while (false);
// 咱这次做的openssl内存泄漏检查的措施挺NB的, 如果下面的指针判断写错了(e.g. if (NULL == _evp_md_ctx) { ... }), 导致内存泄漏, 程序结束时, 就能有断言:P
/* OpenSSL free functions will ignore NULL arguments */
if (NULL != _evp_md_ctx)
{
EVP_MD_CTX_free(_evp_md_ctx);
_evp_md_ctx = NULL;
}
if (NULL != _p_digest_value)
{
OPENSSL_free(_p_digest_value);
_p_digest_value = NULL;
}
if (NULL != _evp_md)
{
EVP_MD_free(_evp_md);
_evp_md = NULL;
}
if (NULL != _ossl_lib_ctx)
{
OSSL_LIB_CTX_free(_ossl_lib_ctx);
_ossl_lib_ctx = NULL;
}
return b_rc;
}
bool CPeFileCheck::is_PE_format_ok()
{
bool b_rc = false;
do {
if (0 != memcmp(m_pu8_map_file, "MZ", 2)) {
break;
}
if (this->m_u32_file_size < 64) {
// printf("Corrupt DOS file - too short\n");
break;
}
/* SizeOfHeaders field specifies the combined size of an MS-DOS stub, PE header,
* and section headers rounded up to a multiple of FileAlignment.
* SizeOfHeaders must be < filesize and cannot be < 0x0000002C (44) in Windows 7
* because of a bug when checking section names for compatibility purposes */
m_header_size = GET_UINT32_LE(this->m_pu8_map_file + 60);
if (m_header_size < 44 || m_header_size > this->m_u32_file_size) {
// printf("Unexpected SizeOfHeaders field: 0x%08X\n", header_size);
break;
}
if (this->m_u32_file_size < m_header_size + 176) {
// printf("Corrupt PE file - too short\n");
break;
}
if (0 != memcmp(this->m_pu8_map_file + m_header_size, "PE\0\0", 4)) {
// printf("Unrecognized DOS file type\n");
break;
}
/* Magic field identifies the state of the image file. The most common number is
* 0x10B, which identifies it as a normal executable file,
* 0x20B identifies it as a PE32+ executable,
* 0x107 identifies it as a ROM image (not supported) */
m_magic = GET_UINT16_LE(this->m_pu8_map_file + m_header_size + 24);
if (m_magic == 0x20b) {
m_pe32plus = 1;
}
else if (m_magic == 0x10b) {
m_pe32plus = 0;
}
else {
// printf("Corrupt PE file - found unknown magic %04X\n", magic);
break;
}
/* The image file checksum */
this->m_dwPeCheckSumOrg = GET_UINT32_LE(this->m_pu8_map_file + m_header_size + 88);
/* NumberOfRvaAndSizes field specifies the number of data-directory entries
* in the remainder of the optional header. Each describes a location and size. */
m_nrvas = GET_UINT32_LE(this->m_pu8_map_file + m_header_size + 116 + m_pe32plus * 16);
if (m_nrvas < 5) {
// printf("Can not handle PE files without certificate table resource\n");
break;
}
/* Certificate Table field specifies the attribute certificate table address (4 bytes) and size (4 bytes) */
m_sigpos = GET_UINT32_LE(this->m_pu8_map_file + m_header_size + 152 + m_pe32plus * 16);
m_siglen = GET_UINT32_LE(this->m_pu8_map_file + m_header_size + 152 + m_pe32plus * 16 + 4);
/* Since fix for MS Bulletin MS12-024 we can really assume
that signature should be last part of file */
if (((m_sigpos != 0) || (m_siglen != 0)) &&
((m_sigpos == 0) || (m_siglen == 0) || (m_sigpos >= this->m_u32_file_size) || ((m_sigpos + m_siglen) != this->m_u32_file_size))) {
// printf("Ignoring PE signature not at the end of the file\n");
m_sigpos = 0;
m_siglen = 0;
}
b_rc = true;
} while (false);
return b_rc;
}
bool CPeFileCheck::calc_PE_checksum(DWORD& dwPeCheckSumCalc)
{
bool b_rc = false;
uint32_t n = 0, checkSum = 0, offset = 0;
BIO* bio = BIO_new(BIO_s_mem());
uint8_t* buf = (uint8_t*)OPENSSL_malloc(SIZE_64K);
do {
dwPeCheckSumCalc = 0;
/* calculate the checkSum */
while (n < this->m_u32_file_size) {
size_t i, written, nread;
size_t left = m_u32_file_size - n;
unsigned short val = 0;
if (left > SIZE_64K)
left = SIZE_64K;
if (!BIO_write_ex(bio, this->m_pu8_map_file + n, left, &written))
{
break;
}
// 如果只想移动指针, 就用BIO_seek
(void)BIO_seek(bio, 0);
// BIO_reset(bio); // BIO_reset对于不是只读的BIO, 会清掉内容
n += (uint32_t)written;
if (!BIO_read_ex(bio, buf, written, &nread))
{
break;
}
for (i = 0; i < nread / 2; i++) {
val = LE_UINT16(buf[i]);
if (offset == m_header_size + 88
|| offset == m_header_size + 90) {
/* The image file checksum */
val = 0;
}
checkSum += val;
checkSum = LOWORD(LOWORD(checkSum) + HIWORD(checkSum));
offset += 2;
}
}
checkSum = LOWORD(LOWORD(checkSum) + HIWORD(checkSum));
checkSum += offset;
dwPeCheckSumCalc = checkSum;
b_rc = true;
} while (false);
if (NULL != buf)
{
OPENSSL_free(buf);
buf = NULL;
}
if (NULL != bio)
{
BIO_free(bio);
bio = NULL;
}
return b_rc;
}
uint32_t CPeFileCheck::get_file_size(const char* infile)
{
int ret = 0;
struct _stat64 st;
ret = _stat64(infile, &st); // 必须是全路径名称, 否则返回-1
if (ret) {
// printf("Failed to open file: %s\n", infile);
return 0;
}
if (st.st_size < 4) {
// printf("Unrecognized file type - file is too short: %s\n", infile);
return 0;
}
if (st.st_size > UINT32_MAX) {
// printf("Unsupported file - too large: %s\n", infile);
return 0;
}
return (uint32_t)st.st_size;
}
uint8_t* CPeFileCheck::map_file(const char* infile, const size_t size)
{
uint8_t* indata = NULL;
HANDLE fhandle, fmap;
(void)size;
fhandle = CreateFileA(infile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (fhandle == INVALID_HANDLE_VALUE) {
return NULL;
}
fmap = CreateFileMapping(fhandle, NULL, PAGE_READONLY, 0, 0, NULL);
CloseHandle(fhandle);
if (fmap == NULL) {
return NULL;
}
indata = (uint8_t*)MapViewOfFile(fmap, FILE_MAP_READ, 0, 0, 0);
CloseHandle(fmap);
return indata;
}
void CPeFileCheck::unmap_file(uint8_t* indata)
{
if (!indata)
return;
UnmapViewOfFile(indata);
}