How To Run Your Own Cell Tower

This post is a collection of notes from about 2 years ago. I keep meaning to post them, but keep getting busy and not having time to get around to editing them for clarity and/or currency, but I keep wanting to reference them, so I’m finally just going to dump them here as-is.

NOTE: doing this may be illegal in your jurisdiction. Specifically, using the radio frequencies required for this to work could potentially be a federal offense (in the US). Be sure you understand the legalities before following this guide, etc.

Also note: this is specifically for GSM network cell traffic. If you are looking for something other than GSM, things are going to be different.

And with that all said, the following basically takes you from “I just installed Ubuntu” to “I have cell phones calling/texting each other through my ‘tower'” in roughly 10 minutes using a BladeRF and a laptop.


  • [ 1 ] of Lenovo Y50-70 Laptop:
  • [ 1 ] of BladeRF x40:
  • [ 1 ] of BladeRF case:
  • [ 2 ] of Superbat 5dbi 700-2600Mhz 4G LTE Omni Directional Antenna:

NOTE: OS used for this project was Linux Mint 17.3 – KDE Spin

$ cat /etc/lsb-release
DISTRIB_DESCRIPTION="Linux Mint 17.3 Rosa"

$ uname -srp
Linux 3.19.0-32-generic x86_64

Getting BladeRF Running

  • Add your username to the plugdev and dialout groups
$ sudo usermod -a -G plugdev <username>
$ sudo usermod -a -G dialout <username>
  • Install the bladeRF PPA and required software packages
$ sudo add-apt-repository ppa:bladerf/bladerf
$ sudo apt-get update
$ sudo apt-get install bladerf libbladerf-dev bladerf-firmware-fx3 bladerf-fpga-hostedx40
  • Create a ~/.Nuand/bladeRF directory
  • Store the FPGA image in the bladeRF directory so it can be autoloaded
$ mkdir -p ~/.Nuand/bladeRF
$ cd ~/.Nuand/bladeRF
$ wget

*NOTE: since we installed the FPGA from the bladeRF PPA, you could also just link to that from your bladeRF directory instead:

$ ln -s /usr/share/Nuand/bladeRF/hostedx40.rbf ~/.Nuand/bladeRF
  • Grab the latest firmware image
$ wget
  • Plug the bladeRF in
  • Verify the system sees the device
$ dmesg | grep usb
[  992.744233] usb 2-2: New USB device found, idVendor=1d50, idProduct=6066
[  992.744235] usb 2-2: New USB device strings: Mfr=1, Product=2, SerialNumber=3
[  992.744237] usb 2-2: Product: bladeRF
[  992.744239] usb 2-2: Manufacturer: Nuand
[  992.744240] usb 2-2: SerialNumber: d1c8b59ff5d36d39d43af24eaca005a3
  • Verify the system recognizes the bladeRF (using bladeRF-cli):
$ bladeRF-cli -p

  Backend:        libusb
  Serial:         d1c8b59ff5d36d39d43af24eaca005a3
  USB Bus:        2
  USB Address:    2

$ bladeRF-cli -i
[INFO @ version_compat.c:116] FPGA version (v0.4.1) is newer than entries in libbladeRF's compatibility table. Please update libbladeRF if problems arise.

bladeRF> info
  Serial #:                 d1c8b59ff5d36d39d43af24eaca005a3
  VCTCXO DAC calibration:   0x8ea1
  FPGA size:                40 KLE
  FPGA loaded:              yes
  USB bus:                  2
  USB address:              5
  USB speed:                SuperSpeed
  Backend:                  libusb
  Instance:                 0
  • Update the firmware on the bladeRF
$  cd /usr/share/Nuand/bladeRF/
$  bladeRF-cli --flash-firmware ./firmware.img

Install Yate

  • Checkout the source code from SVN and compile
$ mkdir svn
$ cd svn/
$ svn checkout yate
$ cd yate/
$ ./
$ ./configure
$ sudo make install
$ which -a yate-config

Install YateBTS

  • Checkout the source code from SVN and compile
$ cd ..
$ svn checkout yatebts
$ cd yatebts/
$ ./
$ ./configure
$ sudo make install
  • Pickup the new libraries
$ sudo ldconfig

Configure YateBTS

  • Setup the GSM band information by setting the following values in the configuration file:
$ sudo vi /usr/local/etc/yate/ybts.conf

Run YateBTS

  • Run the yate binary as root
$ sudo yate -vvvvvv
Yate (23748) is starting Tue Oct 20 14:03:52 2015
2015-10-20_14:03:52.041354 <ALL> Plugin::Plugin("filetransfer",false) [0x7f0204349880]
Loaded module File Transfer
2015-10-20_14:03:52.041441 <ALL> Plugin::Plugin("tonedetect",false) [0x7f0204139300]
Loaded module ToneDetector
2015-10-20_14:03:52.041509 <ALL> Plugin::Plugin("fileinfo",false) [0x7f0203f30c40]
Loaded module FileInfo
2015-10-20_14:03:52.041603 <ALL> Plugin::Plugin("extmodule",false) [0x7f0203d259c0]
Loaded module ExtModule
2015-10-20_14:03:52.041916 <ALL> Plugin::Plugin("javascript",true) [0x7f0203b11640]
Loaded module Javascript
2015-10-20_14:03:52.041998 <ALL> Plugin::Plugin("moh",false) [0x7f0203694440]
Loaded module MOH
2015-10-20_14:03:52.042148 <ALL> Plugin::Plugin("isaccodec",false) [0x7f020348a780]
Loaded module iSAC floating point - based on WebRTC iSAC library version 4.3.0 (SPL version 1.2.0)
2015-10-20_14:03:52.042348 <ALL> Plugin::Plugin("iax",false) [0x7f020324e5c0]
Loaded module YIAX
2015-10-20_14:03:52.042522 <ALL> Plugin::Plugin("yrtp",false) [0x7f0203012280]
Loaded module YRTP
2015-10-20_14:03:52.042643 <ALL> Plugin::Plugin("cdrfile",true) [0x7f0202deb240]
Loaded module CdrFile
2015-10-20_14:03:52.042750 <ALL> Plugin::Plugin("gsmcodec",false) [0x7f0202be6180]
Loaded module GSM - based on libgsm-1.0.10
2015-10-20_14:03:52.042836 <ALL> Plugin::Plugin("regexroute",false) [0x7f02027d1f80]
Loaded module RegexRoute
2015-10-20_14:03:52.042914 <ALL> Plugin::Plugin("callgen",false) [0x7f02025c58c0]
Loaded module Call Generator
2015-10-20_14:03:52.042975 <ALL> Plugin::Plugin("pbx",false) [0x7f02023ba540]
Loaded module PBX
2015-10-20_14:03:52.043061 <ALL> Plugin::Plugin("conf",false) [0x7f02021b34c0]
Loaded module Conference
2015-10-20_14:03:52.043150 <ALL> Plugin::Plugin("rmanager",false) [0x7f0201fa4900]
Loaded module RManager
2015-10-20_14:03:52.043240 <ALL> Plugin::Plugin("wave",false) [0x7f0201d949c0]
Loaded module WaveFile
2015-10-20_14:03:52.043326 <ALL> Plugin::Plugin("analyzer",false) [0x7f0201b83480]
Loaded module Analyzer
2015-10-20_14:03:52.043401 <ALL> Plugin::Plugin("stun",false) [0x7f0201978400]
Loaded module YSTUN
2015-10-20_14:03:52.043652 <ALL> Plugin::Plugin("sip",false) [0x7f020176d980]
Loaded module SIP Channel
2015-10-20_14:03:52.043754 <ALL> Plugin::Plugin("callfork",false) [0x7f02015084c0]
Loaded module Call Forker
2015-10-20_14:03:52.043824 <ALL> Plugin::Plugin("dumb",false) [0x7f02012fd200]
Loaded module DumbChannel
2015-10-20_14:03:52.043895 <ALL> Plugin::Plugin("cdrbuild",false) [0x7f02010f63c0]
Loaded module CdrBuild
2015-10-20_14:03:52.043978 <ALL> Plugin::Plugin("gvoice",false) [0x7f0200ee94c0]
Loaded module GVoice
2015-10-20_14:03:52.044048 <ALL> Plugin::Plugin("enumroute",false) [0x7f0200ce3240]
2015-10-20_14:03:52.044810 <ALL> Plugin::Plugin("jingle",false) [0x7f0200adbac0]
Loaded module YJingle
2015-10-20_14:03:52.044949 <ALL> Plugin::Plugin("msgsniff",false) [0x7f0200646540]
Loaded module MsgSniffer
2015-10-20_14:03:52.045010 <ALL> Plugin::Plugin("cdrcombine",false) [0x7f02004409c0]
Loaded module CdrCombine
2015-10-20_14:03:52.045128 <ALL> Plugin::Plugin("ilbccodec",false) [0x7f020023a080]
Loaded module iLBC - based on iLBC reference library
2015-10-20_14:03:52.045223 <ALL> Plugin::Plugin("socks",true) [0x7f0200017a80]
Loaded module YSOCKS
2015-10-20_14:03:52.045369 <ALL> Plugin::Plugin("ilbcwebrtc",false) [0x7f01ffe00600]
Loaded module iLBC - based on WebRTC iLBC library version 1.1.1
2015-10-20_14:03:52.045465 <ALL> Plugin::Plugin("tone",false) [0x7f01ffbe8680]
Loaded module ToneGen
2015-10-20_14:03:52.045484 <tone:ALL> Building comfort noise at level -10
2015-10-20_14:03:52.045537 <tone:ALL> Building tone of 1336 + 941 Hz
2015-10-20_14:03:52.046160 <tone:ALL> Building tone of 1209 + 697 Hz
2015-10-20_14:03:52.046751 <tone:ALL> Building tone of 1336 + 697 Hz
2015-10-20_14:03:52.047344 <tone:ALL> Building tone of 1477 + 697 Hz
2015-10-20_14:03:52.047936 <tone:ALL> Building tone of 1209 + 770 Hz
2015-10-20_14:03:52.048536 <tone:ALL> Building tone of 1336 + 770 Hz
2015-10-20_14:03:52.049135 <tone:ALL> Building tone of 1477 + 770 Hz
2015-10-20_14:03:52.049737 <tone:ALL> Building tone of 1209 + 852 Hz
2015-10-20_14:03:52.050321 <tone:ALL> Building tone of 1336 + 852 Hz
2015-10-20_14:03:52.050906 <tone:ALL> Building tone of 1477 + 852 Hz
2015-10-20_14:03:52.051496 <tone:ALL> Building tone of 1209 + 941 Hz
2015-10-20_14:03:52.052086 <tone:ALL> Building tone of 1477 + 941 Hz
2015-10-20_14:03:52.052677 <tone:ALL> Building tone of 1633 + 697 Hz
2015-10-20_14:03:52.053268 <tone:ALL> Building tone of 1633 + 770 Hz
2015-10-20_14:03:52.053867 <tone:ALL> Building tone of 1633 + 852 Hz
2015-10-20_14:03:52.054458 <tone:ALL> Building tone of 1633 + 941 Hz
2015-10-20_14:03:52.055048 <tone:ALL> Building tone of 2000 + 125 Hz
2015-10-20_14:03:52.056957 <tone:ALL> Building tone of 2000 modulated by 1000 Hz
2015-10-20_14:03:52.059324 <tone:ALL> Building tone of 2010 Hz
2015-10-20_14:03:52.059486 <tone:ALL> Building tone of 1780 Hz
2015-10-20_14:03:52.059799 <ALL> Plugin::Plugin("mux",true) [0x7f01ff9d8640]
Loaded module MUX

Use Yate Telnet Interface

  • Telnet to port 5038 on the host running YateBTS
telnet 5038
Connected to
Escape character is '^]'.
YATE 5.5.1-devel1 r6056 ( ready on localhost.
  • Get a list of registered devices
nib list registered
IMSI            MSISDN
--------------- ---------------
  • Get a list of rejected devices
nib list rejected
IMSI            No attempts register
--------------- ---------------
001010000000002    2
  • Observe rejections in the yate debug output:
2015-10-20_14:18:33.424894 <ybts-signalling:INFO> Received [0xcd0240]
Primitive: PhysicalInfo
Info: 0
Connection: 3

<PhysicalInfo>TA=0 TE=3.000 UpRSSI=1 TxPwr=33 DnRSSIdBm=-83 time=1445365112.943</PhysicalInfo>
2015-10-20_14:18:33.425026 <ybts-signalling:INFO> Received [0xcd0240]
Primitive: L3Message
Info: 0
Connection: 3


  <Message type="LocationUpdatingRequest">
2015-10-20_14:18:33.425160 <ybts-signalling:ALL> Added connection (0x7f01d4001330,3) [0xcd0240]
2015-10-20_14:18:33.425180 <ybts-mm:ALL> Handling LocationUpdatingRequest conn=3: ident=IMSI/001010000000002
LAI=00101_fffe [0xcd0620]
2015-10-20_14:18:33.425194 <ybts-mm:ALL> Added UE (0x7f01d4002b10) TMSI= IMSI=001010000000002 [0xcd0620]
2015-10-20_14:18:33.425206 <ybts-signalling:ALL> Connection 3 set UE (0x7f01d4002b10) TMSI= IMSI=001010000000
002 [0x7f01d4001330]
2015-10-20_14:18:33.425233 <ybts-signalling:INFO> Sending [0xcd0240]
Primitive: L3Message
Info: 0
Connection: 3

  <Message type="IdentityRequest">
2015-10-20_14:18:33.456519 <gsmtrx:ALL> ARFCN[0]: Slot 0. Excessive TOA error=-3 peak/mean=3.21735 count=1 [0
2015-10-20_14:18:33.796915 <gsmtrx:ALL> ARFCN[0]: Slot 6. Excessive TOA errors 8 [0x7f01d8016150]
2015-10-20_14:18:33.875678 <gsmtrx:ALL> ARFCN[0]: Slot 6. Excessive TOA error=-5 peak/mean=3.09984 count=1 [0
2015-10-20_14:18:34.045461 <gsmtrx:ALL> ARFCN[0]: Slot 5. Excessive TOA error=-5 peak/mean=3.8067 count=12 [0
2015-10-20_14:18:34.116751 <gsmtrx:INFO> ARFCN[0]: Slot 0. Receiver clipping 1.81203 dB (FN=189757) count=17
2015-10-20_14:18:34.130972 <ybts-signalling:INFO> Received [0xcd0240]
Primitive: PhysicalInfo
Info: 0
Connection: 3

<PhysicalInfo>TA=3 TE=-1.000 UpRSSI=1 TxPwr=30 DnRSSIdBm=-83 time=1445365112.931</PhysicalInfo>
2015-10-20_14:18:34.131057 <ybts-signalling:INFO> Received [0xcd0240]
Primitive: L3Message
Info: 0
Connection: 3

  <Message type="IdentityResponse">
2015-10-20_14:18:34.131165 <ybts:ALL> Started location updating thread for (0x7f01d4002b10) TMSI= IMSI=001010
000000002 [0x7f01d4001b70]
2015-10-20_14:18:34.131317 <nib:INFO> Got user.register for imsi='001010000000002', tmsi=''
2015-10-20_14:18:34.131425 <ybts:ALL> Location updating thread for (0x7f01d4002b10) TMSI= IMSI=00101000000000
2 terminated [0x7f01d4001b70]
2015-10-20_14:18:34.131439 <ybts-mm:ALL> UE (0x7f01d4002b10) TMSI= IMSI=001010000000002 register failed [0xcd
2015-10-20_14:18:34.131469 <ybts-signalling:INFO> Sending [0xcd0240]
Primitive: L3Message
Info: 0
Connection: 3

  <Message type="LocationUpdatingReject">
2015-10-20_14:18:34.131520 <ybts-signalling:ALL> Releasing connection (0x7f01d4001330,3) [0xcd0240]
2015-10-20_14:18:34.131530 <ybts-signalling:INFO> Sending [0xcd0240]
Primitive: ConnRelease
Info: 0
Connection: 3

Setting Wildcard Subscriber (IMSI Catcher Mode)

  • Edit /usr/local/etc/yate/subscribers.conf and set the following:
      * country_code=1 ; 1 for US, 44 for UK
      * regexp=.* ; catch all the things!

  • Start yate

$ yate -v
  • Verify registered devices using telnet interface
$ telnet 5038
Connected to
Escape character is '^]'.
YATE 5.5.1-devel1 r6056 ( ready on localhost.

nib list registered
IMSI            MSISDN
--------------- ---------------
001010000000002   10000002

SIM card setup

  • Install the pre-requisite packages
$ sudo apt-get install python-setuptools swig python-dev libpcsclite-dev pcsc-tools
  • Run pcsc_scan, then plug the device in, and insert a SIM card
$ pcsc_scan
PC/SC device scanner
V 1.4.23 (c) 2001-2011, Ludovic Rousseau <>
Compiled with PC/SC lite version: 1.8.11
Using reader plug'n play mechanism
Scanning present readers...
Waiting for the first reader...
Scanning present readers...
0: MSI StarReader SMART [Smart Card Reader Interface] (20070818000000000) 00 00
Tue Oct 20 19:26:25 2015
Reader 0: MSI StarReader SMART [Smart Card Reader Interface] (20070818000000000) 00 00
  • Download and compile pyscard
$ wget ''
$ tar zxvf pyscard-1.9.0.tar.gz
$ cd pyscard-1.9.0
$ sudo python build_ext install
  • Clone the pySim repository
$ git clone git:// pysim

Setting up SIP

  • Install Asterisk
$ sudo apt-get install asterisk asterisk-core-sounds-en* asterisk-moh-opsound-* asterisk-mp3 asterisk-mysql mysql-server postgresql postgresql-cont
rib asterisk-voicemail asterisk-doc libmyodbc oidentd
  • Install Kamailio SIP proxy software
$ sudo apt-get install kamailio kamailio-geoip-modules
  • Acquire a SIP account with a provider, ideally with a DID
      * I used, and created 2 DIDs, both routed to a single SIP URI (the main account I created when registering)
      * Make sure you set up e911 registration so if anyone on your tower dials 911 the call gets routed and 911 data populated.

Simple NodeJS Web Proxy

I was on a project recently where we were performing a network penetration test against an internal network, remotely. To facilitate this, we shipped a server to the client so they could install it into their datacenter. We then log in to this host, and perform our testing from it, rather than sending consultants onsite. This is super efficient, and cost-effective. The host we send out in these cases is referred to as a ‘jump box’ — because it offers a jumping-off point into the network.

For whatever reason, this time the jump box did not have the full set of tools installed. In many cases, this wouldn’t be a terrible problem, we’d just install the tools we need and move on. However, in this case, the client was performing egress filtering — meaning we had no way to get out to the internet from our jump box. This led to a dilemma: how can I access the remote services (like web sites) easily? Usually I’d set up tinyproxy or something similar, and set up a tunnel over SSH to access it. Unfortunately, I didn’t have tinyproxy or any other web proxy servers installed, and had no easy way to get one.

However, I did have nodejs installed on the box: which is quite useful for this type of thing! I did a quick search for nodejs proxies, and found a suitable base code over here. (Remember, I can’t get out to the internet from the jump box, so I need to limit my proxy code to only core nodejs modules, I can’t ‘npm install’ anything.)

That post is quite old (6 years!) and some things have changed since it was written. For one thing, the http.createServer() method has been replaced by http.request(). The sample code there also contained some things I didn’t need, like a blacklist of sites to prevent access to, but it had some things I definitely did want (like a whitelist of IPs that are allowed to talk to the proxy. That’s an important factor when doing this sort of test, if you are going to open up services on a client network, you need to take steps to minimize security risks they may cause. Restricting access to this proxy to only the localhost of the jump box helps me do that.)

You can check out the final results at my pentools github repo.

Once I had the code in place, I simply opened up an SSH tunnel to the jump box, and set up port 8080 on my laptop to tunnel to port 8080 on the jump box, like this:

ssh -L 8080:localhost:8080 user@jumpbox

Once I was logged in, I configured Firefox on my laptop to use localhost port 8080 for a web proxy, and I could now point my browser to the client’s internal network addresses and browse their websites from my browser, through the jump box proxy.

Repo Dorks

Here’s a handy list of Google Dorks for use when searching through (github) source code repositories to find sensitive data

  • SSH hosts and keys: inurl:“known_hosts”“ssh-rsa”
  • Private encryption keys: inurl:“id_rsa” -inurl:“pub”
  • Test configuration info: inurl:“test” filetype:config
  • Ruby on Rails secure token: inurl:secret_token.rb
  • Windows Azure account keys: “;AccountKey=”filetype:config
  • Database connection config: “;User Id=” filetype:config
  • Amazon Web Service access key (Java): “AWS_ACCESS_KEY_ID” filetype:properties
  • Amazon Web Service access key (Other): “AWS_ACCESS_KEY_ID” filetype:config
  • Bash command history: filetype:bash_history
  • Account config data: filetype:xml inurl:accounts.xml
  • SQL containing passwords: filetype:sql where password
  • Django settings file: