Files
sigmaker-ida/idasdk75/dbg/win32/winbase_debmod.cpp
2021-06-05 21:10:25 +03:00

620 lines
19 KiB
C++

#include <windows.h>
#include <ida.hpp>
#include "winbase_debmod.h"
#ifndef __X86__
#define IDA_ADDRESS_SIZE 8
#else
#define IDA_ADDRESS_SIZE 4
#endif
const TCHAR kernel32_dll[] = TEXT("kernel32.dll");
typedef BOOL WINAPI GetProcessDEPPolicy_t(HANDLE hProcess, LPDWORD lpFlags, PBOOL lpPermanent);
static GetProcessDEPPolicy_t *_GetProcessDEPPolicy = NULL;
typedef dep_policy_t WINAPI GetSystemDEPPolicy_t(void);
static GetSystemDEPPolicy_t *_GetSystemDEPPolicy = NULL;
//--------------------------------------------------------------------------
winbase_debmod_t::winbase_debmod_t(void)
: is_wow64(WOW64_NONE),
process_handle(INVALID_HANDLE_VALUE),
dep_policy(dp_always_off),
highdlls()
{
HMODULE k32 = GetModuleHandle(kernel32_dll);
if ( _GetProcessDEPPolicy == NULL )
*(FARPROC*)&_GetProcessDEPPolicy = GetProcAddress(k32, TEXT("GetProcessDEPPolicy"));
if ( _GetSystemDEPPolicy == NULL )
*(FARPROC*)&_GetSystemDEPPolicy = GetProcAddress(k32, TEXT("GetSystemDEPPolicy"));
if ( _GetSystemDEPPolicy != NULL )
dep_policy = _GetSystemDEPPolicy();
win_tool_help = NULL;
set_platform("win32");
}
//--------------------------------------------------------------------------
// Prepare new page protections for a breakpoint of BPTTYPE.
// Use INPUT as starting page protections.
// Return false in the case of failure.
bool winbase_debmod_t::remove_page_protections(
DWORD *p_input,
bpttype_t bpttype,
dep_policy_t dpolicy,
HANDLE proc_handle)
{
// If PAGE_GUARD is already set, do not change anything, it is already ok
DWORD input = *p_input;
if ( (input & PAGE_GUARD) != 0 )
return false;
// Convert between Unix permissions and Win32 page protections using this array:
static const uchar win32_page_protections[] =
{
PAGE_NOACCESS, // 000
PAGE_READONLY, // 001
0xFF, // 010 WRITE_ONLY does not exist on win32
PAGE_READWRITE, // 011
PAGE_EXECUTE, // 100
PAGE_EXECUTE_READ, // 101
0xFF, // 110 EXECUTE_WRITE does not exist on win32
PAGE_EXECUTE_READWRITE, // 111
};
uchar unix;
// convert ..COPY page protections into their non-copy counterparts
// this is the best thing we can do with them because they are automatically
// converted by the system upon a write access
if ( (input & PAGE_WRITECOPY) != 0 )
{
unix = 3; // rw
}
else if ( (input & PAGE_EXECUTE_WRITECOPY) != 0 )
{
unix = 7; // rwx
}
else
{
for ( unix=0; unix < 8; unix++ )
{
uchar p = win32_page_protections[unix];
if ( p != 0xFF && (input & p) != 0 )
break;
}
}
QASSERT(622, unix < 8);
// convert bpttype into unix permissions
int del = 0;
if ( (bpttype & BPT_READ) != 0 )
del |= 1;
if ( (bpttype & BPT_WRITE) != 0 )
del |= 2;
if ( (bpttype & BPT_EXEC) != 0 )
{
del |= 4;
// if DEP is disabled for this process then a program can
// happily execute code in a read only area so we need to
// remove *all* privileges, unfortunately
if ( dpolicy != dp_always_on )
{
// on XP, GetProcessDEPPolicy returns DEP policy for current process (i.e. the debugger)
// so we can't use it
// assume that DEP is disabled by default
DWORD flags = 0;
BOOL permanent = 0;
if ( _GetProcessDEPPolicy == NULL
|| winver.is_strictly_xp()
|| winver.is_GetProcessDEPPolicy_broken()
|| _GetProcessDEPPolicy(proc_handle, &flags, &permanent) )
{
// flags == 0: DEP is disabled for the specified process.
//
// Remarks: if permanent == 0 and global DEP policy is OptIn
// flags may be equal to 1 *but* having DEP disabled because,
// in case the process called SetProcessDEPPolicy the
// permanent argument would be 1, it seems to be a bug in the
// documentation
if ( (dpolicy == dp_opt_in && permanent == 0) || flags == 0 )
del |= 1;
}
}
}
// Remove the access types to trigger on
unix &= ~del;
// Handle WRITE_ONLY and EXECUTE_WRITE cases because win32 does not have them.
// We use stricter page permissions for them. This means that there will
// be more useless exceptions but we cannot do much about it.
if ( unix == 2 || unix == 6 )
unix = 0; // use PAGE_NOACCESS instead of WRITE_ONLY or EXECUTE_WRITE
uchar perm = win32_page_protections[unix];
*p_input = (input & ~0xFF) | perm;
return true;
}
//--------------------------------------------------------------------------
bool idaapi winbase_debmod_t::dbg_enable_page_bpt(
page_bpts_t::iterator p,
bool enable)
{
pagebpt_data_t &bpt = p->second;
if ( (bpt.old_prot != 0) == enable )
return false; // already the desired state
debdeb("dbg_enable_page_bpt(%s): page_ea=%a, old_prot=0x%x, new_prot=0x%x\n", enable ? "true" : "false", bpt.page_ea, bpt.old_prot, bpt.new_prot);
DWORD old;
DWORD prot = enable ? bpt.new_prot : bpt.old_prot;
if ( !VirtualProtectEx(process_handle, (void*)(size_t)bpt.page_ea,
bpt.real_len, prot, &old) )
{
deberr("VirtualProtectEx");
// if the page disappeared while disabling a bpt, do not complain,
// silently return success
if ( enable )
return false;
old = 0;
}
debdeb(" success! old=0x%x\n", old);
bpt.old_prot = enable ? old : 0;
return true;
}
//--------------------------------------------------------------------------
// Should we generate a BREAKPOINT event because of page bpt?
//lint -e{1746} could be made const reference
bool should_fire_page_bpt(
page_bpts_t::iterator p,
ea_t ea,
DWORD failed_access_type,
ea_t pc,
dep_policy_t dep_policy)
{
const pagebpt_data_t &bpt = p->second;
if ( !interval::contains(bpt.ea, bpt.user_len, ea) )
return false; // not in the user-defined interval
int bit;
switch ( failed_access_type )
{
default:
INTERR(623); //-V796 no break
case EXCEPTION_READ_FAULT: // failed READ access
// depending on the DEP policy we mark this access also
// to be triggered in case of EXEC breakpoints
bit = BPT_READ;
if ( dep_policy != dp_always_on && bpt.type == BPT_EXEC && pc == ea )
bit |= BPT_EXEC;
break;
case EXCEPTION_WRITE_FAULT: // failed WRITE access
bit = BPT_WRITE;
break;
case EXCEPTION_EXECUTE_FAULT: // failed EXECUTE access
bit = BPT_EXEC;
break;
}
return (bpt.type & bit) != 0;
}
//--------------------------------------------------------------------------
// returns 0-failure, 2-success
int idaapi winbase_debmod_t::dbg_add_page_bpt(
bpttype_t type,
ea_t ea,
int size)
{
// only one page breakpoint per page is permitted
page_bpts_t::iterator p = find_page_bpt(ea, size);
if ( p != page_bpts.end() )
return 0; // another page bpt exists
// Find out the current page protections
MEMORY_BASIC_INFORMATION meminfo;
ea_t page_ea = calc_page_base(ea);
if ( !VirtualQueryEx(process_handle, (void *)(size_t)page_ea,
&meminfo, sizeof(meminfo)) )
{
deberr("VirtualQueryEx");
return 0;
}
// Make sure the page is loaded
if ( (meminfo.State & MEM_FREE) != 0 )
{
deberr("%a: the page has not been allocated", page_ea);
return 0;
}
// According to MSDN documentation for VirtualQueryEx
// (...)
// AllocationProtect
// The memory protection option when the region was initially allocated. This member can be
// one of the memory protection constants or 0 if the caller does not have access.
//
// Unfortunately, there is no more information about why it my happen so, for now, I'm just
// returning an error.
if ( meminfo.Protect == 0 )
{
deberr("%a: the page cannot be accessed", page_ea);
return 0;
}
// Calculate new page protections
int aligned_len = align_up((ea-page_ea)+size, MEMORY_PAGE_SIZE);
int real_len = 0;
DWORD prot = meminfo.Protect;
if ( remove_page_protections(&prot, type, dep_policy, process_handle) )
{ // We have to set new protections
real_len = aligned_len;
}
// Remember the new breakpoint
p = page_bpts.insert(std::make_pair(page_ea, pagebpt_data_t())).first;
pagebpt_data_t &bpt = p->second;
bpt.ea = ea;
bpt.user_len = size;
bpt.page_ea = page_ea;
bpt.aligned_len = aligned_len;
bpt.real_len = real_len;
bpt.old_prot = 0;
bpt.new_prot = prot;
bpt.type = type;
// for PAGE_GUARD pages, no need to change the permissions, everything is fine already
if ( real_len == 0 )
{
bpt.old_prot = meminfo.Protect;
return 2;
}
return dbg_enable_page_bpt(p, true) ? 2 : 0;
}
//--------------------------------------------------------------------------
// returns true if changed *protect (in other words, if we have to mask
// the real page protections and return the original one)
bool winbase_debmod_t::mask_page_bpts(
ea_t startea,
ea_t endea,
uint32 *protect)
{
// if we have page breakpoints, what we return must be changed to show the
// real segment privileges, instead of the new ones we applied for the bpt
int newprot = 0;
page_bpts_t::iterator p = page_bpts.begin();
while ( p != page_bpts.end() )
{
pagebpt_data_t &pbd = p->second;
if ( pbd.page_ea + pbd.real_len > startea )
{
if ( pbd.page_ea >= endea )
break;
if ( pbd.old_prot != 0 )
{ // bpt has been written to the process memory
if ( *protect == pbd.new_prot )
{ // return the old protection, before setting the page bpt
newprot = pbd.old_prot;
}
else
{
debdeb("mask_page_bpts: app changed our page protection for %a (expected: 0x%x, actual: 0x%x)\n", pbd.page_ea, pbd.new_prot, *protect);
// page protection has been changed by the application
DWORD prot = *protect;
if ( prot == PAGE_WRITECOPY && pbd.new_prot == PAGE_READWRITE
|| prot == PAGE_EXECUTE_WRITECOPY && pbd.new_prot == PAGE_EXECUTE_READWRITE )
{
// in some cases OS may restore WRITECOPY protection; do nothing in such cases since it works the same way for breakpoint purposes
debdeb(" ignoring changes to WRITECOPY protection\n");
}
else if ( remove_page_protections(&prot, pbd.type, dep_policy, process_handle) )
{
pbd.new_prot = prot;
pbd.old_prot = 0; // mark our bpt as non-written
debdeb(" will re-set protection to 0x%x\n", pbd.new_prot);
}
}
}
}
++p;
}
if ( newprot != 0 )
{
*protect = newprot;
return true;
}
return false;
}
//--------------------------------------------------------------------------
// Page breakpoints modify the page protections to induce access violations.
// We must hide the modified page protections from IDA and report the original
// page protections.
// Second, the application may render a page bpt inactive by changing its page protections.
// In this case we must report to IDA the new page protections and also reactivate
// the page breakpoint.
void winbase_debmod_t::verify_page_protections(
meminfo_vec_t *areas,
const win32_prots_t &prots)
{
QASSERT(624, areas->size() == prots.size());
if ( page_bpts.empty() )
return;
for ( int i = 0; i < areas->size(); i++ )
{
uint32 prot = prots[i];
memory_info_t &a = areas->at(i);
if ( mask_page_bpts(a.start_ea, a.end_ea, &prot) )
a.perm = win_prot_to_ida_perm(prot);
}
// reactivate all disabled page bpts, if any
enable_page_bpts(true);
}
//--------------------------------------------------------------------------
#ifndef __X86__
wow64_state_t winbase_debmod_t::check_wow64_process()
{
if ( is_wow64 == WOW64_NONE )
{
is_wow64 = check_wow64_handle(process_handle);
if ( is_wow64 > 0 )
dmsg("WOW64 process has been detected (pid=%d)\n", pid);
}
return is_wow64;
}
#endif
//--------------------------------------------------------------------------
bool highdll_vec_t::has(eanat_t addr) const
{
for ( int i = 0; i < size(); ++i )
if ( (*this)[i].has(addr) )
return true;
return false;
}
//--------------------------------------------------------------------------
bool highdll_vec_t::add(eanat_t addr, size_t sz, HANDLE h)
{
if ( has(addr) )
return false;
// check removed: on new win10 we can have above 4GB:
// ntdll.dll, wow64.dll, wow64win.dll
// QASSERT(1491, size() < 2);
highdll_range_t &r = push_back();
r.start = addr;
r.end = addr + sz;
r.handle = h;
return true;
}
//--------------------------------------------------------------------------
bool highdll_vec_t::add_high_module(
eanat_t addr,
size_t sz,
HANDLE h)
{
if ( ea_t(addr) == addr )
return false;
add(addr, sz, h); //-V779 unreachable code
return true;
}
//--------------------------------------------------------------------------
bool highdll_vec_t::del_high_module(HANDLE *h, eanat_t addr)
{
for ( int i = 0; i < size(); ++i )
{
const highdll_range_t &r = (*this)[i];
if ( r.start == addr )
{
if ( h != NULL )
*h = r.handle;
erase(begin() + i);
return ea_t(addr) != addr;
}
}
return false;
}
//--------------------------------------------------------------------------
void idaapi winbase_debmod_t::dbg_term(void)
{
is_wow64 = WOW64_NONE;
delete win_tool_help;
win_tool_help = NULL;
}
//--------------------------------------------------------------------------
// Check if we need to install a temporary breakpoint to workaround the
// 'freely running after syscall' problem. Exactly, the problem is the
// following: after single stepping over a "jmp far ptr" instruction in
// wow64cpu.dll for a 32bits process under a 64bits OS (Win7), the trap flag
// is lost. Probably, it's a bug in wow64cpu!CpuReturnFromSimulatedCode.
//
// So, if we find an instruction like "call large dword fs:XX" we add a
// temporary breakpoint at the next instruction and re-enable tracing
// when the breakpoint is reached.
bool winbase_debmod_t::check_for_call_large(
const debug_event_t *event,
HANDLE handle)
{
if ( check_wow64_handle(handle) <= 0 )
return false;
uchar buf[3];
if ( dbg_read_memory(event->ea, buf, 3, NULL) == 3 )
{
// is it the call large instruction?
if ( memcmp(buf, "\x64\xFF\x15", 3) == 0 )
return true;
}
return false;
}
//--------------------------------------------------------------------------
// Get process bitness: 32bit - 4, 64bit - 8, 0 - unknown
int idaapi winbase_debmod_t::get_process_bitness(int _pid)
{
if ( _pid != -1 && _pid != GetCurrentProcessId() )
{
if ( !winver.is_64bitOS() )
return 4;
switch ( check_wow64_pid(_pid) )
{
case WOW64_BAD: return 0; // bad process id
case WOW64_YES: return 4; // wow64 process, 32bit
case WOW64_NO: return 8; // regular 64bit process
default: break;
}
}
return IDA_ADDRESS_SIZE;
}
//--------------------------------------------------------------------------
static const char *str_bitness(int addrsize)
{
switch ( addrsize )
{
case 8:
return "[64]";
case 4:
return "[32]";
default:
return "[x]";
}
}
//--------------------------------------------------------------------------
// this function may correct pinfo->addrsize
bool winbase_debmod_t::get_process_path(
ext_process_info_t *pinfo,
char *buf,
size_t bufsize)
{
module_snapshot_t msnap(get_tool_help());
MODULEENTRY32 me;
if ( !msnap.first(TH32CS_SNAPMODULE, pinfo->pid, &me) )
{
if ( msnap.last_err() == ERROR_PARTIAL_COPY && pinfo->addrsize == 0 )
{
// MSDN: If the specified process is a 64-bit process and the caller is a
// 32-bit process, error code is ERROR_PARTIAL_COPY
pinfo->addrsize = 8;
}
qstrncpy(buf, pinfo->name.c_str(), bufsize);
return false;
}
else
{
tchar_utf8(buf, me.szExePath, bufsize);
return true;
}
}
//--------------------------------------------------------------------------
win_tool_help_t *winbase_debmod_t::get_tool_help()
{
if ( win_tool_help == NULL )
win_tool_help = new win_tool_help_t;
return win_tool_help;
}
//-------------------------------------------------------------------------
int winbase_debmod_t::get_process_addrsize(pid_t _pid)
{
int addrsize = get_process_bitness(_pid);
return addrsize != 0 ? addrsize : IDA_ADDRESS_SIZE;
}
//--------------------------------------------------------------------------
//lint -e{1762} could be made const [in fact it cannot be made const in x64 mode]
bool winbase_debmod_t::is_ntdll_name(const char *path)
{
const char *base_name = qbasename(path);
const char *ntdll_name = winver.is_NT()
? "ntdll.dll" // NT
: "kernel32.dll"; // 9X/Me and KERNEL32.DLL
if ( strieq(base_name, ntdll_name) )
return true;
#ifndef __X86__
if ( winver.is_NT()
&& check_wow64_process() == WOW64_YES
&& strieq(base_name, "ntdll32.dll") )
{
return true;
}
#endif
return false;
}
//--------------------------------------------------------------------------
//lint -esym(1762,winbase_debmod_t::build_process_ext_name) could be made const
void winbase_debmod_t::build_process_ext_name(ext_process_info_t *pinfo)
{
char fullname[MAXSTR];
if ( get_process_path(pinfo, fullname, sizeof(fullname))
&& pinfo->addrsize == 0 )
{
// the WOW64 is optional on R2 x64 server
pinfo->addrsize = IDA_ADDRESS_SIZE;
}
pinfo->ext_name = str_bitness(pinfo->addrsize);
if ( !pinfo->ext_name.empty() )
pinfo->ext_name += ' ';
pinfo->ext_name += fullname;
}
//--------------------------------------------------------------------------
int idaapi winbase_debmod_t::get_process_list(procvec_t *list, qstring *)
{
int mypid = GetCurrentProcessId();
list->clear();
process_snapshot_t psnap(get_tool_help());
PROCESSENTRY32 pe32;
for ( bool ok = psnap.first(TH32CS_SNAPNOHEAPS, &pe32); ok; ok = psnap.next(&pe32) )
{
if ( pe32.th32ProcessID != mypid )
{
int addrsize = get_process_bitness(pe32.th32ProcessID);
#ifndef __EA64__
if ( addrsize > 4 )
continue; // skip 64bit processes, we cannot debug them because ea_t is 32bit
#endif
ext_process_info_t pinfo;
pinfo.pid = pe32.th32ProcessID;
pinfo.addrsize = addrsize;
tchar_utf8(&pinfo.name, pe32.szExeFile);
build_process_ext_name(&pinfo);
list->push_back(pinfo);
}
}
return list->size();
}
//--------------------------------------------------------------------------
// Returns the file name assciated with pid
bool idaapi winbase_debmod_t::get_exec_fname(int _pid, char *buf, size_t bufsize)
{
ext_process_info_t pinfo;
pinfo.pid = _pid;
pinfo.name.qclear();
return get_process_path(&pinfo, buf, bufsize);
}
//--------------------------------------------------------------------------
win_tool_help_t *winbase_debmod_t::win_tool_help = NULL;
win_version_t winbase_debmod_t::winver;