CSAPP实验之BOMB

    CSAPP的课程网站上提供了各个实验的材料下载,第2个实验是一个二进制炸弹实验,材料中有一个二进制的可执行文件,需要用户提供一些输入,如果输入不对就会引发炸弹爆炸,程序终止,破解失败,而要输入正确就得去分析程序的二进制代码。

    借助系统提供的二进制分析工具objdump,可以使用objdump –d bomb得到程序的.text的反汇编代码,如果对Linux链接机制了解的话是对理解这个汇编更有帮助的,CSAPP的第7章讲程序的链接讲得很好。

    摘取main函数的一部分如下:

   
   
   
   
080489b0 <main>: 8 0489b0 : 55 push %ebp 8 0489b1 : 89 e5 mov %esp,%ebp 8 0489b3 : 83 ec 14 sub $0x14,%esp 8 0489b6 : 53 push %ebx 8 0489b7 : 8b 45 08 mov 0x8(%ebp),%eax ; %eax = argc 8 0489ba : 8b 5d 0c mov 0xc(%ebp),%ebx ; %ebx = argv 8 0489bd : 83 f8 01 cmp $0x1,%eax ; if (argc == 1) 8 0489c0 : 75 0e jne 8 0489d0 <main+0x20> 8 0489c2 : a1 48 b6 04 08 mov 0x804b648,%eax ; %eax = stdin 8 0489c7 : a3 64 b6 04 08 mov %eax,0x804b664 ; infile = stdin 8 0489cc : eb 62 jmp 8 048a30 <main+0x80> 8 0489ce : 89 f6 mov %esi,%esi 8 0489d0 : 83 f8 02 cmp $0x2,%eax ; else if (argc == 2) 8 0489d3 : 75 3b jne 8 048a10 <main+0x60> 8 0489d5 : 83 c4 f8 add $0xfffffff8,%esp 8 0489d8 : 68 20 96 04 08 push $0x8049620 ; openmode 8 0489dd : 8b 43 04 mov 0x4(%ebx),%eax ; argv[1] 8 0489e0 : 50 push %eax 8 0489e1 : e8 9a fe ff ff call 8048880 <fopen@plt> ; fopen(argv[1], openmode) 8 0489e6 : a3 64 b6 04 08 mov %eax,0x804b664 ; infile = return value 8 0489eb : 83 c4 10 add $0x10,%esp 8 0489ee : 85 c0 test %eax,%eax 8 0489f0 : 75 3e jne 8 048a30 <main+0x80> ; if (infile == NULL) 8 0489f2 : 83 c4 fc add $0xfffffffc,%esp 8 0489f5 : 8b 43 04 mov 0x4(%ebx),%eax ; argv[1] 8 0489f8 : 50 push %eax 8 0489f9 : 8b 03 mov (%ebx),%eax ; argv[0] 8 0489fb : 50 push %eax 8 0489fc : 68 22 96 04 08 push $0x8049622 ; fmt string addr 8 048a01 : e8 0a fe ff ff call 8048810 <printf@plt>

mov %eax, 0x804b664 中有个地址值0x804b664,可以通过检查symbol table看是否有这一项:

   
   
   
   
[gallant@localhost bomb]$objdump -t bomb | grep 8 04b664 0804b664 g O .bss 00000004 infile

有这一项,叫infile,属于.bss段,那就是没有初始化的全局变量,这地方是把寄存器eax的内容存放到变量infile中,前一个语句为 mov 0x804b648, %eax ,通过同样的方法检查symbol table 知道这个变量是stdin@GLIBC,也即是标准输入设备stdin。push $0x8049620,他应该对应于fopen调用时传递的第2个参数,我们知道第2个参数就是打开模式的字符串,传递的应该是这个字串的首地址,我们怎么找到这个地址应该在数据区字符串常量呢?我的办法比较暴力,objdump –D是将整个二进制文件反汇编而不仅仅是可执行的.text区域:

   
   
   
   
[gallant@localhost bomb]$objdump -D bomb | grep 804962 8 0489d8 : 68 20 96 04 08 push $0x8049620 8 0489fc : 68 22 96 04 08 push $0x8049622 8049620 : 72 00 jb 8049622 <_IO_stdin_used+0x1e> 8049622 : 25 73 3a 20 45 and $0x45203a73,%eax 8049627 : 72 72 jb 8 04969b <_IO_stdin_used+0x97> 8049629 : 6f outsl % ds: (%esi),(%dx) 8 04962a : 72 3a jb 8049666 <_IO_stdin_used+0x62> 8 04962c : 20 43 6f and %al,0x6f(%ebx) 8 04962f : 75 6c jne 8 04969d <_IO_stdin_used+0x99>

我们不用去管后面的汇编指令,因为我们知道这应该数据区数据而不是指令代码,找到从地址0x8049620开始的字符串:72 00,遇到00应该结束,C语言中00是字符串的结束标志,72这个ASCII码对应的是字符是r,可能通过以下程序来得到对应的字符,所以fopen是以只读方式打开文件

int main() {
  int ascii_code;
  while (scanf("%x", &ascii_code) != EOF) {
  	printf("%c", (char)(ascii_code & 127));
  }

  exit(0);
}

这个方法比较笨但还是可行的,如果有好的方法请不吝赐教。整个程序这样分析下去,比较难懂的一段是后面是phase_6函数中操纵数据node1到node6时,通过

   
   
   
   
8 048e30 : 8b 76 08 mov 0x8(%esi),%esi ; %esi = %esi->next

及每个node首地址偏移8个字节处的一个字的值应该是一个地址值,并且正好是另一个node的地址,那么这就应该是是一个单链表形式的结构,还有一段是fun7中操纵n1,n21,n22,……一些结点时递归时用偏移4及偏移8处的值,再通过观察各个数据首地址处偏移4及偏移8处应该是两个地址值,并且都是正好指向另外的一个数据,这应该就是一个二叉树的结点结构,通过手工画出来验证完全是这样的,并且还是一颗二叉搜索树。

    花上不少的时间,整个程序的逻辑应该就能够清晰了,这里是我通过逆向得到的C语言代码,当然代码肯定不唯一,但逻辑清楚了,至于循环到底是用while,还是do……while,或者是for,大家各凭喜好吧^_^

#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
#include <signal.h>
#include <unistd.h>

FILE *infile;   /* &infile : 0x804b664 */
int num_input_strings = 0; /* &num_input_strings = 0x804b480 */
char input_strings[20][80]; /* 1600 = 20 * 80 */

struct linknode_t { /* linked list node type */
  int data;
  int index;
  struct linknode_t *next;
};

struct btnode_t { /* binary tree node type */
  int data;
  struct btnode_t *left;
  struct btnode_t *right;
};

void initialize_bomb();
char * read_line();
void phase_1(char *lineptr);
void phase_2(char *lineptr);
void phase_3(char *lineptr);
void phase_4(char *lineptr);
void phase_5(char *lineptr);
void phase_6(char *lineptr);
void phase_defused();
void explode_bomb();
void read_six_numbers(char *buf, int *num);
int string_length(char *s);
int strings_not_equal(char *s1, char *s2);

struct linknode_t
    node6 = {0x1b0, 6, NULL},
    node5 = {0xd4,  5, &node6},
    node4 = {0x3e5, 4, &node5},
    node3 = {0x12d, 3, &node4},
    node2 = {0x2d5, 2, &node3},
    node1 = {0xfd,  1, &node2};

struct btnode_t
    n48 = {0x3e9, NULL, NULL},
    n46 = {0x2f,  NULL, NULL},
    n43 = {0x14,  NULL, NULL},
    n42 = {0x07,  NULL, NULL},
    n44 = {0x23,  NULL, NULL},
    n47 = {0x63,  NULL, NULL},
    n41 = {0x01,  NULL, NULL},
    n45 = {0x28,  NULL, NULL},
    n34 = {0x6b,  &n47, &n48},
    n31 = {0x06,  &n41, &n42},
    n33 = {0x2d,  &n45, &n46},
    n32 = {0x16,  &n43, &n44},
    n22 = {0x32,  &n33, &n34},
    n21 = {0x08,  &n31, &n32},
    n1  = {0x24,  &n21, &n22};


int main(int argc, char **argv) {
  char *lineptr;

  if (argc == 1) {
  	infile = stdin;
  }
  else if (argc == 2) {
  	infile = fopen(argv[1], "r");
  	if (infile == NULL) {
      printf("%s: Error: Couldn't open %s\n", argv[0], argv[1]);
      exit(8);
    }
  }
  else {
  	printf("Usage: %s [<input_file>]\n", argv[0]);
  	exit(8);
  }

  initialize_bomb();

  printf("Welcome to my fiendish little bomb. You have 6 phases with\n");
  printf("which to blow yourself up. Have a nice day!\n");

  lineptr = read_line();
  phase_1(lineptr);
  phase_defused();
  printf("Phase 1 defused. How about the next one?\n");

  lineptr = read_line();
  phase_2(lineptr);
  phase_defused();
  printf("That's number 2.  Keep going!\n");

  lineptr = read_line();
  phase_3(lineptr);
  phase_defused();
  printf("Halfway there!\n");

  lineptr = read_line();
  phase_4(lineptr);
  phase_defused();
  printf("So you got that one.  Try this one.\n");

  lineptr = read_line();
  phase_5(lineptr);
  phase_defused();
  printf("Good work!  On to the next...\n");

  lineptr = read_line();
  phase_6(lineptr);
  phase_defused();

  return 0;
}

void phase_1(char *lineptr) {
  if (strings_not_equal(lineptr, "Public speaking is very easy.") != 0) {
    explode_bomb();
  }
}

void phase_2(char *lineptr) {
  int numbers[6];
  int i;

  read_six_numbers(lineptr, numbers);

  if (numbers[0] != 1)
    explode_bomb();

  i = 1;
  do { /* 1 2 6 24 120 720 */
    if (numbers[i - 1] * (i + 1) != numbers[i])
      explode_bomb();
    i++;
  }while (i <= 5);
}

void phase_3(char *lineptr) {
  int m;
  char ch, ct;
  int n;
  if (sscanf(lineptr, "%d %c %d", &n, &ch, &m) < 3)
    explode_bomb();
  switch (n) {
    case 0:
      ct = 0x71; /* q */
      if (m != 0x309) /* 777 */
        explode_bomb();
      break;
    case 1:
      ct = 0x62; /* b */
      if (m != 0xd6) /* 214 */
        explode_bomb();
      break;
    case 2:
      ct = 0x62;
      if (m != 0x2f3) /* 755 */
        explode_bomb();
      break;
    case 3:
      ct = 0x6b;  /* k*/
      if (m != 0xfb) /* 251 */
        explode_bomb();
      break;
    case 4:
      ct = 0x6f;  /* o */
      if (m != 0xa0) /* 160 */
        explode_bomb();
      break;
    case 5:
      ct = 0x74;  /* t */
      if (m != 0x1ca) /* 458 */
        explode_bomb();
      break;
    case 6:
      ct = 0x76;  /* v */
      if (m != 0x30c) /* 780 */
        explode_bomb();
    case 7:
      ct = 0x62;  /* b */
      if (m != 0x20c) /* 524 */
        explode_bomb();
      break;
    default:
      ct = 0x78;  /* x */
      explode_bomb();
  }
  if (ch != ct)
    explode_bomb();
}

int func4(int n) { /* fibonacci */
  if (n > 1) {
    return func4(n - 1) + func4(n - 2);
  }
  return 1;
}

void phase_4(char *lineptr) {
  int m;
  if (sscanf(lineptr, "%d", &m) != 1 || m <= 0) {
    explode_bomb();
  }
  if (func4(m) != 55) /* m != 9 */
    explode_bomb();
}

void phase_5(char *lineptr) {
  static char array[16] = {'i', 's', 'r', 'v', 'e', 'a', 'w', 'h', 
                      'o', 'b', 'p', 'n', 'u', 't', 'f', 'g'};
  char str[8];
  int i;

  if (string_length(lineptr) != 6)
    explode_bomb();

  for (i = 0; i <= 5; i++) {
    str[i] = array[ lineptr[i] & 0xf ];
  }

  str[6] = '\0';
  if (strings_not_equal(str, "giants")) /* 0xf, 0x0, 0x5, 0xb, 0xd, 0x1 */
    explode_bomb();
  /* 0x61~0x70: 0x6f(o), 0x70(p), 0x65(e), 0x6b(k), 0x6d(m), 0x71(q) 
   * opekmq
   */
}

void phase_6(char *lineptr) {
  int numbers[6];
  struct linknode_t *pnode[6], *q;
  int i, k;

  q = &node1;
  read_six_numbers(lineptr, numbers);

  i = 0;
  do {
    if (numbers[i] - 1 > 5u)
      explode_bomb();
    for (k = i + 1; k <= 5; k++) {
      if (numbers[i] == numbers[k])
        explode_bomb();
    }
    i++;
  } while (i <= 5);

  i = 0;
  do {
    q = &node1;
    k = 1;
    while (k < numbers[i]) {
      q = q->next;
      k++;
    }
    pnode[i] = q;
    i++;
  } while (i <= 5);

  q = pnode[0];
  i = 1;
  do {
    q->next = pnode[i];
    q = pnode[i];
    i++;
  } while (i <= 5);
  q->next = NULL;

  i = 0;
  q = pnode[0];
  do {
    if (q->data < q->next->data)
      explode_bomb();
    q = q->next;
    i++;
  } while (i <= 4);
}

int fun7(struct btnode_t *r, int y) {
  if (r == NULL)
	return -1;
  if (y < r->data) {
    return 2 * fun7(r->left, y);
  }
  else if (y > r->data) {
    return 2 * fun7(r->right, y) + 1;
  }
  else /* y == r->data */
    return 0;
}

void secret_phase() {
  char *lineptr;
  int k;

  lineptr = read_line();
  /* strol(nptr, endptr, base)
   * __strol_internal(nptr, endptr, base, _NL_CURRENT_LOCAL)
   * __strol_internal(lineptr, NULL, 10, 0);
   */
  k = (int)strtol(lineptr, NULL, 10);
  if (k - 1 > 1000u) {
	explode_bomb();
  }
  if (fun7(&n1, k) != 7) {
    explode_bomb();
  }
  printf("Wow! You've defused the secret stage!\n");
  phase_defused();
}

void sig_handler() {
  printf("So you think you can stop the bomb with ctrl-c, do you?\n");
  sleep(3);
  printf("Well...");
  fflush(stdout);
  sleep(1);
  printf("OK. :-)\n");
  exit(16);
}

void invalid_phase(){
}

void read_six_numbers(char *buf, int *num) {
  if (sscanf(buf, "%d %d %d %d %d %d", &num[0], &num[1], 
      &num[2], &num[3], &num[4], &num[5]) < 6) {
    explode_bomb();
  }
}

int string_length(char *str) {
  char *pchar = str;
  int slen = 0;
  while (*pchar++) {
    slen++;
  }

  return slen;
}

int strings_not_equal(char *s1, char *s2) {
  int slen1 = string_length(s1);
  int slen2 = string_length(s2);
  char *pchar1 = s1, *pchar2 = s2;

  if (slen1 != slen2)
    return 1;

  while (*pchar1) {
    if (*pchar1++ != *pchar2++)
      return 1;
  }

  return 0;
}

void initialize_bomb() {
  signal(SIGINT, sig_handler);
}

int blank_line(char *lineptr) {
  char *pchar = lineptr;

  while (*pchar != '\0') {
    if (!isspace(*pchar++)) {
      return 0;
    }
  }

  return 1;
}

char * skip() {
  char *lineptr;
  do {
    lineptr = fgets(input_strings[num_input_strings], 80, infile);
    if (lineptr == NULL || !blank_line(lineptr)) 
      break;
  } while (1);

  return lineptr;
}

char * read_line() {
  char *lineptr;
  int i;

  lineptr = skip();
  if (lineptr == NULL) {
  	if (infile != stdin) {
      if (getenv("GRADE_BOMB") != NULL) {
      	exit(0);
      }
      infile = stdin;
      lineptr = skip();
    }
    if (lineptr == NULL) {
      printf("Error: Premature EOF on stdin\n");
      explode_bomb();
    }
  }
  for (i = 0; lineptr[i] != '\0'; i++);
  if (i == 79) {
    printf("Error: Input line too long\n");
    explode_bomb();
  }
  input_strings[num_input_strings][i - 1] = '\0'; /* delete \n */
  lineptr = input_strings[num_input_strings];
  num_input_strings++;

  return lineptr;
}

void explode_bomb() {
  printf("\nBOOM!!!\n");
  printf("The bomb has blown up.\n");
  exit(8);
}

void phase_defused() {
  char line[80];
  int k;
  if (num_input_strings == 6) {
	if (sscanf(input_strings[3], "%d %s", &k, line) == 2) {
	  if (!strings_not_equal(line, "austinpowers")) {
		printf("Curses, you've found the secret phase!\n");
		printf("But finding it and solving it are quite different...\n");
		secret_phase();
	  }
	}
	printf("Congratulations! You've defused the bomb!\n");
  }
}

你可能感兴趣的:(CSAPP实验之BOMB)