Cheap torification in C with self-applied LD_PRELOAD

So I was playing around with a lab for exploring the OpenSSL C API, and wanted to add a command-line flag to make connections go through Tor, so that I could e.g. fetch SSL certificates from .onion addresses.
An easy way to do it would just have been to use torify, but I figured, why not have it as a command line flag so it shows in the help text of my lab program? This way I’ll see it and I’ll be more likely to use it.

I thought of two options:

  1. make connections go through the SOCKS5 proxy Tor spawns on my computer on port 9050
  2. use libtorsocks which is the library preloaded by torify

The SOCKS5 route required implementing a custom SOCKS5 BIO since OpenSSL itself does not provide SOCKS5 proxying support, and I wasn’t even sure the DNS request would use the proxy too. So I went for the libtorsocks route.

The constraints I had were:

  • have the Tor option as a runtime switch: no compile-time linking against libtorsocks
  • use the system’s libssl: no compiling a non-Tor and a Tor-enabled libssls, and dynamically dlopen()ing the correct one at runtime
  • use libssl as much as possible even for lower-level network stuff: no control over the calls to gethostbyname(), socket() and all other functions overridden by libtorsocks

At first I thought of using dlopen() to load libtorsocks, but I couldn’t find any way to make libssl not use the symbols that were resolved by the loader upon startup of my lab program. So next I thought it would be neat if I could LD_PRELOAD myself with libtorsocks.

Since handling LD_PRELOAD is the work of the loader, it meant once my program was started and the --tor flag was processed, there was no way it could apply LD_PRELOAD on itself… but it would be applied to its children!
I didn’t even need to fork() since I didn’t need the parent to live on: simply set LD_PRELOAD to point to libtorsocks, then execve() and the loader would kick in and my program would be re-spawned with torification built in.

I’ve tried it and it works like a charm.
The only gotcha is that if for some reason libtorsocks can’t be found or loaded, the loader simply prints an error but carries on, and the program ends up not being torified. However this is easily fixed: as I’m re-exec-ing myself, the second run of the program has the exact same command line arguments as the first. This means that if it sees the --tor option, it can first check if LD_PRELOAD is set, and then:

  • if it’s not, set it and execve()
  • if it is, use dlsym() to get the address of a symbol exported only by libtorsocks to ensure it is actually loaded (the GNU version of dlsym() has a pseudo-handle RTLD_DEFAULT that searches the global symbols of the executable and its dependencies)

In the end, the code looks as follows:

if (args.tor) {
    if (getenv("LD_PRELOAD") == NULL) {
        // not preloaded, let's restart
        setenv("LD_PRELOAD", TORSOCKS_LIB, 1);
        setenv("TORSOCKS_ISOLATE_PID", "1", 0); /* torsocks --isolate */
        (void) execvp(argv[0], argv);
        // return from execvp() => error
        DIE(3, "--tor failed: %s\n", strerror(errno));
    } else {
        // already preloaded, ensure it worked
        (void) dlerror();                                       /* clear error status */
        (void) dlsym(RTLD_DEFAULT, "tsocks_connect_to_tor");    /* don't care about the actual symbol address */
        if (dlerror() != NULL) {
            DIE(3, "--tor preloading error\n");
        }
    }
}

And now I can happily switch between Tor and non-Tor SSL certificate fetching:

$ ./x509 --sni duckduckgo.com
Fetching certificate from duckduckgo.com... ✓
  version = 3
  serial# = 0AD9B00801718556792CD5C6FC12421D
  businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = Delaware, serialNumber = 5019303, street = 20 Paoli Pike, postalCode = 19301, C = US, ST = Pennsylvania, L = Paoli, O = "Duck Duck Go, Inc.", CN = duckduckgo.com
  C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Extended Validation Server CA
  sigalg = sha256WithRSAEncryption
  SAN = DNS:duckduckgo.com
  SAN = DNS:www.duckduckgo.com
  notBefore = May 30 00:00:00 2017 GMT
  notAfter = Jun  8 12:00:00 2018 GMT

$ ./x509 --sni 3g2upl4pq6kufc4m.onion
Fetching certificate from 3g2upl4pq6kufc4m.onion... 139904841508672:error:20087002:BIO routines:BIO_lookup:system lib:crypto/bio/b_addr.c:693:Name or service not known
error: can't connect to 3g2upl4pq6kufc4m.onion:443

$ ./x509 --sni --tor 3g2upl4pq6kufc4m.onion
Fetching certificate from 3g2upl4pq6kufc4m.onion... ✓
  version = 3
  serial# = 034392BD5A5F5FF930609512157EA17F
  businessCategory = Private Organization, jurisdictionC = US, jurisdictionST = Delaware, serialNumber = 5019303, C = US, ST = Pennsylvania, L = Paoli, O = "Duck Duck Go, Inc.", CN = 3g2upl4pq6kufc4m.onion
  C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 Extended Validation Server CA
  sigalg = sha256WithRSAEncryption
  SAN = DNS:3g2upl4pq6kufc4m.onion
  SAN = DNS:www.3g2upl4pq6kufc4m.onion
  notBefore = Nov 30 00:00:00 2017 GMT
  notAfter = Jan 25 12:00:00 2019 GMT

Leave a Reply

Your email address will not be published. Required fields are marked *