这次的更新可能有点慢,因为这段时间也比较忙,加上C语言还得和汇编结合,导致小编一个知识点总是得反复揣摩(太菜了),所以免杀的更新篇幅长度可能会达到两个月和三个月,但是小编能保证,只要你能看懂这些笔记,那么我包你能过360的!!!
1.空函数的实现
我们在平时写c或者c++的代码的时候,不知道大家有没有想过,如果我们直接写这样的代码会怎么样? 难道编译器会啥都不干吗???
那当然不会的,我们一起来从汇编的角度来分析一下 (这里建议大家先生成×86的程序)
在进入函数之前,因为我们没有参数的传递,所以我们不用PUSH任何东西
然后我们就会将返回地址 00C713E3压入到堆栈里面,并且ESP加四。 过程如下
然后先是提栈操作
然后就是保留现场
然后就是填充缓冲区和实现函数的功能,但是由于我们是空函数,所以这两步骤就没有了
然后就是到了我们的还原现场的操作了
先把三个寄存器POP出去
然后就是快速降低堆栈(ESP的值)
然后直接POP了EBP的值(其实我这里说的不太准确,因为这里EBP的值是一块地址),而不是我们平时存的数值
然后POP完之后就变了这样(这一步两个寄存器都在改变)
最后ret,将00C717E3的值给到EIP,并且压栈,函数返回(这里其实一开始说错了,call的时候压的是00C717E8,我当时看错了,以为是test函数下面的地址就是汇编返回下一行的地址,不过过程没有任何问题!!!堆栈刚好平衡)
2.手搓一个裸函数
讲完了空函数,我们就要讲裸函数了,我们都是学计算机底层的,怎么可能想让编译器给我写代码,所以我们就想能不能自己在VS上手写一个裸函数,内容要我们自己实现!!!
在写裸函数之前,我们还得先写上这样的代码
cpp
void __declspec(naked)test()
{
__asm
{
}
}
有了上面的代码我们就可以自己写一个函数出来了,我们就以两数相加为例子把!! 对于一个裸函数,我们可以按照下面这几个步骤进行!!!
1.提栈
其实说这样提栈还不准确,应该说想Push了一些参数入栈,然后Call了才进入我们的函数
以下都是进函数之前的操作
然后才真正的到我们的提栈
2.保留现场
然后就是我们的保留现场了,依次压入我们的寄存器
3.填充缓冲区
聪明的你们一定发现了ESP和EBP之间还存在一大片空隙,那么我们现在就来填充缓冲区
但是在写缓冲区之前,我们还需要来看一条指令
这条指令的意思就是将EAX的寄存器的值循环写ECX次
所以我们的汇编就可以这么写
先把缓冲区起始位置mov给edi(虽然我不理解这一步的实际用处),然后就是将eax的值赋为0xcccccccc,ecx的值赋为0x10 ,然后直接循环填充缓冲区
4.实现函数功能
比如说我想实现 b = a + b 其中函数的传入为 (int a , int b )这两个参数位置不能调换(否则对应的指令也要换,这个后面讲)
5.还原现场
然后就是将对应寄存器POP出去
然后就是降低堆栈
并且同时改变两个寄存器!!!
最后就是RET,将返回地址给到EIP,跳回main函数
但是这时候聪明的你就会发现问题了,堆栈不平衡!!!!! 别急我们先运行一下
发现代码成功的跑了起来
我们去看反汇编,就能发现,原来是vs帮我们在自动的函数外面平栈
这步 add esp8就显得很精髓
所以我们的最终就达到了堆栈的平衡,成功手搓了一个裸函数,看,我们最简单的一个a+b的功能也是如此的多步骤呢,在汇编中
源代码如下
cpp
#include<stdio.h>
void __declspec(naked)test(int a,int b)
{
__asm
{
//1.提栈
push ebp
mov ebp ,esp
sub esp ,0x40
//2.保留现场
push ebx
push esi
push edi
//3.填充缓冲区
lea edi ,dword ptr ds:[ebp - 0x40]
mov eax ,0xcccccccc
mov ecx ,0x10
rep stosd
//4.实现函数功能
mov eax , dword ptr ds:[ebp + 0x8]
add eax , dword ptr ds:[ebp + 0xc]
// mov dword ptr ds:[ebp + 0xc],eax
//5.还原现场
pop edi
pop esi
pop ebx
mov esp ,ebp
pop ebp
retn
}
}
int main()
{
test(2,3);
return 0;
}
其中我对一行进行了注释,因为我觉得这段代码是开放的,你也完全可以把他直接存在EAX然后retn回函数之后再对你想要赋值的东西进行赋值(非裸函数的VS编译器就是这么干的)
cpp
mov dword ptr ds:[ebp + 0xc],eax
3.调用约定
上面我们说的是外平栈,其实默认的就是这样的调用
cpp
double __cdecl test(double a, double b)
{
return a + b;
}
直接在函数外面进行平栈
那么如果我们想要内平栈呢??? 那么就要这么写了
cpp
int __stdcall test2(int a, int b)
{
return a + b;
}
他直接Ret8了,其实这个指令就是add esp8 ,然后再retn
然后,就是我们的__fastcall了,这个函数于其他两个函数的区别,他在只有两个参数的情况下是不会对栈进行操作的,而是直接存储在CPU中,这样速度会快很多。
而且还要注意的一点,不知道大家发现没有,我们Push数进栈的时候,是从右往左进行PUSH的,而不是从左往右进行PUSH的!!!!
4.条件语句
首先就是我们的if了,我们先去写一段IF的代码
IF
cpp
int i = 1;
if (i == 1)
{
i = 2;
}
然后我们直接去看它的反汇编
我们从上面就能发现,里面的JCC语句和现实逻辑其实是相反的,cmp如果相等的话ZF位就是1,
我们就能看见它的JCC语句是在如果两个数不相等的时候就会跳转,如果相等就会执行下面的语句
IF ELSE
既然有IF ,那怎么能少的了IF ELSE 呢
我们从汇编的角度不难发现,首先他是通过CMP判断,如果不是的话,JCC语句就跳到ELSE的位置,如果是的话,执行完cout之后他就会JMP到ELSE执行完毕
While
然后就是while了,我们还是通过写一段While代码进行看他的反汇编
我们还是能从反汇编里面看见je这个语句,其实就是Jump if zero,但是我们的test eax ,eax这个汇编语句是恒为真的,所以一定不会Jump!!!!就达到了一直循环的目的。
For
然后就是我们常用的for语句了
我们可以看见执行之前的JG这个JCC语句,它的意思是对于有符号数来说,如果大于则跳转,很明显,一开始我们的i都是小五5的,所以肯定是不会跳转的。
5.DLL
DLL (Dynamic Link Library) 文件为动态链接库文件,又称"应用程序拓展",是软件文件类型。在Windows中,许多应用程序并不是一个完整的可执行文件,它们被分割成一些相对独立的动态链接库,即DLL文件,放置于系统中。当我们执行某一个程序时,相应的DLL文件就会被调用。一个应用程序可使用多个DLL文件,一个DLL文件也可能被不同的应用程序使用,这样的DLL文件被称为共享DLL文件。
当我们程序执行的时候,必须连接到DLL文件,然后通过DLL文件去调用一些Windows的API(像什么 WirteProcessMemory ,VirtualAlloc,CreateThread ......)
说了那么多,我们去VS生成一个DLL文件
生成之后我们能看见这样的东西
重点其实就在这串代码
cpp
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
- DLL_PROCESS_ATTACH :这个事件在当前进程第一次装载该 DLL 时触发,即 DLL 被加载到进程的地址空间中时。在这个分支中,可以执行与 DLL 装载相关的初始化操作。
- DLL_THREAD_ATTACH :这个事件在新线程创建并且该 DLL 被加载到新线程的地址空间时触发。在这个分支中,可以执行与新线程相关的初始化操作。
- DLL_THREAD_DETACH :这个事件在线程退出时触发,如果该线程加载了该 DLL,则会在 DLL 从该线程的地址空间中卸载时触发。在这个分支中,可以执行与线程退出相关的清理操作。
- DLL_PROCESS_DETACH :这个事件在当前进程卸载该 DLL 时触发,即该 DLL 从进程的地址空间中移除时。在这个分支中,可以执行与 DLL 卸载相关的清理操作。
那么下面我们就来演示一下DLL的调用方法,我么这里写一个弹窗的DLL
cpp
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
MessageBox(0, L"我是黑客", L"没有绝对安全的系统", MB_OK);
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
然后我们去生成一下,并且通过工具,注入一个32位的程序(32位的DLL要注32位的EXE)
这里说一下 VS编译的快捷键是 Control + F7
然后我们直接找一个EXE去注入,这里我就找一个自己写的EXE去注入了
然后就能看见弹框了
当然了,除了这种注入的方式去运行DLL,我们还可以去通过专门运行DLL的程序去运行
比如说rundll32就是一个不错的选择,不过被杀软盯得很严重,所以一般实战不会用这个,但是还是讲一下怎么使用!!!!
bash
rundll32.exe custom.dll,CustomFunction
通过上面的命令,我们可以调用DLL里面的函数
cpp
#include <Windows.h>
extern "C" __declspec(dllexport) void rundll(HWND hwnd,HINSTANCE hinst,LPTSTR
lpCmdLine,INT nCmdShow)
{
MessageBox(NULL,TEXT("whoami"),TEXT("Rundll32"),MB_OK);
}
cpp
rundll32 testdll.dll, rundll
通过这个,我们的DLL中的函数也是能被加载进来的
那么,c语言的基础就到这里了,我后面就把Shellcode loader给补上