Sunday 22 September 2013

CSAW CTF Quals 2013 - Reverse 400 - keygenme

Task:

keygenme - 400 Points

Solved by 100 teams.

nc 128.238.66.219 14549
keygenme32.elf
We are given a x86 ELF executable. It takes 3 arguments(username, token1, token2) and prints whether they are good(*<:-)) or not. ( :-( ). In this task we need to write a keygen that will communicate with judges server, which sends username to keygen, and then send them valid token pair for each username.

After testing different inputs it is clear that username should be more than 15 bytes long and you should be very lucky to guess the tokens. Let's reverse this binary.

At the beginning, it parses arguments: first one(username) is passed to the std::string constructor, second and third are converted using strtoul function with base argument equals to zero, that allows us to provide tokens in hex (0x1234) or octal (0123) or decimal (-123) forms.
Then copy and reversed copy of the username string are made. After that cpu class is allocated on the heap and quite long digit string is passed to the constructor with username and reversed username. Then Execute method is called and after that two values T6 and T7 are being compared to the slightly modified tokens in the check funciton. If check succeeds we will see nice smile "*<:-)" otherwise ":-(" is printed.

There is nice feature in this program. After you pass a username to the program even though you will provide wrong tokens it will store in memory valid T6 and T7 values after completion of cpu::Execute method because it depends on username only. So you can provide some username and arbitrary tokens, then watch for T6 and T7 values in debugger and calculate valid token pair, then you can test tokens to ensure that you have calculated them successfully.

To automatize this process you can use ptrace system call. All you have to do is to write a program that does the following:
1) It gets username from judges server.
2) forks and executes ptrace syscall to trace child and executes keygenme32.elf file with username and two arbitrary numbers as tokens.
3) then stops in check function before the comparison of the valid pair to your input by putting a breakpoint at a valid place (e.g. 0x0804A2A5).
4) gets valid pair from registers or stack and calculates valid token pair.
5) sends valid token pair to the judges server.
6) gets response and if there is no flag go to the step number 1.

You can write all by yourself, but there are a lot of examples in the net. I've used a small but very nice debugging framework from here and modified it a bit to accept more arguments that I needed and added communication with judges server. Since one token can be calculated from a compared by xoring with 0x31333337 and another just has the bytes shuffled, we can easily invert those processes to get valid tokens. After I wrote this scipt I confronted an issue that perfectly working tokens were not accepted by the server. I noticed that server rejects negative values and hex values, even though task binary accepts them so I had to send unsigned long values which solved the issue.

Source code:
/* Code sample: Use debuglib for setting breakpoints in a child process.
**
** Eli Bendersky (http://eli.thegreenplace.net)
** This code is in the public domain.
*/
#include <stdio.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <syscall.h>
#include <sys/ptrace.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/reg.h>
#include <sys/user.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h> 

#include "debuglib.h"

long get_at_ebp_offset(pid_t child_pid, void* ebp, int offset)
{
 return ptrace(PTRACE_PEEKTEXT,child_pid, ebp + offset, 0);
}

void run_debugger(pid_t child_pid, long* o_token1, long* o_token2)
{
    long need_addr = 0x0804a2a5;
    procmsg("debugger started\n");
    /* Wait for child to stop on its first instruction */
    wait(0);
    procmsg("child now at EIP = 0x%08x\n", get_child_eip(child_pid));

    /* Create breakpoint and run to it*/
    debug_breakpoint* bp = create_breakpoint(child_pid, (void*)need_addr);
    procmsg("breakpoint created\n");
    ptrace(PTRACE_CONT, child_pid, 0, 0);
    wait(0);

    /* Loop as long as the child didn't exit */
    while (1) {
        /* The child is stopped at a breakpoint here. Resume its
        ** execution until it either exits or hits the
        ** breakpoint again.
        */
  long eip = get_child_eip(child_pid);
  if (eip = need_addr)
  {
   struct user_regs_struct regs;
   long eax,ebx,ecx,ebp;
   int i = 0;
   int count = 4;
   procmsg("child stopped at breakpoint. EIP = 0x%08X\n", get_child_eip(child_pid));
   ptrace(PTRACE_GETREGS, child_pid, 0, &regs);
   procmsg("Registers:\n eax = 0x%08x[%ld]\n ebx = 0x%08x[%ld]\n ecx = 0x%08x[%ld]\n ebp = 0x%08x\n",regs.eax,regs.eax,regs.ebx,regs.ebx,regs.ecx,regs.ecx,regs.ebp);
   procmsg("Stack:\n");
   
   long ebp_m0c = ptrace(PTRACE_PEEKTEXT,child_pid, (void*)(regs.ebp - 0xc),0);
   long ebp_m04 = ptrace(PTRACE_PEEKTEXT,child_pid, (void*)(regs.ebp - 0x4),0);
   long ebp_p08 = ptrace(PTRACE_PEEKTEXT,child_pid, (void*)(regs.ebp + 0x8),0);
   long ebp_p0c = ptrace(PTRACE_PEEKTEXT,child_pid, (void*)(regs.ebp + 0xc),0);
   printf("[ebp-0xc] - %08x - %08x[%ld] \n", (unsigned int) (regs.ebp - 0xc),ebp_m0c,ebp_m0c);
   printf("[ebp-0x4] - %08x - %08x[%ld] \n", (unsigned int) (regs.ebp - 0x4),ebp_m04,ebp_m04);
   printf("[ebp+0x8] - %08x - %08x[%ld] \n", (unsigned int) (regs.ebp + 0x8),ebp_p08,ebp_p08);
   printf("[ebp+0xc] - %08x - %08x[%ld] \n", (unsigned int) (regs.ebp + 0xc),ebp_p0c,ebp_p0c);
   long last = ebp_p0c & 0xff;
   long first = (ebp_p0c & 0x0000ff00) << 16;
   long token1 = ebp_p08 ^ 0x31333337;
   long token2 = ((ebp_p0c >> 8) & 0x00ffff00) | first | last;
   printf("Your tokens: %08x %08x \n", token1,token2);
   *o_token1 = token1;
   *o_token2 = token2;
   
   procmsg("resuming\n");
  }
        int rc = resume_from_breakpoint(child_pid, bp);

        if (rc == 0) {
            procmsg("child exited\n");
            break;
        }
        else if (rc == 1) {
            continue;
        }
        else {
            procmsg("unexpected: %d\n", rc);
            break;
        }
    }

    cleanup_breakpoint(bp);
}

char* getusername(int* fdsck)
{
 int sockfd, portno, n;

 struct sockaddr_in serv_addr;
 struct hostent *server;

 char buffer[256];
 portno = 14549;
 sockfd = socket(AF_INET, SOCK_STREAM, 0);
 if (sockfd < 0) 
  error("ERROR opening socket");
 *fdsck = sockfd;
 server = gethostbyname("128.238.66.219");
 if (server == NULL) {
  fprintf(stderr,"ERROR, no such host\n");
  exit(0);
 }
 bzero((char *) &serv_addr, sizeof(serv_addr));
 serv_addr.sin_family = AF_INET;
 bcopy((char *)server->h_addr, 
   (char *)&serv_addr.sin_addr.s_addr,
   server->h_length);
 serv_addr.sin_port = htons(portno);
 if (connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr)) < 0) 
  error("ERROR connecting");
 printf("Connected\n");
 bzero(buffer,256);
 n = read(sockfd,buffer,58);
 puts("Got:\n");
 buffer[n] = 0;
 puts(buffer);
 n = read(sockfd,buffer,25);
 buffer[n] = 0;
 printf("%s\n",buffer);
 n = read(sockfd,buffer,32);
 buffer[n] = 0;
 printf("Username: %s\n",buffer);
 char* str = (char*)malloc(strlen(buffer) + 1);
 strcpy(str,buffer);
 return str;
}
int send_tokens(int sockfd,long token1, long token2,char* username)
{
 char buffer[1024];
 int i = 0,n;
 int good = 0;
 sprintf(buffer,"%lu %lu\r\n",token1,token2);
 printf("Sending: %s",buffer);
 n = write(sockfd,buffer, strlen(buffer));
 for (i = 0; i < 3; i++)
 {
  n = read(sockfd,buffer,1024);
  buffer[n] = 0;
  printf(" %d got: '%s'\n",i,buffer);

  if(strncmp(":-)",buffer,3) != 0)
  {
   good++;
  }
  if (strlen(buffer) > 27)
  {
   break;
  }
 }
 if (good == 0)
 {
  return -1;
 }
 memcpy(username,&buffer[26],32);
 username[32] = '\0';
 printf("New username:'%s'",username);
 return 0;
}

int main(int argc, char** argv)
{
 pid_t child_pid;
 int sockfd;
 char* username = getusername(&sockfd);
 int i = 0;
 while(i < 10)
 {
  char* temp_args[] = {"trash","keygenme32.elf",username,"1","1",NULL};
  printf("Username:%s\n",username);
  printf("Uname length:%d\n",strlen(username));
  printf("temp_args[2]:%s\n",temp_args[2]);
  long token1;
  long token2;
  child_pid = fork();
  if (child_pid == 0)
   run_target(temp_args);
  else if (child_pid > 0)
   run_debugger(child_pid,&token1, &token2);
  else {
   perror("fork");
   return -1;
  }
  if (send_tokens(sockfd,token1,token2,username) == -1)
  {
   break;
  }
  i++;
 }
 free(username);
 close(sockfd);

    return 0;
}
Proof:






No comments:

Post a Comment