GDB basics with C

This is a beginner level tutorial on learning basics of debugging using GDB by debugging an executable. This post will cover writing a very simple C code, compiling it and then opening the generated executable in GDB for inspecting the working/debugging. The primary objective here is to get familiar with the basics of using GDB.

Requirements:

  1. Any Linux x64 OS. (Linux Mint 20 used in this tutorial)
  2. C compiler – gcc
  3. gdb – for debugging / reverse engineering.
  4. GDB Dashboard (Optional – Makes GDB easier to read)

[Disclaimer: This is blog post adapted from recurse’s original gdb tutorial.(All Credits/References added in the Credits section)]

Setting up the Environment:

  • Install the following tools to setup the basic environment:

sudo apt install vim gcc gdb git python3 python3-pip -y

GDB Dashboard:

  • To make GDB easier to understand and make it non-alien, use GDB Dashboard which is a really good python plugin  for GDB.  Its epic!
  • Run the following to get the gdbinit dotfile.
wget -P ~ https://git.io/.gdbinit

If you would like to check out the source, you can find it  @ https://github.com/cyrus-and/gdb-dashboard

For syntax highlighting, you would need the pygments module. If you are using Python2.x:

pip install pygments

If you are using Python3.x, then use pip3 to install it:

pip3 install pygments

To run gdb without providing any executable, run “gdb” and you should see the something like below:

extr3me@w4rl0ck:gdb$ gdb
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
    <http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word".
>>> 

To exit out of gdb, type  “quit” .

-Below is a simple C program that initializes an integer value and then returns 0.

cat minimal.c
int main()
{
int i = 1337;
return 0;
}

-Compile the C code and make an executable, using the following flags:

gcc -g minimal.c -o minimal

-The directory should now have a executable with filename minimal.

extr3me@w4rl0ck:gdb$ file minimal
minimal: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=c918c072d119be6a9d18991e812a5414ecae67e1, for GNU/Linux 3.2.0, with debug_info, not stripped

Debugging the binary using GDB:

-To examine an executable in GDB, use the following format:

gdb <executable_file>

-Here, I would run the following to open the executable for debugging:

gdb minimal

gdb minimal output

Lets check the functions in this program, to do run “info functions”. You should be able to see that this executable has function main().

>>> info functions
All defined functions:

File minimal.c:
1: int main();

Non-debugging symbols:
0x0000000000001000 _init
0x0000000000001030 __cxa_finalize@plt
0x0000000000001040 _start
0x0000000000001070 deregister_tm_clones
0x00000000000010a0 register_tm_clones
0x00000000000010e0 __do_global_dtors_aux
0x0000000000001120 frame_dummy
0x0000000000001140 __libc_csu_init
0x00000000000011b0 __libc_csu_fini
0x00000000000011b8 _fini
Here is a screenshot:

gdb info functions output screenshot

The output also shows that the line number at which main() function is present in the source minimal.c.

From the info functions output, we can also see that function main() is present at line number 1 in source minimal.c.

Breakpoints:

-Lets say we need to examine what a executable is going at a given point of time or to inspect the value of a variable, then we can set one or more breakpoints and run the code upto that breakpoint. You can then examine the the value of a variable upto that breakpoint or directly examine the memory etc.

Breakpoints example: Setting breakpoints using function name

From the previous section,we know that the executable has function main(). Lets start examining the binary using gdb to see the value of “i” at different stages of the execution. To start with,  set a breakpoint at function main using the syntax break <function_name>:

>>> break main
Breakpoint 1 at 0x1129: file minimal.c, line 2.
>>>

To get the list of breakpoints that were set, you can use “info breakpoints”.

>>> info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000000000001129 in main at minimal.c:2

gdb info breakpoints

The above shows that there is one breakpoint which is set at function main() whose starting address in memory is “0x1129” or “0x0000000000001129“.

Note: You can have create multiple breakpoints and they are numbered.

So we have a breakpoint in place, we can run the binary upto the breakpoint that was set. To run the program, use “run” or “r“.

gdb screenshot

This is what it looks like now. (You could now see the GDB dashboard with an insane amount of matrix looking stuff. :P)

[Note:  Right click the image and click on “View Image” / “Open image in new tab” if the image for better visibility. ]

GDB Dashboard screenshot

For this tutorial, we are more interested in few sections of Dashboard as of now –   “Source” , “Threads”, “Variables” and “Assembly” sections.

-The “Source” section has a blip on the line number which shows where exactly in the code where gdb is looking into. So, here, it shows that its in line 2 of the source. After you ran the program, if you re-run “info breakpoints” you can see that the message “breakpoint already hit 1 time“.

>> info breakpoints
Num Type Disp Enb Address What
1 breakpoint keep y 0x0000555555555129 in main at minimal.c:2
breakpoint already hit 1 time

 

[Note:  Right click the image and click on “View Image” / “Open image in new tab” if the image for better visibility. ]

As of now, the debugger is in line 2 where the variable “i” is not yet yet initiated in the program. So, if you try to print the value of “i”, then it should show a value = 0.

To print value of a variable, use the format “print var” or “p var“.

>> p i
$1 = 0

 

print i output screenshot

To go to the next line of code and execute it, use command “next” or “n“.  [Note: If the next line of code was a function, it would execute the full function). In this example, once we ran “r“, the “Source” section now highlights line number 3.

gdb print i screenshot 2

At this point, if you print variable “i”, it still shows value as “0” as the initialization is not complete.

You can also check the “Assembly” section in the GDB Dashboard which shows the disassembled code. So, the highlighted section in green in the  “Assembly” shows the following:

0x0000555555555131 main+8 movl $0x539,-0x4(%rbp)

This is the disassembled code (in AT&T syntax) for “int i = 1337”. We will come back to reviewing the assembly code later.

Run command “n” again in gdb. Now, checking the “Source” section in GDB dashboard, should that we are now on line 4.

Now. check the value of  variable “i” using print.

>>> p i
$3 = 1337

This shows that the value variable “i” is 1337.

(gdb) print i
$2 = 1337

Here is the screenshot for reference.

GDB print i after initialized

To find the type of the variable and the starting memory address of that variable in memory:
>>> print &i
$4 = (int *) 0x7fffffffdb8c

GDB print directly using variable address output

-The above shows that “i” is of “int” type. Additionally, “i” is stored at memory location starting at address 0x7fffffffdb8c

-To check maximum size of int type in memory using the sizeof() function.

>>> p sizeof(int)
$5 = 4

The above output shows that int would occupy 4 bytes of space in memory.

To examine memory using gdb use “x“.

-From above outputs, we know that variable “i” is stored in memory with starting address as 0x7fffffffdb8c. We also do know that i is of type integer and integer type would occupy a maximum of 4 bytes in memory.

To examine a specific memory address, you could use the following format

(gdb) x/FMT <starting_memory_address> 

Here is information from “help x” section:

(gdb) help x
Examine memory: x/FMT ADDRESS.
ADDRESS is an expression for the memory address to examine.
FMT is a repeat count followed by a format letter and a size letter.
Format letters are o(octal), x(hex), d(decimal), u(unsigned decimal),
t(binary), f(float), a(address), i(instruction), c(char), s(string)
and z(hex, zero padded on the left).
Size letters are b(byte), h(halfword), w(word), g(giant, 8 bytes).
The specified number of objects of the specified size are printed
according to the format. If a negative number is specified, memory is
examined backward from the address.

Defaults for format and size letters are those previously used.
Default count is 1. Default address is following last thing printed
with this command or "print".
(gdb)

Here, is an example, to examine data from memory location 0x7fffffffdb8c upto the next bytes (or 4 bytes above 0x7fffffffdb8c) ,  use the following:

>>> x/4xb 0x7fffffffdb8c
0x7fffffffdb8c: 0x39 0x05 0x00 0x00

Alternatively, you can access provide the memory location directly by passing “&i” i.e.  “x/4xb &i”

GDB examine memory location screenshot

The above output shows raw byte by byte representation in memory. Here, 0x39 is one byte in memory and 0x05 is another byte etc.
We also know that int would occupy 4 bytes in memory. Here, this integer takes only 2 bytes out of 4 bytes in memory.

Important: This raw memory representation “0x39 0x05 0x00 0x00” is in “little-endian” format(the least significant bytes of a number come first in memory).
So, you would need to read the hex bytes  “0x39 0x05 0x00 0x00” in reverse order. Hence, the value reversed 0x00 0x00 0x05 0x39 is 00000539.
To get the actual value, we need to convert the hex value to decimal.

You could use trusty bash  to covert hex to decimal by using format “echo $(( 16#$hexNum ))“. Here is what it does look like:

extr3me@w4rl0ck:~$ echo $(( 16#00000539 ))
1337

So, “1337” is the decimal value stored in memory location 4 bytes starting from 0x7fffffffdb8c.

So, we now know the value of integer variable starting at memory location 0x7fffffffdb8c is 1337. (i.e. the decimal value of i stored in memory is 1337).

An alternative is to use a online hex to decimal converter such as https://www.binaryhexconverter.com/hex-to-decimal-converter to covert 00000539 to decimal.

-You can also print the raw data using the variable itself by using the following format:

(gdb) x/4xb &i

0x7fffffffdb5c: 0x39 0x05 0x00 0x00

Other formats in GDB:

-To print in decimal format, use x/1dw:

(gdb) x/1dw &i
0x7fffffffdb7c: 1337

-Alternatively, give the memory location as well.

(gdb) x/1dw 0x7fffffffdb7c
0x7fffffffdb7c: 1337

Misc Information:

To check the number of threads, run “info threads“.

>>> info threads
Id Target Id Frame 
* 1 process 6570 "minimal" main () at minimal.c:4
>>> 

This shows there is 1 thread with PID 6570.

Other Interesting Stuff:

Here is a screenshot of the dissembled code of “minimal” binary.

#gdb-assembly.png

The one that we are interested in for now is the instruction highlighted below:

0x0000555555555129 main+0 endbr64
0x000055555555512d main+4 push %rbp
0x000055555555512e main+5 mov %rsp,%rbp
0x0000555555555131 main+8 movl $0x539,-0x4(%rbp)
0x0000555555555138 main+15 mov $0x0,%eax
0x000055555555513d main+20 pop %rbp
0x000055555555513e main+21 retq

The instruction “movl $0x539,-0x4(%rbp)” means => move the value “0x539” to the memory location of register rbp -4.

Differences between AT&T and Intel Syntax:

By default, GDB defaults to displaying in AT&T syntax. Here is what main function dissembled looks like in AT&T syntax:

extr3me@w4rl0ck:gdb$ gdb minimal
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from minimal...
>>> disass /m main
Dump of assembler code for function main:
2 {
0x0000000000001129 <+0>: endbr64
0x000000000000112d <+4>: push %rbp
0x000000000000112e <+5>: mov %rsp,%rbp

3 int i = 1337;
0x0000000000001131 <+8>: movl $0x539,-0x4(%rbp)

4 return 0;
0x0000000000001138 <+15>: mov $0x0,%eax

5 }
0x000000000000113d <+20>: pop %rbp
0x000000000000113e <+21>: retq

End of assembler dump.
>>>

-If you don’t like this syntax, you can make GDB use Intel syntax using “set disassembly-flavor intel“.

Here, is an example of main function dissasembled in Intel syntax:

extr3me@w4rl0ck:gdb$ gdb minimal
GNU gdb (Ubuntu 9.2-0ubuntu1~20.04) 9.2
Copyright (C) 2020 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<http://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from minimal...
>>> set disassembly-flavor intel
>>> disass /m main
Dump of assembler code for function main:
2 {
0x0000000000001129 <+0>: endbr64
0x000000000000112d <+4>: push rbp
0x000000000000112e <+5>: mov rbp,rsp

3 int i = 1337;
0x0000000000001131 <+8>: mov DWORD PTR [rbp-0x4],0x539

4 return 0;
0x0000000000001138 <+15>: mov eax,0x0

5 }
0x000000000000113d <+20>: pop rbp
0x000000000000113e <+21>: ret

End of assembler dump.
>>>

Here, you should see subtle differences  such as the AT&T syntax displays “movl $0x539,-0x4(%rbp)” while the same in Intel syntax is “mov DWORD PTR [rbp-0x4],0x539“.

Thats it for now. I will add more tutorials on GDB in the near future.

Happy Debugging!

Sources/References/Credits:

Below are all the credits/references/sources that made writing this blog post possible.

https://www.recurse.com/blog/5-learning-c-with-gdb <- Credits to recurse. I used this as my primary resource to learn GDB and then post my understanding of GDB and C here. Do check them out.
https://github.com/cyrus-and/gdb-dashboard <- Creator of the Epic GDB dashboard
https://www.tutorialspoint.com/gnu_debugger/ <- Great place to start.
https://sourceware.org/gdb/current/onlinedocs/gdb/Set-Breaks.html#Set-Breaks
https://stackoverflow.com/questions/209534/how-to-highlight-and-color-gdb-output-during-interactive-debugging
https://bob.cs.sonoma.edu/IntroCompOrg-RPi/sec-gdb1.html
https://www.binaryhexconverter.com <- Simple online convertor
https://stackoverflow.com/questions/13280131/hexadecimal-to-decimal-in-shell-script
https://www.ibm.com/developerworks/library/l-ia/index.html
Credits to my m8 @bytesareana for decoding the stuff with the memory location.

Decoding IR Signals of a Blue Star Air Conditioner using an Arduino

This is part of my project to understand the IR protocol and try to decode IR signals that being send from my AC’s remote to the AC itself. So that the idea is: If I can figure out the different IR signals that being that transmitted, then I should be able to replay the traffic and control the Aircon from my PC or use my own custom IR hub instead of sending data to some third party – Alexa or Google Assistant. This may be applicable on other devices as well that use an IR transmitter/receiver like a TV/remote controlled fan/hubs.

Disclaimer: I am no means an electronics expert. This is just me experimenting and playing around trying to make sense of how IR works with no prior knowledge of how the protocol works. If you are electronics expert, then this post is probably not for you are better off pressing CTRL+W on your keyboard.

Requirements:

  1. KY-022 Infrared Receiver (38kHz)
  2. Arduino UNO U3
  3. Breadboard
  4. 3x Male to Male Jumper Cables.
  5. Arduino ID with IRremote library installed (Link)
  6. Computer to write your code. Duh!

Here is a picture of the KY-22 IR receiver.

ky-022 IR receiever picture

Circuit Connection:

Connect pin labelled “Y” on the KY-022 to Pin 11 on the Arduino
Connect pin labelled G to GND pin on the Arduino
Connect pin labelled R pin to the 5V pin on Arduino

-Below is the source for the IR decoder:

#include <IRremote.h>

int RECV_PIN = 11; // define input pin on Arduino.
IRrecv irrecv(RECV_PIN); 
decode_results results; // decode_results class is defined in IRremote.h

IRsend irsend; 

void setup() { 
  Serial.begin(9600); // Set Serial monitor baud rate
  irrecv.enableIRIn(); // Start the receiver 
} 

void loop() {

  if (irrecv.decode(&results)) {
    Serial.println(results.value, HEX); 
    irrecv.resume(); // Receive the next value 
  }
 
  delay(1000);
  
}

Upload the code to the Arduino and then navigate to Tools>Serial Monitor.

Now point your IR remote to the IR receiver sensor and press any key in the remote. Now, you should see the HEX value that was send from the remote in the serial console.

For example, a test case I pointed my AC’s remote to the IR receiver(KY-022) and pressed the power button. This result printed on the Serial Monitor was HEX value 90900E0A. Note: While pressing the power On button, the remote had 25 degrees set.

Here is a sample screenshot of the setup.

sending and decoding IR picture using KY-022

Here is a screenshot of the source and the data from the serial session.

arduino code and serial monitor displaying decoded IR signal

Similarly when pressing the Power Off button in the return, a different IR code of 80900C0A was send.

Hell Yeah!!!!! Ok, maybe I got excited bit too much.

I know this probably sounds dumb, but I did notice something interesting on how this is all put together. So, lets say the temperature of the AC in the remote is set to 16C. Now, if I press the “+” button to increase the temperature to 17C, then I see a different code and if I press it again again, I see a different code being send.

Here what the different hex codes for different temperatures look like(used by pressing the increment/+ sign on the remote):

Temp 16: 90000E0A
Temp 17: 90800E0A
Temp 18: 90400E0A
Temp 19: 90C00E0A
Temp 20: 90200E0A
Temp 21: 90A00E0A
Temp 22: 90600E0A
Temp 23: 90E00E0A
Temp 24: 90100E0A
Temp 25: 90900E0A
Temp 26: 90500E0A
Temp 27: 90D00E0A
Temp 28: 90300E0A
Temp 29: 90B00E0A
Temp 30: 90700E0A

Trying to make sense of IR (Sort Of):

So, in a programming point of view, I was expecting something like “new temperature” = “current temperature” + 1 and then set the value rite? Well this sort of the same with a slight difference which I guess is the way IR protocol works. So, the handheld IR remote has some logic in itself. Here is my assumption of what is happening.

  1. Say, the handheld IR remote for the Air Conditioner has current temperate as 16 degree Celsius (16C).
  2. Now lets say the user, clicks the + sign on the AC remote (IR transmitter) to increase the temperature from 16C to 17C. The IR device knows the old value as 16 from its on-board memory and now knows the new temperate has to be set to 17C. So here, the arithmetic operation i.e. increment is done on the client side i.e on the IR remote itself.
  3. The IR remote has a hard coded value of 17C somewhere in its code. The remote simply sends the IR signal with IR code for 17C. I.e. It will send 90800E0A to the IR Receiver.
  4. The IR receiver (in this case the AC), receives the IR code for 17C and then sets the temperature to 17C. Here, the AC has hardcoded values for each IR code for each temperature in its code. Something like: If “IR code received” == “90800E0A“, then set temperature to 17C.
  5. It looks more like each temperate value is hard coded and the IR remote has these values hard-coded.

Next, I will need to learn how to get a IR transmitter and replay these codes so that I can control my Aircon and other electronics from my PC. Maybe create a DIY IR hub and hook it to my PC?

So, I did buy a IR transmitter to replay the signals but the transmitter that I received was DOA. Well that went well. Haha!

ir transmitter picture

Anyways, will probably order a new transmitter to test this out post the whole COVID-19 shiZstorm.

Regards.

ΞXΤЯ3МΞ

Sources/Credits/References:

PS: Credits to Arduino modules for making it super simple to understand this. Below are credits/resources that helped out.

Arduino Modules.info
IR-Project
Duino4projects
Sparkfun
Robojax

Reverse Engineering Router Firmware TP-Link TD-W8970

This is a blog post on reverse engineering TP Link  TD-W8970v3 router firmware.

Requirements:

      • Router Firmware  [ TP-Link TD-W8970 v3 ]
      • Linux Tools – binwalk, unsquashfs, dd, strings
      • Optional: “John” a.k.a john the ripper (for Brute forcing passwords)

Disclaimer:

This is strictly for educational purposes ONLY and not be used for conducting any illegal activities. I hold no responsibility for misuse of this information.

Download the firmware:

First, we need to download the firmware that we need to reverse engineer. I am using the TP Link TD-W8970 v3 firmware.

To download the firmware, go to the below link. -Select “V3” as the version and click on “Firmware”.

https://www.tp-link.com/us/support/download/td-w8970/

TP Link TD-W8970 firmware download page

 

Download the firmware.

TP Link TD-W8970 select latest firmware

Time to dig around!!

Copy the firmware to a new location and extract it.

mkdir ~/firmware
cp ~/Downloads/TD-W8970_V3_150427.zip ~/firmware/
cd firmware/
unzip TD-W8970_V3_150427.zip
cd TD-W8970\(UN\)_V3_150427/

So, here we can see the firmware upgrade image itself with the “.bin” extension along with the firmware upgrade guide.

$ ls -l
total 8296
-rw-rw-r-- 1 extr3me extr3me  317017 Dec 25  2013 'How to upgrade TP-LINK ADSL Modem Router - Copy.pdf'
-rw-rw-r-- 1 extr3me extr3me 8174304 Apr 27  2015 'TD-W8970v3_0.9.1_1.2_up_boot(150427)_2015-04-27_17.48.51.bin'
extr3me@op3n TD-W8970(UN)_V3_150427 $

For sanity purposes, I removed the firmware upgrade guide which I don’t need here.

$ rm How\ to\ upgrade\ TP-LINK\ ADSL\ Modem\ Router\ -\ Copy.pdf

Inspecting the binary with “file” command shows that it of type “data”.

$ file TD-W8970v3_0.9.1_1.2_up_boot\(150427\)_2015-04-27_17.48.51.bin
TD-W8970v3_0.9.1_1.2_up_boot(150427)_2015-04-27_17.48.51.bin: data

Below is a screenshot:

 

checking the binary using "file" command

I tried to run hexdump and filter out some data but did not get any useful info here yet.

$ hexdump -C TD-W8970v3_0.9.1_1.2_up_boot\(150427\)_2015-04-27_17.48.51.bin | head -10
00000000  03 00 00 00 76 65 72 2e  20 32 2e 30 00 ff ff ff  |....ver. 2.0....|
00000010  ff ff ff ff ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
*
00000030  ff ff ff ff 08 97 00 03  00 00 00 35 00 00 00 00  |...........5....|
00000040  9c e8 56 2f 7d cd f2 5a  80 92 27 b5 dd 23 66 ea  |..V/}..Z..'..#f.|
00000050  00 00 00 00 ff ff ff ff  ff ff ff ff ff ff ff ff  |................|
00000060  ff ff ff ff ff ff ff ff  80 01 00 00 80 2e 00 20  |............... |
00000070  00 7e 02 00 00 00 02 00  00 13 d6 d3 00 13 d6 e0  |.~..............|
00000080  00 67 e0 00 00 00 00 00  00 00 e3 90 55 aa 01 02  |.g..........U...|
00000090  a5 00 09 01 55 d1 51 bb  ff ff ff ff ff ff ff ff  |....U.Q.........|

Now, use the tools “strings” to print out all the human readable strings in the binary. Here, I filtered the 1st 10 lines.

-The output does show two strings that seemed interesting.

$ strings TD-W8970v3_0.9.1_1.2_up_boot\(150427\)_2015-04-27_17.48.51.bin | head -10
ver. 2.0
79!8
@ !<
6H!$
cfe-v
e=192.168.1.1:ffffff00 h=192.168.1.100 g= r=f f=vmlinux i=bcm963xx_fs_kernel d=1 p=0
96361I2
  c
!P%@
!H$5)

A bit off topic. But out of curiosity, I searched online for “bcm963xx_fs_kernel” and came across a PDF document “Broadcom BCM963xx CFE Boot Loader and Flash Memory Structure Application Notes”‘ from Jan 2006.

search result

Here is a screenshot of the document itself:

broadcom CFE PDF document

So, it looks like the router firmware has “Broadcom” and we now know the bootloader and we can get more information from the PDF.

Here is a link to the document: Broadcom CFE Link

cfe-v
e=192.168.1.1:ffffff00 h=192.168.1.100 g= r=f f=vmlinux i=bcm963xx_fs_kernel d=1 p=0

The strings output also shows “cfe-v” which looks to “Common Firmware Environment”.

It also has information on the CFE bootloader flash memory:

Broadcom bootloader flash memory architecture

In Page 5 of the Broadcom documentation, it does refer to the TFTP flashing method and could see some similarties from the output of the “strings” command.

minicom update via TFTP

T his is what I could come up so far, comparing the screenshot and the below line:

e=192.168.1.1:ffffff00 h=192.168.1.100 g= r=f f=vmlinux i=bcm963xx_fs_kernel d=1 p=0“,

e=192.168.1.1:ffffff00     => "192.168.1.1" => Board IP. I assume "ffffff00" => subnet mask 255.255.255.0 or CIDR.
h=192.168.1.100            => "192.168.1.100" => Host IP
g=                         => "empty" => Gateway IP
r=f                        => "f" => Run from Flash
f=vmlinux                  => "vmlinux" | Default run hostfilename is "vmlinux"
i=bcm963xx_fs_kernel       => "bcm963xx_fs_kernel" | Default flash filename is "bcm963xx_fs_kernel"
d=1                        => "1" | Delay = 1
p=0                        => "0" | [I have no idea what this is.]

So, the e, h, g, r, f, i, d and p seems to be variables that would be used during the flashing procedure via minicom to emulate a serial device.

If you would like to dig deeper, you could read about CPE by clicking here.

Extracting Router Filesystem:

To check what is in the router firmware binary, I have used “binwalk“. Below is output of binwalk:

$ binwalk TD-W8970v3_0.9.1_1.2_up_boot\(150427\)_2015-04-27_17.48.51.bin

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
13300         0x33F4          LZMA compressed data, properties: 0x6D, dictionary size: 4194304 bytes, uncompressed size: 220576 bytes
66572         0x1040C         LZMA compressed data, properties: 0x6D, dictionary size: 4194304 bytes, uncompressed size: 3876096 bytes
151315        0x24F13         MySQL MISAM index file Version 4
1366752       0x14DAE0        Squashfs filesystem, little endian, non-standard signature, version 4.0, compression:gzip, size: 6806057 bytes, 594 inodes, blocksize: 65536 bytes, created: 2015-04-27 09:45:54

The Ahaaa moment!

So, here we see this binary contains a squashfs filesystem starting at decimal “1366752”.

binwalk binary squashfs

We can extract the squashfs filesystem alone from the firmware binary using the “dd” command by skiping up to “1366752” using the “skip” flag.

$ dd if=TD-W8970v3_0.9.1_1.2_up_boot\(150427\)_2015-04-27_17.48.51.bin skip=1366752 bs=1 of=router-fs.squashfs
6807552+0 records in
6807552+0 records out
6807552 bytes (6.8 MB, 6.5 MiB) copied, 8.81061 s, 773 kB/s

Here, we are providing the input file as the router firmware, setting the block size to “1” and getting data from “1366752” to the end of the binary and storing it to a file “router-fs.squashfs”. Here, if we dont specify the “bs” the copy would most likely fail.

So, the squashfs file system is about ~6.5MB in size compressed.

$ ls -lh
total 15M
-rw-rw-r-- 1 extr3me extr3me 6.5M Jun 10 00:05  router-fs.squashfs
-rw-rw-r-- 1 extr3me extr3me 7.8M Apr 27  2015 'TD-W8970v3_0.9.1_1.2_up_boot(150427)_2015-04-27_17.48.51.bin'

Screenshot: dd to extract router filesystem

– Now, Checking the file shows as “data”.

$ file router-fs.squashfs
router-fs.squashfs: data
  • For verification, check the unpacked binary “router-fs.squashfs” and it does show as “squashfs filesystem”.
$ binwalk router-fs.squashfs

DECIMAL       HEXADECIMAL     DESCRIPTION
--------------------------------------------------------------------------------
0             0x0             Squashfs filesystem, little endian, non-standard signature, version 4.0, compression:gzip, size: 6806057 bytes, 594 inodes, blocksize: 65536 bytes, created: 2015-04-27 09:45:54

Now, we can extract the filesystem using “unsquashfs”.

$ unsquashfs router-fs.squashfs

-You may see some error such as below:

“create_inode: could not create character device squashfs-root/dev/bcmadsl0, because you’re not superuser!”

These logs can be safely ignored. Here is the output for reference:

$ unsquashfs router-fs.squashfs 
[130/91284]
Parallel unsquashfs: Using 8 processors
546 inodes (811 blocks) to write
create_inode: could not create character device squashfs-root/dev/bcmadsl0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/bcmarl, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/bcmfap, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/bcmvlan, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/bcmxtmcfg0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/bpm, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/brcmboard, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/caldata, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/console, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/dk0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/fcache, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/flash0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/gmac, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/gpio, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/gpio1, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ingqos, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/led, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/mtd, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/mtd0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/mtd1, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/mtd2, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/mtd3, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/mtd4, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/mtd5, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/mtdblock0, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/mtdblock1, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/mtdblock2, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/mtdblock3, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/mtdblock4, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/mtdblock5, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/net/tun, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/null, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/pmap, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ppp, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/pppox_iptables, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ptmx, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ptyp0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ptyp1, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ptyp2, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/pwrmngt, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/qostype, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/random, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/sda, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/sda1, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/sda2, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/sdb, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/sdb1, because you're not superuser!
create_inode: could not create block device squashfs-root/dev/sdb2, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/tty, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/tty0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM1, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM10, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM11, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM12, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM13, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM14, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM15, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM2, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM3, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM4, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM5, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM6, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM7, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM8, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyACM9, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyS0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB1, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB10, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB11, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB12, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB13, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB14, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB15, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB2, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB3, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB4, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB5, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB6, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB7, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB8, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyUSB9, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyp0, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyp1, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/ttyp2, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/urandom, because you're not superuser!
create_inode: could not create character device squashfs-root/dev/watchdog, because you're not superuser!

create_inode: could not create character device squashfs-root/dev/zero, because you're not superuser!
[====================================================================================================================================================================================================| ] 722/811 89%

created 370 files
created 48 directories
created 87 symlinks
created 0 devices
created 0 fifos

-Now, we should see a folder named “squashfs-root

$ ls -l
total 14636
-rw-rw-r--  1 extr3me extr3me 6807552 Jun 10 00:05  router-fs.squashfs
drwxrwxr-x 13 extr3me extr3me    4096 Apr 27  2015  squashfs-root
-rw-rw-r--  1 extr3me extr3me 8174304 Apr 27  2015 'TD-W8970v3_0.9.1_1.2_up_boot(150427)_2015-04-27_17.48.51.bin'

Now, change into the folder ”

$ cd squashfs-root/

-Listing the files shows that we have the router’s filesystem.

$ ls -a
.  ..  bin  dev  etc  lib  linuxrc  mnt  proc  sbin  sys  tmp  usr  var  web

-To value a view of the filesystem in a tree like format, I have used the command “tree” and piping it to less:

$ tree -C | less -R

Guessing the Linux Kernel version:

-Checking the files with “.ko” extension for keyword “vermagic” would be the best guess to the Linux kernel version.

$ strings ./lib/modules/NetUSB.ko | grep vermagic
vermagic=2.6.30 SMP preempt mod_unload MIPS32_R1 32BIT

Now, that I know the kernel version I could then check for exploits that affect this kernel version.

For example: Check for CVEs that affect this version. [Link]

Some random things worth checking:

-The list of libraries installed can be found under:

$ tree -C lib/ | less -R

– So, the chrooted environment were the user inputs commands seems to be via a binary “usr/bin/cli” or could be translating the user commands to the actual deamon.

$ strings ./usr/bin/cli | less

-Btw, I found a blooper while checking this binary. Searching this binary, I did find a few misspelled words. Example: “histroy” instead of “history” 😛

serial
wan2lan
start
exit
clear
clear screen
enter config mode
enable
enter privilege mode
leave to the privious mode
help
help info
history
show histroy commands

Guessing GCC Version:

$ strings ./usr/bin/cli | grep GCC
GCC: (GNU) 3.3.2
GCC: (Buildroot 2010.02-git) 4.4.2

 

Cracking password from the router’s filesystem:

-Looking at the filesystem, I could see a file “passswd.bak” under etc/ directory.

-Reading the file shows there are two users with has shell access.

$ cat etc/passwd.bak
admin:$1$$iC.dUsGpxNNJGeOm1dFio/:0:0:root:/:/bin/sh
nobody:*:0:0:nobody:/:/bin/sh

Here:

      Username indicated in green.
      Hashed password indicated in orange.
Assigned shell indicated in pink.

This looks to be the “shadow” file usually located under etc/shadow which my assumption is to be copied to etc/shadow during upgrade.

Here is something wierd. Why does user “nobody” has /bin/bash shell ? Manufacturer backdoor??Mmmmm….! We will come to that later.

Messing around  – Cracking passwords:

Before cracking the password, lets understand something about the hash+salt. Here, the “$” signs are special

passwd.bak file

admin:$1$$iC.dUsGpxNNJGeOm1dFio/:0:0:root:/:/bin/sh

The string that we require is the following:

$1$$iC.dUsGpxNNJGeOm1dFio/

The string is specificied in the following format:

$id$salt$encrypted

$1 => Indicates that  MD5 is used to create the hash the password. Below is a table of the list of possible values for the 1st section.

--------------------
| 1  | MD5         |
--------------------
| 2  | Blowfish    |
--------------------
| 2a | eksBlowfish |
--------------------
| 5  | SHA-256     |
--------------------
| 6  | SHA-512     |
--------------------

For testing purposes, I was able to crack the password with John the ripper.

If the passwords had complex salts + hashing methods, for ex SHA-512 it may take longer than expected. [Again, this is for educational purposes ONLY!]

$ john etc/passwd.bak
Loaded 1 password hash (md5crypt [MD5 32/64 X2])
No password hashes left to crack (see FAQ)

– To view the cracked password, you could use the –show flag along with the input file etc/passwd.bak

$ john --show etc/passwd.bak
admin:1234:0:0:root:/:/bin/sh
1 password hash cracked, 0 left

 

cracked password using john the ripper

-So, the password “admin” user is “1234”.

The user “nobody”:

Coming back to the user “nobody”.

Ideally, for security purposes “nobody” user is used with a combination of a non existing directory along with a nologin shell. Below is a sample of an acceptable configuration to me:

nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin

However, in the router’s FS, it seems wierd that the user “nobody” seems to have “/” or the actual root as mount along with “/bin/bash”.

nobody:*:0:0:nobody:/:/bin/sh

Anyways, may be this is just my paranoia/spidey sense kicking in. 😛

 

If you read this far. Thanks a ton! Hope you learned something from this article. Do bookmark this page for future references. Cheers

 

Regards,
ΞXΤЯ3МΞ

References:

https://openwrt.org/docs/techref/bootloader/cfe
https://en.wikipedia.org/wiki/Common_Firmware_Environment
https://www.cvedetails.com/version/81666/Linux-Linux-Kernel-2.6.30.html
https://charlesreid1.com/wiki/John_the_Ripper/Shadow_File
https://www.openwall.com/john/doc/EXAMPLES.shtml

Click to access bcm963xx_bootloader_appnote.pdf

http://plastilinux.blogspot.com/2009/11/how-to-know-version-of-kernel-without.html
ase1590