#!perl -w # # this script simulates the pc side of microsoft activesync # # it requires an established ppp connection, # it connects to a windows ce device using the activesync protocol, and executes a few commands # and optionally demonstrates a authentication bug in wm2005 ( fixed in AKU1 ) # # use strict; use Carp; use IO::Socket; use threads; use threads::shared; use Getopt::Long; use Dumpvalue; my $d= new Dumpvalue; my $try_skip_pin; my $g_pin; my $localip; my $remoteip; sub usage { return <<__EOF__ Usage: as.pl [-p PIN] -p specify device pin -n try to skip pin dialog ( wm2005 aku 1 ) -l use localhost __EOF__ } GetOptions( "p=s" => \$g_pin, "n" => \$try_skip_pin, "l" => sub { $localip=$remoteip="127.0.0.1"; }, ) or croak usage(); # server state: # localip # remoteip # wakeup # authenticated # sock5721 sub handle_5721 { my ($rapi)= @_; my $sock = new IO::Socket::INET ( LocalHost => $rapi->{localip}, LocalPort => '5721', Proto => 'tcp', Listen => 1, Reuse => 0, ) or croak "new sock $rapi->{localip}:5721 tcp : $! - $@\n"; printf("waiting for incoming 5721\n"); if ($rapi->{sock5721}= $sock->accept()) { $rapi->{sock5721}->autoflush(1); print "connection on 5721\n"; my $data; while ($rapi->{sock5721}->read($data, 4)) { printf("data on 5721 : %s\n", unpack("H*", $data)); } print "disconnect on 5721\n"; $rapi->{sock5721}->shutdown(2); } printf("end of 5721 listener\n"); } sub start_5721_server { my ($rapi)= @_; $rapi->{thread5721}= threads->new(\&handle_5721, $rapi) or croak "create 5721 thread: $!\n"; #$rapi->{thread5721}->detach; $rapi->{thread5679}= threads->new(\&send_wakeup_5679_packets, $rapi) or croak "create wakeup thread: $!\n"; #$rapi->{thread5679}->detach; } ################################################################## sub send_command { my ($conn, $cmd)= @_; printf("sending cmd %08lx\n", $cmd); $conn->print(pack("VV", 4, $cmd)) or croak "send_command $cmd: $!\n"; } sub read_reply { my ($conn)= @_; printf("reading reply\n"); my $lenpkt; $conn->read($lenpkt, 4) or croak "recv reply len: $!\n"; my $pkt; $conn->read($pkt, unpack("V", $lenpkt)) or croak "recv reply data: $!\n"; printf("PKT: %s\n", unpack("H*", $pkt)); } sub handle_conn990 { my ($rapi, $conn, $sock)= @_; printf("starting 990 conversation for conn %d\n", $conn->{id}); send_command($sock, 0x3d); # CeGetSystemInfo read_reply($sock); send_command($sock, 0x43); # CeGetVersionEx read_reply($sock); send_command($sock, 0x39); # CeGetStoreInformation read_reply($sock); send_command($sock, 0x48); # CeGlobalMemoryStatus read_reply($sock); send_command($sock, 0x08); # GetSystemMemoryDivision read_reply($sock); send_command($sock, 0x05); # GetPasswordActive read_reply($sock); send_command($sock, 0x01); # CeSyncTimeToPc read_reply($sock); $rapi->{done}= 1; printf("end 990 conversation\n"); } # connection dispatcher sub handle_990 { my ($rapi, $sock)= @_; printf("waiting for new 990 connections\n"); while (my $connsock= $sock->accept()) { $connsock->autoflush(1); print "connection on 990\n"; my $idpkt; $connsock->read($idpkt, 4) or croak "recv idpkt: $!\n"; printf("990 connection id : %s\n", unpack("H*", $idpkt)); my ($id)= unpack("V", $idpkt); my $conn= $rapi->{connections}{sprintf("%08lx", $id)}; printf("conn id =%d\n", $conn->{id}); # hack: running it directly handle_conn990($rapi, $conn, $connsock); # todo: figure out why this thread is never started. # probably because the id was set in another thread. $conn->{connthread}= threads->new(\&handle_conn990, $rapi, $conn, $connsock) or croak "create conn990 thread: $!\n"; #$conn->{connthread}->detach; } print("end 990 dispatcher - $!\n"); } ################################################################## sub reply_root990_1 { my ($rapi)= @_; printf("sending root-1\n"); $rapi->{root990}->print(pack("V", 1)) or croak "reply 990 : 1: $!\n"; } sub reply_root990_3 { my ($rapi)= @_; printf("sending root-3\n"); $rapi->{root990}->print(pack("V", 3)) or croak "reply 990 : 3: $!\n"; } sub reply_root990_7_asversion { my ($rapi)= @_; printf("sending root-7: asversion\n"); $rapi->{root990}->print(pack("VVVV", 7, 8, 4, 5)) or croak "reply 990 : 7: 4.5 $!\n"; } sub encode_password { my ($password, $pegid)= @_; my $unicode= pack("v*", unpack("C*", $password)); return $unicode ^ (pack("C", $pegid&0xff) x length($unicode)); } sub reply_root990_password { my ($rapi)= @_; printf("sending password\n"); my $encpw= encode_password($rapi->{password}, $rapi->{info}{pegid}); while (1) { $rapi->{root990}->print(pack("va*", length($encpw), $encpw)) or croak "send password: $!\n"; my $answerpkt; $rapi->{root990}->read($answerpkt, 2) or croak "recv pw answer: $!\n"; last if (length($answerpkt)==2 && unpack("v", $answerpkt)==1); } printf("authenticated\n"); $rapi->{wakeup}= 0; } # request new connection sub reply_root990_5 { my ($rapi)= @_; my $id= $rapi->{id}++; printf("send cmd 5 - alloc connection %d\n", $id); $rapi->{root990}->print(pack("VVV", 5, 4, $id)) or croak "reply 990 5: $!\n"; my %conn; share(%conn); $conn{id}= $id; $rapi->{connections}{sprintf("%08lx", $id)}= \%conn; $d->dumpValue(\%conn); } # a16 2e6772cc75d39fb70afaa81844bbbefe # V 05000000 # V 01000000 # V 09000000 # a* 50006f0063006b00650074005f0050004300 0000 l=2*(9+1) # V 0501c300 # V 110a0000 # V 05000000 # V 00000000 # V 00000000 # V 0f000000 # a* 506f636b65745043005353444b0000 l = 15 # V 05000000 # a* 504d333030 00 # V 02000000 # V 04000000 00000000 # V 05000000 00000000 # V 30f7b601 sub parse_info_packet { my $pkt= shift; my %info; my $rest; my $namelen; ( $info{guid}, # a16 $info{osmaj}, # V $info{osmin}, # V $namelen, # V $rest )= unpack("a16VVVa*", $pkt); $info{name}= pack 'C*', unpack("v*", substr($rest, 0, $namelen*2)); $rest= substr($rest, ($namelen+1)*2); ( $info{osversion}, # V $info{processor}, # V $info{syncflags}, # V $info{partner1}, # V $info{partner2}, # V $namelen, # V $rest, )= unpack("VVVVVVa*", $rest); $info{platforms}= [ split /\x00/, substr($rest, 0, $namelen-2) ]; $rest= substr($rest, $namelen); ( $namelen, $rest, ) = unpack("Va*", $rest); $info{oeminfo}= substr($rest, 0, $namelen); $rest= substr($rest, $namelen+1); ( $info{nrplatforms}, $info{spi0xe0_1}, $info{spi0xe0_2}, $info{pegid}, $rest, )= unpack("Va8a8Va*", $rest); printf("leftover: %s\n", unpack("H*", $rest)) if (defined $rest && length($rest)); $d->dumpValue(\%info); return \%info; } # connection setup sub handle_root990 { my ($rapi)= @_; my $sock = new IO::Socket::INET ( LocalHost => $rapi->{localip}, LocalPort => '990', Proto => 'tcp', Listen => 1, Reuse => 0, ) or croak "new sock $rapi->{localip}:990 tcp : $! - $@\n"; # handle first connection differently # printf("waiting for initial 990\n"); if ($rapi->{root990}= $sock->accept()) { $rapi->{root990}->autoflush(1); $rapi->{wakeup}= 0; $rapi->{dispatchthread}= threads->new(\&handle_990, $rapi, $sock) or croak "create dispatch thread: $!\n"; #$rapi->{dispatchthread}->detach; print "connection on 990\n"; while (1) { my $typepkt; if (!$rapi->{root990}->read($typepkt, 4)) { carp "recv typepkt: $!\n"; last; } my ($type)= unpack("V", $typepkt); printf("root990 - %d\n", $type); # sm 0 -> it expects 3 # cmd_3 -> ... it answers '6' -> it wants 7 # cmd_7+asversion -> ... it answers '4'+infopacket -> it wants password # cmd_1 -> ... it answers '2' # cmd_5+connid -> device connects to new 990 port if ($type==0) { # '5' is not allowed here. if ($try_skip_pin) { reply_root990_5($rapi); } else { reply_root990_3($rapi); } } elsif ($type==2) { reply_root990_3($rapi); } elsif ($type==6) { reply_root990_7_asversion($rapi); } elsif ($type==4) { my $lenpkt; $rapi->{root990}->read($lenpkt, 4) or croak "recv lenpkt: $!\n"; my $infolen= unpack("V", $lenpkt); printf("info packet length=%08lx\n", $infolen); my $infopkt; $rapi->{root990}->read($infopkt, $infolen) or croak "recv infopkt: $!\n"; printf("info: %s\n", unpack("H*", $infopkt)); $rapi->{info}= parse_info_packet($infopkt); if ($rapi->{info}{pegid}) { reply_root990_password($rapi); } reply_root990_5($rapi); } } print "disconnect on 990\n"; $rapi->{root990}->shutdown(2); } print("end initial 990 - $!\n"); } sub start_root990_server { my ($rapi)= @_; $rapi->{thread990}= threads->new(\&handle_root990, $rapi) or croak "create initial thread: $!\n"; #$rapi->{thread990}->detach; } sub send_wakeup_5679_packets { my ($rapi)= @_; $rapi->{wakeup}= 1; printf("sending wakeup packets\n"); my $sock = new IO::Socket::INET ( PeerHost => $rapi->{remoteip}, PeerPort => '5679', Proto => 'udp', Reuse => 0, ) or croak "new connect $rapi->{remoteip}:5679 udp : $! - $@\n"; while ($rapi->{wakeup}) { $sock->send("\x7f") or croak "send wakeup: $!\n"; sleep(1); } printf("stopping wakeup\n"); } my $rapi= { password=>'1234', remoteip=>$remoteip || '169.254.2.1', localip=>$localip || '169.254.2.2', id=>1, connections=>{}, defined($g_pin) ? (password=>$g_pin) : (), }; share($rapi->{connections}); start_5721_server($rapi); start_root990_server($rapi); while (!$rapi->{done}) { sleep(1); }