traceapi.c -
annotated code of API trace plugin
////////////////////////////////////////////////////////////////////////////////
//
//
//
SAMPLE API CALLS TRACE PLUGIN FOR OLLYDBG
v2.01
//
//
//
//
This plugin allows to trace all calls to system DLLs in a single thread
by //
//
setting one-time memory
breakpoints.
//
//
//
//
API trace can be started only if process is paused. Plugin sets break
on //
//
access on user code. When user code is reached, it removes break on
access //
//
from user code and sets it on system code, and so
on.
//
//
//
//
Of course, it is possible that user code accesses data in the system
area //
//
or vice versa. In this case I step over this command and
restore
//
//
breakpoint. Such cases are
rare.
//
//
//
//
This plugin is by no means ideal. It runs only single thread, and
there //
//
may be problems if program calls ZwContinue(). If DLL unloads,
plugin //
//
doesn't delete call records. It doesn't check whether one-time
breakpoints //
//
are already set by user. It doesn't allow to protocol only selected
APIs, //
//
and so
on.
//
//
//
//
This code is distributed "as is", without warranty of any kind,
expressed //
//
or implied, including, but not limited to warranty of fitness for
any //
//
particular purpose. In no event will Oleh Yuschuk be liable to you for
any //
//
special, incidental, indirect, consequential or any other damages
caused //
//
by the use, misuse, or the inability to use of this code, including
any //
//
lost profits or lost savings, even if Oleh Yuschuk has been advised of
the //
//
possibility of such damages. Or, translated into English: use at your
own //
//
risk!
//
//
//
//
This code is free. You can modify it, include parts of it into your
own //
//
programs and redistribute modified code provided that you remove
all //
//
copyright messages or substitute them with your own
copyright.
//
//
//
////////////////////////////////////////////////////////////////////////////////
//
Microsoft compilers hate (and maybe justifiably) old-school functions
like
//
wcscpy() that may cause buffer overflow and all related dangers. Still,
I
//
don't want to see all these bloody warnings.
#define
_CRT_SECURE_NO_DEPRECATE
#include
<windows.h>
#include
<stdio.h>
#include
<string.h>
#include
"plugin.h"
#define
PLUGINNAME L"Trace
API" // Unique plugin name
#define
VERSION
L"2.00.00" // Plugin
version
#define
NCALL
1023
// Size of call data extent, calls
//
Current state of the API trace.
#define
TRACE_OFF
0
// No trace
#define
TRACE_TILLUSER
1
// Wait till user code is reached
#define
TRACE_TILLSYS
2
// Wait till system code is called
#define
CODE_GUARDED
0x00010000 // Whether
code block is guarded
#define
CODE_USER
0x00020000 // Whether
code block is user
#define
CODE_SYS
0x00040000 // Whether
code block is system
//
Descriptor of call location. Calls are chained in the order of ascending
//
addresses.
typedef
struct t_call
{
// Call location
ulong
from;
// Address of call command
struct t_call
*next;
// Next location or NULL
}
t_call;
//
API list consists of elements of type t_api.
typedef
struct t_api
{
// Descriptor of API function
// Obligatory header, its layout _must_ coincide with t_sorthdr!
ulong
addr;
// Address of the API entry point
ulong
size;
// Always 1
ulong
type;
// Currently unused
// Custom data follows header.
ulong
count;
// Number of hits
struct t_call
*call;
// Pointer to chain of call locations
int
ncall;
// Number of elements in call chain
}
t_api;
#define
RET_ACTIVE
0x00010000 //
Temporary breakpoint is set
typedef
struct t_ret
{
// Descriptor of return address
ulong
addr;
// Return address
ulong
size;
// Always 1
ulong
type;
// One of RET_xxx
ulong
aapi;
// Address of the called function
ulong
from;
// Call address
int
count;
// Number of recursive breaks
}
t_ret;
//
List of code blocks consists of elements of type t_code.
typedef
struct t_code {
ulong
base;
// Base address of code block
ulong
size;
// Size of code block
ulong
type;
// Additional information, CODE_xxx
}
t_code;
static
HINSTANCE
hdllinst;
// Instance of plugin DLL
static
int
trace;
// Trace state, one of TRACE_xxx
static
ulong
threadid;
// ID of the traced thread
static
t_table
api;
// List of APIs that were hit
static
t_sorted
code;
// List of code blocks
static
t_sorted
retlist;
// List of return addresses
static
t_call
*pfreecall;
// Pointer to list of unused calls
static
int
restoretempbp;
// Some temp breakpoint must be restored
static
int
protocolapi;
// Protocol API calls
static
int
skipgdi32;
// Don't protocol GDI32 APIs
static
int
skipuser32;
// Don't protocol USER32 APIs
static
int
skipkernel32;
// Don't protocol KERNEL32 APIs
static
int
skipmsvc;
// Don't protocol MSVC* APIs
static
int
protocolret;
// Protocol returned values
static
int
autoupdate;
// Whether window in autoupdate mode
////////////////////////////////////////////////////////////////////////////////
/////////////////////////
ONE-SHOT MEMORY BREAKPOINTS //////////////////////////
//
Guarding memory breakpoints are one-shot access breakpoints supported
//
directly by Windows. If program accesses guarded memory, operating
system
//
removes guarding and raises STATUS_GUARD_PAGE exception.
//
//
Routines in this section set and remove guarding breakpoints on the code
//
sections of the loaded modules. Such manipulations are time-consuming.
To
//
accelerate process, plugin keeps list of code sections in the sorted
data
//
named code. if code section is guarded, it is marked as CODE_GUARDED.
//
//
If module loads or unloads, ODBG2_Pluginnotify()
receives PN_NEWMOD or
//
PN_ENDMOD. In the first case it reinstalls guards by calling Setguard().
//
In the second case, it removes module from the list of code sections.
//
Guards all system (guardsys=1) or user code (guardsys=0). If flag force
is
//
set, guarding is forced dependless on its current state.
static
void Setguard(int guardsys,int force) {
int i;
t_memory *pmem;
t_module *pmod;
t_code c,*pc;
if (force)
Deletesorteddatarange(&code,0x00000000,0xFFFFFFFF);
// Walk all memory blocks. Note that this function is called when
process is
// paused, so memory list can't change.
for (i=0; i<memory.sorted.n;
i++) {
// Get next memory block.
pmem=(t_memory *)Getsortedbyindex(&memory.sorted,i);
if (pmem==NULL)
continue;
// In fact, severe internal error
if ((pmem->type & MEM_GAP)!=0)
continue;
// Unallocated memory
// Check whether it contains executable code.
if ((pmem->type & (MEM_CODE|MEM_SFX))==0)
continue;
// Not a code
// Get code descriptor, or, if descriptor is missing, create new one.
pc=(t_code *)Findsorteddata(&code,pmem->base,0);
if (pc==NULL || pc->base!=pmem->base ||
pc->size!=pmem->size) {
// Note that it makes no harm if guarding remains set.
memset(&c,0,sizeof(c));
c.base=pmem->base;
c.size=pmem->size;
pc=(t_code *)Addsorteddata(&code,&c);
if (pc==NULL) continue;
}; // Low
memory or internal error
// If memory block is already guarded, do nothing.
if (pc->type & CODE_GUARDED)
continue;
// Check whether code belongs to the system DLL.
if ((pc->type & (CODE_USER|CODE_SYS))==0) {
pmod=Findmodule(pmem->base);
if (pmod==NULL)
continue;
// Code without module?..
if (pmod->type & MOD_SYSTEMDLL)
pc->type|=CODE_SYS;
else
pc->type|=CODE_USER;
;
};
if ((guardsys==0 && (pc->type &
CODE_USER)!=0) ||
(guardsys!=0 && (pc->type & CODE_SYS)!=0)
) {
Guardmemory(pmem->base,pmem->size,1);
pc->type|=CODE_GUARDED;
};
};
};
//
Unguards code block containing specified address.
static
void Unguardblock(ulong addr) {
t_code *pc;
pc=(t_code *)Findsorteddata(&code,addr,0);
if (pc!=NULL && (pc->type &
CODE_GUARDED)!=0) {
Guardmemory(pc->base,pc->size,0);
pc->type&=~CODE_GUARDED;
};
};
//
Unguards all code blocks.
static
void Unguardall(void) {
int i;
t_memory *pmem;
Deletesorteddatarange(&code,0x00000000,0xFFFFFFFF);
Deletesorteddatarange(&retlist,0x00000000,0xFFFFFFFF);
restoretempbp=0;
// Walk all memory blocks. Note that this function is called when
process is
// paused, so memory list can't change.
for (i=0; i<memory.sorted.n;
i++) {
// Get next memory block.
pmem=(t_memory *)Getsortedbyindex(&memory.sorted,i);
if (pmem==NULL)
continue;
// In fact, severe internal error
if ((pmem->type & MEM_GAP)!=0)
continue;
// Unallocated memory
// Check whether it contains executable code.
if ((pmem->type & (MEM_CODE|MEM_SFX))==0)
continue;
// Not a code
Guardmemory(pmem->base,pmem->size,0);
};
};
//
Sets state of the variable trace. If state changes from TRACE_OFF to any
//
other, or back to TRACE_OFF, writes message to log.
static
void Settracestate(int state) {
if (trace==TRACE_OFF && state!=TRACE_OFF)
Addtolist(0,DRAW_NORMAL,L"API
trace is ON");
else if (trace!=TRACE_OFF && state==TRACE_OFF)
Addtolist(0,DRAW_NORMAL,L"API
trace is OFF");
trace=state;
};
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////
CALL DATA ///////////////////////////////////
//
The user usually wants to know not only which API functions are called,
but
//
even more from which locations. Most APIs are called from one or two
places,
//
but in extreme cases (EnterCriticalSection(), SelectObject()) there may
be
//
hundreds. Therefore list of fixed size is not an option. I use linked
lists
//
of records of type t_call. Free records reside in a separate list
pointed to
//
by pfreecall. If there are no nore free call records, I allocate new
extent
//
containing NCALL records at once. Extents (t_cext) are also chained.
//
//
After call location is added, plugin keeps it in the list till
application
//
terminates. Therefore there are no routines that return call locations
back
//
to the list of free calls, the only option is to discard all data at
once.
//
Descriptor of call data extent. If there are no more free call
locations,
//
plugin allocates next block and places it before the previous.
typedef
struct t_cext
{
// Call data extent
t_call
call[NCALL];
// Block of call addresses
struct t_cext
*nextextent;
// Pointer to next data extent or NULL
}
t_cext;
static
t_cext
*extentlist;
// Pointer to first call data extent
//
Allocates new call data extent and adds all its elements to the chain of
//
free calls. Returns 0 on success and -1 on error.
static
int Allocatenewextent(void) {
int i;
t_cext *pext;
t_call *pcall;
// Allocate new data extent.
pext=(t_cext *)Memalloc(sizeof(t_cext),ZEROINIT);
if (pext==NULL)
return
-1;
// Low memory
// Add elements to the chain of free calls.
for (i=0,pcall=pext->call; i<NCALL; i++,pcall++) {
pcall->next=pfreecall;
pfreecall=pcall; };
// Add extent to the chain of extents.
pext->nextextent=extentlist;
extentlist=pext;
return 0;
};
//
Gets free call descriptor. On success, removes it from the queue of free
//
calls and returns pointer. On error, returns NULL.
static
t_call *Getfreecall(void) {
t_call *pcall;
// Assure that we have free call records.
if (pfreecall==NULL) {
Allocatenewextent();
if (pfreecall==NULL) return NULL; };
// Get free call record.
pcall=pfreecall;
pfreecall=pcall->next;
pcall->next=NULL;
// Just a precaution
return pcall;
};
//
Frees all call data.
static
void Discardcalldata(void) {
int i;
t_cext *pext;
t_api *pa;
// Walk list of extents and free all data extents.
while (extentlist!=NULL) {
pext=extentlist->nextextent;
Memfree(extentlist);
extentlist=pext; };
// There are no more free call descriptors.
pfreecall=NULL;
// For the case that API list is not yet freed, discard call data in
the list.
for (i=0; i<api.sorted.n; i++) {
pa=(t_api *)Getsortedbyindex(&(api.sorted),i);
if (pa==NULL) continue;
pa->call=NULL;
};
};
////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////
API LIST ///////////////////////////////////
//
Information about called API functions is kept in the table api. This
//
section contains code necessary to maintain and display API list window.
//
Menu function that processes
command "Follow API in Disassembler".
static
int Mfollowapi(t_table
*pt,wchar_t *name,ulong index,int mode) {
t_api *papi;
papi=(t_api *)Getsortedbyselection(&(api.sorted),
api.sorted.selected);
if (papi==NULL)
return
MENU_ABSENT;
// No selection
if (mode==MENU_VERIFY)
return MENU_NORMAL;
else if (mode==MENU_EXECUTE) {
Setcpu(0,papi->addr,0,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
return MENU_REDRAW; };
return MENU_ABSENT;
};
//
Menu function that
processes INT3 breakpoint-related
commands "Toggle"
//
(index=0), "Conditional" (index=1) and "Conditional log" (index=2). Note
//
that found locations are sure code, we don't need to verify them.
static
int Mbreakpoint(t_table
*pt,wchar_t *name,ulong index,int mode) {
int answer;
t_api *papi;
t_bpoint *pbrk;
POINT coord;
papi=(t_api *)Getsortedbyselection(&(api.sorted),
api.sorted.selected);
if (papi==NULL)
return
MENU_ABSENT;
// No selection
if (mode==MENU_VERIFY)
return MENU_NORMAL;
else if (mode==MENU_EXECUTE) {
pbrk=(t_bpoint
*)Findsorteddata(&(bpoint.sorted),papi->addr,0);
if (index==0) {
// Toggle unconditional breakpoint.
if (pbrk!=NULL && pbrk->type & BP_MANUAL)
Removeint3breakpoint(papi->addr,BP_MANUAL);
else {
answer=Setint3breakpoint(papi->addr,BP_MANUAL|BP_BREAK,
0,0,0,0,L"",L"",L"");
if (answer!=0) {
Flash(L"Unable to set
breakpoint");
return MENU_NOREDRAW;
};
}; }
else {
// Set conditional breakpoint (simple and full forms).
if (Gettableselectionxy(&api,2,&coord)<0)
coord.x=coord.y=-1;
// Unknown coordinates, use defaults
if (index==1)
answer=Condbreakpoint(pt->hw,&(papi->addr),1,NULL,
coord.x,coord.y,api.font);
else
answer=Condlogbreakpoint(pt->hw,&(papi->addr),1,0,NULL,
coord.x,coord.y,api.font);
if (answer<0) Flash(L"Unable
to set breakpoint");
if (answer<=0) return MENU_NOREDRAW; };
return MENU_REDRAW; };
return MENU_ABSENT;
};
//
Callback function that fills Browse
code location dialog with the list
of
//
call locations.
int
Browsecallcallback(int
index,void *data,ulong *addr,wchar_t *s) {
t_call **pcall;
if (index<0 || data==NULL || addr==NULL || s==NULL)
return
0;
// Error in input parameters
pcall=(t_call **)data;
if (*pcall==NULL)
return
0;
// No more call locations
*addr=(*pcall)->from;
if (*addr==0)
StrcopyW(s,TEXTLEN,L"<unknown
location>");
else
Decoderelativeoffset(*addr,DM_MODNAME,s,TEXTLEN);
*pcall=(*pcall)->next;
return
1;
// Code location is available
};
//
Menu function that
processes command "Follow call in Disassembler"
(index=0)
//
and "Browse calls in Disassembler" (index=1).
static
int Mfollowcall(t_table
*pt,wchar_t *name,ulong index,int mode) {
t_api *papi;
t_call *pcall;
papi=(t_api *)Getsortedbyselection(&(api.sorted),
api.sorted.selected);
if (papi==NULL)
return
MENU_ABSENT;
// No selection
if (mode==MENU_VERIFY) {
if (papi->ncall==1 && index==0)
return MENU_NORMAL;
if (papi->ncall>1 && index==1)
return MENU_NORMAL;
return MENU_ABSENT; }
else if (mode==MENU_EXECUTE) {
pcall=papi->call;
if (papi->ncall==1 && pcall!=NULL
&& pcall->from!=0)
Setcpu(0,pcall->from,0,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
else if (papi->ncall>1)
Browsecodelocations(pt->hparent,L"Select
call location",
Browsecallcallback,(void *)&pcall);
return MENU_REDRAW; };
return MENU_ABSENT;
};
//
Menu function that
processes breakpoint-related
commands "Breakpoint on
all
//
calls" (index=0), "Conditional breakpoint on all calls" (index=1),
//
"Conditional log breakpoint on all calls" (index=2) and "Remove
breakpoint
//
from all calls" (index=3).
static
int Mbreakpointall(t_table
*pt,wchar_t *name,ulong index,int mode) {
int nlist,hasbp,nobp,ncond,success,answer;
ulong *list;
t_api *papi;
t_call *pcall;
t_bpoint *pbrk;
POINT coord;
papi=(t_api *)Getsortedbyselection(&(api.sorted),
api.sorted.selected);
if (papi==NULL)
return
MENU_ABSENT;
// No selection
if (mode==MENU_VERIFY) {
// Calculate how many calls already have breakpoints.
hasbp=nobp=ncond=0;
pcall=papi->call;
while (pcall!=NULL) {
pbrk=(t_bpoint
*)Findsorteddata(&(bpoint.sorted),pcall->from,0);
if (pbrk==NULL || (pbrk->type & BP_MANUAL)==0)
nobp++;
// No breakpoint
else {
hasbp++;
// Breakpoint set
if (pbrk->type & BP_COND) ncond++; };
pcall=pcall->next;
};
// Check whether menu item applies.
if (index==0 && nobp==0 && ncond==0)
return MENU_ABSENT;
if ((index==1 || index==2) && hasbp+nobp==0)
return MENU_ABSENT;
if (index==3 && hasbp==0)
return MENU_ABSENT;
return MENU_NORMAL; }
else if (mode==MENU_EXECUTE) {
if (index==1 || index==2) {
list=(ulong
*)Memalloc(papi->ncall*sizeof(ulong),SILENT|ZEROINIT);
if (list==NULL) {
Flash(L"Low memory"); return
MENU_NOREDRAW; };
nlist=0;
pcall=papi->call;
while (pcall!=NULL && nlist<papi->ncall) {
list[nlist++]=pcall->from;
pcall=pcall->next; };
if (Gettableselectionxy(pt,2,&coord)<0)
coord.x=coord.y=-1;
// Unknown coordinates, use defaults
if (index==1) answer=Condbreakpoint(pt->hw,list,nlist,NULL,
coord.x,coord.y,pt->font);
else answer=Condlogbreakpoint(pt->hw,list,nlist,0,NULL,
coord.x,coord.y,pt->font);
if (answer<0)
Flash(L"Unable to set all
breakpoints");
Memfree(list); }
else {
pcall=papi->call;
success=1;
while (pcall!=NULL) {
if (index==0)
answer=Setint3breakpoint(pcall->from,BP_MANUAL|BP_BREAK,
0,0,0,0,L"",L"",L"");
else
answer=Removeint3breakpoint(pcall->from,BP_MANUAL);
if (answer!=0) success=0;
pcall=pcall->next; };
if (success==0) {
if (index==0) Flash(L"Unable
to set all breakpoints");
else Flash(L"Unable to
remove all breakpoints");
};
};
return MENU_REDRAW; };
return MENU_ABSENT;
};
static
t_menu apimenu[] =
{
// Menu of the API list window
{ L"Follow API in Disassembler",
L"Follow address of selected API function in CPU Disassembler",
K_NONE, Mfollowapi, NULL, 0 },
{ L"|Toggle breakpoint on API",
L"Toggle unconditional breakpoint on selected API function",
K_BREAK, Mbreakpoint, NULL, 0 },
{ L"Conditional breakpoint on API...",
L"Set or edit conditional breakpoint on selected API function",
K_CONDBREAK, Mbreakpoint, NULL, 1 },
{ L"Conditional log on API...",
L"Set or edit conditional logging breakpoint on selected API function",
K_LOGBREAK, Mbreakpoint, NULL, 2 },
{ L"|Follow call in Disassembler",
L"Go to location where API selected function was called",
K_FOLLOWDASM, Mfollowcall, NULL, 0 },
{ L"Browse calls in Disassembler",
L"Show list of locations from which selected API function was called",
K_FOLLOWDASM, Mfollowcall, NULL, 1 },
{ L"|Breakpoint on all calls",
L"Set unconditional breakpoint on all call locations",
K_BREAKALL, Mbreakpointall, 0, 0 },
{ L"Conditional breakpoint on all calls",
L"Set or edit conditional breakpoint on all call locations",
K_CONDBPALL, Mbreakpointall, 0, 1 },
{ L"Conditional log on all calls",
L"Set or edit conditional logging breakpoint on all call locations",
K_LOGBPALL, Mbreakpointall, 0, 2 },
{ L"Remove breakpoint from all calls",
L"Remove breakpoint from all call locations",
K_DELBPALL, Mbreakpointall, 0, 3 },
{ L"|>STANDARD",
L"",
// Forwarder to standard menus
K_NONE, NULL, NULL, 0
}
};
//
Function that compares
two APIs according to the specified criterium.
//
Returns -1 if first item is "lower" (should precede second on the
screen),
//
+1 if first item is "higher" and 0 if they are equal. Criterium n is the
//
index of the column in the table.
int
Apisortfunc(const t_sorthdr
*sh1,const t_sorthdr
*sh2,const int n) {
int i=0;
wchar_t s1[TEXTLEN],s2[TEXTLEN];
t_api *a1,*a2;
a1=(t_api *)sh1;
a2=(t_api *)sh2;
if (n==1)
{
// Sort by name, may be time-consuming
Decodeaddress(a1->addr,a1->addr,
DM_VALID|DM_INMOD|DM_NOMODNAME,s1,TEXTLEN,NULL);
Decodeaddress(a2->addr,a2->addr,
DM_VALID|DM_INMOD|DM_NOMODNAME,s2,TEXTLEN,NULL);
i=_wcsnicmp(s1,s2,TEXTLEN); }
else if (n==2)
{
// Sort by hit count
if (a1->count>a2->count) i=-1;
else if (a1->count<a2->count) i=1; };
if (i==0)
{
// Additionally sort by API address
if (a1->addr<a2->addr) i=-1;
else if (a1->addr>a2->addr) i=1; };
return i;
};
//
Drawing function of API
list window.
int
Apidraw(wchar_t *s,uchar *mask,int *select,
t_table *pt,t_drawheader *ph,int
column,void *cache) {
int m,n,color;
ulong mode;
wchar_t text[TEXTLEN];
t_bpoint *pbrk;
t_api *papi;
t_call *pcall;
n=0;
// For simple tables, t_drawheader
is the pointer to the data element.
It
// can't be NULL, except in DF_CACHESIZE, DF_FILLCACHE and DF_FREECACHE.
papi=(t_api *)ph;
switch (column) {
case
DF_CACHESIZE:
// Request for draw cache size
return 0;
case
DF_FILLCACHE:
// Request to fill draw cache
break;
case
DF_FREECACHE:
// Request to free cached resources
break;
case
DF_NEWROW:
// Request to start new row in window
break;
case
0:
// API address
mode=ADDR_HEXONLY|ADDR_CHECKEIP;
pbrk=(t_bpoint
*)Findsorteddata(&(bpoint.sorted),papi->addr,0);
if (pbrk!=NULL && (pbrk->type &
BP_MANUAL)!=0) {
if (pbrk->type & BP_DISABLED) mode|=ADDR_DISBRK;
else if (pbrk->type & BP_COND) mode|=ADDR_CONDBRK;
else mode|=ADDR_BREAK; };
n=Labeladdress(s,papi->addr,0,REG_UNDEF,0,mask,select,mode);
break;
case
1:
// API name
n=Decodeaddress(papi->addr,papi->addr,
DM_VALID|DM_INMOD|DM_MODNAME,s,TEXTLEN,NULL);
break;
case
2:
// Hit count
n=swprintf(s,L"%i.",papi->count);
break;
case
3:
// Call locations
pcall=papi->call;
while (pcall!=NULL && n<TEXTLEN-1) {
if (n>0) {
m=StrcopyW(s+n,TEXTLEN-n,L",
");
if (m!=0) memset(mask+n,DRAW_NORMAL,m);
n+=m; };
Hexprint8W(text,pcall->from);
m=StrcopyW(s+n,TEXTLEN-n,text);
pbrk=(t_bpoint
*)Findsorteddata(&(bpoint.sorted),pcall->from,0);
if (pbrk==NULL || (pbrk->type & BP_MANUAL)==0)
color=DRAW_NORMAL;
// No user INT3 breakpoint
else if (pbrk->type & BP_DISABLED)
color=DRAW_BDIS;
// Disabled breakpoint
else if (pbrk->type & BP_COND)
color=DRAW_COND;
// Conditional breakpoint
else
color=DRAW_BREAK;
// Unconditional breakpoint
if (m!=0) memset(mask+n,color,m);
n+=m;
pcall=pcall->next; };
if (n>=TEXTLEN-1) {
s[n-1]=L'.'; mask[n-1]=DRAW_NORMAL;
s[n-2]=L'.'; mask[n-2]=DRAW_NORMAL;
s[n-3]=L'.'; mask[n-3]=DRAW_NORMAL; };
*select|=DRAW_MASK;
// Uses mask
break;
default: break;
};
return n;
};
//
Custom table function of API list window. This function is called on
//
WM_DESTROY, WM_CLOSE (by returning -1, you can prevent window from
closing),
//
WM_SIZE (custom tables only), WM_CHAR (only if TABLE_WANTCHAR is set)
and
//
different custom messages WM_USER_xxx (depending on table type). See
//
documentation for details.
long
Apifunc(t_table *pt,HWND hw,UINT msg,WPARAM wp,LPARAM lp) {
t_api *papi;
t_call *pcall;
switch (msg) {
case WM_USER_CREATE:
// Start autoupdating of the API list window. This means that OllyDbg
// will periodically send WM_USER_UPD messages. Whether the window will
// be repainted or not depends on the reaction on this message.
Setautoupdate(&api,1);
break;
case
WM_USER_UPD:
// Autoupdate contents of the window
if (trace!=TRACE_OFF && run.status!=STAT_IDLE
&&
run.status!=STAT_PAUSED && run.status!=STAT_FINISHED)
InvalidateRect(pt->hw,NULL,FALSE);
break;
case
WM_USER_DBLCLK:
// Doubleclick
// Get selection.
papi=(t_api *)Getsortedbyselection(
&(pt->sorted),pt->sorted.selected);
if (papi!=NULL) {
if (HIWORD(wp)==3)
// Doubleclick in the column 3 (list of call locations). If there is
// only one call location, follow it in CPU Disassembler pane. If
// there are several locations, open browsing dialog. I don't want
// to repeat the functionality of Mfollowcall(), therefore I reuse
// menu function.
Callmenufunction(&api,apimenu,Mfollowcall,papi->ncall==1?0:1);
else
// Columns 0 to 2. Follow address of the API function in the CPU
// Disassembler pane.
Setcpu(0,papi->addr,0,0,0,CPU_ASMHIST|CPU_ASMCENTER|CPU_ASMFOCUS);
;
};
return 1;
default: break;
};
return 0;
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////////
PLUGIN OPTIONS ////////////////////////////////
static
t_control traceapioptions[] = {
{ CA_COMMENT,
-1,
0, 0, 0, 0,
NULL,
PLUGINNAME,
NULL },
{ CA_TITLE,
OPT_TITLE,
80, 4, 160, 15, NULL,
PLUGINNAME,
NULL },
{ CA_CHECK,
OPT_1,
90, 26, 120, 10, &protocolapi,
L"Protocol API calls",
L"Protocol calls and call arguments to the log" },
{ CA_CHECK,
OPT_2,
105, 38, 160, 10, &skipgdi32,
L"Don't protocol GDI32",
L"Ignore all APIs that reside in GDI32" },
{ CA_CHECK,
OPT_3,
105, 50, 160, 10, &skipuser32,
L"Don't protocol USER32",
L"Ignore all APIs that reside in USER32" },
{ CA_CHECK,
OPT_4,
105, 62, 160, 10, &skipkernel32,
L"Don't protocol KERNEL32",
L"Ignore all APIs that reside in KERNEL32" },
{ CA_CHECK,
OPT_5,
105, 74, 160, 10, &skipmsvc,
L"Don't protocol MSVC*",
L"Ignore all APIs that reside in Visual C libraries" },
{ CA_CHECK,
OPT_6,
105, 86, 160, 10, &protocolret,
L"Protocol returned values",
L"Protocol values that called API function returned" },
{ CA_END,
-1,
0, 0, 0, 0,
NULL,
NULL,
NULL
}
};
// Optional entry, implements plugin's options page in the Plugin options
// dialog. First time this function is called with msg=WM_INITDIALOG, wp=0 and
// lp=0. Note that this is the artificial message. If plugin wants to insert
// its own page (or even several pages), it must return pointer to page
// description (array of structures t_control). Each new page must begin with
// CA_COMMENT. When user makes any actions and dialog receives WM_COMMAND, it
// passes this message here. Returned value is ignored. Finally, after user
// pressed OK or Cancel, this entry is called for the last time with
// msg=WM_CLOSE and wp=1 on OK and 0 on cancel. This is the best time for
// plugin to adopt new settings. Do not forget to undo custom controls on
// Cancel!
extc t_control * __cdecl ODBG2_Pluginoptions(UINT msg,WPARAM wp,LPARAM lp) {
if (msg==WM_CLOSE && wp!=0) {
// User pressed OK in the Plugin options dialog. Options are updated, save
// them to the .ini file.
Writetoini(NULL,PLUGINNAME,L"Protocol API calls",L"%i",protocolapi);
Writetoini(NULL,PLUGINNAME,L"Skip GDI32",L"%i",skipgdi32);
Writetoini(NULL,PLUGINNAME,L"Skip USER32",L"%i",skipuser32);
Writetoini(NULL,PLUGINNAME,L"Skip KERNEL32",L"%i",skipkernel32);
Writetoini(NULL,PLUGINNAME,L"Skip MSVC",L"%i",skipmsvc);
Writetoini(NULL,PLUGINNAME,L"Protocol returned values",L"%i",protocolret);
};
// It makes no harm to return page descriptor on all messages.
return traceapioptions;
};
////////////////////////////////////////////////////////////////////////////////
///////////////////////////
EVENTS AND NOTIFICATIONS ///////////////////////////
//
When trace starts, plugin guards user code and assumes that debuggee is
in
//
the system code. When guarded page is hit, OllyDbg passes event to
//
ODBG2_Pluginexception(). Let's assume that thread is in the user code
now.
//
Plugin should guard system code and unguard user. But such actions are
//
time-consuming, so plugin assumes that code is usually compact and
removes
//
protection only from the code block that was hit. Now plugin guards
system
//
code and returns PE_CONTINUE (continue execution, keep run status).
//
//
When debuggee calls API function, another exception occurs. Plugin
//
determines call and return addresses and adds them to the list. If
plugin
//
is requested to protocol returned values, it also sets temporary
breakpoint
//
with action flag BA_PLUGIN (report break to plugins) on the address of
//
return. Some other plugins may also set breakpoint here. To be sure,
plugin
//
adds this address to the sorted data retlist.
//
//
Why is retlist necessary? Were it not sufficient to save return address
to
//
some variable? No. Consider following scenario: Debugged application
calls
//
DispatchMessage(). This API function passes message to the user-defined
//
window function. Function calls another API, say, EnumChildWindows().
This
//
calls supplied callback which calls third API, GetWindowText(). Now we
have
//
three nested APIs, and in theory there is no depth limit. Even recursive
//
calls are possible!
//
//
Temporary breakpoint (BP_TEMP) is automatically removed when hit. If
there
//
is a recursive call, return values from the previous calls will not be
//
protocolled. To avoid this, retlist items count calls and mark removed
//
breakpoints. They are restored when execution returns into the system
code.
//
//
But what if we are waiting for system code and user code accesses data
in
//
the code block of system code, or vice versa? Breaks on access do not
//
distinguish between execution, reading or writing. We can't restore
break
//
instantly, first we must execute the command. What should we do? In this
//
case ODBG2_Pluginexception() returns PE_STEP. OllyDbg sets temporary
//
breakpoint (INT3 or hardware) with action BA_PLUGIN on the next command
and
//
plugin gets chance to restore protection when breakpoint is hit and
//
ODBG2_Plugintempbreakpoint() is called.
//
//
When there are several concurrent threads, it may happen that one
thread is
//
in the user code and another in system. The resulting chaos will make
//
reliable trace impossible. Therefore only one thread is allowed. When
user
//
runs application (STAT_RUNNING), ODBG2_Pluginnotify() intercepts this
action
//
and substitutes with STAT_RUNTHR.
//
Optional entry, called when Windows report exception (debugging event of
//
type EXCEPTION_DEBUG_EVENT in the debugged application).
extc
int __cdecl ODBG2_Pluginexception(t_run *prun,const t_disasm *da,
t_thread *pthr,t_reg *preg,wchar_t *message) {
int i,n,nstack,narg;
ulong addr,amem,aret,acall,size,from,stack[NARG+1];
wchar_t name[TEXTLEN],s[TEXTLEN];
t_module *pmod;
t_argdec adec[NARG];
t_memory *pmem;
t_ret ret,*pret;
t_api a,*pa;
t_call *pcall,**ppcall;
// If trace is inactive, or there is an error in parameters, do nothing.
if (trace==TRACE_OFF || prun==NULL || da==NULL || preg==NULL)
return PE_IGNORED;
// Traceapi processes only guarded page exceptions.
if
(prun->de.u.Exception.ExceptionRecord.ExceptionCode!=EXCEPTION_GUARD_PAGE)
return PE_IGNORED;
// Address of command that caused exception.
addr=(ulong)prun->de.u.Exception.ExceptionRecord.ExceptionAddress;
// Address of guarded memory block.
amem=(ulong)prun->de.u.Exception.ExceptionRecord.ExceptionInformation[1];
// If guarded memory block is in the memory area opposite to expected,
remove
// guard. We didn't do it before to spare time.
pmod=Findmodule(amem);
if (pmod==NULL)
return
PE_IGNORED;
// Better ideas?
if (((pmod->type & MOD_SYSTEMDLL)!=0 &&
trace==TRACE_TILLUSER) ||
((pmod->type & MOD_SYSTEMDLL)==0 &&
trace==TRACE_TILLSYS)
) {
Unguardblock(amem); return PE_CONTINUE; };
pmod=Findmodule(addr);
// If address is in the wrong area, most probably command accesses
protected
// data and must be stepped. Note that we must restore guarding after
// temporary breakpoint is hit.
if (trace==TRACE_TILLUSER && pmod!=NULL
&& (pmod->type & MOD_SYSTEMDLL)!=0)
return
PE_STEP;
// User code protected but break in sys
if (trace==TRACE_TILLSYS && (pmod==NULL ||
(pmod->type & MOD_SYSTEMDLL)==0))
return
PE_STEP;
// Sys code protected but break in user
if (trace==TRACE_TILLUSER) {
// We have reached user code. Unguard user code block and guard system
code.
Unguardblock(addr);
Setguard(1,0);
Settracestate(TRACE_TILLSYS); }
else {
// We have reached system code. If necessary, restore temporary
breakpoints
// on return address.
if (restoretempbp)
{
// Rare event
for (i=0; i<retlist.n; i++) {
pret=(t_ret *)Getsortedbyindex(&retlist,i);
if (pret==NULL || (pret->type & RET_ACTIVE)!=0) continue;
Setint3breakpoint(pret->addr,BP_TEMP,-1,-1,-1,BA_PLUGIN,NULL,NULL,NULL);
pret->type|=RET_ACTIVE;
};
restoretempbp=0;
};
// Unguard this block and guard user code.
Unguardblock(addr);
Setguard(0,0);
Settracestate(TRACE_TILLUSER);
// Read stack and determine the caller.
size=Readmemory(stack,preg->r[REG_ESP],sizeof(stack),MM_SILENT|MM_PARTIAL);
nstack=size/sizeof(ulong);
if (nstack==0)
aret=from=0;
else {
aret=stack[0];
from=Isretaddr(aret,NULL);
if (from==0) aret=0; };
// Add entry to API list.
pa=(t_api *)Findsorteddata(&(api.sorted),addr,0);
if (pa==NULL)
{
// First time the API is called
memset(&a,0,sizeof(a));
a.addr=addr;
a.size=1;
pa=(t_api *)Addsorteddata(&(api.sorted),&a); };
if (pa!=NULL)
{
// Opposite would mean severe error
pa->count++;
// Even when from is 0, I add it to the call list, as an indication that
// call origin is unknown.
ppcall=&(pa->call);
while (1) {
if (*ppcall!=NULL &&
(*ppcall)->from<from) {
ppcall=&((*ppcall)->next);
continue; };
if (*ppcall!=NULL && (*ppcall)->from==from)
break;
// Call address is already in the chain
pcall=Getfreecall();
if (pcall==NULL)
break;
// Low memory
pcall->from=from;
pcall->next=*ppcall;
*ppcall=pcall;
pa->ncall++;
break;
};
};
if (protocolapi) {
// If requested not to protocol some functions, check whether this is
// the skipped function.
if ((skipgdi32!=0 &&
_wcsicmp(pmod->modname,L"GDI32")==0) ||
(skipuser32!=0 &&
_wcsicmp(pmod->modname,L"USER32")==0) ||
(skipkernel32!=0 &&
_wcsicmp(pmod->modname,L"KERNEL32")==0) ||
(skipmsvc!=0 &&
_wcsnicmp(pmod->modname,L"MSVC",4)==0)) ;
else {
// If requested, set temporary breakpoint on return address to protocol
// the value returned by the API. I add this address to retlist so that
// I know that this is my breakpoint. Note possible source of errors:
// I silently assume that if call is recursive, it always calls the
// same API function.
if (protocolret!=0 && aret!=0 &&
from!=0) {
Setint3breakpoint(aret,BP_TEMP,-1,-1,-1,BA_PLUGIN,NULL,NULL,NULL);
pret=(t_ret *)Findsorteddata(&retlist,aret,0);
if (pret==NULL) {
ret.addr=aret;
ret.size=1;
ret.type=0;
ret.aapi=addr;
ret.from=from;
ret.count=0;
pret=(t_ret *)Addsorteddata(&retlist,&ret); };
if (pret!=NULL) {
pret->type|=RET_ACTIVE; // BP_TEMP
breakpoint set
pret->count++;
// Recursive if count>1
};
};
// Decode arguments. Note that first doubleword in the stack is return
// address.
if (nstack<=1)
narg=0;
else {
for (i=0; i<NARG; i++) {
if (i<nstack-1) {
adec[i].mode=ADEC_VALID|ADEC_CHGNAME;
adec[i].value=stack[i+1]; }
else {
adec[i].mode=0;
adec[i].value=0;
};
adec[i].pushaddr=0;
};
narg=Decodeknownbyaddr(addr,NULL,adec,NULL,name,0,1);
};
// Write name of function and, if available, call address to log.
n=StrcopyW(s,TEXTLEN,L"Call to ");
n+=Decodeaddress(addr,0,DM_WIDEFORM|DM_MODNAME,s+n,TEXTLEN-n,NULL);
if (from!=0) {
n+=StrcopyW(s+n,TEXTLEN-n,L" from ");
Decodeaddress(from,0,DM_WIDEFORM|DM_MODNAME,s+n,TEXTLEN-n,NULL); };
Addtolist(from,DRAW_HILITE,L"%s",s);
// Write arguments to log.
for (i=0; i<narg; i++) {
if (adec[i].name[0]==L'=') //
Substituted argument without name
Addtolist(0,DRAW_NORMAL,L" %08X
%s",adec[i].value,adec[i].text);
else {
Addtolist(0,DRAW_NORMAL,L" %08X %s = %s",
adec[i].value,adec[i].name,adec[i].text);
};
};
};
};
};
// Continue execution.
return PE_CONTINUE;
};
//
Optional entry, called when temporary breakpoint BP_TEMP with action
flag
//
BA_PLUGIN is hit. Note that several plugins may set temporary breakpoint
//
on the same command. Each BA_PLUGIN breakpoint is reported to all
plugins.
extc
void __cdecl ODBG2_Plugintempbreakpoint(ulong addr,const t_disasm * da,
t_thread *pthr,t_reg *preg) {
int n;
wchar_t s[TEXTLEN],type[TEXTLEN];
t_ret *pret;
pret=(t_ret *)Findsorteddata(&retlist,addr,0);
if (pret!=NULL) {
// Write name of called function to log.
Decodeaddress(pret->aapi,0,DM_WIDEFORM|DM_MODNAME,s,TEXTLEN,NULL);
Addtolist(addr,DRAW_HILITE,L"Return from %s",s);
if (preg!=NULL) {
// For known functions, Analyser saves type of return in the prefixed
// name of type NM_RETTYPE. Refer to the documentation to learn what
// prefix is.
if (FindnameW(addr,NM_RETTYPE,type,TEXTLEN)!=0)
Decodeargument(NULL,type,preg->r+REG_EAX,sizeof(ulong),
s,TEXTLEN,NULL);
else
s[0]=L'\0';
Addtolist(0,DRAW_NORMAL,L" EAX = %08X
%s",preg->r[REG_EAX],s);
};
pret->count--;
if (pret->count==0)
Deletesorteddata(&retlist,addr,0);
else {
pret->type&=~RET_ACTIVE;
// Temp breakpoint is autoremoved
restoretempbp=1;
}; }
else if (trace==TRACE_TILLSYS)
Setguard(1,1);
else if (trace==TRACE_TILLUSER)
Setguard(0,1);
;
};
//
Optional entry, notifies plugin on relatively infrequent events.
extc
void __cdecl ODBG2_Pluginnotify(int action,void *data,ulong p1,ulong
p2) {
switch (action) {
case
PN_NEWMOD:
// New module is added to the table
if (trace==TRACE_TILLSYS)
Setguard(1,1);
else if (trace==TRACE_TILLUSER)
Setguard(0,1);
break;
case
PN_ENDMOD:
// Module is removed from the memory
Deletesorteddatarange(&code,((t_module *)data)->base,
((t_module *)data)->base+((t_module *)data)->size);
break;
case
PN_RUN:
// User continues code execution
if (trace!=TRACE_OFF && *((t_status *)data)==STAT_RUNNING)
*((t_status *)data)=STAT_RUNTHR;
break;
default:
break;
// No action necessary
};
};
////////////////////////////////////////////////////////////////////////////////
//////////////////
PLUGIN MENUS EMBEDDED INTO OLLYDBG WINDOWS //////////////////
//
Menu function of main menu, opens or brings to top API list.
static
int Mopenapilist(t_table *pt,wchar_t *name,ulong index,int mode) {
if (mode==MENU_VERIFY)
return
MENU_NORMAL;
// Always available
else if (mode==MENU_EXECUTE) {
if (api.hw==NULL)
// Create table window. Third parameter (ncolumn) is the number of
// visible columns in the newly created window (ignored if appearance is
// restored from the initialization file). If it's lower than the total
// number of columns, remaining columns are initially invisible. Fourth
// parameter is the name of icon - as OllyDbg resource.
Createtablewindow(&api,0,api.bar.nbar,NULL,L"ICO_PLUGIN",PLUGINNAME);
else
Activatetablewindow(&api);
return MENU_NOREDRAW;
};
return MENU_ABSENT;
};
//
Menu function of main menu, starts or stops API trace.
static
int Mstarttrace(t_table *pt,wchar_t *name,ulong index,int mode) {
if (mode==MENU_VERIFY) {
if (run.status==STAT_IDLE || run.status==STAT_FINISHED)
return
MENU_ABSENT;
// Application is no more
else if (index==0 && trace!=TRACE_OFF)
return
MENU_NORMAL;
// OK to stop trace
else if (index==1 && trace==TRACE_OFF
&& run.status!=STAT_RUNNING)
return
MENU_NORMAL;
// OK to start trace
else
return
MENU_ABSENT;
// Action is not allowed
; }
else if (mode==MENU_EXECUTE) {
if (index==0)
{
// Stop trace
Unguardall();
Settracestate(TRACE_OFF); }
else
{
// Start trace
Setguard(0,1);
Settracestate(TRACE_TILLUSER); };
return MENU_NOREDRAW;
};
return MENU_ABSENT;
};
//
Menu function of main menu, opens plugin-related options.
static int Mopenoptions(t_table *pt,wchar_t *name,ulong index,int mode) {
if (mode==MENU_VERIFY)
return
MENU_NORMAL;
// Always available
else if (mode==MENU_EXECUTE) {
Pluginshowoptions(traceapioptions);
return MENU_REDRAW;
};
return MENU_ABSENT;
};
//
Menu function of main menu, displays About dialog.
static
int Mabout(t_table *pt,wchar_t *name,ulong index,int mode) {
int n;
wchar_t s[TEXTLEN];
if (mode==MENU_VERIFY)
return
MENU_NORMAL;
// Always available
else if (mode==MENU_EXECUTE) {
// Debuggee should continue execution while message box is displayed.
Resumeallthreads();
// In this case, Swprintf() would be as good as a sequence of
StrcopyW(),
// but secure copy makes buffer overflow impossible.
n=StrcopyW(s,TEXTLEN,L"Trace API plugin v");
n+=StrcopyW(s+n,TEXTLEN-n,VERSION);
// COPYRIGHT POLICY: This bookmark plugin is an open-source freeware.
It's
// just a sample. The copyright below is also just a sample and applies
to
// the unmodified sample code only. Replace or remove copyright message
if
// you make ANY changes to this code!
StrcopyW(s+n,TEXTLEN-n,L"\nCopyright (C) 2012 Oleh Yuschuk");
MessageBox(hwollymain,s,
L"Bookmark plugin",MB_OK|MB_ICONINFORMATION);
// Suspendallthreads() and Resumeallthreads() must be paired, even if
they
// are called in inverse order!
Suspendallthreads();
return MENU_NOREDRAW;
};
return MENU_ABSENT;
};
//
Plugin menu that will appear in the main OllyDbg menu. Note that this
menu
//
must be static and must be kept for the whole duration of the debugging
//
session.
static
t_menu mainmenu[] = {
{ L"Open API list",
L"Open API list window",
K_NONE, Mopenapilist, NULL, 0 },
{ L"Start API trace",
L"Start API trace",
K_NONE, Mstarttrace, NULL, 1 },
{ L"Stop API trace",
L"Stop API trace",
K_NONE, Mstarttrace, NULL, 0 },
{ L"|Options",
L"Open plugin-related options",
K_NONE, Mopenoptions, NULL, 0 },
{ L"|About",
L"About Trace API plugin",
K_NONE, Mabout, NULL, 0 },
{ NULL, NULL, K_NONE, NULL, NULL, 0 }
};
//
Optional entry, adds items either to main OllyDbg menu (type=PWM_MAIN)
or to
//
popup menu in one of the standard OllyDbg windows, like PWM_DISASM. When
//
type matches, plugin should return address of menu. When there is no
menu of
//
given type, it must return NULL. If menu includes single item, it will
//
appear directly in menu, otherwise OllyDbg will create a submenu with
the
//
name of plugin. Therefore, if there is only one item, make its name as
//
descriptive as possible.
extc
t_menu * __cdecl ODBG2_Pluginmenu(wchar_t *type) {
if
(wcscmp(type,PWM_MAIN)==0)
// Main menu
return mainmenu;
return
NULL;
// No menu
};
////////////////////////////////////////////////////////////////////////////////
////////////////////////////
PLUGIN INITIALIZATION /////////////////////////////
//
Entry point of the plugin DLL. Many system calls require DLL instance
//
which is passed to DllEntryPoint() as one of parameters. Remember it. Do
//
not make any initializations here, preferrable way is to place them into
//
ODBG_Plugininit() and cleanup in ODBG_Plugindestroy().
BOOL
WINAPI DllEntryPoint(HINSTANCE hi,DWORD reason,LPVOID reserved) {
if (reason==DLL_PROCESS_ATTACH)
hdllinst=hi;
// Mark plugin instance
return
1;
// Report success
};
//
ODBG2_Pluginquery() is a "must" for valid OllyDbg plugin. It must check
//
whether given OllyDbg version is correctly supported, and return 0 if
not.
//
Then it should fill plugin name and plugin version (as UNICODE strings)
and
//
return version of expected plugin interface. If OllyDbg decides that
this
//
plugin is not compatible, it will be unloaded. Plugin name identifies it
//
in the Plugins menu. This name is max. 31 alphanumerical UNICODE
characters
//
or spaces + terminating L'\0' long. To keep life easy for users, name
must
//
be descriptive and correlate with the name of DLL. Parameter features is
//
reserved for the future. I plan that features[0] will contain the number
//
of additional entries in features[]. Attention, this function should not
//
call any API functions: they may be incompatible with the version of
plugin!
//
(Windows' APIs are OK).
extc
int __cdecl ODBG2_Pluginquery(int ollydbgversion,ulong *features,
wchar_t pluginname[SHORTNAME],wchar_t pluginversion[SHORTNAME]) {
// Check whether OllyDbg has compatible version. This plugin uses only
the
// most basic functions, so this check is done pro forma, just to
remind of
// this option.
if (ollydbgversion<201)
return 0;
// Report name and version to OllyDbg.
wcscpy(pluginname,PLUGINNAME);
// Name of plugin
wcscpy(pluginversion,VERSION);
// Version of plugin
return
PLUGIN_VERSION;
// Expected API version
};
//
Optional entry, called immediately after ODBG2_Pluginquery(). Plugin
should
//
make one-time initializations and allocate resources. On error, it must
//
clean up and return -1. On success, it must return 0.
extc
int __cdecl ODBG2_Plugininit(void) {
int restore;
OSVERSIONINFO osversion;
// Check Windows version. If somebody dares to start this plugin on
Win95,
// tell user that this OS does not support one-time memory breakpoints.
osversion.dwOSVersionInfoSize=sizeof(OSVERSIONINFO);
GetVersionEx(&osversion);
if (osversion.dwPlatformId==VER_PLATFORM_WIN32_WINDOWS) {
Addtolist(0,DRAW_HILITE,L"%s will not work on Win95-based systems",
PLUGINNAME);
return -1; };
// Initialize API list.
if (Createsorteddata(&(api.sorted),sizeof(t_api),256,
(SORTFUNC *)Apisortfunc,NULL,0)!=0)
return -1;
// Initialize list of code blocks.
if (Createsorteddata(&code,sizeof(t_code),128,NULL,NULL,0)!=0) {
Destroysorteddata(&(api.sorted));
return -1; };
// Initialize list of return addresses. I don't expect much data here.
if (Createsorteddata(&retlist,sizeof(t_ret),32,NULL,NULL,0)!=0)
{
Destroysorteddata(&(api.sorted));
Destroysorteddata(&code);
return -1; };
restoretempbp=0;
// Restore options.
protocolapi=0;
Getfromini(NULL,PLUGINNAME,L"Protocol API
calls",L"%i",&protocolapi);
skipgdi32=1;
Getfromini(NULL,PLUGINNAME,L"Skip GDI32",L"%i",&skipgdi32);
skipuser32=0;
Getfromini(NULL,PLUGINNAME,L"Skip USER32",L"%i",&skipuser32);
skipkernel32=0;
Getfromini(NULL,PLUGINNAME,L"Skip
KERNEL32",L"%i",&skipkernel32);
skipmsvc=1;
Getfromini(NULL,PLUGINNAME,L"Skip MSVC",L"%i",&skipmsvc);
protocolret=1;
Getfromini(NULL,PLUGINNAME,L"Protocol returned
values",L"%i",&protocolret);
// Initialize API table. OllyDbg uses table name to save the appearance
to
// the main initialization file. Keep this name unique, or else.
StrcopyW(api.name,SHORTNAME,PLUGINNAME);
api.mode=TABLE_SAVEALL|TABLE_AUTOUPD;
api.bar.visible=1;
api.bar.name[0]=L"Address";
api.bar.expl[0]=L"Address of API function";
api.bar.mode[0]=BAR_SORT;
api.bar.defdx[0]=9;
api.bar.name[1]=L"Name";
api.bar.expl[1]=L"Name of API function";
api.bar.mode[1]=BAR_SORT;
api.bar.defdx[1]=24;
api.bar.name[2]=L"Hits";
api.bar.expl[2]=L"Number of calls to API function";
api.bar.mode[2]=BAR_SORT;
api.bar.defdx[2]=9;
api.bar.name[3]=L"Called from";
api.bar.expl[3]=L"Locations this function was called from";
api.bar.mode[3]=BAR_FLAT;
api.bar.defdx[3]=256;
api.bar.nbar=4;
api.tabfunc=Apifunc;
api.custommode=0;
api.customdata=NULL;
api.updatefunc=NULL;
api.drawfunc=(DRAWFUNC *)Apidraw;
api.tableselfunc=NULL;
api.menu=apimenu;
// OllyDbg saves positions of plugin windows with attribute
TABLE_SAVEPOS to
// the .ini file but does not automatically restore them. Let us add
this
// functionality here. To conform to OllyDbg norms, window is restored
only
// if corresponding option is enabled.
if (restorewinpos!=0) {
restore=0;
// Default
Getfromini(NULL,PLUGINNAME,L"Restore window",L"%i",&restore);
if (restore)
Createtablewindow(&api,0,api.bar.nbar,NULL,L"ICO_PLUGIN",PLUGINNAME);
;
};
// Report success.
Settracestate(TRACE_OFF);
return 0;
};
//
Optional entry, called when user opens new or restarts current
application.
//
Plugin should reset internal variables and data structures to the
initial
//
state.
extc
void __cdecl ODBG2_Pluginreset(void) {
Deletesorteddatarange(&code,0x00000000,0xFFFFFFFF);
Deletesorteddatarange(&retlist,0x00000000,0xFFFFFFFF);
restoretempbp=0;
Deletesorteddatarange(&(api.sorted),0x00000000,0xFFFFFFFF);
Discardcalldata();
Settracestate(TRACE_OFF);
};
//
OllyDbg calls this optional function when user wants to terminate
OllyDbg.
//
All MDI windows created by plugins still exist. Function must return 0
if
//
it is safe to terminate. Any non-zero return will stop closing
sequence. Do
//
not misuse this possibility! Always inform user about the reasons why
//
termination is not good and ask for his decision! Attention, don't make
any
//
unrecoverable actions for the case that some other plugin will decide
that
//
OllyDbg should continue running.
extc
int __cdecl ODBG2_Pluginclose(void) {
// For automatical restoring of open windows, mark in .ini file whether
// API list is still open.
Writetoini(NULL,PLUGINNAME,L"Restore window",L"%i",api.hw!=NULL);
return 0;
};
//
OllyDbg calls this optional function once on exit. At this moment, all
MDI
//
windows created by plugin are already destroyed (and received WM_DESTROY
//
messages). Function must free all internally allocated resources, like
//
window classes, files, memory etc.
extc
void __cdecl ODBG2_Plugindestroy(void) {
Destroysorteddata(&code);
Destroysorteddata(&retlist);
Destroysorteddata(&(api.sorted));
Discardcalldata();
};
See
also: