Skip to main content

Command Palette

Search for a command to run...

Digging into Vande Bharat's Infotainment system

Updated

Recently I traveled on Vande Bharat Express, from BLR to ED(20641), for those unfamiliar with the train, it’s a medium to long-distance higher-speed rail Express train service offering reserved, air-conditioned chair car accommodations. (Ref: Wikipedia).

It has a lot of features such as onboard-WiFi for infotainment, reading lights, onboard-pantry, electric outlets, and automatic doors.

The infotainment system/onboard wifi caught my eye, I was curious and I decided to dig into it after finishing a movie on Netflix(with my own hotspot, not the onboard-wifi).

to clarify at the start,
the onboard-wifi doesn’t give you internet access, it gives you intranet access. Somewhere in the train, there’s a server hosting the minimal 1950’s movies and songs.

I connected to the Vandebharatinfotainment SSID, which was an Open Network, and upon connecting my browser and my operating systems pushed the Captive Portal signin notifications, turns out that it’s the infotainment system! Yup, it intercepts all requests and presents the infotainment system on them.

It was boring, nothing interesting that I could watch, so I decided to dig in further. Fired my developer tools and monitored the requests.

There wasn’t much, it looked like a static site to me, but there were few interesting configuration files,

http://vandebharat.myinfotain.com/assets/config/config.js

config = {

    crewApiUrl: "http://myinfotain.com:3000",
}

http://vandebharat.myinfotain.com/assets/config/pisconfig.js

{
    DisplayEnglish: [{ Name: "Last Updated On", Index1: 0, Index2:1 },
    { Name: "Speed", Index1: 2 },{ Name: "Next Stop", Index1: 5 }
    ],

    TripStartDate: 5,
    TripStartTime: 6,
    PAS: 7,
     startIndex : 2,
     totalLanguages : 5,
     totalStations : 5,
    presentStation :{ id:2, name:"Present Station" },
    nextStation :{id:4, name:"Next Station"} ,
    time:5,
    pas :"Please Listen to Announcement...!",
    df:"102",
    j:"No Data Available"
}

PIS… that stands for Passenger Information System. Interesting.

There was also a dedicated page to see the current train speed, departing station, arriving station and time.
It was pulling the data from a bunch of text files.

GET http://vandebharat.myinfotain.com/assets/pis/routedata.txt
22-09-25
17:10
55 kmph
01:04
00:34
56 km
GET http://vandebharat.myinfotain.com/assets/pis/PresentNext.txt

0
2
Salem
GET http://vandebharat.myinfotain.com/assets/pis/sd.txt

0
Bangalore cantt
Coimbatore
GET http://vandebharat.myinfotain.com/assets/pis/jounney.txt

1

I looked through the source code and found these functions which described the text files.

           getAPips() {
              return this.httpClient.get(this.config.crewApiUrl + '/getAP', {
                responseType: 'text'
              })
            }
            getTrainData() {
              return this.httpClient.get(
                'assets/pis/routedata.txt?date=' + this.getCacheBreaker(),
                {
                  responseType: 'text'
                }
              )
            }
            getPASData() {
              return this.httpClient.get(
                'assets/pis/pas.txt?date=' + this.getCacheBreaker(),
                {
                  responseType: 'text'
                }
              )
            }
            getDynamicRoute() {
              return this.httpClient.get(
                'assets/pis/PresentNext.txt?date=' + this.getCacheBreaker(),
                {
                  responseType: 'text'
                }
              )
            }
            getJourneyStatus() {
              return this.date = new Date,
              this.httpClient.get(
                'assets/pis/jounney.txt?r=' + this.getCacheBreaker(),
                {
                  responseType: 'text'
                }
              )
            }
            getSD() {
              return this.date = new Date,
              this.httpClient.get(
                'assets/pis/sd.txt?r=' + this.getCacheBreaker(),
                {
                  responseType: 'text'
                }
              )
            }

getAPip() function was interesting, it was using the URL from config.js
I tried the endpoint and it returned two IP addresses

GET http://192.168.190.1:3000/getAP 

192.168.190.45
192.168.191.61

I wasn’t able to connect to the second IP since it wasn’t in my subnet range, but the first IP was running some sort of DLINK router(confirmed by the splash login page when I tried port 443)

The Crew API is some sort of NodeJS/Express server

GET http://192.168.190.1:3000/getAP -I
HTTP/1.1 200 OK
X-Powered-By: Express
Access-Control-Allow-Origin: *
Content-Type: text/html; charset=utf-8
Content-Length: 29
ETag: W/"1d-8a+on1OgGx3fJp7w78P968fKH/U"
Date: Mon, 22 Sep 2025 22:49:43 GMT
Connection: keep-alive

I tried some other ports on 192.168.190.1 since I noticed the info-tv’s randomly rebooting and for a split second you can see the browser trying to load something on the IP.

Yup. I was correct, it was running on port 8085, and it also had SSH and FTP ports open, though I wasn’t able to connect to them.

Port 8085 was querying live data over a websocket connection.

151.js

(
  () => {
    'use strict';
    var e,
    o = !0,
    i = function (t) {
      var s = new FileReader;
      s.readAsText(t.data),
      s.onloadend = p => {
        let r = JSON.parse(s.result.toString());
        253 == r.FunctionCode ? e.send('4,32') : postMessage(r)
      }
    },
    c = function (t) {
      o = !1
    },
    u = function (t) {
      o = !0,
      n()
    },
    l = function (t) {
      o = !0
    };
    function n() {
      setTimeout(
        function () {
          (e = new WebSocket('ws://' + location.hostname + ':8082')).onopen = c,
          e.onclose = u,
          e.onmessage = i,
          e.onerror = l
        },
        7000
      )
    }
    setInterval(() => {
      o &&
      postMessage({
        FunctionCode: 255
      })
    }, 11000),
    n()
  }
) ();

I dumped some websocket data, here below

��{ 
    "FunctionCode":253
}

��{
    "FunctionCode":7,
    "System": {
    "SystemVal": 3,
        "Date": "22-09-25",
        "Time": "17:13",
        "Speed": {
            "Title":"Speed",
            "Value":"62 kmph"
        },
        "ExpTimetoStn":  {
            "Title":"Time to Reach",
            "Value":"00:51"
        },
        "Delay":  {
            "Title":"Delay",
            "Value":"00:34"
        }, 
    "DNS":  {
            "Title":"Next Stop",
            "Value":"52 km"
        },       
        "LId": 0
    }
}
��{
    "FunctionCode":7,
    "System": {
    "SystemVal": 1,
        "Date": "22-09-25",
        "Time": "17:13",
        "Speed": {
            "Title":"Speed",
            "Value":"62 kmph"
        },
        "ExpTimetoStn":  {
            "Title":"Time to Reach",
            "Value":"00:51"
        },
        "Delay":  {
            "Title":"Delay",
            "Value":"00:34"
        }, 
    "DNS":  {
            "Title":"Next Stop",
            "Value":"52 km"
        },       
        "LId": 0
    }
}
��{
    "FunctionCode":7,
    "System": {
    "SystemVal": 1,
        "Date": "22-09-25",
        "Time": "17:13",
        "Speed": {
            "Title":"Speed",
            "Value":"62 kmph"
        },
        "ExpTimetoStn":  {
            "Title":"Time to Reach",
            "Value":"00:51"
        },
        "Delay":  {
            "Title":"Delay",
            "Value":"00:34"
        }, 
    "DNS":  {
            "Title":"Next Stop",
            "Value":"52 km"
        },       
        "LId": 0
    }
}
��{ 
    "FunctionCode":253
}

��{
    "FunctionCode":7,
    "System": {
    "SystemVal": 2,
        "Date": "22-09-25",
        "Time": "17:13",
        "Speed": {
            "Title":"Speed",
            "Value":"62 kmph"
        },
        "ExpTimetoStn":  {
            "Title":"Time to Reach",
            "Value":"00:51"
        },
        "Delay":  {
            "Title":"Delay",
            "Value":"00:34"
        }, 
    "DNS":  {
            "Title":"Next Stop",
            "Value":"52 km"
        },       
        "LId": 0
    }

Yes, there’s some weird characters present. I have no clue why, perhaps it uses some different encoding(?)

I also tried SSH and FTP,

SSH only accepted ssh-rsa, and further attempts failed because of modern libcrypto.
Unable to negotiate with 192.168.191.1 port 22: no matching host key type found. Their offer: ssh-rsa

SSH

soundar@fedora:~$ ssh -o HostKeyAlgorithms=+ssh-rsa -o PubkeyAcceptedAlgorithms=+ssh-rsa root@192.168.191.1 
The authenticity of host '192.168.191.1 (192.168.191.1)' can't be established. 
RSA key fingerprint is SHA256:y8tZWtbdyaF6aduhunz/hoQG4qPollA5PhFWCb0+GD4. 
This key is not known by any other names. 
Are you sure you want to continue connecting (yes/no/[fingerprint])? yes 
Warning: Permanently added '192.168.191.1' (RSA) to the list of known hosts. 
ssh_dispatch_run_fatal: Connection to 192.168.191.1 port 22: error in libcrypto

FTP

soundar@fedora:~$ curl ftp://192.168.190.1:21
curl: (67) Access denied: 530

That’s it for now. Maybe the next time I board a VB train I’ll dig in further.