Level 07

About

Reverse engineering level with library re-usage.

OptionSetting
Vulnerability TypeStack
Position Independent ExecutableYes
Read only relocationsNo
Non-Executable stackYes
Non-Executable heapYes
Address Space Layout RandomisationYes
Source FortificationYes

Source code

#include "../common/common.c"  

#include <pth.h>
#include <openssl/rsa.h>

#include "utlist.h"

struct ops {
  void (*register_cmd)(unsigned int opcode, unsigned int flags, void *(*fp)(void *));
  void (*unregister_cmd)(unsigned int opcode);
};

int parse_pak(unsigned char *pakaddr, size_t paklen, size_t base, struct ops *ops);

#define DB (572)

int udp;

struct pa {
  unsigned char *buf;
  ssize_t len;
  struct sockaddr_in sin;

  unsigned char *p;
  ssize_t remainder;
  
};

void free_pa(struct pa *pa)
{
  if(! pa) return;

  if(! pa->buf) {
    memset(pa->buf, 0, pa->len);
    free(pa->buf);
  }

  memset(pa, 0, sizeof(struct pa));
  free(pa);
}

typedef struct cmdtab {
  unsigned int opcode;
  unsigned int flags;
  void *(* fp)(void *);
  struct cmdtab *prev, *next;
} cmdtab;

cmdtab *cmdtab_head;

void *dispatch(void *arg)
{
  struct pa *p = (struct pa *)(arg);
  int *ip;
  cmdtab *c = NULL;
  
  if(p->len < sizeof(int)) goto bail;

  ip = (int *)(p->buf);

  p->p = p->buf + 4;
  p->remainder = p->len - 4;

  DL_FOREACH(cmdtab_head, c) {
    if(c->opcode == ip[0]) {
      c->fp(p);
      break;
    }
  }

bail:
  free_pa(p);
  return NULL;
}

void register_cmd(unsigned int opcode, unsigned int flags, void *(*fp)(void *))
{
  cmdtab *c;

  c = calloc(1, sizeof(cmdtab));
  c->opcode = opcode;
  c->flags = flags;
  c->fp = fp;

  DL_APPEND(cmdtab_head, c);
}

void unregister_cmd(unsigned int opcode)
{
  cmdtab *c, *tmp;

  DL_FOREACH_SAFE(cmdtab_head, c, tmp) {
    if(c->opcode == opcode) {
      DL_DELETE(cmdtab_head, c);
    }
  }
}


struct ops regops = {
  .register_cmd = register_cmd,
  .unregister_cmd = unregister_cmd
};

#define PAKFILE "/opt/fusion/res/level07.pak"

void load_and_parse_default_pak()
{
  void *m;
  int fd;
  struct stat statbuf;
  int status;
  unsigned int base;

  fd = open(PAKFILE, O_RDONLY);
  if(! fd) err(1, "Unable to open %s", PAKFILE);
  if(fstat(fd, &statbuf) == -1) err(1, "Unable to fstat %s", PAKFILE);

  m = mmap(NULL, statbuf.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
  if(m == MAP_FAILED) err(1, "Unable to mmap %s", PAKFILE);

  // printf("got %d bytes to process\n", statbuf.st_size);

  status = parse_pak(m, statbuf.st_size, 0, &regops);

  // printf("parse_pak result: %08x\n", status);

}

int download_pak_file(char *host, char *port, unsigned char *key, unsigned char **pakfile, size_t *pakfile_len)
{
  struct sockaddr_in sin;
  size_t blue;
  int ret;
  size_t alloc;
  int status;
  int keyidx;
  int keylen;
  int i;
  int fd;

  status = -1;

  keylen = strlen(key);
  keyidx = 0;

  memset(&sin, 0, sizeof(struct sockaddr_in));

  sin.sin_addr.s_addr = inet_addr(host);
  sin.sin_port = htons(atoi(port));
  sin.sin_family = AF_INET;

  *pakfile = NULL;
  *pakfile_len = 0;

  fd = socket(AF_INET, SOCK_STREAM, 0);
  if(fd == -1) return;
  if(pth_connect(fd, (void *)(&sin), sizeof(struct sockaddr_in)) == -1) goto closefd; 

  if(pth_read(fd, &alloc, sizeof(alloc)) != sizeof(alloc)) goto closefd;

  blue = 0;
  *pakfile = calloc(alloc, 1);
  if(*pakfile == NULL) goto closefd;
  *pakfile_len = alloc;

  while(alloc - blue) {
    ret = pth_read(fd, (*pakfile) + blue, alloc - blue);

    if(ret == -1) goto freemem;
    if(ret == 0) goto freemem;

    for(i = 0; i < ret; i++) {
      //printf("key byte is %02x/%c\n", key[keyidx], key[keyidx]);
      (*pakfile)[blue + i] ^= key[keyidx];
      keyidx = (keyidx + 1) % keylen;
    }

    blue += ret;
  }

  status = 0;
  goto closefd;

freemem:
  free(*pakfile);
  *pakfile = NULL;
  *pakfile_len = 0;
  
closefd:
  close(fd);
  return status;

}

void *load_new_pakfile(void *arg)
{
  struct pa *p = (struct pa *)(arg);
  unsigned char *q;
  unsigned char *host, *port, *key = NULL;
  unsigned char *pakfile;
  size_t pakfile_len;

  host = p->p;
  q = strchr(p->p, '|');
  if(! q) return NULL;
  *q++ = 0;
  port = q;
  q = strchr(q, '|');
  if(! q) return NULL;
  *q++;
  key = q;

  if(strlen(key) < 8) return NULL;

  // printf("key is '%s'\n", key);

  if(download_pak_file((char *)(host), (char *)(port), key, &pakfile, &pakfile_len) == 0) {
    parse_pak(pakfile, pakfile_len, 0, &regops);
    free(pakfile);
  }

  return NULL;
}

void *execute_command(void *arg)
{
  struct pa *p = (struct pa *)(arg);
  if(fork() != 0) {
    system(p->p);
  }
}

int main(int argc, char **argv, char **envp)
{
  background_process(NAME, UID, GID);  

  pth_init();

  udp = get_udp_server_socket(PORT);

  register_cmd(1347961165, 0, load_new_pakfile);
  register_cmd(2280059729, 0, execute_command);

  load_and_parse_default_pak();

  while(1) {
    struct pa *p;
    int l;

    p = calloc(sizeof(struct pa), 1);
    p->buf = calloc(DB, 1);
    l = sizeof(struct sockaddr_in);
    p->len = pth_recvfrom(udp, p->buf, DB, 0, (void *)(&p->sin), &l); 

    pth_spawn(PTH_ATTR_DEFAULT, dispatch, p);
  }  
}