mirror of
https://github.com/x64dbg/StaticAnalysis.git
synced 2026-11-12 08:01:30 -10:00
Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a40b1f4508 | ||
|
|
ceaa1d661c |
No files matched your search
@@ -1,6 +1,19 @@
|
||||
x64dbg-StaticAnalysis
|
||||
=====================
|
||||
|
||||
x64dbg-StaticAnalysis
|
||||
- plugin for x64_dbg
|
||||
- see [release page](https://github.com/x64dbg/x64dbg-StaticAnalysis/releases) for a working plugin
|
||||
|
||||
a plugin for www.x64dbg.com
|
||||
##Roadmap
|
||||
see [dev branch](https://github.com/x64dbg/x64dbg-StaticAnalysis/tree/enhance)
|
||||
|
||||
- x64 calling convention (x64 version)
|
||||
- Stackemulator (for MingGM-argument-passing)
|
||||
- function-finder
|
||||
- case-switch detection
|
||||
- "MB_OK"-like symbols
|
||||
- loop-detection
|
||||
- flow-heuristic against obfuscation instead of linear scanning
|
||||
|
||||
## License
|
||||
GLPv3
|
||||
+50
-6
@@ -35,10 +35,32 @@ void AnalysisRunner::start(){
|
||||
_plugin_logputs("[StaticAnalysis] analysis finished ...");
|
||||
}
|
||||
|
||||
unsigned int AnalysisRunner::getInstruction(const duint bytesOffset,DISASM* disasm ){
|
||||
|
||||
|
||||
memset(&disasm, 0, sizeof(disasm));
|
||||
|
||||
if(currentEIP + bytesOffset > mSize)
|
||||
return UNKNOWN_OPCODE;
|
||||
|
||||
duint baseaddr = mBaseAddress;
|
||||
duint size = mSize;
|
||||
|
||||
|
||||
#ifdef _WIN64
|
||||
disasm.Archi=64;
|
||||
#endif // _WIN64
|
||||
disasm->EIP=currentEIP + bytesOffset;
|
||||
disasm->VirtualAddr=currentVirtualAddr + bytesOffset;
|
||||
|
||||
return (int) Disasm(disasm);
|
||||
|
||||
}
|
||||
|
||||
void AnalysisRunner::run()
|
||||
{
|
||||
unsigned char* data = new unsigned char[mSize];
|
||||
if(!DbgMemRead(mBaseAddress, data, mSize))
|
||||
EIPdata = new unsigned char[mSize];
|
||||
if(!DbgMemRead(mBaseAddress, EIPdata, mSize))
|
||||
{
|
||||
//ERROR
|
||||
_plugin_logputs("[StaticAnalysis] could not read memory ...");
|
||||
@@ -55,24 +77,30 @@ void AnalysisRunner::run()
|
||||
#ifdef _WIN64
|
||||
disasm.Archi=64;
|
||||
#endif // _WIN64
|
||||
disasm.EIP=(UIntPtr)data;
|
||||
disasm.VirtualAddr=(UInt64)baseaddr;
|
||||
currentEIP = (UIntPtr)EIPdata;
|
||||
disasm.EIP=currentEIP;
|
||||
currentVirtualAddr = (UInt64)baseaddr;
|
||||
disasm.VirtualAddr=currentVirtualAddr;
|
||||
duint i=0;
|
||||
while(i<size)
|
||||
{
|
||||
int len=Disasm(&disasm);
|
||||
if(len!=UNKNOWN_OPCODE)
|
||||
{
|
||||
emulateStack(&disasm);
|
||||
see(&disasm);
|
||||
}
|
||||
else{
|
||||
unknownOpCode(&disasm);
|
||||
len=1;
|
||||
}
|
||||
disasm.EIP+=len;
|
||||
disasm.VirtualAddr+=len;
|
||||
currentEIP += len;
|
||||
currentVirtualAddr += len;
|
||||
disasm.EIP=currentEIP;
|
||||
disasm.VirtualAddr=currentVirtualAddr;
|
||||
i+=len;
|
||||
}
|
||||
delete EIPdata;
|
||||
}
|
||||
void AnalysisRunner::clear( )
|
||||
{
|
||||
@@ -106,3 +134,19 @@ void AnalysisRunner::setDB( ApiDB *api )
|
||||
{
|
||||
mDb = api;
|
||||
}
|
||||
|
||||
void AnalysisRunner::emulateStack( DISASM* disasm )
|
||||
{
|
||||
/* track all events:
|
||||
- sub/add esp
|
||||
- mov [esp+x], ???
|
||||
- push/pop
|
||||
- ret
|
||||
*/
|
||||
|
||||
if ((disasm->Argument1.AccessMode == WRITE)
|
||||
&& (disasm->Argument1.ArgType & MEMORY_TYPE)
|
||||
&& (disasm->Argument1.Memory.BaseRegister & REG4)) {
|
||||
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,9 @@ class AnalysisRunner
|
||||
S_IntermodularCalls *_Calls;
|
||||
ApiDB *mDb;
|
||||
|
||||
unsigned char* EIPdata;
|
||||
UIntPtr currentEIP;
|
||||
UInt64 currentVirtualAddr;
|
||||
|
||||
public:
|
||||
|
||||
@@ -46,5 +49,6 @@ public:
|
||||
void clear( );
|
||||
void think( );
|
||||
void initialise();
|
||||
unsigned int getInstruction(const duint bytesOffset, DISASM* disasm );
|
||||
};
|
||||
|
||||
+6
-1
@@ -1,5 +1,5 @@
|
||||
#include "ICommand.h"
|
||||
|
||||
#include "AnalysisRunner.h"
|
||||
|
||||
ICommand::ICommand(AnalysisRunner *parent) : mParent(parent)
|
||||
{
|
||||
@@ -15,3 +15,8 @@ void ICommand::initialise( const duint Base,const duint Size )
|
||||
mBase = Base;
|
||||
mSize = Size;
|
||||
}
|
||||
|
||||
unsigned int ICommand::getInstruction( const duint bytesOffset, DISASM* disasm )
|
||||
{
|
||||
return mParent->getInstruction(bytesOffset,disasm);
|
||||
}
|
||||
@@ -19,6 +19,7 @@ protected:
|
||||
duint mBase;
|
||||
duint mSize;
|
||||
AnalysisRunner *mParent;
|
||||
unsigned int getInstruction(const duint bytesOffset, DISASM* disasm );
|
||||
|
||||
public:
|
||||
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
#include "S_IntermodularCallsX64.h"
|
||||
|
||||
|
||||
S_IntermodularCalls::S_IntermodularCalls(void)
|
||||
{
|
||||
}
|
||||
|
||||
|
||||
S_IntermodularCalls::~S_IntermodularCalls(void)
|
||||
{
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
/* calling convention
|
||||
|
||||
1. arg -> RCX (floating point: XMM0)
|
||||
2. arg -> RDX (floating point: XMM1)
|
||||
3. arg -> R8 (floating point: XMM2)
|
||||
4. arg -> R9 (floating point: XMM3)
|
||||
|
||||
additional arguments are pushed onto the stack (right to left)
|
||||
|
||||
*/
|
||||
|
||||
|
||||
class S_IntermodularCalls
|
||||
{
|
||||
public:
|
||||
S_IntermodularCalls(void);
|
||||
~S_IntermodularCalls(void);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,9 @@
|
||||
#include "AnalysisRunner.h"
|
||||
#include "meta.h"
|
||||
|
||||
/*#define _isPush(disasm) (QString(disasm->Instruction.Mnemonic).trimmed().toLower() == "push")
|
||||
#define _isCall(disasm) ((disasm->Instruction.BranchType == CallType) && !(disasm->Argument1.ArgType ®ISTER_TYPE))))*/
|
||||
|
||||
#define _isPush(disasm) ((strcmp(disasm->Instruction.Mnemonic ,"push ") == 0) )
|
||||
#define _isCall(disasm) ((disasm->Instruction.Opcode == 0xE8) && (disasm->Instruction.BranchType) && (disasm->Instruction.BranchType!=RetType) && !(disasm->Argument1.ArgType ®ISTER_TYPE))
|
||||
|
||||
@@ -33,24 +36,60 @@ void S_IntermodularCalls::see(const DISASM* disasm )
|
||||
{
|
||||
// no part of api-call
|
||||
mCurrent.clear();
|
||||
//_plugin_logprintf("[StaticAnalysis:IntermodularCalls] found 0xFF : %x and mne (%s)\n", disasm->Instruction.Opcode,disasm->Instruction.Mnemonic);
|
||||
char labelText[MAX_LABEL_SIZE];
|
||||
bool hasLabel = DbgGetLabelAt(disasm->Argument1.Memory.Displacement, SEG_DEFAULT, labelText);
|
||||
|
||||
if(hasLabel){
|
||||
// api calls have 0xFF-jumps as target addresses, we need to remember them
|
||||
_JumpFF j;
|
||||
j.RVA = disasm->VirtualAddr;
|
||||
j.RelativeAddressValue = disasm->Argument1.Memory.Displacement;
|
||||
strcpy(j.label,labelText);
|
||||
mApiJumps.insert(j);
|
||||
}
|
||||
|
||||
}else {
|
||||
if(_isCall(disasm))
|
||||
{
|
||||
// set missing informations
|
||||
mCurrent.RVA = disasm->VirtualAddr ;
|
||||
mCurrent.RelativeAddressValue = disasm->Instruction.AddrValue ;
|
||||
|
||||
|
||||
// retrieve label from the instratuction at "addrValue"
|
||||
DISASM target_instr;
|
||||
int len = getInstruction(disasm->Instruction.AddrValue-disasm->VirtualAddr,&target_instr);
|
||||
|
||||
if(len != UNKNOWN_OPCODE){
|
||||
// it is a jump to a dll
|
||||
if(target_instr.Instruction.Opcode == 0xFF){
|
||||
mCurrent.isApiCall = true;
|
||||
// prepare gathering labelText
|
||||
char labelText[MAX_LABEL_SIZE];
|
||||
bool hasLabel = DbgGetLabelAt(target_instr.Argument1.Memory.Displacement, SEG_DEFAULT, labelText);
|
||||
if(hasLabel){
|
||||
// we have a label from TitanEngine --> look up function header in database
|
||||
APIFunction f;
|
||||
f = mParent->db()->find(labelText);
|
||||
if(!f.invalid){
|
||||
// found!
|
||||
// yeah we know everything about the api-call!
|
||||
std::string functionComment;
|
||||
functionComment = f.ReturnType + " " + f.Name + "(...)";
|
||||
DbgSetCommentAt(disasm->VirtualAddr ,functionComment.c_str());
|
||||
int pos=0;
|
||||
// set comments for the arguments
|
||||
if( (f.Arguments.size() > 0) && (mCurrent.arguments.size() > 0) ){
|
||||
for(std::list<duint>::reverse_iterator a= mCurrent.arguments.rbegin();a!=mCurrent.arguments.rend();a++)
|
||||
{
|
||||
// more push" instructions than arguments
|
||||
if(pos >=f.Arguments.size())
|
||||
break;
|
||||
|
||||
std::string ArgComment = f.arg(pos).Type + " "+ f.arg(pos).Name;
|
||||
DbgSetCommentAt((*a) ,ArgComment.c_str());
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// copy structure
|
||||
_Call t = mCurrent;
|
||||
mCalls.push_back(t);
|
||||
@@ -75,37 +114,8 @@ bool S_IntermodularCalls::think()
|
||||
// iterate all calls
|
||||
for (std::list<_Call>::iterator c = mCalls.begin();c != mCalls.end();c++)
|
||||
{
|
||||
bool is_api = false;
|
||||
_JumpFF findJump;
|
||||
findJump.RVA = c->RelativeAddressValue;
|
||||
std::set<_JumpFF>::iterator j = mApiJumps.find(findJump);
|
||||
if(j != mApiJumps.end()){
|
||||
// having a label we can look in our database to get the arguments list
|
||||
APIFunction f;
|
||||
f = mParent->db()->find(j->label);
|
||||
if(!f.invalid){
|
||||
// yeah we know everything about the api-call!
|
||||
std::string functionComment;
|
||||
functionComment = f.ReturnType + " " + f.Name + "(...)";
|
||||
DbgSetCommentAt(c->RVA ,functionComment.c_str());
|
||||
int pos=0;
|
||||
// set comments for the arguments
|
||||
if( (f.Arguments.size() > 0) && (c->arguments.size() > 0) ){
|
||||
for(std::list<duint>::reverse_iterator a= c->arguments.rbegin();a!=c->arguments.rend();a++)
|
||||
{
|
||||
// more push" instructions than arguments
|
||||
if(pos >=f.Arguments.size())
|
||||
break;
|
||||
|
||||
std::string ArgComment = f.arg(pos).Type + " "+ f.arg(pos).Name;
|
||||
DbgSetCommentAt((*a) ,ArgComment.c_str());
|
||||
pos++;
|
||||
}
|
||||
}
|
||||
}
|
||||
is_api = true;
|
||||
if(c->isApiCall)
|
||||
numberOfApiCalls++;
|
||||
}
|
||||
}
|
||||
|
||||
_plugin_logprintf("[StaticAnalysis:IntermodularCalls] of which are %i intermodular calls\n", numberOfApiCalls);
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
/* plugin: (StaticAnalysis) for x64dbg <http://www.x64dbg.com>
|
||||
* author: [email protected] <http://blog.traceflow.com>
|
||||
* license: GLPv3
|
||||
*/
|
||||
#include "StackEmulator.h"
|
||||
|
||||
|
||||
|
||||
StackEmulator::StackEmulator( void) : mStackpointer(0)
|
||||
{
|
||||
for (unsigned int i=0;i<MAX_STACKSIZE;i++)
|
||||
mStack[i]=0;
|
||||
}
|
||||
|
||||
|
||||
StackEmulator::~StackEmulator(void)
|
||||
{
|
||||
}
|
||||
|
||||
/*
|
||||
0x01234: mov [esp+C], eax
|
||||
--> modifyFrom(0xC,0x01234)
|
||||
*/
|
||||
void StackEmulator::modifyFrom(int relative_offset,duint addr){
|
||||
const unsigned int internal_pointer = pointerByOffset(-1*relative_offset);
|
||||
mStack[internal_pointer] = addr;
|
||||
}
|
||||
/*
|
||||
0x01234: pop eax
|
||||
--> popFrom(0x01234), because it is:
|
||||
|
||||
mov eax, [esp]
|
||||
add esp, -1 ; one to the past
|
||||
|
||||
*/
|
||||
void StackEmulator::popFrom( duint addr )
|
||||
{
|
||||
moveStackpointer(+1);
|
||||
}
|
||||
/*
|
||||
0x01234: push eax
|
||||
--> pushFrom(0x01234), because it is:
|
||||
|
||||
add esp, +1
|
||||
mov [esp], eax ; one to the future
|
||||
*/
|
||||
void StackEmulator::pushFrom( duint addr )
|
||||
{
|
||||
mStack[mStackpointer] = addr;
|
||||
moveStackpointer(-1);
|
||||
}
|
||||
/*
|
||||
0x01234: add esp, 0xA
|
||||
--> moveStackpointer(0xA)
|
||||
|
||||
offset = offset to the past
|
||||
*/
|
||||
void StackEmulator::moveStackpointer( int offset )
|
||||
{
|
||||
mStackpointer = pointerByOffset(-offset);
|
||||
}
|
||||
|
||||
unsigned int StackEmulator::pointerByOffset(int offset){
|
||||
return (mStackpointer + ((offset+MAX_STACKSIZE) %MAX_STACKSIZE) + MAX_STACKSIZE) %MAX_STACKSIZE;
|
||||
}
|
||||
|
||||
/* returns addr from last access
|
||||
|
||||
0x155: mov [esp+8], eax
|
||||
|
||||
lastAccessAt(0x8) would be 0x155
|
||||
|
||||
*/
|
||||
duint StackEmulator::lastAccessAt(int offset){
|
||||
int p = pointerByOffset(-offset);
|
||||
return mStack[p];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/* plugin: (StaticAnalysis) for x64dbg <http://www.x64dbg.com>
|
||||
* author: [email protected] <http://blog.traceflow.com>
|
||||
* license: GLPv3
|
||||
*/
|
||||
#pragma once
|
||||
#include "meta.h"
|
||||
|
||||
/* since some compilers don't use "push" to change the stack for arguments (see MingGW)
|
||||
we have to track all modifications to the stack for better analysis
|
||||
therefore we emulate the stack by only storing the virtual address from the last write-access
|
||||
|
||||
-> advantage: we can track all modifications of the register for better placing of comments
|
||||
*/
|
||||
|
||||
const unsigned int MAX_STACKSIZE = 50;
|
||||
|
||||
class StackEmulator
|
||||
{
|
||||
|
||||
|
||||
duint mStack[MAX_STACKSIZE];
|
||||
unsigned int mStackpointer;
|
||||
|
||||
public:
|
||||
StackEmulator(void);
|
||||
~StackEmulator(void);
|
||||
|
||||
|
||||
void pushFrom(duint addr);
|
||||
void popFrom(duint addr);
|
||||
void modifyFrom(int relative_offset,duint addr);
|
||||
|
||||
void moveStackpointer(int offset);
|
||||
unsigned int pointerByOffset(int offset);
|
||||
duint lastAccessAt(int offset);
|
||||
|
||||
};
|
||||
|
||||
Reference in new issue
Block a user