Back

Listening to Switzerland’s Unencrypted Paging Network

sdr radio security pocsag · 2026-10-04 · Loading views...


TL;DR

With a ~$40 RTL-SDR and a Raspberry Pi, I listened to Switzerland's POCSAG paging network (147.3-147.4MHz and 169.7MHz). It's full of unencrypted messages: network monitoring alerts, heartbeats, ambulance and fire dispatches, and even customer service requests with names and phone numbers. On 169.7MHz, I also found base64-encoded GPS ephemeris data (u-blox UBX format). I anonymized everything private in this article.

While exploring radio signals using my rtl-sdr, I came across those small bursts around 147.3-147.4MHz:

147 sdr++ waterfall

After further investigation, I discovered they were POCSAG messages, and part of a relatively large network. I'll show some cool stuff I found, but before that,

What Is POCSAG?

Radio-paging code No. 1, usually called POCSAG, referring to the name of the group that developed the code, Post Office Code Standardisation Advisory Group, is an asynchronous protocol used to transmit data via radio. It's mostly meant for pagers (small communication devices), but from what I saw, it's used by a variety of automated systems.

The Setup

To listen to the frequencies I'll be talking about later, I'm using a simple setup composed of a Raspberry Pi 4 Model B, and a RTL-SDR Blog V4. The ideal antenna length should be around 45cm per arm (if you're using a dipole), and placed vertically. To be honest I just used a way longer antenna and it still works fine, so whatever.

rpi4 + rtl-sdr

Decoding & Frequencies

To decode this protocol, I'm currently using the multimon-ng CLI tool. It supports plenty of communication protocols, including POCSAG. To use it, I pipe the output of rtl_fm into it, and it decodes content automatically:

rtl_fm -f 147.300M -s 22050 -g 40 | multimon-ng -t raw -a POCSAG1200 -f alpha /dev/stdin

This produces output looking like this:

POCSAG1200: Address: 0000000  Function: 3  Alpha:   Message Content
POCSAG1200: Address: 0000000  Function: 0 

We can see three relevant fields:

As for the frequencies, you probably noticed I'm using 147.3MHz in the rtl_fm command. The TELEPAGE network, operated by Swissphone, has 4 documented frequencies:

Network Frequency Status
F1 147.400MHz Documented
F2 147.375MHz Documented
F3 147.325MHz Documented
F4 147.300MHz Documented
F5 169.700MHz Undocumented, see below

The Wikipedia article also mentions a "SIKAN" network at 169.5MHz, but I couldn't find anything regarding that network online, and nothing was broadcasting on it. HOWEVER, at 169.7MHz, I found what could possibly be a "F5" TELEPAGE network:

POCSAG1200: Address: 0000000  Function: 3  Alpha:   Paessler Test F5 ALLIP Zugang<NUL><NUL>

This is basically how I decode POCSAG messages; however, I also added two more pipes to the chain to get a cleaner output and save my logs to a file:

rtl_fm -f 169.700M -s 22050 -g 40 \
| multimon-ng -t raw -a POCSAG1200 -f alpha /dev/stdin \
| sed -u -n 's/.*Alpha: *//p' \
| tee ~/etc/pocsag.169.7.log

The sed line strips out everything that is not message content, so I don't have to "manually" remove the metadata.

That's everything for the "technical" side of it. Let's move on to the various interesting messages I've seen.

Paessler Tests

This is the most common message I see. These are periodically sent messages that look like this:

Paessler Test <network> ALLIP Zugang<NUL><NUL>

Where <network> is F2, F3, F4, or F5 on 169.7.

After some research, it looks like those messages are coming from a Paessler PRTG Network Monitor system. It is a network monitoring software that is meant to work independent of the regular network. It is possible to connect the software to gateways that send notifications, which apparently end up on the TELEPAGE network.

Other Heartbeats

Aside from Paessler, I also saw plenty of other heartbeat notifications, such as:

Message Interval Notes
Testmeldung Repair Center <hh:mm:ss> Every 5 min
Testruf HYBRALERT <dd.MM.yyyy HH:mm:ss> Every 10 min
TESTALARM: Zur Funktionsprüfung der Pager. Täglich <hh:mm>Uhr Daily "TEST ALARM: For functional testing of the pagers. Daily at <hh:mm>"
<hh>H<mm> - Superviseur - Test de fonctionnement ? Same as above, in French
IT-Kontrollmeldung: Telalert test message from KRONOS/GEMINI ? IT monitoring heartbeat
Autotest RCH Circle Raiffeisen-Stoerung Prio 1 ? Seems to come from the Raiffeisen network (a Swiss bank)

Ambulances and Medical Dispatches

I also logged some medical dispatches and notifications, notably (translated and anonymized):

Team 221 P1 Cerebral Event at some company
P1 Med-S at some municipality
P1 Obstetrics at some municipality
Team 717 P1 allergy at some municipality

Those seem to come from the ambulance dispatch centre (emergency number 144). I guessed that P1 is the priority level.

Responderplus Uri - A2UR Gotthard Service Area (Southbound),
-, Gotthard Service Area - P1 Cerebral event

This one seems to be an alert from the A2 motorway, which passes through the Saint-Gotthard Massif.

Fatality in <exact address>. Please call back the alarm
center at XXX XXX XX XX.

Fire and Rescue Dispatches

Along with the medical ones, I've also logged fire-related messages:

#<identification code>, Vegetation fire, <exact address>
Fire large object R1 D1 <exact address>

This message was probably a call-out for support for this fire at St.-Gallen.

BACKUP - Odor - Burning smell. Incident details: 2nd and 3rd
floors; no smoke, only an odor; the caller explains
<exact address>
Smoke – Building – Villa; incident details: abandoned
building, <exact address>
COMMITMENT. Infranet - <id> - 12 - * CLE - CITY POP -
SURVILLE # <exact address> # First Aid Van Crew: 036 CE:
<names>

These last three messages seem to come from the Geneva fire brigade dispatch

IT Alarms

Some IT alarms were logged as well. They aren't encrypted, which is interesting, since they leak hostnames and other private information:

PROBLEM/1 <internal hostname>.cyberlink.cloud CR VSAN
Compute-01 Status CRIT - Cluster OK, 12 nodes, vSAN
object health(!!), Operation health(!!), raw d<NUL><NUL>
<hostname> (<10.x.x.x private IP>) is Down at least 5 min.

Then followed by, later:

<hostname> (<10.x.x.x private IP>) is Up.
Kanton Bern (KAIO) OPEN dev:<internal hostname>.be.ch
<internal hostname>.be.ch Unavailable by ICMP ping

The Canton of Bern's internal infrastructure monitor?

Customer Service Requests

Other logs were customer service requests, with, let's say, a lot of private info leaking through the air, lol.

dd.MM.YYYY - hh:mm:ss On-call service call-out – Client:
<client name>, <client address>, <client phone number>
<client name> <client phone number> <client id>
<client address> TV/BOX Problem Receipt to
<customer service phone number>

Satellite Information

This was an interesting one. And I'm still not sure why it was being broadcast on POCSAG, but it was a GPS satellite information broadcast. Let's take a look at the data.

And this is the only one coming from the F5 network (on 169.7MHz).

I first noticed a bunch of nonsense among the pretty "normal" logs:

POCSAG1200: Address: 1752178  Function: 0  Alpha:   +++=,*.AgISMAUktWILMWgAFAAAAIAZDgAA0GEAAAAAAAAAAAAAAAAA8gAAAE4Mig
POCSAG1200: Address: 1752178  Function: 0  Alpha:   ,*.AgISMQUmVQBBAAAA7DQnAN70VQCq1DIAWxylAABp9gDujvIAoT8NAG4TDQBI<NUL><NUL>
POCSAG1200: Address: 1752178  Function: 0  Alpha:   ,*.AgISMgUmfE4MADP7/wCceAIAJx4AAHklNwC8GCAAsDoNACun///U////sxPN<NUL><NUL>
POCSAG1200: Address: 1752178  Function: 0  Alpha:   +++=,*.AgITMAUktWILMWgAFQAAAIAZDgAC0GEAAAAAAAAAAAAAAAAA6wAAAE4MvA
POCSAG1200: Address: 1752178  Function: 0  Alpha:   ,*.AgITMQUmAwCr/wAA7H8tAKQAAwCpmTIATIkYAACGAAC+5XMAoRUHADUbDQB5<NUL><NUL>
POCSAG1200: Address: 1752178  Function: 0  Alpha:   ,*.AgITMgUmfE4MAAr8/wBFdgcAJ+X/AIQyVQD28ScAYrZxAGql//8EBwMAQPZk<NUL><NUL>
POCSAG1200: Address: 1752178  Function: 0  Alpha:   +++=,*.AgIUMAUktWILMWgAFgAAAIAZDgAA0GEAAAAAAAAAAAAAAAAA6AAAAE0MiQ
POCSAG1200: Address: 1752178  Function: 0  Alpha:   ,*.AgIUMQUmNwBMAAAABJ8MAFMDNwBZdjMAJNXZAAURAwAU3egAoW8OAISrDQCP<NUL><NUL>
POCSAG1200: Address: 1752178  Function: 0  Alpha:   ,*.AgIUMgUmfE0MAIxBAAD2/+4AJyIAAANIAQDYSx4At57NAHmp///o+v//L/7G<NUL><NUL>

Those strings annoyed me. I sensed a pattern, but couldn't spot it. And then it hit me: those strings contain base64-encoded information.

I'm still not sure what the +/=,*. is, but the following string is clearly base64. So I decoded it using a small Python script:

import base64

lines: list[str] = [
    "AgISMAUktWILMWgAFAAAAIAZDgAA0GEAAAAAAAAAAAAAAAAA8gAAAE4Mig",
    "AgISMQUmVQBBAAAA7DQnAN70VQCq1DIAWxylAABp9gDujvIAoT8NAG4TDQBI",
    "AgISMgUmfE4MADP7/wCceAIAJx4AAHklNwC8GCAAsDoNACun///U////sxPN",
    "AgITMAUktWILMWgAFQAAAIAZDgAC0GEAAAAAAAAAAAAAAAAA6wAAAE4MvA",
    "AgITMQUmAwCr/wAA7H8tAKQAAwCpmTIATIkYAACGAAC+5XMAoRUHADUbDQB5",
    "AgITMgUmfE4MAAr8/wBFdgcAJ+X/AIQyVQD28ScAYrZxAGql//8EBwMAQPZk",
    "AgIUMAUktWILMWgAFgAAAIAZDgAA0GEAAAAAAAAAAAAAAAAA6AAAAE0MiQ",
    "AgIUMQUmNwBMAAAABJ8MAFMDNwBZdjMAJNXZAAURAwAU3egAoW8OAISrDQCP",
    "AgIUMgUmfE0MAIxBAAD2/+4AJyIAAANIAQDYSx4At57NAHmp///o+v//L/7G"
]

for line in lines:
    line += "=" * (-len(line) % 4) # fix padding
    print(base64.b64decode(line), end="")

However, it was binary data, and I wasn't really sure what to do with it. I tried decoding it to text, but it didn't match. So I asked Claude.ai, and it figured that they were frames of a single message, with a header and a counter. Once all the frames were put together, they were forming a U-blox UBX binary protocol message. To keep it simple, U-blox makes GNSS (GPS and friends) chips that can decode GPS signals in two ways:

  1. As human-readable text, like $GPGGA,123519,4807.038,N,...
  2. In a proprietary binary protocol, UBX. And what I got was one of those binary protocol messages.

Thanks to the pyubx2 library, I was able to decode the protocol, and got """readable""" content:

# ..previous code

from pyubx2 import UBXReader

messages: dict[int, dict[int, bytes]] = {}

for line in lines:
    line += "=" * (-len(line) % 4) # fix padding
    decoded = base64.b64decode(line)

    # decoded[2] = msg counter, decoded[3] = part number (0x30, 0x31, 0x32)
    messages.setdefault(decoded[2], {})[decoded[3]] = decoded[6:-1]

for counter, parts in messages.items():
    data = b"".join(parts[k] for k in sorted(parts))
    print(UBXReader.parse(data))

This printed "proper" content, well, kinda:

<UBX(AID-EPH, svid=20, how=924032, sf1d1_01=6410240, sf1d2_01=0, sf1d3_01=0, sf1d4_01=0, sf1d5_01=242, sf1d6_01=5573710, sf1d7_01=65, sf1d8_01=2569452, sf2d1_01=5633246, sf2d2_01=3331242, sf2d3_01=10820699, sf2d4_01=16148736, sf2d5_01=15896302, sf2d6_01=868257, sf2d7_01=856942, sf2d8_01=806524, sf3d1_01=16775987, sf3d2_01=161948, sf3d3_01=7719, sf3d4_01=3614073, sf3d5_01=2103484, sf3d6_01=866992, sf3d7_01=4294944555, sf3d8_01=4294967252)>
<UBX(AID-EPH, svid=21, how=924032, sf1d1_01=6410242, sf1d2_01=0, sf1d3_01=0, sf1d4_01=0, sf1d5_01=235, sf1d6_01=199758, sf1d7_01=65451, sf1d8_01=2981868, sf2d1_01=196772, sf2d2_01=3316137, sf2d3_01=1608012, sf2d4_01=34304, sf2d5_01=7595454, sf2d6_01=464289, sf2d7_01=858933, sf2d8_01=806524, sf3d1_01=16776202, sf3d2_01=489029, sf3d3_01=16770343, sf3d4_01=5583492, sf3d5_01=2617846, sf3d6_01=7452258, sf3d7_01=4294944106, sf3d8_01=198404)>
<UBX(AID-EPH, svid=22, how=924032, sf1d1_01=6410240, sf1d2_01=0, sf1d3_01=0, sf1d4_01=0, sf1d5_01=232, sf1d6_01=3607629, sf1d7_01=76, sf1d8_01=827140, sf2d1_01=3605331, sf2d2_01=3372633, sf2d3_01=14275876, sf2d4_01=200965, sf2d5_01=15260948, sf2d6_01=946081, sf2d7_01=895876, sf2d8_01=806268, sf3d1_01=16780, sf3d2_01=15663094, sf3d3_01=8743, sf3d4_01=83971, sf3d5_01=1985496, sf3d6_01=13475511, sf3d7_01=4294945145, sf3d8_01=4294965992)>

Yeah, ngl, who is even able to read that? So I asked Claude again to adapt the code to display proper information. It did, but added 50 lines of code just to do that, wtf Claude. Anyway, here is the data I received:

satellite G20
  week (mod 1024) : 391   health: 0   iodc/iode: 85/85
  clock           : toc=50400s  af0=2.991e-04s  af1=7.390e-12  af2=0.0e+00  tgd=-6.519e-09s
  orbit           : sqrtA=5153.6345  e=0.001851  toe=50400s
                    i0=55.1467°  Ω0=71.7323°  ω=-95.5523°  M0=-120.0305°
                    Ωdot=-4.654e-07°/s  Δn=2.663e-07°/s  Crs=-89.06m
satellite G21
  week (mod 1024) : 391   health: 0   iodc/iode: 515/3
  clock           : toc=50400s  af0=3.471e-04s  af1=-9.663e-12  af2=0.0e+00  tgd=-9.779e-09s
  orbit           : sqrtA=5153.6383  e=0.000884  toe=50400s
                    i0=55.3118°  Ω0=14.1035°  ω=-13.4379°  M0=-122.2090°
                    Ωdot=-4.746e-07°/s  Δn=2.651e-07°/s  Crs=5.12m
satellite G22
  week (mod 1024) : 391   health: 0   iodc/iode: 55/55
  clock           : toc=50384s  af0=9.629e-05s  af1=8.640e-12  af2=0.0e+00  tgd=-1.118e-08s
  orbit           : sqrtA=5153.7087  e=0.011542  toe=50384s
                    i0=54.8508°  Ω0=-161.8121°  ω=-55.1205°  M0=126.3528°
                    Ωdot=-4.533e-07°/s  Δn=2.696e-07°/s  Crs=26.59m

As you can see, these messages contain information about three satellites: G20, G21, and G22. The G stands for GPS, the US satellite constellation (the PRNs are 20, 21, and 22).

Satellite Information, 2

Literally as I was writing the previous paragraph, my Raspberry Pi logged another b64 message. What a coincidence.

This time, it contained information about the satellites G18 and G26:

satellite G18
  week (mod 1024) : 391   health: 0   iodc/iode: 316/60
  clock           : toc=72000s  af0=-2.411e-04s  af1=8.754e-12  af2=0.0e+00  tgd=-8.382e-09s
  orbit           : sqrtA=5153.6098  e=0.006575  toe=72000s
                    i0=55.5741°  Ω0=-45.8301°  ω=-161.0918°  M0=160.8777°
                    Ωdot=-4.467e-07°/s  Δn=2.309e-07°/s  Crs=68.03m
satellite G26
  week (mod 1024) : 391   health: 0   iodc/iode: 82/82
  clock           : toc=72000s  af0=-3.942e-04s  af1=-5.684e-13  af2=0.0e+00  tgd=6.519e-09s
  orbit           : sqrtA=5153.6106  e=0.011549  toe=72000s
                    i0=53.1745°  Ω0=-172.9840°  ω=42.3993°  M0=20.2715°
                    Ωdot=-5.003e-07°/s  Δn=3.278e-07°/s  Crs=28.09m

If you're interested in getting the code, you can find the full decoder snippet on GitHub gists.

Encrypted Data

Last and least, the F5 network also features plenty of encrypted messages, looking like this:

<FS>:<ETB>L'<SUB><ETB>ApAil@,<DC3>i<LF>'o<STX>\W5*<ETB>nZs<ACK>oZS<<DC3>.p<SYN>d<ENQ>t<GS>8?<BS>PV+;:[<<ESC><LF>Zh5<DLE>7 `$<CAN>Ig<DC3>5#`au>\!w d5J<SUB>m<CAN><US>wR5`<NAK>+<SYN><NAK>@v<DC4>Yh+<CR><EOT><CR><ENQ>(w'<EM>*<SO>D<RS>nl<SYN><NUL><NUL><NUL>
<RS>9)<CR>&e<ENQ>%C[A<EM><SI>?t5qUV<ENQ>`~L<]H&=a*_;\%l<RS><FF><DEL>A<VT>y<DLE>\@qPv<CAN>j<ESC>e<CAN>6.<RS>t<DLE><ENQ>zf<ESC>Keswp%)<DC1><US>S(2Ad"<STX>-}edsB8^U22>b/<BEL>dPz&c<HT>2}LOs\#<RS><NUL><NUL>
]FRlYXp<NAK>ON\H3S;x<ACK><DLE>v<ETB>_hq<NAK>N<`4@(CIzDOP<DEL>y*%O]H|<NAK>~<VT>-<FF>v)1<CR>K <VT>VYHkxl'f@<DC2>tC\~,0<DC4>LtC}FIFDy<US>89<ENQ>\ ;Gj%M9<HT>c"r^|@Q<DC4><DC2>P<DEL>uY<RS>e<SUB><NUL><NUL><NUL>
+++TIME=2117041026+++TIME=2117041026<NUL>

Good luck decoding that xd.

For The End

That's pretty much everything I've found so far. I'm honestly surprised by how much stuff is flying around in plain text, unencrypted, from ambulance dispatches to bank alerts and customer phone numbers. I replaced all the private stuff with placeholders here (names, addresses, hostnames, etc.), but anyone with a ~$40 dongle and a Raspberry Pi can read it. Even without the Pi, an RTL-SDR is cheap enough that it's not really a barrier.

And I'm still not done with the F5 network. I still have no idea why a GPS ephemeris was being sent over POCSAG, or what the +++TIME= messages are for. If you have any idea, feel free to tell me!

If you want to try this yourself, the commands are above, so just grab an antenna and listen. Just please don't do anything dumb with what you hear.

Thanks for reading! :)

Back to articles

Comments


    Leave a comment