|
楼主 |
发表于 2011-5-13 23:17:18
|
显示全部楼层
FreePhoneLine and Tom – A Truly Deep Reverse Engineering Adventure! September 1, 2010 by izaakschroeder
NOTE: If you haven’t read the previous post related to this subject, you need to do so before you can make sense of any of this. This article is long and complicated and not for the faint of heart. It is, however, terribly fun!

So I was minding my own business, as usual, until I received this email:
Dear Mr Schroeder!
My name is Tom Shao from Canada. I was excited that you can reverse Engineering Java and obtain the secret password. Your instruction in the weblog is very detailed. However I don’t have eclipse installed on my desktop. I just wonder if you can do me a favor to help me get my password. My information is as follows:
[Omitted for obvious privacy reasons]
Any help would be much appreciated.
Thanks,
Tom
Now normally I don’t go off and just do work for people asking for stuff, given it’s supposed to be a learning experience; not a “hurr hurr give me free stuff” experience. However, only a few hours later I receive ANOTHER email!
I downloaded Eclipse and tried to debug it with no lock [sic] as the class KC is different with yours. I assume they updated the new version and fixed the problem.
Can you send me your Freephoneline software? I am desperate to need your help?
Thanks,
Tom
So you at least get some points for trying. As it turns out, they did indeed update their software, but it certainly wasn’t to “fix” that problem; in fact if anything they turned their program from a reasonable softphone client to a horrendously ugly (like I’ve taken shits that look better kind of ugly), slow, ad-filled piece of garbage.

The reason you can’t exactly follow my previous post is that the obfuscation process randomizes class names, methods and so on; every time they release a new version you will end up with a different naming scheme. The process of finding your credentials works the exact same way in the new version, introducing a manual break in the program and hunting through the class variables until you find the password.
But all is not lost for you Tom! I will help you redeem yourself! I have provided a handy comparison chart for the rest of our readers:

Thanks to you Tom, (but mostly me), other people now too can have free calls with little effort! This blog post covers something a bit more interesting and useful than what was discussed previously. It deals with discovering the FreePhoneLine authentication protocol itself and how to emulate it; it also exposes a possible security hole in that particular protocol.

Shall we find out?
Let’s start with what we know. You provide your FreePhoneLine username and password, which gets sent to the server and they send you your SIP username and password back; all of this is encrypted and therefore one can’t simply WireShark the information off the network.
We want to be able to achieve two things:
- Be able to construct the appropriate request to get our encrypted username/password
- Be able to decrypt the response so that we may use our SIP username/password
We’ll start things off the same way we did the previous article, breaking the program and finding out which class holds the clear-text password, and from there which class actually decrypts the password to its clear-text form. I’m not going to bore you with old hat; setup the project the same way as before and use the same techniques achieve the following result:

We’ve discovered that the class called “f” contains the connection information; particularly of interest are fields “e” and “g” which hold the SIP username and password respectively. We’re interested in finding out what modifies those variables; since they are public variables, (denoted by the green circle), they can be modified by any class; so we can use a feature in Eclipse very similar to a breakpoint called a watchpoint!
Watchpoints monitor variables; they will pause the execution of the program when a variable is read from, written to or both. Those familiar with x86 debuggers will note a similar feat can be achieved with hardware breakpoints. To set a watchpoint, find the class in the package viewer and select “Toggle watchpoint”.

Close the program if it’s being run and start debugging it again. The program will suspend immediately and drop you back into the debugger. What’s happened? The password isn’t being loaded in so soon obviously, as we have not even logged in yet. The answer is disappointingly simple; the variable is simply being initialized to null, (i.e. the program is writing null into the variable). We can just resume the program. But wait! It pauses again! What’s going on this time? Again, the answer is disappointingly simple. The variable is being set to the empty string “”. Remember! Null is not the same as the empty string! The empty string is still a string object, the null string is nothing. You can see this by hitting the “Step over” button and you can watch “g” change from null to the empty string.

Anyway, we can go back to running the program and you should be able to hit up the login box and enter your credentials before the program breaks you back into the debugger again. You should then end up with something like the following:

This looks promising! You’ll note if you look in the variables view that the field “g” still does not have your password. That means if we hit “step over” your password will suddenly appear! That’s because something within “wo.c” (remember, “c” is a method of the class “wo”) is saying “g = SIPPassword”. That’s the whole purpose of a watchpoint. So we basically want to understand what goes on inside “wo.c” that is setting our password.

But how on Earth do we begin to do that when we don’t have any source code to work with? Well, we just make source code! Using a tool called a decompiler we can turn a Java “.class” file back into a “.java” file! Pretty impressive! Not all decompilers were created equal, and a lot of semantic information is lost in the decompilation process, but the result is still extremely useful!
I have chosen to use a Java decompiler called JD. (Original name, eh?) You can get your copy here. Extract “JD-GUI”, and run it. Open the “wo.class” file with it.

And then find the “c(ActionEvent)” method within it. There’s a lot of code in that class, so I won’t bother pasting it all. I will, however, highlight a few parts that tell us we’re probably going in the right direction!
1 | String str1 = this.a.getText(); |
2 | String str2 = String.valueOf(this.i.getPassword()); |
See that “getPassword()” there? That’s probably getting the password you entered, and the “getText()” above it is probably fetching your username! And as we scroll down that code chunk:
2 | this.l.g = dc.a(zc.f(), this.l.e); //THIS LINE TRIGGERED OUR WATCHPOINT! l.g IS ASSIGNED HERE! |
And once you see it visually the correlation becomes even more obvious:

We can make an educated guess that “zc.d” fetches the username, “zc.f” fetches the encrypted password and “dc.a” handles the decryption! And once everything is done the login windows calls “setVisible(false)” to hide itself from view. Things are coming together nicely! In JD simply click on the underlined “dc” class and we can start checking out the “dc.a” function.
1 | import javax.crypto.Cipher; |
2 | import javax.crypto.spec.IvParameterSpec; |
3 | import javax.crypto.spec.SecretKeySpec; |
When you see things like that at the top the file, you just know you’re in the right spot; they’re using standard ol’ Java cryptography APIs! That will make things even easier on us because all those APIs are very well documented. We can check out the rest of the file and we realize that there are a few functions called “a”. The one that’s being called takes two arguments.
1 | this.l.g = dc.a( //returns a string because l.g is a string, obvious from the debugger |
2 | zc.f(), //we don't know this type; we could check the return type of zc.f though |
3 | this.l.e //this is a string type, obvious from the debugger |
But since this function, “dc.a” is called from “wo.c”, we know that whatever particular “a” is being called also needs to be a public method! This narrows it down to just one function:
1 | public static String a(byte[] paramArrayOfByte, String paramString) |
Which we observe basically only does one important thing:
1 | byte[] arrayOfByte = a(false, paramArrayOfByte, paramString); |
2 | return new String(arrayOfByte).trim(); |
It calls ANOTHER a and returns its value in string form! That particular “a” is easily identifiable as being the only one which takes a boolean argument initially.
1 | private static byte[] a(boolean paramBoolean, byte[] paramArrayOfByte, String paramString) |
4 | byte[] arrayOfByte1 = a(paramString); |
5 | SecretKeySpec localSecretKeySpec = new SecretKeySpec(arrayOfByte1, z[1]); |
6 | Cipher localCipher = Cipher.getInstance(z[2]); |
7 | IvParameterSpec localIvParameterSpec = new IvParameterSpec(z[0].getBytes()); |
Is obviously the most interesting one; it actually makes use of cryptographic functions! A quick trip to their API documentation pages reveals the purpose of their arguments.
Now we know:
01 | private static byte[] a(boolean paramBoolean, byte[] paramArrayOfByte, String paramString) |
04 | byte[] arrayOfByte1 = a(paramString); |
05 | //First argument is the decryption/encryption key, second argument is the algorithm being used |
06 | SecretKeySpec localSecretKeySpec = new SecretKeySpec(arrayOfByte1, z[1]); |
08 | //Only argument is the algorithm being used |
09 | Cipher localCipher = Cipher.getInstance(z[2]); |
11 | //Only argument is the data for the initialization vector |
12 | IvParameterSpec localIvParameterSpec = new IvParameterSpec(z[0].getBytes()); |
We are, however, lacking all values of “z”; these values are important as they’ll clearly tell us both the algorithm used for decryption and the initialization vector. They are not present in the decompiled code, so we’ll have to get them from the debugger. We also note “z” is a static class variable, and so we should be able to pick out said values any time. Given we’re interested how the decryption function works, we should breakpoint that and then we can start to kill two birds with one stone!

We can also now disable the watchpoint on “f.g” as we’ve found the method of interest. Restart the process and wait till you hit the breakpoint from logging in. (You’ll hit the breakpoint once when the program starts, but I have yet to figure out why).

Expressions let us inspect arbitrary parts of the program. It’s time to add one so we can find out the value of that mysterious “z”! Right click in the expressions pane and select “Add Watch Expression…”

And then enter the value “dc.z” as that is the variable we are interested in.

And then watch the magic!

This tells us some very important things! They’re just using plain old AES to encrypt their data and what the initialization vector is they use to encrypt it! So that’s one bird. Now for the other; we want to see how the function is actually used and what values are fed into it. If you go back to the variables tab you’ll see what the arguments to the function are. But things are not as they seem…

What on earth is the web username doing there?
2 | zc.f(), //we assume this returns encrypted data |
3 | this.l.e //this is the SIP username, NOT the web username!! So what gives? |
Well clearly “dc.a” is being called from somewhere other than just that line above or we’ve really messed things up; but to be safe we’ll hit “Continue” in the debugger and see if we can reach a point where “arg2″ is the value we expect, the SIP username (i.e. phone number).

Our patience is rewarded! Let’s go back and see how this plays out in our function.
01 | private static byte[] a(boolean paramBoolean, byte[] paramArrayOfByte, String paramString) |
05 | //So this basically calls a("16042834338") and sets arrayOfByte1 to its value |
06 | byte[] arrayOfByte1 = a(paramString); |
08 | //This creates a new key using the output of the previous function for an "AES" cipher |
09 | SecretKeySpec localSecretKeySpec = new SecretKeySpec(arrayOfByte1, z[1]); |
11 | //Creates an "AES/CBC/NoPadding" cipher instance |
12 | Cipher localCipher = Cipher.getInstance(z[2]); |
14 | //Creates an initialization vector using the string "fedcba9876543210" |
15 | IvParameterSpec localIvParameterSpec = new IvParameterSpec(z[0].getBytes()); |
The next part appears dependent on the boolean parameter. We need to know a little more about the “Cipher” object before we can determine what this function does. A quick trip to the API reference shows the first parameter determines the ciphers operating mode (encryption vs decryption for example), with the other arguments being self explanatory: the encryption key and the initialization vector. “arrayOfByte2″ appears to be the data which is either encrypted or decrypted.
04 | if (paramBoolean) //false in our case |
06 | //initialize to encryption mode |
07 | localCipher.init(1, localSecretKeySpec, localIvParameterSpec); |
09 | //set the data to be encrypted via "md.a" using the third parameter of this function |
10 | arrayOfByte2 = md.a(paramArrayOfByte); |
15 | //initialize to decryption mode |
16 | localCipher.init(2, localSecretKeySpec, localIvParameterSpec); |
20 | //data to be processed is just the input |
21 | arrayOfByte2 = paramArrayOfByte; |
23 | //perform the encryption/decryption of arrayOfByte2 |
24 | byte[] arrayOfByte3 = localCipher.doFinal(arrayOfByte2); |
26 | //return the processed data |
The “i” variable is a little confusing and it may be a side-affect of the decompiler not doing something right; from here on out we have to start making some educated guesses. We know the processing key is ALWAYS altered by some function, (ironically ALSO called “a”), we know one function does both encryption and decryption, we know that when the SIP username is involved as part of the key argument this function operates in decryption mode. It is likely safe to assume that the second parameter is therefore the encrypted SIP password.
I’m going to briefly force you to go back in time, to when the web username was present instead of the SIP username. Look at the first parameter; it’s true instead of false! This means that your web username is being used to encrypt something! This might be useful knowledge for later!
We’re getting really close now! We basically need to understand what the key modification function does.
1 | byte[] arrayOfByte1 = a(paramString); |
You will find its basic purpose is thus:
1 | ec localec = new ec(paramString); |
2 | byte[] arrayOfByte = md.b(localec.a().substring(0, 16)); |
We’re now involving another TWO new classes! Egads! So we need to understand “ec” as well as the function “md.b”. Let’s deal with “ec” first by opening it up in the decompiler. The constructor is nothing interesting, it just saves the given key to a local variable. We’re interested in the “ec.a” function, which does nothing really but call “ec.b”. So let’s look at that.
It seems to involve message digests and another missing variable!
1 | localMessageDigest = MessageDigest.getInstance(z[1]); |
Not even looking I imagine just like how “AES” was selected as the crypto algorithm, “z[1]” represents the choice of message digest algorithm. What is “z[1]“? Remember how we found “dc.z”? You can use exactly the same trick to find “ec.z”. Add a method breakpoint to “b”, add a watch expression for “ec.z”, disable all your other breakpoints and restart the debugger. You’ll have your z in no time!

So we now know that MD5 is somehow involved in transforming the given crypto key. Not like MD5 and AES has ever been used before…

1 | arrayOfByte = localMessageDigest.digest(this.a.getBytes()); |
Seems to perform the actual MD5 on the crypto key. We then observe what happens to the variable “arrayOfByte”. We see a loop and observe a lot of “Integer.toHexString(arrayOfByte);” calls. The educated guess says that the returned MD5 hash is raw binary data and they are turning it into its hex form. However, we should attempt to verify this. We note at the very end:
They use b to store the result of the transform. We can simply add a watchpoint to “ec.b” and we’ll be able to test!

We know “ec.a” stores the plaintext value, so we just disable all other breakpoints, restart the program and compare! Remember, when the breakpoint hits, use “Step Over” so the value actually gets assigned. The first breakpoint you hit will likely be “b” being initialized to “null”, but the second… well that should be just magical.

And now we just check to make sure our hypothesis is correct:
1 | echo -n "16042834338" | md5sum |
2 | b904785204cd684ff3965bc263220fc6 |
And it is! Fantastic! We now know “ec” is basically an MD5 hashing class. We go back to the line of code dealing with the transform and note that it appears to take only the resultant first 16 characters of the hex form of the MD5 hash and then pass those 16 characters into “md.b”.
1 | md.b(localec.a().substring(0, 16)); |
So once again open up the decompiler and find the “md.b” that takes a single string as its argument. It seems to involve a bunch of checks, but its main purpose seems to revolve around the line:
1 | arrayOfByte = (byte)paramString.charAt(i); |
It essentially seems to just convert a string into an array of bytes. Why they weren’t using String.toBytes() who knows. But to say the least that function doesn’t seem to bastardize anything about the crypto key.
With all this information it is hard to think that we are not done yet. We still don’t know how to get the encrypted password and we still need to test our ability to decrypt that password. We know the encrypted password is sent to us over the air, so we can at least start there. Disable all your breakpoints, restart the program and fire up WireShark.
NOTE: For your own good, turn off your torrents, streaming music and other things which obsessively fire packets off over your network. It will make your life MUCH easier trying to sift through things in WireShark.

With as little delay as possible start capturing packets and log into FreePhoneLine. As soon as you see yourself log in stop capturing packets and start to browse through the dump looking for things related to FreePhoneLine. One quickly finds that the authentication happens over HTTP and that the typical response looks something like:

One can make an educated guess that the encrypted password is base-64 encoded, simply by seeing the “==” on the end and knowing that encrypted data almost always falls outside the printable ASCII range.
So let’s build a program to decrypt these passwords! I’m going to use (yea haters gonna hate) PHP because it comes with cryptography functions built in and is fast to prototype things with.
03 | function new_freephoneline_crypto_context($Key) { |
04 | $Key = substr(md5($Key), 0, 16); //The first 16 characters of the hex represntation of the MD5 of the key |
05 | $IV = 'fedcba9876543210'; //The IV they use |
06 | if ($CryptoModule = mcrypt_module_open('rijndael-128', '', 'cbc', '')) { //Open the cipher (AES _is_ Rijindael) |
07 | mcrypt_generic_init($CryptoModule, $Key, $IV); //Initialize |
08 | return $CryptoModule; //Return |
10 | die('Initializing mcrypt failed!'); |
14 | function delete_freephoneline_crypto_context($CryptoModule) { |
15 | mcrypt_generic_deinit($CryptoModule); |
16 | mcrypt_module_close($CryptoModule); |
19 | $SIPUsername = '16042834338'; |
20 | $EncryptedSIPPassword = base64_decode('WhubSHQz2nqFNK/N0xGMvw=='); |
22 | $Context = new_freephoneline_crypto_context($SIPUsername); |
23 | $DecryptedPassword = mdecrypt_generic($Context, $EncryptedSIPPassword); |
24 | delete_freephoneline_crypto_context($Context); |
26 | $Output = 'Username is: '.$SIPUsername.', and password is: '.$DecryptedPassword; |
Funny thing is, it works. You can now successfully take any of their encrypted SIP passwords and derive their plaintext ones. But we’re not done yet. So far we still need to use WireShark to get at those encrypted passwords. We want a truly automated process. We need to know how FreePhoneLine requests that information as well. You can easily find it in WireShark.

Le sigh. It looks like they encrypt the web password going out as well, and we’re going to have to mimic that. But wait… don’t you remember that one time we saw the debugger stop and use our web username as a key for ENCRYPTION? It’s starting to come together now isn’t it? We can make another educated guess in that it simply encrypts our web password with our web username in the exact same manner it decrypts the SIP one. We can write another program to test this!
This is also a bit of a crapshoot, as I have no idea how “key” is generated, or even if its necessary. But after some testing it turned out that not providing “key” doesn’t make any difference.
03 | $WebUsername = 'izaak.schroeder@gmail.com'; |
04 | $WebPassword = 'lolfreephoneline'; |
06 | $Context = new_freephoneline_crypto_context($WebUsername); |
07 | $EncryptedWebPassword = mcrypt_generic($Context, $WebPassword); |
08 | delete_freephoneline_crypto_context($Context); |
11 | curl_setopt($CURLHandle, CURLOPT_RETURNTRANSFER, true); |
12 | curl_setopt($CURLHandle, CURLOPT_HEADER, false); |
13 | curl_setopt($CURLHandle, CURLOPT_POST, true); |
14 | curl_setopt($CURLHandle, CURLOPT_POSTFIELDS, '' |
15 | .'web_username='.urlencode($WebUsername).'&' |
16 | .'web_password='.urlencode(base64_encode($EncryptedWebPassword)).'&' |
21 | foreach(explode("\r\n", curl_exec($CURLHandle)) as $Line) |
22 | if (false !== $Target = strpos($Line, '=')) |
23 | $Data[trim(substr($Line,0,$Target))] = trim(substr($Line, $Target+1)); |
24 | curl_close($CURLHandle); |
25 | echo 'SIP username: '.$Data['sip_username'].', encrypted SIP password: '.$Data['sip_password']; |
27 | die('Initializing CURL failed!'); |
Oh look, it works! We have effectively and nearly completely (save for whatever “key” does) emulated the FreePhoneLine authentication protocol. Combining those two scripts might leave you with something like:
03 | //This file is called ffd.php |
05 | $Output = 'Nothing interesting to show here Capt\'n'; |
07 | function new_freephoneline_crypto_context($Key) { |
08 | $Key = substr(md5($Key), 0, 16); |
09 | $IV = 'fedcba9876543210'; |
10 | if ($CryptoModule = mcrypt_module_open('rijndael-128', '', 'cbc', '')) { |
11 | mcrypt_generic_init($CryptoModule, $Key, $IV); |
14 | die('Initializing mcrypt failed!'); |
18 | function delete_freephoneline_crypto_context($CryptoModule) { |
19 | mcrypt_generic_deinit($CryptoModule); |
20 | mcrypt_module_close($CryptoModule); |
23 | if (isset($_REQUEST['u']) && isset($_REQUEST['p'])) { |
25 | $WebUsername = $_REQUEST['u']; $WebPassword = $_REQUEST['p']; |
27 | $Context = new_freephoneline_crypto_context($WebUsername); |
28 | $EncryptedWebPassword = mcrypt_generic($Context, $WebPassword); |
29 | delete_freephoneline_crypto_context($Context); |
32 | curl_setopt($CURLHandle, CURLOPT_RETURNTRANSFER, true); |
33 | curl_setopt($CURLHandle, CURLOPT_HEADER, false); |
34 | curl_setopt($CURLHandle, CURLOPT_POST, true); |
35 | curl_setopt($CURLHandle, CURLOPT_POSTFIELDS, '' |
36 | .'web_username='.urlencode($WebUsername).'&' |
37 | .'web_password='.urlencode(base64_encode($EncryptedWebPassword)).'&' |
42 | foreach(explode("\r\n", curl_exec($CURLHandle)) as $Line) |
43 | if (false !== $Target = strpos($Line, '=')) |
44 | $Data[trim(substr($Line,0,$Target))] = trim(substr($Line, $Target+1)); |
45 | curl_close($CURLHandle); |
47 | if (!isset($Data['sip_username']) || !isset($Data['sip_password'])) { |
48 | $Output = 'Error getting credentials; perhaps wrong username or password?)'; |
50 | $SIPUsername = $Data['sip_username']; |
51 | $EncryptedSIPPassword = base64_decode($Data['sip_password']); |
53 | $Context = new_freephoneline_crypto_context($SIPUsername); |
54 | $DecryptedPassword = mdecrypt_generic($Context, $EncryptedSIPPassword); |
55 | delete_freephoneline_crypto_context($Context); |
57 | $Output = 'Username is: '.$SIPUsername.', and password is: '.$DecryptedPassword; |
60 | die('Initializing CURL failed!'); |
65 | <title>Freephoneline SIP Credentials Decryptor</title> |
68 | <form method="post" action="ffd.php"> |
70 | <legend>FreePhoneLine Login Information</legend> |
72 | <label>Username:</label> |
73 | <input style="width: 200px;" type="text" name="u" placeholder="username" value="<?php echo isset($_REQUEST['u']) ? $_REQUEST['u'] : '' ; ?>"/> |
76 | <label>Password:</label> |
77 | <input style="width: 200px;" type="password" name="p" placeholder="password" value="<?php echo isset($_REQUEST['p']) ? $_REQUEST['p'] : ''; ?>"/> |
80 | <div><input type="submit" value="Get My SIP Info"/></div> |
82 | <hr/> <?php echo $Output; ?> |
After such a long journey seeing something like:

is immensely satisfying, isn’t it Tom?
And what about that security hole I mentioned? Well thing is, they encrypt your web password with your web username… both of which are sent in plaintext over the wire. You can already see how dumb of an idea this is. If someone is using FreePhoneLine on your network and you have WireShark going and you get their encrypted web password and their login… guess what! You have their decrypted web password too!
Anyway, hope you’ve enjoyed this little adventure. And remember kids, only you can prevent bad designs from being used!
Until next time!
9 Votes
Filed under Uncategorized
About izaakschroeder
I sell cheese and cheese accessories.
Like
Be the first to like this post.
12 Responses to FreePhoneLine and Tom – A Truly Deep Reverse Engineering Adventure! -
Matt says:
November 6, 2010 at 7:26 pm
after work FPL call me said I didn’t buy SIP setting and they won’t allow me to use that function. how do they know I am using a ATA?
Reply
-
izaakschroeder says:
November 6, 2010 at 7:31 pm
I imagine they keep a record of all phone numbers which have had SIP settings purchased for; I then imagine they check that against the User-Agent SIP header or something similar to see if you’re using their client or another (e.g. Asterisk). I’m sure it’s maskable if one was to look at the protocol more in-depth.
Cheers,
Reply
-
Jimmy T says:
November 15, 2010 at 12:46 pm
Howdy!
My question is very simple. I followed the last post on the original version, compared to this one. I got lost, then managed to follow along again. My question is, how can I build the php code to work, as you posted it there?
I am not a code wizard, but understand the principle. I just wanna use the code now, but have nothing to build or test it with, that works–I tried using firefox, IE, and even things like http://forumferney.free.fr/stester.html. How can I get the script to work, or a compiled executable version for win32?
Thanks big papa
Reply
-
Jimmy T says:
November 15, 2010 at 1:18 pm
P.S. The html form looks normal, but the fields are already prefilled. The username is filled with “”
the password is filled with hidden info, I bet it’s the same code format as above. Something wrong with splicing php and html forms?
Please let me know, what is wrong with me.
Merci
Reply
-
sb says:
January 31, 2011 at 10:05 am
Thanks a lot!
The PHP code is working perfect!!
Reply
-
MJ says:
February 19, 2011 at 10:57 pm
I don’t have a webserver running, so I tried a hosted server to run php. It looks like most servers don’t have the ‘mcrypt’ module installed. It throws me an error – “Call to undefined function mcrypt_module_open()”
Any way around it? Do anyone know of any php hosts with mcrypt installed?
Reply
-
izaakschroeder says:
February 20, 2011 at 9:06 pm
Hey hey,
CodeRun supports both mcrypt and curl: http://www.coderun.com/ide/. Naturally you’ll have to replace “ffd.php” with “index.php”, but other than that it should work.
Reply
-
mic says:
March 3, 2011 at 1:41 am
this is really awesome.
any help on masking so fpl cannot find out if we are using their client or asterisk / ata ?
thanks
-
izaakschroeder says:
March 4, 2011 at 4:14 pm
Glad you enjoyed it! Your question was mentioned previously, though to reiterate: I’m not entirely sure if it’s possible to make it so they can’t find out, but it’s simple enough to do things like change the SIP user agent header from Asterisk to the one used by their client. (Likewise matching up other configuration options such as codecs used to ones as close to their client as you can).
|
|