How do those hackers' tools work? Proxychains

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:

  1. We start a server waiting for connections
  2. When we get a connection, we read the client request. This request will tell us where we should connect to.
  3. 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 :wink:

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.

  1. Declare a function pointer
  2. 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);
    }
  1. 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.
  2. Instead of calling connect, we call real_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

27 Likes

You are on fire mate.

2 Likes

:grin: Thanks

I started this some time ago, and it looked like a good moment to share it following the user view we’ve got yesterday.

Unfortunately I will be busy with other projects in the coming weeks so it will be difficult to post these long tutorials

1 Like

Well done! :clap:, and youve saved me the time of posting on writing a proxy in python! (which i may still do for those non c users.)

Also, well noted on the nmap through proxychains as i failed to specify it requires a full connect scan :wink: oops.

Nice read, well written. I may play with your code a bit in breaks from my other projects.

3 Likes

@Airth is correct, You truly are on fire! This again is absolute gold! I am so pleased you are in our community, you are such a valuable asset!

2 Likes

I’m PySec by the way, in case you didn’t know :slight_smile:

I didn’t know… That makes the comment a lot more valuable.

I’m missing your network posts :cry:

I’m trying to finish my subnetting series. Currently writing an article on VLSM :slight_smile: I apologize for my lack of uploads, I’ve just been busy with a ton of university assignments unfortunately.

2 Likes

I’m looking forward to it. Hope your assignments went/are going well!

I think I will be in a similar situation soon…

1 Like

Stay strong buddy! :slight_smile:

1 Like

Is there any chance your tutorial in Python could be posted?

Yeah, im free tomorrow, ill get one up. Hope to get another post up as well so ill add it to my to-do list.

3 Likes

The connect function you speak of, is that the function I find with command:
objdump -T /usr/bin/lynx | grep connect ?

2 Likes

Nice! Holy cow, I don’t think I will be finished reading this article today but so far its incredibly detailed and informative. Great job!

1 Like

@unh0lys0da yes that is the one!
@Wawa thanks for reading and I’m glad to hear you liked it

http://www.cprogramming.com/tutorial/shared-libraries-linux-gcc.html
For more info on linking

Another world opened for me @0x00pf
Thanks again !!

2 Likes

I will be re-reading this soon! My adventures in C will be nicely aided by this.

1 Like

Impressive ! Great article mate. I hope to see more soon !

1 Like

This topic was automatically closed after 30 days. New replies are no longer allowed.