Windows反调试技术(下)
OD的DBGHELP模块
检测DBGHELP模块,此模块是用来加载调试符号的,所以一般加载此模块的进程的进程就是调试器。绕过方法也很简单,将DBGHELP.DLL改名。
#include <Windows.h>
#include <TlHelp32.h>
int main(int argc, char * argv[])
{
HANDLE hSnapProcess;
HANDLE hSnapModule;
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
MODULEENTRY32 md32;
md32.dwSize = sizeof(MODULEENTRY32);
hSnapProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if(hSnapProcess != INVALID_HANDLE_VALUE)
{
Process32First(hSnapProcess, &pe32);
do{
hSnapModule = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, pe32.th32ProcessID);
Module32First(hSnapModule, &md32);
do{
if(lstrcmp(md32.szModule, "DBGHELP.DLL") == 0)
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
ExitProcess(NULL);
}
}while(Module32Next(hSnapModule, &md32));
}while(Process32Next(hSnapProcess, &pe32));
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
}
else
CloseHandle(hSnapProcess);
return 0;
}
查看窗口
通过GetWindowText( )获取窗口标题文本,绕过方法也很简单就是更改窗口标题名。我们下面是检测OD调试器的示例,类比可以用来检测其他调试器如X64dbg等。
#include <Windows.h>
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam );
int main(int argc, char* argv[])
{
EnumWindows(EnumWindowsProc, NULL);
MessageBox(NULL,TEXT("程序正常运行!"), NULL, MB_OK);
}
BOOL CALLBACK EnumWindowsProc(HWND hwnd, LPARAM lParam )
{
char szWindowText[256] = {0};
GetWindowText(hwnd, szWindowText, 256); //获取的是标题栏的文本
if(lstrcmp(szWindowText, "OllyDbg") == 0)
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
ExitProcess(NULL);
return FALSE;
}
return TRUE;
}
也可以通过FindWindow来查找窗口。
int main(int argc, char* argv[])
{
if(NULL != FindWindow(TEXT("OLLYDBG"),TEXT("OllyDbg")))
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
ExitProcess(NULL);
}
MessageBox(NULL,TEXT("程序正常运行!"), NULL, MB_OK);
return 0;
}
创建进程快照来检测是否存在调试器进程
这种方法和查看窗口类似,当然也很容易被绕过。直接将程序名称更改就可以轻松绕过检测。
判断进程是否有SeDebugPrivilege权限
对于一般进程而言,如果用OpenProcess()打开csrss.exe程序则会返回无权限访问。如果以管理员身份登录并且进程被调试器调试的话,调试器会赋予进程SeDebugPrivilege权限,有了此权限程序就可以打开csrss.exe程序了。当然如果采用非管理员身份登录则这种检测将失效,因为非管理员身份下不会赋予进程SeDebugPrivilege权限。
typedef DWORD (NTAPI *pfnCsrGetProcessId)();
int main(int argc, char* argv[])
{
pfnCsrGetProcessId CsrGetProcessId;
CsrGetProcessId = (pfnCsrGetProcessId)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("CsrGetProcessId"));
DWORD a = CsrGetProcessId();
if(NULL != OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, CsrGetProcessId()))
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
ExitProcess(NULL);
}
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
return 0;
}
利用OD漏洞攻击调试器
OutputDebugString()漏洞,程序调用OutputDebugString()会产生一个特殊标志的软件异常,如果程序正在被调试那么调试器线程的WaitForDebugEvent函数会将此异常捕获并转化为OUTPUT_DUBGU_STRING_EVENT调试事件,OD在捕获此调试事件后会接着调用Sprintf()将OutPutDebugString中的字符串打印出来。而Springf函数并不会对参数进行检查,如果OutputDebugStringA(TEXT("%s%s%s")),OD中是Springf(目标缓冲区,"调试字符串"),因为现在调试字符串是“%s%s%s”,那么Springf(目标缓冲区,“%s%s%s”,X1,X2,X3),而此X1,X2,X3就会随机从栈中取出数据作为字符换的首地址,所以很容易取到的数据是一个无效指针会产生缓冲区异常。但是目前多数版本的OD已经将此漏洞修复。
#include <Windows.h>
int main(int argc, char* argv[])
{
MessageBox(NULL, TEXT("程序开始运行!"), NULL, MB_OK);
OutputDebugStringA(TEXT("%s%s%s")); //
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
}
判断父进程
通过判断当前进程父进程的PID是否等于explorer.exe或cmd.exe或services.exe的PID来判断其是否是调试器创建的进程。
#include <TlHelp32.h>
int main(int argc, char* argv[])
{
DWORD dwPid;
DWORD dwParentPid;
DWORD dwPidExplorer = 0;
DWORD dwPidCmd = 0;
DWORD dwPidServices = 0;
HANDLE hSnapProcess;
DWORD dwFlag = 0;
dwPid = GetCurrentProcessId();
hSnapProcess = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(PROCESSENTRY32);
if(hSnapProcess != INVALID_HANDLE_VALUE)
{
Process32First(hSnapProcess, &pe32);
do{
if(pe32.th32ProcessID == dwPid)
dwParentPid = pe32.th32ParentProcessID;
if(lstrcmp(pe32.szExeFile, "explorer.exe") == 0)
dwPidExplorer = pe32.th32ProcessID;
if(lstrcmp(pe32.szExeFile, "cmd.exe") == 0)
dwPidCmd = pe32.th32ProcessID;
if(lstrcmp(pe32.szExeFile, "services.exe") == 0)
dwPidServices = pe32.th32ProcessID;
}while(Process32Next(hSnapProcess, &pe32));
if(dwParentPid == dwPidExplorer)
dwFlag = 1;
else if(dwParentPid == dwPidCmd)
dwFlag = 1;
else if(dwParentPid == dwPidServices)
dwFlag = 1;
}
else
{
CloseHandle(hSnapProcess);
return 0;
}
if(dwFlag == 1)
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
else if(dwFlag == 0)
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
return 0;
}
实时调试器检测
当程序崩溃时其一般会弹出弹框来询问是否用事先设置的JIT及时调试器附加程序。一旦设置了JIT调试器就会在注册表的对应位置留下对应调试器的路径。我们可以通过检测注册表对应位置的键值来达到反调试的目的
int main(int argc, char* argv[])
{
HKEY hKey; //注册表键的句柄
BOOL bExe64 = FALSE;
IsWow64Process(GetCurrentProcess(), &bExe64); //判断系统是32位还是64位,32位和64位其对应需要检测注册表的位置不同
TCHAR * szRegedit = bExe64 ?
TEXT("SOFTWARE\\WOW6432Node\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug")
: TEXT("SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\AeDebug");
TCHAR szValue[256];
DWORD dwLen = 256;
//打开注册表键
RegCreateKey(HKEY_LOCAL_MACHINE, szRegedit, &hKey);
//查询对应项的值
RegQueryValueEx(hKey, TEXT("Debugger"), NULL, NULL, (LPBYTE)szValue, &dwLen);
//关闭注册表键
RegCloseKey(hKey);
if(_tcsstr(szValue, TEXT("吾爱破解.exe"))) //检测是否为调试器名称
{
MessageBox(NULL, TEXT("已检测到调试器!"),NULL, MB_OK);
ExitProcess(NULL);
}
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
return 0;
}
时间差
一般对于直接运行的程序而言,连续的几条指令执行所需的时间是很少的,因此指令与指令之间的时间差是很小的。而对于调试中的程序而言,就算我们按着F8不放让程序执行,其两条指令执行后也是会有时间差的。RDTSC指令可以计算出CPU自启动以后的运行周期,那么我们就可以用两条RDTSC指令计算出这两条指令执行所用的时间差。RDTSC指令执行后会将CPU运行周期的高32位放到edx,低32位放到eax中。
int main(int argc, char* argv[])
{
if(_AntiDebug() != 0)
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
else
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
return 0;
}
DWORD _AntiDebug()
{
_asm{
rdtsc
mov ecx,eax
mov ebx,edx
//一些运算
rdtsc
cmp edx,ebx
jne s
sub eax,ecx
cmp eax,0x200
ja s
xor eax,eax
jmp s1
s:
mov eax,1
s1:
}
}
TF位检测
因为一般调试器都会在TF为1时处理单步异常让eip指向下一条指令。我们可以利用此特点,主动将TF为置1让调试器误认为是单步运行从而eip指向下一条指令。我们可以在正常的程序流程中设置异常处理程序,在异常处理程序中我们做一些处理,这样如果被调试器就会忽略异常处理程序从而不能够执行正确的程序流程。
int main(int argc, char* argv[])
{
BOOL isDebugged = TRUE;
__try
{
__asm
{
pushfd
or dword ptr[esp], 0x100
popfd
nop
}
}
__except (EXCEPTION_EXECUTE_HANDLER)
{
isDebugged = FALSE;
}
if (isDebugged)
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
ExitProcess(NULL);
}
}
调试模式检测
在进行双机内核调试时,虚拟机会处于调试状态。我们通过检测虚拟机的状态来判断是否正在进行内核调试
typedef NTSTATUS (NTAPI *pfnNtQuerySystemInformation)(
IN SYSTEM_INFORMATION_CLASS SystemInformationClass,
OUT PVOID SystemInformation,
IN ULONG SystemInformationLength,
OUT PULONG ReturnLength OPTIONAL
);
struct _SYSTEM_KERNEL_DEBUGGER_INFORMATION
{
BOOLEAN DebuggerEnabled;
BOOLEAN DebuggerNotPresent;
}DebuggerInfo = {0};
int main(int argc,char* argv[])
{
pfnNtQuerySystemInformation NtQuerySystemInformation;
NtQuerySystemInformation = (pfnNtQuerySystemInformation)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("NtQuerySystemInformation"));
NtQuerySystemInformation((SYSTEM_INFORMATION_CLASS)0x23, &DebuggerInfo, sizeof(DebuggerInfo), NULL);
if(DebuggerInfo.DebuggerEnabled != 0)
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
return 0;
}
MessageBox(NULL, TEXT("正常运行!"), NULL, MB_OK);
return 0;
}
TLS线程本地存储
利用TLS回调函数可以在到达main()前被调用非常的隐蔽,我们可以利用这一点在TLS回调函数中进行反调试的操作。
void NTAPI Tls_Call(PVOID DllHandle, DWORD Reason, PVOID Reserved); //声明TLS回调函数
#pragma comment(linker, "/INCLUDE:__tls_used") //告知连接器使用TLS
#pragma data_seg(".CRT$XLS") //在共享数据段中存储TLS回调函数的地址
PIMAGE_TLS_CALLBACK pTlsAddress = Tls_Call;
#pragma data_seg()
int main(int argc, char* argv[])
{
MessageBox(NULL, TEXT("Main()"), NULL, MB_OK);
return 0;
}
void NTAPI Tls_Call(PVOID DllHandle, DWORD dwReason, PVOID Reserved)
{
switch (dwReason)
{
case DLL_THREAD_ATTACH: //Reason会有4种参数
break;
case DLL_PROCESS_ATTACH: //主线程在调用Main函数前调用TLS回调函数的原因就是DLL_PROCESS_ATTACH
//可以在此处进行反调试的操作(较隐蔽)
if(IsDebuggerPresent())
{
MessageBox(NULL, TEXT("已检测到调试器!"),NULL, MB_OK);
ExitProcess(NULL);
}
break;
case DLL_THREAD_DETACH:
break;
case DLL_PROCESS_DETACH:
break;
}
}
IMAGE_LOAD_CONFIG_DIRECTORY的GlobalFlagsClear
通过检查磁盘或内存中的可执行文件中PIMAGE_LOAD_CONFIG_DIRECTORY结构(程序加载到内存的一些其他配置信息)的GlobalFlagsClear字段。
默认是文件中是没有次结构,可以手动添加。此结构不为0则表示存在调试器。
有问题:无法获得__load_config_used结构的值。
extern "C"
IMAGE_LOAD_CONFIG_DIRECTORY _load_config_used = {sizeof(IMAGE_LOAD_CONFIG_DIRECTORY)};
软件断点
一般调试器会利用0xCC也就时INT3指令实现软件断点功能,我们可以通过对特定的代码片段进行检验检测是否有指令被下断点,从而达到反调试的目的。
//可以让链接器生成的代码函数调用采用CALL [ ]的形式,否则器默认采用call,jmp dword的形式
#pragma comment(linker, "/INCREMENTAL:NO")
DWORD OldCrc = 0x2159;
#pragma auto_inline(off) //防止编译器嵌入函数(关)
void DebugFunc()
{
DWORD dwNum = 0;
dwNum++;
dwNum >> 3;
dwNum = dwNum - 3;
}
void DebugFuncEnd()
{
}
#pragma auto_inline(on) //防止编译器嵌入函数(开)
int main(int argc, char* argv[])
{
DWORD dwCrc = 0;
for(DWORD i = (DWORD)DebugFunc; i <= (DWORD)DebugFuncEnd; i++)
dwCrc = *(BYTE*)i + dwCrc;
if(dwCrc != OldCrc)
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
ExitProcess(NULL);
}
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
return 0;
}
硬件断点
通过检测调试寄存器的值来检测是否有硬件断点,达到反调试的目的。
int main(int argc, char* argv[])
{
CONTEXT stContext;
stContext.ContextFlags = CONTEXT_ALL;
GetThreadContext(GetCurrentThread(),&stContext);
if(stContext.Dr0 | stContext.Dr1 | stContext.Dr2 | stContext.Dr3)
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
ExitProcess(NULL);
}
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
return 0;
}
SEH和VEH
程序主动产生异常,然后利用SEH或VEH设置异常处理程序。然后在异常处理程序中进行反调试。
SetUnhandledExceptionFilter()
利用SEH的顶级异常处理程序过滤函数UnhandledExceptionFilter()会检测调试器是否存在,如果不存在就执行SetUnhandledExceptionFilter()设置的顶级异常处理过滤干扰函数。如果存在就直接掠过SetUnhandledExceptionFilter()设置的顶级异常处理过滤干扰函数。那么我们就可以SetUnhandledExceptionFilter()设置的顶级异常处理过滤干扰函数,主动产生异常然后将程序一部分流程放到此函数中。如果被调试的话此函数中正常的程序流程将不会执行。
INT 0x2D
OD并不会把int 0x2d认定为是一个异常,也就是其并不能够被传递给异常处理程序(忽略异常也没用),如果在OD中使用F8/F9,程序会直接运行知道遇到断点后暂停。
int main(int argc, char* argv[])
{
BOOL bReturn;
bReturn = AntiDebug();
if(bReturn == 1)
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
exit(0);
}
else if(bReturn == 0)
MessageBox(NULL, TEXT("程序正常运行!"), NULL, MB_OK);
return 0;
}
BOOL AntiDebug()
{
BOOL bReturn = 0;
__asm
{
push offset handler
push dword ptr fs:[0]
mov dword ptr fs:[0],esp
int 0x2D
nop
mov bReturn,1;
jmp end
handler:
mov eax, dword ptr ss : [esp + 0xc]
mov dword ptr ds : [eax + 0xb8], offset end //修改CONTEXT的eip
xor eax,eax
retn
end:
pop dword ptr fs:[0]
add esp,4
}
return bReturn;
}
句柄追踪机制
windows提供内核对象句柄跟踪机制,如果程序被调试则用CloseHandle关闭无效句柄时会产生异常。如果不是从调试器中启动进程,则该CloseHandle返回FALSE
EXCEPTION_DISPOSITION _ExceptionProc(
PEXCEPTION_RECORD ExceptionRecord,
PVOID EstablisherFrame,
PCONTEXT ContextRecord,
PVOID DispatcherContext)
{
if (EXCEPTION_INVALID_HANDLE == ExceptionRecord->ExceptionCode)
{
MessageBox(NULL, TEXT("已检测到调试器!"), NULL, MB_OK);
ExitProcess(NULL);
}
return ExceptionContinueExecution;
}
int main()
{
__asm
{
push _ExceptionProc
push dword ptr fs : [0]
mov dword ptr fs : [0], esp
}
CloseHandle((HANDLE)0xBAAD);
__asm
{
mov eax, [esp]
mov dword ptr fs : [0], eax
add esp, 8
}
return 0;
}
调试输出异常
从win10开始,调试输出异常需要由调试器处理,以下两种异常需要可以检测调试器是否存在。
DBG_PRINTEXCEPTION_C(0x40010006)和DBG_PRINTEXCEPTION_W(0x4001000A)
int main(int argc, char* argv[])
{
__try
{
RaiseException(0x4001000A, 0, 4, (ULONG_PTR *)"SDFSDF");
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
MessageBox(NULL, TEXT("无调试器!"), NULL, MB_OK);
}
}
双进程自调试自修改
利用在进程中创建进程,或者通过自调试创建两个进程。其中一个为父进程,另一个为子进程。两个进程都是同一个可执行文件只不过执行流程不一样。然后通过在父进程中修复子进程,子进程修改自身的自修改手段达到反调试的目的。
Hook DbgUiRemoteBreakin防附加
为了使运行中进程能够即时中断到调试器中,操作系统提供了一个函数DbgUiRemoteBreakin,其内部通过调用DbgBreakPoint产生一个中断异常从而被调试器捕获,为了实现及时中断我们需要在运行中的进程中创建远程线程,线程回调函数就是DbgUiRemoteBreakin函数。实际在我们附加进程时,调试器就时这么做的。所以我们通过hookDbgUiRemoteBreakin函数可以达到反附加的目的。
我们下面代码通过将DbgUiRemoteBreakin函数的入口点代码改为jmp ExitProcess函数的入口点,这样一旦有调试器附加进程就会调用ExitProcess结束运行。
int main(int argc, char* argv[])
{
BYTE bBuffer[0x10] = {0};
DWORD dwBreakAddress; //DbgUiRemoteBreakin函数的地址
DWORD dwOldProtect;
DWORD dwNum;
dwBreakAddress = (DWORD)GetProcAddress(LoadLibrary(TEXT("ntdll.dll")), TEXT("DbgUiRemoteBreakin"));
bBuffer[0] = 0xE9; //jmp指令字节码
*((DWORD *)(bBuffer + 1)) = (DWORD)ExitProcess - dwBreakAddress; //ExitProcess函数偏移地址
VirtualProtect((LPVOID)dwBreakAddress, 0x10, PAGE_EXECUTE_READWRITE, &dwOldProtect);
WriteProcessMemory(GetCurrentProcess(), (LPVOID)dwBreakAddress, bBuffer, 5, &dwNum);
VirtualProtect((LPVOID)dwBreakAddress, 0x10, dwOldProtect, &dwOldProtect);
//此死循环是为了检测
while(1)
{
}
return 0;
}
参考资料: 看雪学院《加密解密》
张银奎《软件调试》
https://www.apriorit.com/dev-blog/367-anti-reverse-engineering-protection-techniques-to-use-before-releasing-software
Windows反调试技术(下)的更多相关文章
- Windows 反调试技术——OpenProcess 权限过滤 - ObRegisterCallback
转载: https://blog.xpnsec.com/anti-debug-openprocess/ 看雪翻译:https://bbs.pediy.com/thread-223857.htm 本周我 ...
- Windows反调试技术(上)
写在前面 在逆向工程中为了防止破解者调试软件,通常都会在软件中采用一些反调试技术来防破解.下面就是一些在逆向工程中常见的反调试技巧与示例. BeingDebuged 利用调试器加载程序时调试器会通过C ...
- Linux下的反调试技术
Linux下的反调试技术 2014年01月30日 ⁄ 综合 ⁄ 共 2669字 ⁄ 字号 小 中 大 ⁄ 评论关闭 转自 http://wangcong.org/blog/archives/310 ...
- 反调试技术常用API,用来对付检测od和自动退出程序
在调试一些病毒程序的时候,可能会碰到一些反调试技术,也就是说,被调试的程序可以检测到自己是否被调试器附加了,如果探知自己正在被调试,肯定是有人试图反汇编啦之类的方法破解自己.为了了解如何破解反调试技术 ...
- 基于TLS的反调试技术
TLS(Thread Local Storage 线程局部存储) 一个进程中的每个线程在访问同一个线程局部存储时,访问到的都是独立的绑定于该线程的数据块.在PEB(进程环境块)中TLS存储槽共64个( ...
- Windows Kernel Way 1:Windows内核调试技术
掌握Windows内核调试技术是学习与研究Windows内核的基础,调试Windows内核的方式大致分为两种: (1)通过Windbg工具在Windows系统运行之初连接到Windows内核,连接成功 ...
- Windows服务调试状态下用Console启动
最近一直在用服务,发现服务也没有那么难调试. Windows服务调试状态下用Console启动:步骤分两步 第一步改Program,启动代码 static class Program { /// &l ...
- 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 ...
随机推荐
- 时间&空间(complexity)
时间&空间复杂度 时间复杂度: 通俗来说就是随着数据量的增加,程序运行的时间花费量是怎么变化的,时间复杂度常用大o表示.举个例子,猜数字,猜10个,100个.1000个,猜数的数据量是在增加的 ...
- python-顺序队列的实现
class seqqueue(object): 8 def __init__(self,maxsize): 9 self.maxsize = maxsize 10 self.queueelem = [ ...
- python课程设计--学生管理系统
系统要求 1.添加学生 2.删除学生 3.修改学生信息 4.查询学生 5.查看所有学生信息 6.学生信息数据的存储与读取 源码:student.py #coding:utf-8 2 #定义学员类 3 ...
- P1208 [USACO1.3]混合牛奶 Mixing Milk(JAVA语言)
思路 按单价排序然后贪心 题目描述 由于乳制品产业利润很低,所以降低原材料(牛奶)价格就变得十分重要.帮助Marry乳业找到最优的牛奶采购方案. Marry乳业从一些奶农手中采购牛奶,并且每一位奶农为 ...
- 生成元(JAVA语言)
package 第三章; import java.util.Scanner; public class 生成元 { public static void main(String[] args) { / ...
- [BFS]A. 【例题1】走迷宫
A . [ 例 题 1 ] 走 迷 宫 解析 简单的BFS模板题 Code #include <bits/stdc++.h> #define N 1005 using namespace ...
- hello world!goodbye world~
我有个朋友,做ios开发做了5年,年前回家转行赚大钱去了,这个标题,其实就是因他而生. 我本人做的.net开发,也差不多快5年时间了,在这个时候暂借博客园这个平台说几句心里话,骚了勿喷:) 其实我是个 ...
- Spring Cloud Alibaba(2)---RestTemplate微服务项目
RestTemplate微服务项目 前言 因为要运用 Spring Cloud Alibaba 开源组件到分布式项目中,所以这里先搭建一个不通过 Spring Cloud只通过 RestTemplat ...
- Ugly Numbers UVA - 136
Ugly numbers are numbers whose only prime factors are 2, 3 or 5. The sequence 1, 2, 3, 4, 5, 6, 8, 9 ...
- 刨死你系列——手撕ArrayList
不多BB,直接上代码: public class MyArrayList { //创建数组对象 private Object[] elements; //已使用数组长度 private int siz ...