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"); } }