So I was bored the other day, browsing HotOrNot and particularly intrigued and interested by their new feature, the live chat.
I was wondering how they implemented it, and if it would be possible to exploit a security bug to live chat with anyone. So of course, I started a little reversing session! ![]()
Contents:
- Decompiling the live chat application
- Understanding the user ID <-> emid/eid conversion
- User ID Converter
- Conclusion
Decompiling the live chat application
The first natural step was to look at the source of the live chat web page. As expected it was a flash application. Here is a snippet of the web page source:
-
<script type="text/javascript">
-
var livechat = new SWFObject("livechat_proxy.php", "livechat1", "650", "450", "7", "#e6e6e6");
-
livechat.addVariable("flashVarUser","xxxxxxx");
-
livechat.addVariable("flashVarPass","yyyyyyyyyyyyyyyy");
-
livechat.addVariable("flashVarChat","");
-
livechat.addVariable("flashVarBrowser","non-ie");
-
livechat.write("livechatdiv");
-
</script>
with "xxxxxxx" a number and "yyyyyyyyyyyyyyyy" a 16 letters password with only capital letters.
So, I downloaded the flash application using wget (by the way, when I first did it the file was called livechat.swf, livechat_proxy.php is nevertheless a flash application as well, just rename the file if you need to) and decompiled it with flare.
Following the variables flashVarPass and flashVarUser you quickly realize that they used a software from jivesoftware and they are in fact using a jabber protocol:
-
v1.NS = 'jabber:iq:auth';
-
v1.ELEMENT = 'query';
-
v2.addProperty('digest', v2.__get__digest, v2.__set__digest);
-
v2.addProperty('password', v2.__get__password, v2.__set__password);
-
v2.addProperty('resource', v2.__get__resource, v2.__set__resource);
-
v2.addProperty('username', v2.__get__username, v2.__set__username);
-
ASSetPropFlags(org.jivesoftware.xiff.data.auth.AuthExtension.prototype, null, 1);
We still follow the white rabbit and we end up with the server ip and port:
-
var connectScreen = new com.hotornot.ConnectScreen(_root, v3++);
-
connectScreen.showConnecting();
-
var v7 = _root.flashVarUser;
-
var v10 = _root.flashVarPass;
-
var chatWith = _root.flashVarChat;
-
var v9 = _root.flashVarBrowser;
-
var v14 = '66.151.156.175';
-
var connection = com.hotornot.Main.initializeConnection(v7, v10, v14);
(The port is the default jabber port 5222 as found somewhere else in the code).
If you quickly fire your favorite jabber client and copy/paste the values, you'll indeed see it works perfectly fine. And your contact list will contain only your double matches (as a bunch of numbers instead of their names).
Understanding the user ID <-> emid/eid conversion
Now of course, a couple questions to answer:
- Where does this flashVarUser number come from?
- Where does the password come from?
- Could we potentially crack it and spoof other people's identity?
First, I'll quickly answer the second and third question: turned out the password is the session ID that you can find in your cookie (key="session"). Therefore, it's unlikely that you would be able to spoof someone else identify by this mean.
Well, ok... It doesn't matter, my interest is still running. Let's try to see where this flasVarUser number is coming from.
Of course, you kind of expect this number to be your "client ID" in their database, or something like that. The first thing which comes to mind is the "emid" string that you see in the meetme url. For example, for this lovely lady: http://meetme.hotornot.com/r/?emid=OMRQKEH (if only she could double match me...
), her emid is "OMRQKEH".
My first thought was that they probably used a simple 26 digit-base number, using a simple formula such as (pseudocode):
-
for i = 0 to length of emid - 1 do
-
clientid = clientid * 26 + emid[i] - 'A'
-
end for
Or something like that...
But I glanced through my double matches and my contact list numbers in the jabber client and quickly realized that the length of the emid matches the length of the corresponding number... Ok, that's interesting, and probably the only thing we needed!
I made the following table to try to see any patterns:
-
A 7
-
B 5
-
E 6; 8
-
G 3
-
H 0
-
K 1
-
L 4
-
M 0
-
N 2
-
O 4
-
Q 3
-
R 5; 8
-
S 9; 2
-
U 6
-
Y 1
-
Z 9
-
8 7
But I couldn't see any, apart from the numbers being used more than once and the 8 being used in the emid. I was a little bit puzzled by how you can know whether, for example, a 4 should be encoded using an L or an O. I thought about its position in the emid or maybe its neighbour influencing its value to break repetitions or something like that.
The "breakthrough" came by rewriting the table as its inverse:
-
0 H; M
-
1 K; Y
-
2 N; S
-
3 G; Q
-
4 L; O
-
5 B; R
-
6 E; U
-
7 A; 8
-
8 E; R
-
9 S; Z
Written like that, the structure is quite apparent, and going again through the contact list numbers vs. their emid, we can easily infer the algorithm used to convert a number (user ID) to its emid:
-
for i = 0 to length of userID - 1 do:
-
if i is even
-
then take the first letter
-
otherwise takes the second one
The table above had a couple of errors (letter swapped), so here is the final table:
-
0 H; M
-
1 K; Y
-
2 N; S
-
3 G; Q
-
4 O; L
-
5 B; R
-
6 E; U
-
7 A; 8
-
8 R; E
-
9 S; Z
With the following javascript code as an example of the conversion:
-
var idmap = new Array(new Array("H", "K", "N", "G", "O", "B", "E", "A", "R", "S"),
-
new Array("M", "Y", "S", "Q", "L", "R", "U", "8", "E", "Z"));
-
-
function userid2eid(form)
-
{
-
// Quick sanity check
-
var expr = /^[0-9]+$/;
-
if(!form.userid.value.match(expr)) {
-
form.eid.value = "Bad input";
-
return;
-
}
-
-
var eid = "";
-
for (i = 0; i <form.userid.value.length; ++i) {
-
var c = form.userid.value.charAt(i) - '0';
-
eid = eid + idmap[i & 1][c];
-
}
-
-
form.eid.value = eid;
-
}
-
-
function eid2userid(form)
-
{
-
// Quick sanity check
-
for (i = 0; i <form.eid.value.length; ++i) {
-
if (idmap[i & 1].indexOf(form.eid.value.charAt(i)) == -1) {
-
form.userid.value = "Bad input";
-
return;
-
}
-
}
-
-
userid = "";
-
for (i = 0; i <form.eid.value.length; ++i) {
-
var n = idmap[i & 1].indexOf(form.eid.value.charAt(i));
-
userid += n;
-
}
-
-
form.userid.value = userid;
-
}
User ID Converter
For the sake of simplicity, here is a javascript form to convert an user ID to the corresponding emid/eid:
The same algorithm is applied for eid. You might wonder how I know that? Well, in the cookie again, you have a key called "user_id". Therefore you can easily double check that this user_id matches your converted rating eid (from the url of your profile).
Conclusion
So what can we do with all of that? Nothing much so far.
While looking further into the source code of the flash application I realized I could have find the conversion function right away. This makes sense since their jabber application is capable of retrieving the names and the photos of your contacts. The conversion code was almost implemented in the same way than I did above (but in flash of course):
-
v1.getSenderEmid = function (senderJid) {
-
var v5 = new Array(new Array('M', 'Y', 'S', 'Q', 'L', 'R', 'U', '8', 'E', 'Z'), new Array('H', 'K', 'N', 'G', 'O', 'B', 'E', 'A', 'R', 'S'));
-
var v2 = false;
-
var v3 = '';
-
var v4 = com.hotornot.Util.getSenderId(senderJid);
-
var v6 = v4.length;
-
var v1 = 0;
-
while (v1 <v6) {
-
if (v2) {
-
v3 += v5[0][v4.charAt(v1)];
-
} else {
-
v3 += v5[1][v4.charAt(v1)];
-
}
-
v2 = !v2;
-
v1 += 1;
-
}
-
return v3;
-
};
Oh well... It was more fun to reverse the algorithm by just looking at the patterns anyway
Furthermore, in the source code, we can also find the API Key. Of course I won't give it away. You can anyway get one for free at http://dev.hotornot.com/.
Knowing the conversion algorithm, and assuming they use an incremental user ID, I guess we could predict the next subscribing users and inferring the number of users in the database. But to tell the truth I don't really care, I was more interested by the thrill of reversing something again than the outcome. It reminds me the good old days... The endless IRC conversations about all kind of technical matters, the best way of doing global API hooking, getting ring0 access, the dirty little tricks about the PE Format, the packing and anti-debugging strategies, the CD sub-channels, the ECMA 168 and the likes... I miss these days; I miss those people.
digg
del.icio.us
Reddit
NewsVine
Man those HotOrNot guys c0de some hella baller scriptz!
hahaha cool!! we got a kick out of finding this post!
james
HOTorNOT
When I wrote this post I never thought James Hong himself will read it,
Thanks for stopping by
I’ve been connecting to HotOrNot’s chat server for months using Pidgin and the above method. I even thought of doing a custom plugin so that I don’t have to go and copy/paste the session ID from the HTML source.
These days I get a “Not authorised” error message a lot. I’ve made sure about my inputs. Can you confirm this problem and do you have any solutions?
thanks,
Francois
Sorry, I haven’t used HotOrNot’s chat for a very long time and I’m currently swamped with my Med’ school.
My guess is that they changed something, I’ll probably take a quick look during my vacation (3 more weeks and I’m finally in vacation!
), I’ll let you know if I find anything interesting.
Thx for stopping by