Hacking away at Messages (iMessage iChat)

I got curious the other day if Apple Messages was hackable. In short, yes it is, but not without some headache. You must have a “bridge” to allow you to relay the message, but for people like me who have a mac that is always on this isn’t an issue, here is how the hack works..

Use our bridge.scpt The whole purpose of this applescript is to take the incoming message and send it to perl (you know something useful)

using terms from application "Messages"
	on message received the_message from the_buddy for the_chat
		set qMessage to quoted form of the_message
		set qHandle to handle of the_buddy
		set qName to first name of the_buddy
		#do shell script "echo " & qName & "handle " & qHandle &  ": " & qMessage & " > /tmp/out.txt"
		do shell script "echo /usr/bin/perl /Users/john/messages_bridge/messages_bridge.pl " & qName & " " & qHandle & " " & qMessage & ">/tmp/out"
		do shell script "/usr/bin/perl /Users/john/messages_bridge/messages_bridge.pl " & "'" & qName & "'" & " " & qHandle & " " & qMessage
	end message received
end using terms from

Here is our very simple perl script.

#!/usr/bin/perl
use JSON;
use HTTP::Request;
use LWP::UserAgent;
 
$key = "pKTjY29WWy";
 
 
$name = $ARGV[0];
$handle = $ARGV[1];
$message = $ARGV[2];
$command = "rec";
 
%message = ("name", $name,
         "handle", $handle,
         "message", $message,
	 "key",$key,
         "command", $command);
 
my $json = encode_json \%message;
 
my $uri = 'http://www.yourdomain.com/chat/api.php';
my $req = HTTP::Request->new('POST', $uri);
$req->header( 'Content-Type' => 'application/json' );
$req->content( $json );
my $lwp = LWP::UserAgent->new;
$lwp->request( $req );

The script converts all the plain-text variables to json for easy sending to the web server, keep in mind there is NO encryption on this, so what is api.php on the webserver?

<?php
$connect = mysql_connect("localhost","youruser","yourpassword") or die (mysql_error());
$db = mysql_select_db("imessage",$connect);
 
$postdata = file_get_contents("php://input");
$json = json_decode($postdata);
$message = $json->{'message'};
$name = $json->{'name'};
$handle = $json->{'handle'};
$key = $json->{'key'};
 
$sql = "INSERT into messages (userkey,name,handle,message,rec) values ('$key','$name','$handle','$message',now())";
$result = mysql_query($sql,$connect);
 
 
?>

simply said, it stores all the messages into the messages table just looking to be read!

How can we send a message? Well unfortunately the only way I have figured out is doing polling from the machine running the messages bridge. here is a quick sample.

#!/usr/bin/perl
use JSON;
use HTTP::Request;
use LWP::UserAgent;
 
$key = "pKTjY29WWy";
$command = "get";
 
 
%message = ("key",$key,
         "command", $command);
 
my $json = encode_json \%message;
 
while(1) {
 
my $uri = 'http://www.yourdomain.com/chat/getmessages.php';
my $req = HTTP::Request->new('POST', $uri);
$req->header( 'Content-Type' => 'application/json' );
$req->content( $json );
my $lwp = LWP::UserAgent->new;
$response = $lwp->request( $req );
 
 
if ($response->decoded_content ne "") {
	my $decoded_json = decode_json($response->decoded_content);
	print $decoded_json->{'message'};
 
	`osascript send_message.scpt "$decoded_json->{'to_user'}" "$decoded_json->{'message'}"`;
}
print "Sleeping\n";
sleep 5;
}
on run argv
	set theHandle to item 1 of argv
	set theMessage to item 2 of argv
	tell application "Messages"
		set theService to first service whose service type is iMessage
		set theBuddy to first buddy that its service is theService and handle is theHandle
		send theMessage to theBuddy
	end tell
end run

The above two scripts the perl script runs preferrably in the background and connects to the outbound queue of the webserver, to get waiting messages, then runs the applescript to actually send them, hacky at best, but sorta works.

What does the getmessages.php file look like? well I am so glad you asked…

<?php
$connect = mysql_connect("localhost","username","password") or die (mysql_error());
$db = mysql_select_db("imessage",$connect);
 
$postdata = file_get_contents("php://input");
$json = json_decode($postdata);
$key = $json->{'key'};
 
 
$sql = "SELECT * from outbound where userkey=\"$key\" and seen=0";
$result = mysql_query($sql,$connect);
while ($row = mysql_fetch_array($result)) {
	$id = $row[4];
	error_log($id);
	$line = array("to_user"=>$row[1],"message"=>$row[2]);
	echo json_encode($line);
	$sql = "UPDATE outbound set seen = 1 where id=\"$id\"";
	$r = mysql_query($sql,$connect);
}
 
?>

there you go, now you have all the scripts required to send/receive messages on a non Apple device, but how can you make imessage function??
You MUST setup Messages to run an applescript when a message comes into you.

Open up Messages preferences
Screen Shot 2013-09-25 at 8.26.13 AM

Go to the Alerts Tab.

Use the built in apple script to automatically accept new messages.
Screen Shot 2013-09-25 at 8.26.30 AM

Screen Shot 2013-09-25 at 8.26.50 AM

Next go to Message received
Screen Shot 2013-09-25 at 8.27.02 AM

Screen Shot 2013-09-25 at 8.27.11 AM

Screen Shot 2013-09-25 at 8.27.19 AM

Select our bridge.scpt file:
Screen Shot 2013-09-25 at 8.27.31 AM

I built a quick sample. If you read the source code above you see a key, this key is used when your Apple Messages sends to your webserver. Here is my sample code and a few pictures.

index.php

<html>
<head>
<title>iMessage Chat!</title>
</head>
<body>
<form method="get" action="generate.php">
	<table border="0">
		<tr>
			<td><input type="submit" value="Generate message ID"></td>
		</tr>
	</table>
</form>
<hr />
<form method="get" action="chat.php">
	<table border="0">
		<tr>
			<td>Message ID:</td><td><input type="text" name="id"></td>
		</tr>
		<tr>
			<td colspan="2"><input type="submit" value="Chat Now!"></td>
		</tr>
	</table>
</form>
 
 
</form>
</body>
</html>

generate.php used for generating the user key, probably not the most secure, but “works”

<?php
$connect = mysql_connect("localhost","username","password") or die (mysql_error());
$db = mysql_select_db("imessage",$connect);
$found = true;
$string = "";
 
while ($found) {
	$string = generateRandomString();
	$sql = "SELECT count(*) from users where user_key=\"$string\"";
	$result = mysql_query($sql,$connect);
	$row = mysql_fetch_row($result);
	if ($row[0] == 0) {
		$found = false;	
	}
 
}
$sql = "INSERT into users (user_key) values (\"$string\")";
$result = mysql_query($sql,$connect);
echo "Your Key is: $string";
function generateRandomString($length = 10) {
    $characters = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
    $randomString = '';
    for ($i = 0; $i < $length; $i++) {
        $randomString .= $characters[rand(0, strlen($characters) - 1)];
    }
    return $randomString;
}
?>

chat.php for the conversation (one at a time convo)

<html>
<head>
<title>Chatting</title>
<script type="text/javascript">
var last_epoch = 0;
</script>
<script type="text/javascript" src="jquery-1.10.2.min.js"></script>
<script type="text/javascript">
function callAjax() {
	$.get('checkmessages.php?key=<?php echo $id?>&last_check='+last_epoch, function( data ) {
		$( ".chat" ).append( data );
	});
	last_epoch = new Date() / 1000;
}
setInterval(callAjax,2000);
callAjax();
function sendMessage() {
	var message =document.getElementById('message').value;
        $.get('sendmessage.php?key=<?php echo $id?>&message='+message, function( data ) {
		$( ".chat" ).append( "You: " + message +"<br />"); 
        });	
	document.getElementById('message').value="";
 
 
}
</script>
</head>
<body>
 
<div style="margin:0 auto; width:800px; height:95%; overflow:hidden">
<div class="chat" id="chat" style="border: 1px solid red; width:800px; position:absolute; bottom:50px; top:0px; overflow:auto">
 
 
</div>
</div>
<table border="0" width="100%">
<tr>
	<td width="10">Message</td>
	<td><input type=text" id="message" name="message" size=100> <input type="button" value="Send" onClick="sendMessage()"></td>
</tr>
</table>
</body>
</html>

checkmessages.php actually checks for new messages.

<?php
$start = $_GET['last_check'];
$key = $_GET['key'];
$ts = time($start);
$start = date("Y-m-d H:i:s");
error_log($start);
$connect = mysql_connect("localhost","username","password") or die (mysql_error());
$db = mysql_select_db("imessage",$connect);
 
$sql = "SELECT * from messages where userkey=\"$key\" and seen = 0";
$result = mysql_query($sql,$connect);
while ($row = mysql_fetch_array($result)) {
	$name = $row[1];
	$handle = $row[2];
	$message = $row[3];
	$id = $row[6];
	if (strstr($name,"missing value")) {
		echo "$handle: $message<br />\n";
	} else {
		echo "$name ($handle): $message<br />\n";
	}
	error_log("$name ($handle): $message");
	$sql = "UPDATE messages set seen=1 where id=\"$id\"";
	$r = mysql_query($sql,$connect);
 
}
?>

Here is a sample of how it looks in firefox:
Screen Shot 2013-09-25 at 8.48.58 AM

I had a grandious vision of creating an Messages to android bridge, but alas winter is coming, and that means beer brewing time, and less time to code! Let me know if you came up with anything with the help or inspiration of the above code!!

John
“Hack the planet”

One thought on “Hacking away at Messages (iMessage iChat)

  1. baz

    HI.. great solution 🙂
    Im not sure if apple have invoked some form of security but I can’t send messages with the script to a new buddy. If I have previously had a chat with them in Messages, even if the conversation has been deleted, it works.
    If I try to send a message to a New Handle it fails with :

    Messages got an error: Can’t get buddy 1 whose service = service id \….

    Can the script be made to send a message to a new contact number?
    Thanks

    Reply

Leave a Reply

Your email address will not be published. Required fields are marked *