免杀笔记 ---> C语言

这次的更新可能有点慢,因为这段时间也比较忙,加上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给补上

相关推荐
Uu_05kkq3 小时前
【C语言1】C语言常见概念(总结复习篇)——库函数、ASCII码、转义字符
c语言·数据结构·算法
嵌入式科普5 小时前
十一、从0开始卷出一个新项目之瑞萨RA6M5串口DTC接收不定长
c语言·stm32·cubeide·e2studio·ra6m5·dma接收不定长
A懿轩A6 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
1 9 J7 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
仍然探索未知中8 小时前
C语言经典100例
c语言
爱吃西瓜的小菜鸡8 小时前
【C语言】矩阵乘法
c语言·学习·算法
Stark、9 小时前
【Linux】文件IO--fcntl/lseek/阻塞与非阻塞/文件偏移
linux·运维·服务器·c语言·后端
deja vu水中芭蕾10 小时前
嵌入式C面试
c语言·开发语言
stm 学习ing11 小时前
HDLBits训练3
c语言·经验分享·笔记·算法·fpga·eda·verilog hdl
CSND74015 小时前
Ubuntu vi(vim)编辑器配置一键补全main函数
linux·c语言·ubuntu·编辑器·vim