Zignatures

November 5, 2014

by http://twitter.com/j0sm1 modified by @Obaied

In this blog post, we are going to show a simple example of the radare2 “zignatures” functionality. To manage “zignatures” in radare2, the only thing you have to do is type ‘z’ in the radare console. Here you can get more info on this ‘z’ command:

r2console> z?
|Usage: z[abcp/*-] [arg]Zignatures
| z                 show status of zignatures
| z*                display all zignatures
| z-prefix          unload zignatures with corresponding prefix
| z-*               unload all zignatures
| z/[ini] [end]     search zignatures between these regions
| za ...            define new zignature for analysis
| zb name bytes     define zignature for bytes
| zc @ fcn.foo      flag signature if matching (.zc@@fcn)
| zf name fmt       define function zignature (fast/slow, args, types)
| zg prefix [file]  generate signature for current file
| zh name bytes     define function header zignature
| zp prefix         define prefix for following zignatures
| zp                display current prefix
| zp-               unset prefix
| NOTE:             bytes can contain '.' (dots) to specify a binary mask

To show this functionality, we are going to use a simple C server. You can see its code here:

/* A simple server listening on TCP port 4001
   from http://www.linuxhowtos.org/data/6/server.c, modified by Sam Bowne
*/
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>

int copier(char *str) {
    char buffer[1024];
    strcpy(buffer, str);
    return 0;
}

void error(const char *msg) {
    perror(msg);
    exit(1);
}

int main(int argc, char *argv[]) {
    int sockfd, newsockfd, portno;
    socklen_t clilen;
    char buffer[4096], reply[5100];
    struct sockaddr_in serv_addr, cli_addr;
    int n;
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) error("ERROR opening socket");
    bzero((char *)&serv_addr, sizeof(serv_addr));
    portno = 4001;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_addr.s_addr = INADDR_ANY;
    serv_addr.sin_port = htons(portno);
    if (bind(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
        error("ERROR on binding");
    listen(sockfd, 5);
    clilen = sizeof(cli_addr);
    newsockfd = accept(sockfd, (struct sockaddr *)&cli_addr, &clilen);
    if (newsockfd < 0) error("ERROR on accept");
    while (1) {
        n = write(newsockfd, "Welcome to my server!  Type in a message!\n", 43);
        bzero(buffer, 4096);
        n = read(newsockfd, buffer, 4095);
        if (n < 0) error("ERROR reading from socket");

        copier(buffer);

        printf("Here is the message: %s\n", buffer);
        strcpy(reply, "I got this message: ");
        strcat(reply, buffer);
        n = write(newsockfd, reply, strlen(reply));
        if (n < 0) error("ERROR writing to socket");
    }
    close(newsockfd);
    close(sockfd);
    return 0;
}

First, we are going to compile this example statically and without stripping the compiled code:

$ clang server.c -static -o server
$ r2 server
## We can check if the binary is stripped with this r2 command
> i~strip
strip false

On the other hand, we compiled statically this same example with name server2, and this time we also executed the strip command to delete information from this binary (symbols, etc.):

$ clang server.c -static -o server2
$ strip server2
$ r2 server2

We can check if the binary is stripped with this r2 command

> i~strip
$ strip true

We can see that the symbols are stripped when loading the stripped binary with r2

$ r2 server2
> aa
> afl

0x00401a30    1 47           entry0
0x004020c0  101 1835         fcn.004020c0

Now, we create the “zignatures” from our static and unstripped binary (server). After that, we will apply this “zignatures” over the stripped binary (server2).

We create “zignature” file with command ‘zg’ and zos.

$ r2 server
> aa
> zg            # Generate zignatures from the binary
  ...
  ...
  ...
  generated zignatures: 845
> zos zigs.z    # Save the zignatures to a file zigs.z

It’s time to open our stripped binary and watch which information is available. Load stripped binary with auto analysis:

$ r2 server2
> aa
> zo zigs.z     # Load zigs.z as a zignatures file
> z/            # Scan the binary in search for flags that correspond to the found zignatures

To show the zignatures loaded use the ‘z’ command.

> z
Loaded 4702 signatures
  4702 byte signatures
  0 head signatures
  0 func signatures

Now, we are able to identify the stripped binary from the generated flags. For example, let’s see if we can identify the copier() method

> f~sym
  0x00401000 27 sign.bytes.sym._IO_list_resetlock_0
  0x004010b0 16 sign.bytes.sym.__assert_fail_base.cold.0_0
  0x00401000 27 sign.bytes.sym._init_0
  0x004010c5 500 sign.bytes.sym.abort_0
  0x004012e7 333 sign.bytes.sym._IO_new_fclose.cold.0_0
  0x00401434 246 sign.bytes.sym._IO_fputs.cold.0_0
  0x00401580 16 sign.bytes.sym.__cxa_atexit_0
  0x00401a70 33 sign.bytes.sym.deregister_tm_clones_0
> f~copier
  0x00401b60 127 sign.bytes.sym.copier_0
...
...
...
> pd 10 @ sign.bytes.sym.copier_0
Free fake stack
            ;-- sign.bytes.sym.copier_0:
            0x00401b60      55             push rbp
            0x00401b61      4889e5         mov rbp, rsp
            0x00401b64      4881ec200400.  sub rsp, 0x420
            0x00401b6b      64488b042528.  mov rax, qword fs:[0x28]    ; [0x28:8]=0 ; '('
            0x00401b74      488945f8       mov qword [rbp - 8], rax
            0x00401b78      4889bde8fbff.  mov qword [rbp - 0x418], rdi
            0x00401b7f      488bb5e8fbff.  mov rsi, qword [rbp - 0x418]
            0x00401b86      488dbdf0fbff.  lea rdi, [rbp - 0x410]

We can trace the execution of the code and identify that the loaded zignature xrefs in the Main function.

In this post we have seen how zignatures can help us to make the analysis of stripped binaries easier.