-
Notifications
You must be signed in to change notification settings - Fork 176
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
detect socks5 proxy capabilities #971
Comments
Hi! I would like to try this out! Can I get some headstart? |
Great! I've assigned this issue to you. If you haven't already, I'd recommend reviewing our documentation on capa rules to get familiar with how they work. Please ask any specific questions you may have here 😄 |
How do I set these programs up? Can I code them from scratch in C and will they work the same or are there any examples present in https://github.com/mandiant/capa-testfiles that I can leverage? Can I also use proxychains and setup a socksv5 proxy to analyse the same (This I believe is an overkill)?
Are these values always constant? What should be a good reference for this other than Wikipedia link? Is is possible to extract them from a binary?
Are there any existing rules that I can refer? I was referring https://github.com/mandiant/capa-rules/blob/master/communication/c2/shell/execute-shell-command-received-from-socket-on-linux.yml and HTTP rules https://github.com/mandiant/capa-rules/tree/master/communication/http/server. Also regarding the examples how do I provide/add them to https://github.com/mandiant/capa-testfiles? |
You're welcome to create a toy program that demonstrates the concept or use a publicly available program. capa supports static analysis of 32- and 64-bit Intel PE and ELF programs, so aim for that should you go the example route. We do not require all capa rules to have an example as there may be cases where a program is not public, etc.. In these cases, the corresponding capa rule should be placed in the
This isn't relevant here unless you want develop and test a toy program that supports socks5. capa does not support network traffic analysis, so the goal here is to develop a capa rule that targets the parsing code found in programs that implement socks5, specifically parsing of the client connection request.
In terms of the socks5 protocol, yes, these are constant. However, there may be cases where a program is obfuscated, making the constants indecipherable through static analysis only. Let's focus on the former here and worry about the latter if it becomes an issue.
I don't have additional references on hand, you're welcome to search for additional references that include information on the socks5 client connection request.
capa should extract
This rule will need to match
Simply create a PR containing your example program. You'll find details on the required naming scheme in the README. But see my earlier comment about not needing an example program. |
I have created this following C code for socks client (assumed TCP-Stream and IPv4) and compiled with MinGW compiler set on windows. #include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <conio.h> // For _kbhit()
#pragma comment(lib, "ws2_32.lib")
#define SOCKS_VERSION 5
#define BUFFER_SIZE 4096
// SOCKS5 authentication methods
#define NO_AUTH 0x00
#define GSSAPI 0x01
#define USERNAME_PASSWORD 0x02
#define NO_ACCEPTABLE_METHODS 0xFF
// SOCKS5 commands
#define CONNECT 0x01
#define BIND 0x02
#define UDP_ASSOCIATE 0x03
// SOCKS5 address types
#define IPV4 0x01
#define DOMAIN_NAME 0x03
#define IPV6 0x04
// SOCKS5 replies
#define SUCCEEDED 0x00
#define GENERAL_FAILURE 0x01
#define CONNECTION_NOT_ALLOWED 0x02
#define NETWORK_UNREACHABLE 0x03
#define HOST_UNREACHABLE 0x04
#define CONNECTION_REFUSED 0x05
#define TTL_EXPIRED 0x06
#define COMMAND_NOT_SUPPORTED 0x07
#define ADDRESS_TYPE_NOT_SUPPORTED 0x08
int socks5_connect(const char* proxy_host, int proxy_port, const char* dest_host, int dest_port);
void print_usage(const char* prog_name);
int main(int argc, char* argv[]) {
WSADATA wsaData;
char buffer[BUFFER_SIZE];
int n, sock, ret;
// Check command line arguments
if (argc != 5) {
print_usage(argv[0]);
return 1;
}
const char* proxy_host = argv[1];
int proxy_port = atoi(argv[2]);
const char* dest_host = argv[3];
int dest_port = atoi(argv[4]);
// Initialize Winsock
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
fprintf(stderr, "WSAStartup failed\n");
return 1;
}
// Connect through SOCKS5 proxy
sock = socks5_connect(proxy_host, proxy_port, dest_host, dest_port);
if (sock == INVALID_SOCKET) {
WSACleanup();
return 1;
}
printf("Connected to %s:%d through SOCKS5 proxy %s:%d\n",
dest_host, dest_port, proxy_host, proxy_port);
printf("Type your message. Press Enter to send. Ctrl+C to exit.\n");
// Use select to monitor both stdin and socket
fd_set readfds;
while (1) {
FD_ZERO(&readfds);
FD_SET(sock, &readfds);
// Use select to wait for socket activity only
struct timeval tv = {0, 100000}; // 100ms timeout
ret = select(sock + 1, &readfds, NULL, NULL, &tv);
if (ret == SOCKET_ERROR) {
fprintf(stderr, "Select failed: %d\n", WSAGetLastError());
break;
}
// Check if socket has data to read
if (ret > 0 && FD_ISSET(sock, &readfds)) {
memset(buffer, 0, BUFFER_SIZE);
n = recv(sock, buffer, BUFFER_SIZE - 1, 0);
if (n <= 0) {
printf("Connection closed by remote host\n");
break;
}
// Print received data
printf("Received: %s", buffer);
}
// Check if stdin has data to read (non-blocking)
if (_kbhit()) {
memset(buffer, 0, BUFFER_SIZE);
if (fgets(buffer, BUFFER_SIZE - 1, stdin) == NULL) {
break;
}
// Send data to remote host
if (send(sock, buffer, strlen(buffer), 0) <= 0) {
fprintf(stderr, "Send failed: %d\n", WSAGetLastError());
break;
}
}
}
// Close socket and cleanup
closesocket(sock);
WSACleanup();
return 0;
}
int socks5_connect(const char* proxy_host, int proxy_port, const char* dest_host, int dest_port) {
SOCKET sock;
struct sockaddr_in proxy_addr;
struct hostent* proxy_info;
unsigned char buffer[BUFFER_SIZE];
int n;
// Resolve proxy hostname
proxy_info = gethostbyname(proxy_host);
if (proxy_info == NULL) {
fprintf(stderr, "Could not resolve proxy hostname: %s\n", proxy_host);
return INVALID_SOCKET;
}
// Create socket
sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sock == INVALID_SOCKET) {
fprintf(stderr, "Failed to create socket: %d\n", WSAGetLastError());
return INVALID_SOCKET;
}
// Set up proxy address
memset(&proxy_addr, 0, sizeof(proxy_addr));
proxy_addr.sin_family = AF_INET;
proxy_addr.sin_port = htons(proxy_port);
memcpy(&proxy_addr.sin_addr, proxy_info->h_addr, proxy_info->h_length);
// Connect to proxy
if (connect(sock, (struct sockaddr*)&proxy_addr, sizeof(proxy_addr)) == SOCKET_ERROR) {
fprintf(stderr, "Failed to connect to proxy: %d\n", WSAGetLastError());
closesocket(sock);
return INVALID_SOCKET;
}
printf("Connected to SOCKS5 proxy at %s:%d\n", proxy_host, proxy_port);
// SOCKS5 handshake - method negotiation
buffer[0] = SOCKS_VERSION; // SOCKS version
buffer[1] = 1; // Number of authentication methods
buffer[2] = NO_AUTH; // No authentication
if (send(sock, buffer, 3, 0) <= 0) {
fprintf(stderr, "Handshake send failed: %d\n", WSAGetLastError());
closesocket(sock);
return INVALID_SOCKET;
}
// Receive server's response
n = recv(sock, buffer, 2, 0);
if (n <= 0) {
fprintf(stderr, "Handshake receive failed: %d\n", WSAGetLastError());
closesocket(sock);
return INVALID_SOCKET;
}
// Check server's response
if (buffer[0] != SOCKS_VERSION) {
fprintf(stderr, "Invalid SOCKS version in response\n");
closesocket(sock);
return INVALID_SOCKET;
}
if (buffer[1] == NO_ACCEPTABLE_METHODS) {
fprintf(stderr, "No acceptable authentication methods\n");
closesocket(sock);
return INVALID_SOCKET;
}
printf("Authentication method negotiated successfully\n");
// Prepare connection request
struct hostent* dest_info = gethostbyname(dest_host);
if (dest_info == NULL) {
fprintf(stderr, "Could not resolve destination hostname: %s\n", dest_host);
closesocket(sock);
return INVALID_SOCKET;
}
memset(buffer, 0, BUFFER_SIZE);
// Create request
int idx = 0;
buffer[idx++] = SOCKS_VERSION; // SOCKS version
buffer[idx++] = CONNECT; // Command: Connect
buffer[idx++] = 0; // Reserved
// Determine address type and set address
if (dest_info->h_addrtype == AF_INET) {
// IPv4 address
buffer[idx++] = IPV4;
memcpy(buffer + idx, dest_info->h_addr, 4);
idx += 4;
} else {
// Domain name
buffer[idx++] = DOMAIN_NAME;
int len = strlen(dest_host);
buffer[idx++] = len;
memcpy(buffer + idx, dest_host, len);
idx += len;
}
// Set destination port
buffer[idx++] = (dest_port >> 8) & 0xFF; // Port high byte
buffer[idx++] = dest_port & 0xFF; // Port low byte
// Send request
if (send(sock, buffer, idx, 0) <= 0) {
fprintf(stderr, "Connection request send failed: %d\n", WSAGetLastError());
closesocket(sock);
return INVALID_SOCKET;
}
// Receive reply
n = recv(sock, buffer, 4, 0);
if (n <= 0) {
fprintf(stderr, "Connection response receive failed: %d\n", WSAGetLastError());
closesocket(sock);
return INVALID_SOCKET;
}
// Check reply
if (buffer[0] != SOCKS_VERSION) {
fprintf(stderr, "Invalid SOCKS version in response\n");
closesocket(sock);
return INVALID_SOCKET;
}
if (buffer[1] != SUCCEEDED) {
fprintf(stderr, "Connection failed, error code: %d\n", buffer[1]);
closesocket(sock);
return INVALID_SOCKET;
}
// Read the bound address and port (we don't use them in this example)
unsigned char atyp = buffer[3];
int atyp_len = 0;
if (atyp == IPV4)
atyp_len = 4;
else if (atyp == IPV6)
atyp_len = 16;
else if (atyp == DOMAIN_NAME) {
n = recv(sock, buffer, 1, 0); // Get domain length
if (n <= 0) {
closesocket(sock);
return INVALID_SOCKET;
}
atyp_len = buffer[0];
}
// Skip the bound address
if (atyp_len > 0) {
n = recv(sock, buffer, atyp_len, 0);
if (n <= 0) {
closesocket(sock);
return INVALID_SOCKET;
}
}
// Skip the bound port
n = recv(sock, buffer, 2, 0);
if (n <= 0) {
closesocket(sock);
return INVALID_SOCKET;
}
printf("SOCKS5 connection established\n");
return sock;
}
void print_usage(const char* prog_name) {
printf("Usage: %s <proxy_host> <proxy_port> <destination_host> <destination_port>\n", prog_name);
printf("Example: %s localhost 1080 www.example.com 80\n", prog_name);
} I ran the exe through Binary Ninja to get the socks_connect() function. Here's the call graph as follows: The corresponding features are: bb: 0x140001A34: basic block
insn: 0x140001A34: mnemonic(mov)
insn: 0x140001A3A: mnemonic(mov)
insn: 0x140001A41: mnemonic(mov)
insn: 0x140001A44: mnemonic(mov)
insn: 0x140001A47: mnemonic(lea)
insn: 0x140001A47: string(Connected to SOCKS5 proxy at %s:%d\n)
insn: 0x140001A4E: mnemonic(mov)
insn: 0x140001A51: mnemonic(call)
insn: 0x14000186A: 0x140001A51: characteristic(calls from) -> 0x1400032B0
insn: 0x140001A56: mnemonic(mov)
insn: 0x140001A56: number(0x5)
insn: 0x140001A56: operand[1].number(0x5)
insn: 0x140001A5A: mnemonic(mov)
insn: 0x140001A5A: number(0x1)
insn: 0x140001A5A: operand[1].number(0x1)
insn: 0x140001A5E: mnemonic(mov)
insn: 0x140001A5E: number(0x0)
insn: 0x140001A5E: operand[1].number(0x0)
insn: 0x140001A62: mnemonic(lea)
insn: 0x140001A66: mnemonic(mov)
insn: 0x140001A6D: mnemonic(mov)
insn: 0x140001A6D: number(0x0)
insn: 0x140001A6D: operand[1].number(0x0)
insn: 0x140001A73: mnemonic(mov)
insn: 0x140001A73: number(0x3)
insn: 0x140001A73: operand[1].number(0x3) Need some guidance to write the rule based on https://github.com/mandiant/capa-rules/blob/5fb8cee820ab87a11b7cc9a4c08a07ce982152a5/load-code/pe/parse-pe-header.yml :). Thanks! |
@ArkaprabhaChakraborty would you please confirm this program is tested to work as a compatible SOCKS5 proxy? Just want to be extra clear, and no offense implied, since our rules need to be rooted in real world examples. I'm a little concerned about writing new programs to use as examples, because their structure may be contrived, such as everything in a single function, etc. Which then makes us think our rules work, when they're not general enough for real-world software. Could we use proxychains as an example instead? |
ah sorry I see that proxychains is mentioned above and not a candidate because it's the proxy, not the client. |
socat 1.8.0.0 has SOCKS5 client support |
TBH even I don't trust this code. I didn't exactly code it from scratch and referred to gemini code suggestions too as I just wanted to get a quick PoC setup. It does work! I have built it based on the wiki suggestions and I have a socks server + proxy code that works with this :) I did suggest proxychains but went this route instead to be quick. I'll use proxychains as that would be better. Edit: didn't see the socat comment. Will try that instead |
I'll provide the necessary details about the code too! |
Ok great I understand. Thanks for your candor and promptness! |
socat-features.txt Source used: The source has test files for socks4 and socks4a (socks4a-echo.sh and socks4echo.sh exists and their description suggests they are test files) like this:
But I haven't seen socks5 tests. Edit: I have found 1.8.0.3 apparently has socksv5 full support switching to use that. |
So I found out that I have collected all the features, but I'm facing the same problem after that. Need some guidance. Thanks! |
@ArkaprabhaChakraborty I appreciate all of your work here but we're getting off track of the original intent for this rule. The goal here is to develop a new capa rule that detects when a file is capable of parsing a SOCKS5 client connection request packet. This is a packet that is sent from a SOCKS5 client to a SOCKS5 server where the server is responsible for parsing the packet and connecting the client to its destination. Therefore, your focus here should be on the SOCKS5 server. I completed a quick Google search and found the open-source SOCKS5 server named MicroSocks that has a recent Feb 6 release binary that you can use to write your rule. Because MicroSocks is open-source you also have the source code availible to help you get started writing your rule. Specifically, these lines are responsible for parsing the client connection request packet. You can open the MicroSocks release binary in your tool of choice to view the corresponding assembly code or use capa's scripts/show-features.py to help guide your rule writing. Your next step should be to develop a new capa rule that searches for a combination of number, offset, and api features that match the client connection request handling supported by MicroSocks. |
Ah! I had the socks server in mind as well because it's generally the malware being the server which is intercepted by the client/receiver, which is why I had asked about proxychains from the perspective. I had also implemented a socks server along with my client code. I'll try out Microsocks now thanks! |
I've analyzed a few, small programs that function as SOCKS5 proxies and I've been able to identify the protocol based on the same offset and constant parsing completed in the code. Specifically, the client connection request when the parsing the
DSTADDR
field. This requires checking the address type (0x1, 0x3, 0x4) and command code (0x1, 0x2, 0x3).I'm not sure if it's possible to check for comparisons to these constant values without introducing false positives but I wanted to note the idea here because I think it'd be helpful to quickly identify this common functionality.
Source: https://en.wikipedia.org/wiki/SOCKS
The text was updated successfully, but these errors were encountered: