A step-by-step tutorial explaing how to reverse engineering VStarCam H6837WI firmware

In this tutorial I will explain how to debug the encoder application to discover how it is controlling camera motor.

Inside camera (serial terminal) execute:

# ./gdbserver /mnt/system/bin/encoder                   
Process /mnt/system/bin/encoder created; pid = 326                              
Listening on port 3333

On your workstation:

$ arm-linux-gdb encoder
(gdb) target remote

Listing all defined functions:

(gdb) info fun
Non-debugging symbols:
0x0000a930  _init
0x0000a958  fileno
0x0000a964  getpagesize
0x0000a970  fputs
0x0000a97c  abort
0x0000a988  __errno_location
0x0000a994  sigemptyset
0x0000a9a0  sprintf
0x0000a9ac  popen
0x0000a9b8  open
0x0000a9c4  connect
0x0000a9d0  mmap
0x0000a9dc  snd_pcm_hw_params_any
0x0000a9e8  statfs64
0x0000a9f4  getpid
0x0000aa00  snd_output_close
0x0000aa0c  strerror
0x0000aa18  snd_pcm_close
0x0000aa24  getsockname
0x0000aa30  memcmp
0x0000aa3c  inet_ntoa
0x0000aa48  shutdown
0x0000aa54  snd_pcm_status_get_state
0x0000aa60  msgget
0x0000aa6c  signal
0x0000aa84  realloc
0x0000aa90  snd_pcm_hw_params_set_period_time_near
0x0000aa9c  snd_pcm_sw_params_get_xfer_align
0x0000aaa8  __xstat64
0x0000aab4  localtime
0x0000aac0  snd_pcm_prepare
0x0000aacc  snd_pcm_sw_params_sizeof
0x0000aad8  getgrnam
0x0000aae4  strchr
0x0000aaf0  vsnprintf
0x0000aafc  recv
0x0000ab08  getenv
0x0000ab14  sem_wait
0x0000ab20  inet_addr
0x0000ab2c  system
0x0000ab38  strncpy
0x0000ab44  putchar
0x0000ab50  write
0x0000ab5c  sendto
0x0000ab68  listen
0x0000ab74  lseek64
0x0000ab80  fgets
0x0000ab8c  memset
0x0000ab98  setitimer
0x0000aba4  fopen64
0x0000abb0  snd_pcm_status_get_trigger_tstamp
0x0000abbc  __strtol_internal
0x0000abc8  snd_pcm_hw_params_get_period_size
0x0000abd4  __libc_start_main
0x0000abe0  execl
0x0000abec  _IO_getc
0x0000abf8  _exit
0x0000ac04  snd_pcm_hw_params
0x0000ac10  snd_pcm_sw_params_set_avail_min
0x0000ac1c  strrchr
0x0000ac28  tcgetattr
0x0000ac34  snd_pcm_hw_params_sizeof
0x0000ac40  set_gpio_input
0x0000ac4c  piulib_init
0x0000ac58  read
0x0000ac64  msgsnd
0x0000ac70  perror
0x0000ac7c  usleep
0x0000ac88  snd_pcm_open
0x0000ac94  gettimeofday
0x0000aca0  fdopen
0x0000acac  __ctype_toupper_loc
0x0000acb8  free
0x0000acc4  siglongjmp
0x0000acd0  __lxstat64
0x0000acdc  snd_pcm_sw_params_set_xfer_align
0x0000ace8  snd_pcm_sw_params_set_sleep_min
0x0000acf4  access
0x0000ad00  sigaction
0x0000ad0c  fflush
0x0000ad18  snd_pcm_sw_params
0x0000ad24  piulib_exit
0x0000ad30  opendir
0x0000ad3c  accept
0x0000ad48  tcflush
0x0000ad54  ioctl
0x0000ad60  socket
0x0000ad6c  dup2
0x0000ad78  __ctype_b_loc
0x0000ad84  fseek
0x0000ad90  inet_aton
0x0000ad9c  pthread_mutex_unlock
0x0000ada8  snd_pcm_status_sizeof
0x0000adb4  isatty
0x0000adc0  umask
0x0000adcc  fclose
0x0000add8  setuid
0x0000ade4  snd_pcm_writei
0x0000adf0  mktime
0x0000adfc  readdir64
0x0000ae08  memcpy
0x0000ae14  cfsetospeed
0x0000ae20  strlen
0x0000ae2c  snd_pcm_wait
0x0000ae38  fopen
0x0000ae44  snd_pcm_readi
0x0000ae50  snd_pcm_resume
0x0000ae5c  snd_strerror
0x0000ae68  unlink
0x0000ae74  snd_pcm_sw_params_set_stop_threshold
0x0000ae80  getpwuid
0x0000ae8c  getppid
0x0000ae98  waitpid
0x0000aea4  feof
0x0000aeb0  strcpy
0x0000aebc  longjmp
0x0000aec8  printf
0x0000aed4  ftok
0x0000aee0  chdir
0x0000aeec  ctime
0x0000aef8  bind
0x0000af04  getuid
0x0000af10  snd_config_update_free_global
0x0000af1c  atoi
0x0000af28  mkstemp64
0x0000af34  select
0x0000af40  closedir
0x0000af4c  close
0x0000af58  fwrite
0x0000af64  snd_pcm_format_physical_width
0x0000af70  snd_pcm_hw_params_get_buffer_time_max
0x0000af7c  fprintf
0x0000af88  strstr
0x0000af94  time
0x0000afa0  setvbuf
0x0000afac  execve
0x0000afb8  malloc
0x0000afc4  snd_pcm_state_name
0x0000afd0  snd_pcm_hw_params_get_buffer_size
0x0000afdc  pthread_mutex_lock
0x0000afe8  sigprocmask
0x0000aff4  snd_pcm_info_sizeof
0x0000b000  gethostname
0x0000b00c  snd_pcm_hw_params_set_rate_near
0x0000b018  pthread_create
0x0000b024  snd_pcm_hw_params_set_access
0x0000b030  sleep
0x0000b03c  sigaddset
0x0000b048  msgrcv
0x0000b054  strncasecmp
0x0000b060  memmove
0x0000b06c  strcat
0x0000b078  send
0x0000b084  snd_pcm_hw_params_set_channels
0x0000b090  getcwd
0x0000b09c  sem_post
0x0000b0a8  snd_pcm_format_set_silence
0x0000b0b4  settimeofday
0x0000b0c0  puts
0x0000b0cc  fork
0x0000b0d8  __fxstat64
0x0000b0e4  setsockopt
0x0000b0f0  tcsetattr
0x0000b0fc  fcntl
0x0000b108  snd_pcm_hw_params_set_buffer_time_near
0x0000b114  sscanf
0x0000b120  gmtime
0x0000b12c  memchr
0x0000b138  strncmp
0x0000b144  open64
0x0000b150  snd_pcm_info
0x0000b15c  munmap
0x0000b168  pipe
0x0000b174  fread
0x0000b180  getsockopt
0x0000b18c  setgid
0x0000b198  snprintf
0x0000b1a4  mmap64
0x0000b1b0  init_gpio_lib
0x0000b1bc  snd_output_stdio_attach
0x0000b1c8  gethostbyname
0x0000b1d4  recvfrom
0x0000b1e0  ferror
0x0000b1ec  getpwnam
0x0000b1f8  strcmp
0x0000b204  __sigsetjmp
0x0000b210  herror
0x0000b21c  piu_register
0x0000b228  __strdup
0x0000b234  exit
0x0000b240  cfsetispeed
0x0000b24c  pclose
0x0000b258  sem_init
0x0000b264  set_gpio_output
0x0000b270  snd_pcm_hw_params_set_format
0x0000b27c  getgid
0x0000b288  snd_pcm_sw_params_current
0x0000b294  get_gpio_value
0x0000b2a0  snd_pcm_status
0x0000b2ac  piu_tx
0x0000b2b8  snd_pcm_sw_params_set_start_threshold
0x0000b2c4  geteuid
0x00049070  _fini
0x40008b20  _dl_rtld_di_serinfo
0x0000b294  get_gpio_value
0x0000b2a0  snd_pcm_status
0x0000b2ac  piu_tx
0x0000b2b8  snd_pcm_sw_params_set_start_threshold
0x0000b2c4  geteuid
0x00049070  _fini
0x40008b20  _dl_rtld_di_serinfo
0x4000f660  _dl_debug_state
0x40010f60  _dl_mcount
0x40011320  __tls_get_addr
0x40011670  _dl_tls_setup
0x40011740  _dl_get_tls_static_info
0x40011850  _dl_allocate_tls_init
0x40011a90  _dl_allocate_tls
0x40011ac0  _dl_deallocate_tls
0x40011e60  ___tls_get_addr
0x400122a0  _dl_make_stack_executable
0x400157b0  __libc_memalign
0x400158c0  malloc
0x400158f0  calloc
0x40015950  free
0x40015a30  realloc

We are insterested to know which files were opened and what was write to it.

Then we will put breackpoints at open, open64, fopen and write:

(gdb) b open
Breakpoint 1 at 0xa9b8
(gdb) b fopen
Breakpoint 2 at 0xae38
(gdb) b write
Breakpoint 3 at 0xab50
(gdb) b open64
Breakpoint 6 at 0xb144
(gdb) b popen
Breakpoint 7 at 0xa9ac
(gdb) c

Now let us to discover what is happening.

(gdb) target remote run
A program is being debugged already. Kill it? (y or n) n
Program not killed.
(gdb) c

Breakpoint 3, 0x0000ab50 in write ()

Let’s find out which file descriptor and what is written on it:

(gdb) p /x $r0
$1 = 0x4

Hmm, file descriptor 4, let us to know what is it…

$ telnet
Connected to
Escape character is '^]'.

(none) login: root
warning: cannot change to home directory
/ # ps ax
  323 root       1728 S   ./gdbserver /mnt/system/bin/encode
  326 root       3868 T   /mnt/system/bin/encoder 
  327 root       2840 S   -sh 
  351 root       2840 R   ps ax 
/ #

Ok, Process ID of encoder is 326, then let see which files are opened for this process:

/ # ls -l /proc/326/fd/
lrwx------    1 root     root           64 Jan  1 01:13 0 -> /dev/ttyS0
lrwx------    1 root     root           64 Jan  1 01:13 1 -> /dev/ttyS0
lrwx------    1 root     root           64 Jan  1 01:13 2 -> /dev/ttyS0
lrwx------    1 root     root           64 Jan  1 01:13 3 -> socket:[148]
lrwx------    1 root     root           64 Jan  1 01:13 4 -> /dev/i2c-0

Wow, it is /dev/i2c-0

Now let us to return to gdb interface to know what will be write to it:

Breakpoint 3, 0x0000ab50 in write ()
(gdb) p /x $r0
$1 = 0x4
(gdb) x /1x $r1
0xbed7d9d7:	0x0000000a
(gdb) p /x $r2
$2 = 0x1

Hmm, it will write only 1 byte (r2 = 1) and this byte it 0x0A (pointer at r1).

Ok, move on…

Another write operation:

Breakpoint 3, 0x0000ab50 in write ()
(gdb) p /x $r0
$3 = 0x4
(gdb) x /1x $r1
0xbed7d9d7:	0x0000000b
(gdb) p /x $r2
$4 = 0x1

Hey, it should be reading something from i2c as well, let us to put a breakpoint at read function:

(gdb) b read
Breakpoint 4 at 0xac58
(gdb) c

Breakpoint 4, 0x0000ac58 in read ()

Let gather more information:

(gdb) info frame
Stack level 0, frame at 0xbed7d9d4:
 pc = 0xac58 in read; saved pc 0x28d5c
 called by frame at 0xbed7d9d4
 Arglist at 0xbed7d9d4, args: 
 Locals at 0xbed7d9d4, Previous frame's sp is 0xbed7d9d4

This function was called from 0x28d5c (saved pc).

We could obtain this information from link register (lr) as well, lr is an alias to r14:

(gdb) p /x $r14
$5 = 0x28d5c

Good, let put a breakpoint at 0x28d5c

(gdb) b *0x28d5c
Breakpoint 5 at 0x28d5c

Now, let run the read function and wait it return to 0x28d5c

Breakpoint 5, 0x00028d5c in ?? ()
(gdb) p /x $r0
$6 = 0x1
(gdb) x /1x $r1
0xbed7d9d6:	0x21

Ok, it read 1 byte as expected and this byte is 0x21.

Keep going:

(gdb) c

Breakpoint 3, 0x0000ab50 in write ()

A new write:

(gdb) p /x $r0
$7 = 0x4
(gdb) x /1x $r1
0xbed7d9e0:	0x12
(gdb) p /x $r2
$8 = 0x2

It is writing at same file descriptor (4 = i2c-0), but now it is write 2 bytes, first one is 0x12, but let us see both:

(gdb) x /1x 0xbed7d9e0
0xbed7d9e0:	0x12
(gdb) x /1x 0xbed7d9e1
0xbed7d9e1:	0x80

Ok, let’s to continue:

(gdb) c

Breakpoint 3, 0x0000ab50 in write ()
(gdb) p /x $r0
$9 = 0x4
(gdb) x /1x $r1
0xbed7d9e0:	0x12
(gdb) p /x $r2
$10 = 0x2
(gdb) x /1x 0xbed7d9e1
0xbed7d9e1:	0x00

And another write, but now the second value is 0x00 instead of 0x80.

In this mean time I noticed these text printed at minicom (where gdbserver is running) :

ov7725 id1 id2:77-21                                                            
this is ov7725                                                                  
reglen 97

So, this writing/reading is to initialize ov7725 camera, nice to know, but I’m not interested on camera sensor, not yet.

Then let to remove breakpoints at write and read, and keep only with open and fopen:

Delete write breakpoint:
(gdb) delete 3
Delete read breakpoint:
(gdb) delete 4
Delete breakpoint of read function return:
(gdb) delete 5

Program received signal SIG32, Real-time event 32.
0x4002712c in ?? ()
(gdb) c

Then it will open in the sequence:

And now this is a very suspect file:

Breakpoint 6, 0x0000b144 in open64 ()
(gdb) x /1s $r0
0x4ad40:	"/dev/ttyS1"

Let us to put a breakpoint at return of this function, then we will know the file descriptor number of this file:

(gdb) p /x $r14
$12 = 0x12ac4
(gdb) b *0x12ac4
Breakpoint 9 at 0x12ac4
(gdb) c

You will see many this signal events, before open64 function to return:

Program received signal SIG32, Real-time event 32.
[Switching to Thread 369]
0x4002712c in ?? ()
(gdb) c
[Switching to Thread 326]

If you get bored then you could remove the breakpoint at open64, it will speed up this process:

Breakpoint 6, 0x0000b144 in open64 ()
(gdb) delete 6
(gdb) c

And finally:

Breakpoint 9, 0x00012ac4 in ?? ()

Now we can see the file descriptor number:

(gdb) p /x $r0
$13 = 0xc

In fact I can see it at proc:

/ # ls -l /proc/326/fd/
lrwx------    1 root     root           64 Jan  1 01:47 12 -> /dev/ttyS1

This is file descriptor 12 (0xC).

Please continue with debugging until point where camera start to do self calibration.

Now we can return with breackpoint at write function:

(gdb) b write
Breakpoint 10 at 0xab50
(gdb) c

Now switch to telnet terminal and execute the CGI script to move camera up:

/tmp # export QUERY_STRING=command=1\&user=admin\&pwd=
/tmp # ./decoder_control.cgi 

/tmp #

Switch back to gdb terminal and continue with execution:

(gdb) c
[New Thread 355]
[Switching to Thread 355]

Breakpoint 10, 0x0000ab50 in write ()

The encoder is now trying to write something in the camera:

(gdb) p /x $r0
$16 = 0xc
(gdb) x /1s $r1
0xbe5fe044:	"\34201\n\377"
(gdb) p /x $r2
$17 = 0x8

It is expecting to write 8 bytes, let see what are these bytes:

(gdb) x /1x 0xbe5fe044
0xbe5fe044:	0xe2
(gdb) x /1x 0xbe5fe045
0xbe5fe045:	0x01
(gdb) x /1x 0xbe5fe046
0xbe5fe046:	0x0a
(gdb) x /1x 0xbe5fe047
0xbe5fe047:	0xff
(gdb) x /1x 0xbe5fe048
0xbe5fe048:	0x00
(gdb) x /1x 0xbe5fe049
0xbe5fe049:	0x00
(gdb) x /1x 0xbe5fe04a
0xbe5fe04a:	0x00
(gdb) x /1x 0xbe5fe04b
0xbe5fe04b:	0x23

Great, we could try to send these sequence directly from telnet terminal:

/tmp # printf "\xE2\x01\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Worked fine!!! Now these are other commands:

Move Up:
# printf "\xE2\x01\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Move Down:
# printf "\xE2\x02\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Move Right:
# printf "\xE2\x03\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Move Left
# printf "\xE2\x06\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Move Right-Up
# printf "\xE2\x04\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Move Left-Down:
# printf "\xE2\x08\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Move Right-Down:
# printf "\xE2\x05\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Move Left-Up:
# printf "\xE2\x07\x0A\xFF\x00\x00\x00\x23" > /dev/ttyS1

Other commands are also supported as horizontal/vertical patrol.

Update: Other interesting start point to you know more about GDB:

4 thoughts on “A step-by-step tutorial explaing how to reverse engineering VStarCam H6837WI firmware

  1. Hi Tinti,

    Thanks for your suggestion. I used strace as well, but strace shows only the tip of iceberg.
    To use strace in multithread programs (like this one) use: strace -fxi program.

    You can use -e parameter to show only syscalls you are interested.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s