反调试技术常用API,用来对付检测od和自动退出程序
一、Windows API方法
Win32提供了两个API, IsDebuggerPresent和CheckRemoteDebuggerPresent可以用来检测当前进程是否正在被调试,以IsDebuggerPresent函数为例,例子如下:
BOOL ret = IsDebuggerPresent();
printf("ret = %d\n", ret);
破解方法很简单,就是在系统里将这两个函数hook掉,让这两个函数一直返回false就可以了,网上有很多做hook API工作的工具,也有很多工具源代码是开放的,所以这里就不细谈了
二、查询进程PEB的BeingDebugged标志位
当进程被调试器所附加的时候,操作系统会自动设置这个标志位,因此在程序里定期查询这个标志位就可以了,例子如下:
bool PebIsDebuggedApproach()8
R& ]/ {7 V6 T' z, K
{
char result = 0; q"
V3 N9 u5 @# q% d+ E$ J
__asm
{
// 进程的PEB地址放在fs这个寄存器位置上"
C* e( ], k7 m
mov eax, fs:[30h]7
C/ M0 X; b- G. L' S+ x, S" V
// 查询BeingDebugged标志位6
J5 o, `6 U: a
mov al, BYTE PTR [eax + 2] &
k5 `; M9 v( _6 [
mov result, al
}
)
n& r& d8 i5 c# t$ P8 s4 K
return result != 0;,
d; p" C' q, u8 I8 \
}
三、查询进程PEB的NtGlobal标志位 6
p6 C* _ T- V/ @' l. }
2
g; ~6 k* y# S) R
"
\( O( p& i' H, f" T& K
跟第二个方法一样,当进程被调试的时候,操作系统除了修改BeingDebugged这个标志位以外,还会修改其他几个地方,其中NtDll中一些控制堆(Heap)操作的函数的标志位就会被修改,因此也可以查询这个标志位,例子如下:
bool PebNtGlobalFlagsApproach() I V7
g0 J& G9 P2 {2 j$ F# ]
{
int result = 0;
__asm.
]& d" Z1 D$ h. m) d
{"
v+ s- m( Z$ c2 T* z8 f
// 进程的PEB
mov eax, fs:[30h]
// 控制堆操作函数的工作方式的标志位
mov eax, [eax + 68h]"
c7 F2 s2 \7 ^7 d7 O5 y; [
// 操作系统会加上这些标志位FLG_HEAP_ENABLE_TAIL_CHECK, "
O7 I& t, {9 r: g. g) V {9 J
// FLG_HEAP_ENABLE_FREE_CHECK and FLG_HEAP_VALIDATE_PARAMETERS,
// 它们的并集就是x705
W5 z6 _$ a( b D" `8 u$ W
//'
z6 B) L' P1 g! |
// 下面的代码相当于C/C++的5
W3 w5 s$ ?! Y3 u( ^+ N4 _* _
// eax = eax & 0x70#
C* f! ^$ B' y
and eax, 0x70
mov result, eax
};
_7 j* E4 {% y) {# u
(
`1 B: r, ?8 K# O6 f/ K3 T
return result != 0;+
c5 L# A! R# A' ?
}-
T/ K/ B) X/ O
6
k" i* H( ?# l4 O8 D2 y, V, V: L7 {
四、查询进程堆的一些标志位"
A) t* ^' n% E. ?. Z( F& L
#
x- t; ^1 U/ d7 ?+ O4 s2 P
这个方法是第三个方法的变种,只要进程被调试,进程在堆上分配的内存,在分配的堆的头信息里,ForceFlags这个标志位会被修改,因此可以通过判断这个标志位的方式来反调试。因为进程可以有很多的堆,因此只要检查任意一个堆的头信息就可以了,所以这个方法貌似很强大,例子如下:
0
P# v: B: N+ z' n* |) f
bool HeapFlagsApproach()
{
int result = 0;
&
L9 V% Z l) p) {, e9 Q
__asm
{'
k) s' P6 p+ `& A, M9 T: f4 F5 G+ o
// 进程的PEB
mov eax, fs:[30h]'
K6 Q" {4 [! |; s: T
// 进程的堆,我们随便访问了一个堆,下面是默认的堆
mov eax, [eax + 18h]
// 检查ForceFlag标志位,在没有被调试的情况下应该是4
z1 D s$ I: ^% q P5 _
mov eax, [eax + 10h]7
@7 s3 o8 Y* q5 Z W
mov result, eax%
H# F6 I4 z3 Y( h7 ?
}
.
m4 Q7 Z3 Q; p3 B
return result != 0;
}8
w; p9 N& P6 k1 K: P7 L+ H
S(
R5 V0 X! c# r4 n' C2 ~0 L
5
p" G# F! V3 W) @" |; P: V
-
G9 N+ ^4 j6 W0 u
&
B; E) _/ ]. \( B: r8 @
五、使用NtQueryInformationProcess函数
%
d8 A0 |& v, T$ p- }9 J: l0 T
NtQueryInformationProcess函数是一个未公开的API,它的第二个参数可以用来查询进程的调试端口。如果进程被调试,那么返回的端口值会是-1,否则就是其他的值。由于这个函数是一个未公开的函数,因此需要使用LoadLibrary和GetProceAddress的方法获取调用地址,示例代码如下:
,
o) s( p( Q+ [
A0
j" n9 P2 i
// 声明一个函数指针。7
r7 W/ w. T7 N! c
typedef NTSTATUS (WINAPI *NtQueryInformationProcessPtr)(
HANDLE processHandle,
PROCESSINFOCLASS processInformationClass,
PVOID processInformation,
ULONG processInformationLength,
PULONG returnLength);7
z( z/ }+ I! u6 f4 ?, s: W
(
B7 _7 ]2 W; K9 a. l* S% z7 a: z
bool NtQueryInformationProcessApproach()#
W5 g& c# g& Z6 Y: G8 g5 X
{!
k9 t/ F" e4 i4 _% T8 @
int debugPort = 0;
HMODULE hModule = LoadLibrary(TEXT("Ntdll.dll "));7
R, @8 S6 c q- Y+ G
NtQueryInformationProcessPtr NtQueryInformationProcess = (NtQueryInformationProcessPtr)GetProcAddress(hModule,
"NtQueryInformationProcess");&
n' @! [6 D8 n$ X3 f
if ( NtQueryInformationProcess(GetCurrentProcess(), (PROCESSINFOCLASS)7,
&debugPort, sizeof(debugPort), NULL) )9
]/ @& h" y) n- o" F) o" E
printf("[ERROR NtQueryInformationProcessApproach] NtQueryInformationProcess
failed\n");4
O# M$ r9 [4 G' K6 x
else
return debugPort == -1;
8
z6 T$ A6 M8 f& u- U8 p
return false;
}
,
P; K$ f5 g5 v! n- {
!
Y5 F; y9 a; E( s( t# o3 ]3 L- z: O
六、NtSetInformationThread方法
"
W' Q4 a$ o3 y% R% |. C/ ]* l
这个也是使用Windows的一个未公开函数的方法,你可以在当前线程里调用NtSetInformationThread,调用这个函数时,如果在第二个参数里指定0x11这个值(意思是ThreadHideFromDebugger),等于告诉操作系统,将所有附加的调试器统统取消掉。示例代码:1
J" g8 F, u# \3 A
3
b2 h) Z4 P" ]) m- E" R
// 声明一个函数指针。
typedef NTSTATUS (*NtSetInformationThreadPtr)(HANDLE threadHandle,2
q7 n ?% d. U5 S
THREADINFOCLASS threadInformationClass,
PVOID threadInformation,9
K) ]# i# c7 |2 }4 @
ULONG threadInformationLength);6
F* o' |: S: {/ d5 f
"
{; s4 Q1 q" x: _) \( C! q$ v
void NtSetInformationThreadApproach()1
x8 s( E- n0 q# }
{
HMODULE hModule = LoadLibrary(TEXT("ntdll.dll"));
NtSetInformationThreadPtr NtSetInformationThread = (NtSetInformationThreadPtr)GetProcAddress(hModule,
"NtSetInformationThread");5
b$ k4 e7 o, Z0 }
NtSetInformationThread(GetCurrentThread(), (THREADINFOCLASS)0x11, 0,
0);:
v; o' h2 p' W/ q) q% g* Z V7 s
}
-
u& ~) T9 o6 T q$ s* e+ Y
(
f3 V( |! s$ ~8 y0 R9 U
6
u/ F" t( ~( e! S5 m" f
七、触发异常的方法2
b' V& l& z# j" m
:
\* L/ a( M) g
这个技术的原理是,首先,进程使用SetUnhandledExceptionFilter函数注册一个未处理异常处理函数A,如果进程没有被调试的话,那么触发一个未处理异常,会导致操作系统将控制权交给先前注册的函数A;而如果进程被调试的话,那么这个未处理异常会被调试器捕捉,这样我们的函数A就没有机会运行了。
8
c8 L# _+ a! _+ ?% v
#
y% j* t5 a: P- B0 ]# I$ N% y, k
这里有一个技巧,就是触发未处理异常的时候,如果跳转回原来代码继续执行,而不是让操作系统关闭进程。方案是在函数A里修改eip的值,因为在函数A的参数_EXCEPTION_POINTERS里,会保存当时触发异常的指令地址,所以在函数A里根据这个指令地址修改寄存器eip的值就可以了,示例代码如下:/
|$ r: ]% X5 O
"
z6 t# \6 s2 o3 E* M; k
)
H# x9 U/ t$ w5 H2 `
// 进程要注册的未处理异常处理程序A/
{1 m! t/ S r( A b2 o7 z
LONG WINAPI MyUnhandledExceptionFilter(struct _EXCEPTION_POINTERS *pei)$
M. a( ~0 s# M! d7 m
{'
B/ V" Q: D5 J) r7 G8 [5 K1 ?
SetUnhandledExceptionFilter((LPTOP_LEVEL_EXCEPTION_FILTER)
pei->ContextRecord->Eax);
// 修改寄存器eip的值
pei->ContextRecord->Eip += 2;5
Y7 B9 A {5 N- D1 B. \
// 告诉操作系统,继续执行进程剩余的指令(指令保存在eip里),而不是关闭进程+
\4 i- I( [/ F9 z% H1 `0 e/ n
return EXCEPTION_CONTINUE_EXECUTION;7
[9 s6 K. R4 x7 i6 M x3 o
}
bool UnhandledExceptionFilterApproach()
{5
h% s9 O! v8 }- Q
SetUnhandledExceptionFilter(MyUnhandledExceptionFilter);
__asm
{.
j2 P# |9 H( b
// 将eax清零
xor eax, eax
// 触发一个除零异常
div eax9
q8 l3 f* v0 c4 Q
}6
Q5 N7 g- J8 D2 h( z9 F y1 u
0
_2 j* U% D9 n" M
return false;6
w7 ]! y1 r* p( r
} \/
z* ?( k" W
八、调用DeleteFiber函数*
o9 g' y6 r; y" o
-
h, g$ t3 F4 v
如果给DeleteFiber函数传递一个无效的参数的话,DeleteFiber函数除了会抛出一个异常以外,还是将进程的LastError值设置为具体出错原因的代号。然而,如果进程正在被调试的话,这个LastError值会被修改,因此如果调试器绕过了第七步里讲的反调试技术的话,我们还可以通过验证LastError值是不是被修改过来检测调试器的存在,示例代码:
bool DeleteFiberApproach()%
_5 i% ?, d5 g
{%
Y0 y) Q+ b1 }7 o: Y& _2 O# e# o
char fib[1024] = {0};
// 会抛出一个异常并被调试器捕获
DeleteFiber(fib);
// 0x57的意思是ERROR_INVALID_PARAMETER
return (GetLastError() != 0x57);&
\9 m2 J, F" T" V
}
反调试技术常用API,用来对付检测od和自动退出程序的更多相关文章
- 基于TLS的反调试技术
TLS(Thread Local Storage 线程局部存储) 一个进程中的每个线程在访问同一个线程局部存储时,访问到的都是独立的绑定于该线程的数据块.在PEB(进程环境块)中TLS存储槽共64个( ...
- Windows反调试技术(上)
写在前面 在逆向工程中为了防止破解者调试软件,通常都会在软件中采用一些反调试技术来防破解.下面就是一些在逆向工程中常见的反调试技巧与示例. BeingDebuged 利用调试器加载程序时调试器会通过C ...
- Linux下的反调试技术
Linux下的反调试技术 2014年01月30日 ⁄ 综合 ⁄ 共 2669字 ⁄ 字号 小 中 大 ⁄ 评论关闭 转自 http://wangcong.org/blog/archives/310 ...
- Windows 反调试技术——OpenProcess 权限过滤 - ObRegisterCallback
转载: https://blog.xpnsec.com/anti-debug-openprocess/ 看雪翻译:https://bbs.pediy.com/thread-223857.htm 本周我 ...
- Delphi_OD_代码_调试_Delphi反调试技术(以OD为例附核心原代码) (转)
1.程序窗口[chuang kou]句柄[ju bing]检测原理:用FindWindow函数[han shu]查找[cha zhao]具有相同窗口[chuang kou]类名和标题的窗口[chuan ...
- Delphi_OD_代码_调试_Delphi反调试技术(以OD为例附核心原代码)
1.程序窗口[chuang kou]句柄[ju bing]检测原理:用FindWindow函数[han shu]查找[cha zhao]具有相同窗口[chuang kou]类名和标题的窗口[chuan ...
- 反调试技术(Delphi版)
1.程序窗口句柄检测原理:用FindWindow函数查找具有相同窗口类名和标题的窗口,如果找到就说明有OD在运行//****************************************** ...
- Windows反调试技术(下)
OD的DBGHELP模块 检测DBGHELP模块,此模块是用来加载调试符号的,所以一般加载此模块的进程的进程就是调试器.绕过方法也很简单,将DBGHELP.DLL改名. #include <Wi ...
- Android 中的反调试技术
比较简单的有下面这两种 调试端口检测, 23946(0x5D8A) Demo: void CheckPort23946ByTcp() { FILE* pfile=NULL; char buf[0x10 ...
随机推荐
- js 时间操作和随机数操作
function Data() { var date = new Date(); var year = date.getFullYear(); ; var strDate = date.getDate ...
- poj1006Biorhythms(同余定理)
转自:http://blog.csdn.net/dongfengkuayue/article/details/6461298 本文转自head for better博客,版权归其所有,代码系本人自己编 ...
- fscanf()函数基本用法
FILE *fp; while(!feof(fp)) { fscanf(fp,"%s%d%lf",a,&b,&c);//这里%s对应的a不需要加上取地址符号& ...
- android 性能优化-电量篇
消耗电量的几个主要原因.功能:1.大数据量的网络传输(网络)2.不停的网络切换(网络)3.解析大量的数据(CPU) 关于网络方面的优化: .网络请求之前,检查网络连接.没有网络连接不进行请求 .判断网 ...
- ThinkPhp 验证码不显示图片
验证码不显示原因分析: 1.先确保是否开启了 gd库 2.页面是否存在bom头 3.导入的ORG.Util.Image是否存在 4.入口文件中是否有define('APP_DEBUG', TRUE); ...
- python 3编码
python 3和2很大区别就是python本身改为默认用unicode编码. 字符串不再区分"abc"和u"abc", 字符串"abc"默 ...
- linux下实现在程序运行时的函数替换(热补丁)
声明:以下的代码成果,是参考了网上的injso技术,在本文的最后会给出地址,同时非常感谢injso技术原作者的分享. 但是injso文章中的代码存在一些问题,所以后面出现的代码是经过作者修改和检测的. ...
- PHP简单 对象(object) 与 数组(array) 的转换
数组是PHP的灵魂,非常强大,但有时候面向对象编程也是挺方便的,数组 与 对象 之间切换也是常有的事: /** * 数组 转 对象 * * @param array $arr 数组 * @return ...
- iptables4张表5条链
4张表:filter nat mangle raw filter:协议过滤: nat:地址转换,端口映射等: mangle:协议修改 TTL等: raw:This table is used m ...
- 3.从Node.js操作MongoDB文档
1.更新文档结构,而非SQL 2.数据库更新运算符 在MongoDB中执行对象的更新时,需要确切的指定需要改变什么字段.需要如何改变.不像SQL语句建立冗长的查询字符串来定义更新. MongoDB中可 ...