Why is my Android keyboard sending unencrypted data? And where?

I recently switched phones from a LG G4 to a Sony Xperia X Performance (isn't that a mouthful!), so I had a spare Android phone lying around. What better use for than to sniff some traffic!


First up is ai.type Free Emoji Keyboard, a free keyboard that has built in text-swiping features (Google Play), emoji, a calculator and much more. Android keyboards have the ability to read what you type, mostly just to improve text correction and prediction. This is an understandable permission but has the opportunity for misuse.

I loaded up Burp Suite, added the proxy setting to the phone and off I went. First request right off:

Holy leaking information batman! The app sent my email addresses, latitude, longitude, IP address and device info to their server over HTTP.

Now you may be thinking "well ads display in apps too, they must send juicy tracking information back too", so let's take a look.

Here's a request after opening up Don't Tap The White Tile:

GET /v2/config.json?u-id-key=82549130&u-key-ver=1&h-user-agent=Mozilla%2F5.0+%28Linux%3B+Android+6.0%3B+LG-H812+Build%2FMRA58K%3B+wv%29+AppleWebKit%2F537.36+%28KHTML%2C+like+Gecko%29+Version%2F4.0+Chrome%2F53.0.2785.124+Mobile+Safari%2F537.36&tz=-25200000&u-appbid=com.umonistudio.tile&mk-version=pr-SAND-ETFTH-20151009&u-appver= HTTP/1.1
User-Agent: Mozilla/5.0 (Linux; Android 6.0; LG-H812 Build/MRA58K; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/53.0.2785.124 Mobile Safari/537.36
If-Modified-Since: Thu, 29 Sep 2016 17:31:14 GMT
Host: config-ltvp.inmobi.com
Connection: close
Accept-Encoding: gzip

And the response is nothing spectacular:

HTTP/1.1 200 OK
Content-Type: application/json
Last-Modified: Mon, 03 Oct 2016 05:32:46 GMT
Vary: Accept-Encoding
Content-Length: 1867
Connection: keep-alive

    "common": {
        "ids": {
            "O1": true,
            "SO1": true,
            "IX": true,
            "SID": true,
            "LID": true,
            "UDID": true,
            "UM5": true,
            "IDA": true,
            "IDV": true,
            "FBA": true,
            "GID": true,
            "LTVID": true,
            "si": 60,
            "srt": 3,
            "shs": 50,
            "e": true,
            "sced": true,
            "scoe": true,
            "scvw": false,
            "ep": "https://sdkm.w.inmobi.com/user/e.asm",
            "mr": 3,
            "ri": 3,
            "wfd": 0,
            "cof": 0,
            "ao": false,
            "aoep": "https://sdkm.w.inmobi.com/user/e.asm",
            "aoi": 86400,
            "as": true,
            "assi": 180,
        "metric": {
            "url": "https://sdkm.w.inmobi.com/metrics/e.asm",
            "nri": 5,
            "max": 1000,
            "sf": 50,
            "dt": 10
        "endpoints": {
            "house": "http://a.ai.inmobi.com/v2/ad.html",
            "rules": "https://rules-ltvp.inmobi.com/v2/rules.json",
            "events": "https://e-ltvp.inmobi.com/storm/v1/event"
        "mdb": 1000,
        "elim": 100,
        "pint": 30,
        "mkey": 20,
        "mval": 50,
        "plim": 100,
        "mec": 5,
        "aidl": 100,
        "mr": 5,
        "at": {
            "session": false,
            "purchase": false,
        "ltvid": 0
    "IOS": {
    "AND": {


At least it's over HTTPS and just has non-personal identifiable information.

Here's another example call from Vertigo Racing via Flurry analytics:

Again, over SSL and just sending back phone data information.


Back to our target, I found that  the keyboard would instantly generate a HTTP request after I'm done typing. This sounds safe!

It sends a GET request to http://textreport.aitype.net with information that identifies what app was in use, date, and some weird long string, see below for the request.

GET /aggregationserver/agreggate?d_ins_id=dzi389isCLU&fl=en_KZ&cal_dow=2&cal_doy=277&cal_y=2016&cal_dom=3&l=en&t=rVjlC57xipA0sUVkCk49twiILhTcPv42C2DNGgrZ0cxilyN1QTONhIW2%2BHkiimGXcDkiH2f6Klzt%0AGlBi3gqmTOIZhRD%2B%2Fyi8TCmCnVB%2FPak%3D%0A&c=CA&p=com.lge.qmemoplus&cal_hod=0&d=c65c7885266ec9e3&cal_moh=30&cal_woy=41&aid=8775b9ea-80ed-42a8-ba29-597bd896c452& HTTP/1.1
User-Agent: Dalvik/2.1.0 (Linux; U; Android 6.0; LG-H812 Build/MRA58K)
Host: textreport.aitype.net
Connection: close
Accept-Encoding: gzip

I haven't been able to decode the string yet, so I haven't been able to come down to what it is sending but one can only imagine. The data it is sending is base64 encoded, possibly padded with some other device information. I'll update the post if I get any further. It is possible that the information it sends back is encrypted and "helps improve" the text prediction service. Even so, why must it send that information back? Why can the app not improve a user's predictive text on the device itself? I tested a few other keyboards ( Minuum, Google Keyboard, KitKat Keyboard, Chrooma Keyboard) and nothing was sent. 

But wait there's more! The keyboard will periodically want to send some large binary data to http://foxserver.aitype.net/server/kcctc

93 kilobytes of data? Not suspicious at all. I've uploaded the above data if anyone else would like to poke around in it, see the attachments at the bottom of this post.

It is quite scary when an app sends this type of information over plain text, considering how much one trusts their mobile device.



Remote code execution with Hitron CGNM-2250

Edit: This has been fixed in the latest firmare update

The routers that you receive from your ISP are almost always garbage: not many options to configure and pitiful wifi range. The router/modem that Shaw customers receive is the Hitron CGNM-2250 thankfully isn't completely terrible, 802.11ac plus gigabit ports. I was poking about and researching the model and came upon an exploit for a similar model version for remote code execution. The CGNM-2250 is vulnerable as well, for reference my software version is with hardware version 1A. The input for the ping utility through the web interface isn't sanitized so you can enter arbitrary input. I discovered that it has a few basic utilities, including Dropbear.


From there you can ssh into your router with

ssh mso@ -p 29

The password is msopassword (go figure!)

You'll be greeted with a fun menu driven interface. 

Most of the menus are for debugging and initial programming purposes. There is a menu option to set the TFTP download image URL to flash the device, so it might be possible to flash your own firmware though I don't know how possible that is.

Going back to the web interface, I was able to see what tools and programs are available on the router.

It is your basic Linux system, busybox plus some manufacturer utilities. For a full list of binaries available see below: #/sbin wlanconfig wifitool wifirrm watchdog vconfig utelnetd udhcpd udhcpc udevstart udevsend udevd udev ti_udhcpc ti_todc ti_tftp ti_syslogd ti_dhcp6c start-stop-daemon route rmmod reboot radartool poweroff pktlogdump pktlogconf manufacture_ath_throughput_sta manufacture_ath_throughput_ap makeVAP lsmod klogd killVAP iwpriv iwlist iwconfig insmod init ifconfig ht_wifi_ioctl ht_atom_cmd hostapd_cli hostapd halt fsck.ext3 fdisk cfg blockdev atomcmdlist athstatsclr athstats athcfg_api arp apup apstats apdown apcfg activateVAP 80211stats #/usr/bin which uptime traceroute6 traceroute tr top tftp test renice pstree pmap mkfifo logger killall hexdump head free flock find expr dirname cut crontab basename awk add-shell [[ [ #/bin xmlwf umount touch tar sync sleep sh sed rm pwd ps ping6 ping pcap-config netstat mv mount more mknod mkdir luac lua ls login ln kill iptunnel iprule iproute iplink ipaddr ip hostname grep getopt false echo dnsdomainname dmesg df dd date cp clnkstat clnkrst clnkqos clnkpm clnkmocamib clnkmem clnkmcast clnkhwtst clnkfwupd clnkcfg clinkd chown chmod cat busybox bash ash DCAP.46 DCAP.45 DCAP.44 DCAP.42 DCAP.41 DCAP.40 DCAP.38 DCAP.37 DCAP.35 DCAP.19 DCAP.18 DCAP.16 DCAP.137 DCAP.132 DCAP.126 DCAP.125 DCAP.123 DCAP.122 DCAP.116 DCAP.115 DCAP.112 DCAP.111 DCAP.110 DCAP.109 DCAP.107 DCAP.104 DCAP.103 DCAP.102 DCAP.101 DCAP.08 DCAP.03 DCAP.02 DCAP CandDdvr.ko ##/usr/sbin watchdog_rt upstream_manager_1q upstream_manager upgradebox update testmode_handle.sh testmode sync_app_np_reboot swdl2 sw_dl snmpcmd snmp_agent_cm setstartup setkey setenv sched runall rpc_reverse_server_util rpc_reverse_server rpc_management_server rpc_ifconfig rfs.cfg rfs regs qos_dsx_sm productionmode printenv pp_fw_download portmap pmap_set pmap_dump pcd nvtst nvread mptint mlx logger ledcfg l2switch_iram.img l2switch_init l2switch_dram.img iostat iccctl icc_genEvent htxvendordb ht_iwcmd ht_buttond hal_tuner_mgr hal_event_mbox hal_cmd_mbox gptimer gim getenv getPortByMAC.sh flash_eraseall fdump ext_switch_init eventmgr_cm energy_manager_app dpp_dev_init.sh downstream_manager docsis_mac_manager docsis_mac_driver docsis_init_once docsis_dl_box docsis_config_to_text dmg_provisioning dl dispatcher dfltr_class_init.sh ddl dbridge_mdf_init.sh dbridge_l2vpn_ds_init.sh dbridge_init crond cmdl cm_status cli_net.sh cli_mem.sh cli chroot cefdk cc_init_once brctl bpi_tek bpi_sa_map bpi_auth MxL_HRCLS_FW_4.1.5.5.mbin MxL_HRCLS_FW.mbin FwUpstreamDocsis3_I.bin FwUpstreamDocsis3_D.bin FwUpstreamDocsis2_I.bin FwUpstreamDocsis2_D.bin Through digging in the CLI menu I found that /etc/scripts/sys_startup.sh is ran on startup, making it a easier to inject any commands you want to run at startup. I haven't been able to get to a shell yet, since logging in with the 'mso' user it takes you to the CLI menu. But cat /etc/passwd reveals:


One should be able to symlink /bin/sh to /usr/sbin/cli so upon login it would drop to a shell. Entering commands via the web interface is a bit tricky since it doesn't like pipes (|), it's just a matter of getting around the JS validation. Once I can get to a shell I'll write a followup to this post Issue patched in version