Welcome to this new series intended to explain the guts of all those hacker’s tools out there you use or want to use… Basically, we are going to explain you how does they work and how do you can build your own… Keep reading if you do not want to be a Skid ;).
We will start the series with Proxychains. Before, keeping on reading, make sure you had read the 0x00sec introduction from @OilSlick to () ProxyChains.
To understand how ProxyChains works we first need to understand how does a proxy works…So let’s code one!
Coding a Proxy
Proxychains supports HTTP and SOCKS proxies. Conceptually they are the same thing. They just differ in the way, the clients (your web browser for instance) interchange information with the proxy itself…
For the sake of simplicity we are going to code a simple (very simple) HTTP proxy. Improving this proxy and adding SOCKS capabilities will be an interesting exercise for the reader (do you know that access to Tor is perform via a SOCKS proxy?..)
So, our proxy is going to be pretty simple:
- We start a server waiting for connections
- When we get a connection, we read the client request. This request will tell us where we should connect to.
- After that, we just read data from the client and send it to the target server, and we also read the replies for the server and send them back to the client
If you had read my series on Remote Shells (Part I and Part II) all these should sound familiar. Yes, we have already written most of the code we need. So let’s start from the part II code and do some modifications
Changing a Remote Shell into a Proxy
The first thing to do is to delete the remote_shell function. We do not need that. Then we just need to change our main function, and add an extra function… Yes. Really. It’s that easy.
This is how the main
function will look like:
int
main (int argc, char *argv[])
{
int s, c;
char buffer[2048];
s = server_init (atoi(argv[1]));
read (s, buffer, 2048);
c = process_request (buffer);
async_read (s, c);
return 0;
}
We just start our server listening in the port we passed as parameter from the command line. Then we read the request from the just connected client and call a function we have called process_request
. This function will parse the client request and establish the proper connection as per the client request.
Now we have a client socket s
, connected to our proxy, for instance from a web browser. Another socket (c
), connects our proxy to the server the client wanted to connect to. So, we just call our async_read
function and we get those two ends up and running interchanging information.
HTTP Proxies
To complete our proxy we have to write the process_request
function, and for that we have to know how does an HTTP proxy request looks like. Well, it looks like a normal HTTP request, something like this:
GET http://server:port/page HTTP/1.0
This is what we are going to receive from a browser using a HTTP proxy. Let’s try:
In one terminal launch a listening netcat:
$ nc -l 8081
In another terminal, configure your proxy. It has to be localhost:8081 so we get the request in the netcat server running in the first terminal. Finally, launch the lynx web browser:
$ export http_proxy=http://127.0.0.1:8081
$ lynx http://127.0.0.1:1234
In your netcat terminal you will see the information sent by the browser:
GET http://127.0.0.1:1234/ HTTP/1.0 Host: google.com Accept: text/html, text/plain, text/css, text/sgml, */*;q=0.01 Accept-Encoding: gzip, compress, bzip2 Accept-Language: en User-Agent: Lynx/...
So, let’ build a quick and dirty parser for these requests.
Parsing an HTTP Proxy request
This is how our parser looks like.
int
process_request (char *buffer)
{
int port, c;
char *aux;
char ip[1024];
sscanf (buffer, "GET http://%s/", ip);
if ((aux = strchr (ip, ':')))
{
*aux = 0;
sscanf (aux+1, "%d", &port);
}
else port = 80;
printf ("Request to connect to: %s(%d)\n", ip, port);
c = client_init (ip, port);
return c;
}
It is ugly, but parsing strings is not the topic we are actually interested on. The code just extracts the ip and the port from the HTTP request and then it creates a client socket to connect to that server.
Note that you cannot use server names. For doing that you have to change the client_init
function to actually resolve the name. It is not difficult but, as it happened with the parser, it does not bring much value to the current discussion. So, you had just got another exercise if you want.
Testing our proxy
To test our proxy, we need 3 terminals!.. WoW
In the first terminal we launch our proxy:
$ ./proxy 8081
In the second we launch our target server. The one we want to connect to using the proxy:
$ nc -l 1234
Finally, in the third one we launch lynx
connecting through our proxy:
$ export http_proxy=http://127.0.0.1:8081
$ lynx http://127.0.0.1:1234
To complete the test, in the target netcat terminal (the one listening in port 1234 and acting as the final web server), we send back a HTTP reply so lynx
gets the page it was requesting.
$ nc -v -l 1234
HTTP/1.0 200 OK
Content-Type: text/html
<html><body><h1>Hello</h1></body></html>
You have to press CTRL+C on the netcat server terminal to close the connection and force lynx
to show the page.
Very good. Now we have a pretty simple proxy server for our tests. Yes, indeed we could have used one of the already available proxies in our GNU/Linux distros… but then, you will not know how to build your own
Back to ProxyChains
So, ProxyChains basically gets a list of proxies, and, when the user request to access some service, it sends a bunch of requests, similar to the ones we used in our example below, to connect from one proxy to another, until the actual request to the target machine reaches the last proxy in the chain.
So, it connects to a proxy, and instead of providing the address for the server to access, it provides a new request to another proxy, opening one by one multiple connections through different machines.
There is a bunch of code (you should take a look) in the project to manage the list of proxies, select them re-connect, etc… All that part is complex, but does not really have to do with security, it is normal software business.
However what it is hacking related is how does ProxyChains allows any application (that does not even supports proxy connections) to establish that chain. The answer is LD_PRELOAD
.
LD_PRELOAD
LD_PRELOAD
is an environmental variable that allows us to specify some dynamic libraries that will be pre-loaded automatically and can, therefore, overwrite certain functions in the code.
So, how is this used by Proxychains?. You just need to provide a shared library that changes the behavior of the connect
function. Instead of connecting to where the application wants it to connect, we will change the function to connect to our proxy and generate a connection request to get our data through the proxy.
This is a possible simple implementation:
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen)
{
int s, port;
struct sockaddr_in serv, *cli;
char req[1024], *ip;
int (*real_connect) (int sockfd,
const struct sockaddr *addr,
socklen_t addrlen);
/* get pointer to original connect function */
real_connect = dlsym (RTLD_NEXT, "connect");
/* Obtain text info to build the proxy connection request */
cli = (struct sockaddr_in*)addr;
ip = inet_ntoa (cli->sin_addr);
port = ntohs (cli->sin_port);
/* Create a new socket as the other one is currently trying to connect*/
/* Otherwise we get a 'connect:: Operation now in progress' error */
if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0)
{
perror ("socket:"); exit (EXIT_FAILURE);
}
/* Swap file descriptors */
close (sockfd);
dup2 (s,sockfd);
/* Connect to proxy */
serv.sin_family = AF_INET;
serv.sin_port = htons(atoi(getenv("MY_PROXY_PORT")));
serv.sin_addr.s_addr = inet_addr(getenv("MY_PROXY"));
if (real_connect (s, (struct sockaddr *) &serv, sizeof(serv)) < 0)
{
perror("connect:"); exit (EXIT_FAILURE);
}
/* Send proxy connection request */
sprintf (req, "GET http://%s:%d HTTP/1.1\r\n\r\n", ip, port);
write (s, req, strlen(req));
return 0;
}
OK, it is not that simple and it might even look scary at first glance, but it is not that bad. Just take a cup of coffee and keep calm.
Getting the original connect
We are overwriting the connect
function but, at some point we will have to actually connect to somewhere. That means that we will have to invoke the original connect
function later in our code.
So, the first thing we have to do is to grab the pointer to the original function. And we have to do two things to do that.
- Declare a function pointer
- Get the pointer to the original function.
A function pointer in C is declared as a normal function prototype but enclosing the function name in parenthesis and prefixing it with an *. Our function pointer looks like this:
int (*real_connect) (int sockfd, const struct sockaddr *addr, socklen_t addrlen);
That declares a variable named real_connect
that will point to a function with the indicated prototype. In this case the prototype of the connect
function. So far, so good.
To actually get the pointer to the original connect
we use the dlsym
function provided by the dl
(dynamic linking) library. Yes, we have to link against that library and we also have to add a header file and ask for the GNU extensions… Just take a look to the final code in github.
The line that gets the function pointer is:
real_connect = dlsym (RTLD_NEXT, "connect");
This function looks for the “NEXT” definition of the connect
function in the process symbol list. Note that you can pre-load many libraries and you may have multiple definitions of the same function. The RTLD_NEXT
allows you to find the next pointer for the indicated symbol in the chain.
Obtaining the original connection information
The next thing we have to do is to convert the server information provided to the connect
function (received as the parameter addr
) to strings, so we can build an HTTP request for our HTTP proxy.
We easily get this information using inet_ntoa
(ntoa stands for network to ASCII/string) and ntohs
(network to host short). The three lines below do the trick.
cli = (struct sockaddr_in*)addr;
ip = inet_ntoa (cli->sin_addr);
port = ntohs (cli->sin_port);
Preparing the connection
If we just try to call the real_connect
now, passing the socket we got from the top level application, we will get an error (at least that is what I’ve got):
Operation now in progress' error
I haven’t fully investigated why does this happen, so I just went for a workaround. I will create a new socket, and then use the dup2
function call to assign it the same file descriptor that the original socket created by the top level application. This way, all the later operations on the socket will just be performed on this new created socket, instead of on the original socket created by the top level application.
if ((s = socket (AF_INET, SOCK_STREAM, 0)) < 0)
{
perror ("socket:");
exit (EXIT_FAILURE);
}
/* Swap file descriptors */
close (sockfd);
dup2 (s,sockfd);
Connect to the proxy
Now we can connect to our proxy server. The code for this process, is overall the same that the one we used in the Remote Shell series (check the client_init
function). There are only two minor changes we have done to the code.
/* Connect to proxy */
serv.sin_family = AF_INET;
serv.sin_port = htons(atoi(getenv("MY_PROXY_PORT")));
serv.sin_addr.s_addr = inet_addr(getenv("MY_PROXY"));
if (real_connect (s, (struct sockaddr *) &serv, sizeof(serv)) < 0)
{
perror("connect:");
exit (EXIT_FAILURE);
}
- We are taking the proxy IP and the proxy port from two environmental variables. We did it like this so we do not have to parse the URL required by the
http_proxy
env var, and we do not write all the code to manage files with proxies lists as ProxyChains does. - Instead of calling
connect
, we callreal_connect
, otherwise we will call this same function again and we will get in an infinite loop.
Sending the HTTP request
Everything is in place right now, so we just need to send our connection request to the proxy to get the top level application connected to the original server. Nothing special here, just build the request string and send it out.
/* Send proxy connection request */
sprintf (req, "GET http://%s:%d HTTP/1.1\r\n\r\n", ip, port);
write (s, req, strlen(req));
Compiling the shared library
Our library code is ready and now we have to compile it. You can do it as you prefer (setup some autotools project, using cmake, with a Makefile, manually). We just put a couple of rules in a Makefile
proxify.o:proxify.c
${CC} -c -fpic -o $@ $<
libproxify.so:proxify.o
${CC} -shared -o $@ $< -ldl
The first rule produces a pic (Position Independent Code) object file and the second one links it into a dynamic library (.so) and adds the dl
library we mentioned before (in order to use dlsym
).
If on doubt just clone the github repo.
Testing our Proxyfier
So, the testing will be exactly the same that in our previous case but, instead of setting the system http_proxy
env var, we are going to preload our proxifier library.
Launch the target netcat at 1234 and the proxy at whatever port you like. I will use 8081 as example in the commands below:
In a different console:
$ unset http_proxy
$ lynx http://127.0.0.1:1234
We should get a direct connection to the netcat listening on 1234. Nothing shown in our proxy terminal. Now let’s proxify lynx (you may need to relaunch netcat in the first terminal).
$ export MY_PROXY=127.0.0.1
$ export MY_PROXY_PORT=8081
$ LD_PRELOAD= ./libproxify.so lynx http://127.0.0.1:1234
Did it work?.. Hope so, it worked for me.
Going Further
Now, you know the basis of how does a proxy works, how to override functions in a program to proxify it and the basics of how proxy chain works. What you can do from this point on:
- Modify the program to chain multiple proxies. Basically you will have to produce many connection request in your version of
connect
- Extend the program to use domain names instead of just IP addresses, otherwise it will not work in a real world scenario
- Implement a SOCKS proxy and a SOCK proxify library
Note that, the code as presented in this tutorial will not properly work on the wild. This is done on purpose… because… you do not want to be a skid!, don’t you?
A Final note
You may have heard that you can run nmap through a proxy chain. This is indeed true but only if you use a fully connected scan, that is pretty noisy… None of the stealth scans will work and I hope you know now why.
Happy Hacking
pico