Sign up here and you can log into the forum!

WDTVExt disassembly 101

Have a question about devices internals, memory layout, reverse engineering, etc---This is the place for anything so technical that it would cause a n00b's head to 'splode

WDTVExt disassembly 101   

Postby mad_ady » Thu Nov 27, 2014 6:48 am

I thought I'd start a new thread and discuss possible improvements in wdtvext performance on 1.05-wdtvext firmware. Also, poking around might be helpful for future projects.

So, the problems we are facing now with wdtvext in its currrent state are:
1. Trace debugging is active and pollutes the logs
2. Trace debugging is active and slows down execution
3. WDTVExt crashes sometimes and we don't know why
4. It would be nice to use wdtvext on plus and 1.06 in the future

1.
So, to fix the first problem I managed to find a quick fix and patch the libext.so binary replacing the extra log strings that I didn't want displayed with null strings (actually I only replaced the first character of the string with null). Everyone can do this relatively easily with a hex editor. So, now the logs are clear at least.

2. This is more tricky to fix (and I'm actually still analizing it). I was hoping to disassemble the library and find some calls that set up logging/tracing and maybe flip a bit to turn it off. So far, I haven't found a simple solution, but in theory there are options. If you were to disassemble the libext.so binary (with IDA Pro) you can quickly identify some interesting subroutines: trace_method_enter, trace_method_leave, trace_message, logPacketSize.

Looking inside trace_method_enter one can see the following:
Code: Select all
.text:00026B8C
.text:00026B8C  # =============== S U B R O U T I N E =======================================
.text:00026B8C
.text:00026B8C
.text:00026B8C                 .globl trace_method_enter
.text:00026B8C trace_method_enter:                      # CODE XREF: extSettingsEntryProc+6Cp
.text:00026B8C                                          # sub_1D028+60p ...
.text:00026B8C
.text:00026B8C var_1038        = -0x1038
.text:00026B8C var_1034        = -0x1034
.text:00026B8C var_1030        = -0x1030
.text:00026B8C var_1028        = -0x1028
.text:00026B8C var_1020        = -0x1020
.text:00026B8C var_101C        = -0x101C
.text:00026B8C var_1018        = -0x1018
.text:00026B8C var_1014        = -0x1014
.text:00026B8C var_14          = -0x14
.text:00026B8C var_10          = -0x10
.text:00026B8C stack_offset    = -0xC
.text:00026B8C var_8           = -8
.text:00026B8C format          = -4
.text:00026B8C arg_0           =  0
.text:00026B8C arg_4           =  4
.text:00026B8C
.text:00026B8C                 la      $gp, (aSS_2+4)
.text:00026B94                 addu    $gp, $t9
.text:00026B98                 addiu   $sp, -0x1040
.text:00026B9C                 sw      $ra, 0x1040+format($sp)
.text:00026BA0                 sw      $fp, 0x1040+var_8($sp)
.text:00026BA4                 move    $fp, $sp
.text:00026BA8                 sw      $gp, 0x1040+var_1020($sp)
.text:00026BAC                 move    $at, $ra
.text:00026BB0                 addiu   $sp, -8
.text:00026BB4                 la      $t9, _mcount
.text:00026BB8                 jalr    $t9 ; _mcount
.text:00026BBC                 nop
.text:00026BC0                 lw      $gp, 0x1048+var_1028($fp)
.text:00026BC4                 sw      $a0, 0x1048+var_8($fp)
.text:00026BC8                 sw      $a2, 0x1048+arg_0($fp)
.text:00026BCC                 sw      $a3, 0x1048+arg_4($fp)
.text:00026BD0                 sw      $a1, 0x1048+format($fp)
.text:00026BD4                 li      $v0, 0x1000
.text:00026BD8                 sw      $v0, 0x1048+var_1018($fp)
.text:00026BDC                 addiu   $v0, $fp, 0x1048+arg_0
.text:00026BE0                 sw      $v0, 0x1048+var_14($fp)
.text:00026BE4                 lw      $v1, 0x1048+var_1018($fp)
.text:00026BE8                 lw      $v0, 0x1048+var_14($fp)
.text:00026BEC                 addiu   $a0, $fp, 0x1048+var_1014  # s
.text:00026BF0                 move    $a1, $v1         # maxlen
.text:00026BF4                 lw      $a2, 0x1048+format($fp)  # format
.text:00026BF8                 move    $a3, $v0         # arg
.text:00026BFC                 la      $v0, vsnprintf
.text:00026C00                 move    $t9, $v0
.text:00026C04                 jalr    $t9 ; vsnprintf
.text:00026C08                 nop
.text:00026C0C                 lw      $gp, 0x1048+var_1028($fp)
.text:00026C10                 addiu   $v0, $fp, 0x1048+var_1014
.text:00026C14                 sw      $v0, 0x1048+var_1038($sp)
.text:00026C18                 li      $v0, 1
.text:00026C1C                 sw      $v0, 0x1048+var_1034($sp)
.text:00026C20                 li      $v0, 1
.text:00026C24                 sw      $v0, 0x1048+var_1030($sp)
.text:00026C28                 la      $v0, 0x120000
.text:00026C2C                 addiu   $a0, $v0, (aWdtvext - 0x120000)  # "WDTVExt"
.text:00026C30                 move    $a1, $zero
.text:00026C34                 la      $v0, 0x120000
.text:00026C38                 addiu   $a2, $v0, (aWdtvlive - 0x120000)  # "WDTVLIVE"
.text:00026C3C                 lw      $a3, 0x1048+var_8($fp)
.text:00026C40                 la      $v0, __createMessage
.text:00026C44                 move    $t9, $v0
.text:00026C48                 jalr    $t9 ; __createMessage
.text:00026C4C                 nop
.text:00026C50                 lw      $gp, 0x1048+var_1028($fp)
.text:00026C54                 sw      $v0, 0x1048+var_101C($fp)
.text:00026C58                 li      $a0, 0xC         # size
.text:00026C5C                 la      $v0, malloc
.text:00026C60                 move    $t9, $v0
.text:00026C64                 jalr    $t9 ; malloc
.text:00026C68                 nop
.text:00026C6C                 lw      $gp, 0x1048+var_1028($fp)
.text:00026C70                 sw      $v0, 0x1048+var_1020($fp)
.text:00026C74                 lw      $a0, 0x1048+var_1020($fp)  # s
.text:00026C78                 move    $a1, $zero       # c
.text:00026C7C                 li      $a2, 0xC         # n
.text:00026C80                 la      $v0, memset
.text:00026C84                 move    $t9, $v0
.text:00026C88                 jalr    $t9 ; memset
.text:00026C8C                 nop
.text:00026C90                 lw      $gp, 0x1048+var_1028($fp)
.text:00026C94                 lw      $v1, 0x1048+var_101C($fp)
.text:00026C98                 lw      $v0, 0x1048+var_1020($fp)
.text:00026C9C                 sw      $v1, 4($v0)
.text:00026CA0                 lw      $a0, 0x1048+var_101C($fp)
.text:00026CA4                 la      $v0, logPacketSize
.text:00026CA8                 move    $t9, $v0
.text:00026CAC                 jalr    $t9 ; logPacketSize
.text:00026CB0                 nop
.text:00026CB4                 lw      $gp, 0x1048+var_1028($fp)
.text:00026CB8                 move    $v1, $v0
.text:00026CBC                 lw      $v0, 0x1048+var_1020($fp)
.text:00026CC0                 sw      $v1, 0($v0)
.text:00026CC4                 lw      $a0, 0x1048+var_1020($fp)
.text:00026CC8                 la      $v0, _trace_send
.text:00026CCC                 move    $t9, $v0
.text:00026CD0                 jalr    $t9 ; _trace_send
.text:00026CD4                 nop
.text:00026CD8                 lw      $gp, 0x1048+var_1028($fp)
.text:00026CDC                 move    $sp, $fp
.text:00026CE0                 lw      $ra, 0x1048+stack_offset($sp)
.text:00026CE4                 lw      $fp, 0x1048+var_10($sp)
.text:00026CE8                 jr      $ra
.text:00026CEC                 addiu   $sp, 0x1040
.text:00026CEC  # End of function trace_method_enter


Now, this roughly translates into pseudocode into:
_mcount(params);
_vsnprintf(otherParams);
_createMessage(someWDTVExtParams);
malloc(size);
memset(size); #overwrite with zeros?
logPacketSize();
_trace_send(params);


If we look at the big picture (press "-" while in the function code) we can see a lot of unnamed subroutines that call trace_method_enter:

Image

So, let's look at one such function...
Code: Select all
.text:0001E11C  # =============== S U B R O U T I N E =======================================
.text:0001E11C
.text:0001E11C
.text:0001E11C sub_1E11C:                               # DATA XREF: .data:0013997Co
.text:0001E11C
.text:0001E11C var_20          = -0x20
.text:0001E11C var_18          = -0x18
.text:0001E11C var_10          = -0x10
.text:0001E11C var_C           = -0xC
.text:0001E11C var_8           = -8
.text:0001E11C var_4           = -4
.text:0001E11C arg_0           =  0
.text:0001E11C arg_4           =  4
.text:0001E11C
.text:0001E11C                 la      $gp, aErflow_error  # "ERFLOW_ERROR"
.text:0001E124                 addu    $gp, $t9
.text:0001E128                 addiu   $sp, -0x28
.text:0001E12C                 sw      $ra, 0x28+var_4($sp)
.text:0001E130                 sw      $fp, 0x28+var_8($sp)
.text:0001E134                 move    $fp, $sp
.text:0001E138                 sw      $gp, 0x28+var_18($sp)
.text:0001E13C                 move    $at, $ra
.text:0001E140                 addiu   $sp, -8
.text:0001E144                 la      $t9, _mcount
.text:0001E148                 jalr    $t9 ; _mcount
.text:0001E14C                 nop
.text:0001E150                 lw      $gp, 0x30+var_20($fp)
.text:0001E154                 sw      $a0, 0x30+var_8($fp)
.text:0001E158                 sw      $a1, 0x30+var_4($fp)
.text:0001E15C                 sw      $a2, 0x30+arg_0($fp)
.text:0001E160                 sw      $a3, 0x30+arg_4($fp)
.text:0001E164                 la      $v0, 0x120000
.text:0001E168                 addiu   $a0, $v0, (a__page_show_se - 0x120000)  # "__page_show_settings__"
.text:0001E16C                 la      $v0, 0x120000
.text:0001E170                 addiu   $a1, $v0, (aSD_3 - 0x120000)  # "(%s:%d)"
.text:0001E174                 la      $v0, 0x120000
.text:0001E178                 addiu   $a2, $v0, (a__SrcJsPage_c - 0x120000)  # "..\\src\\js\\Page.c"
.text:0001E17C                 li      $a3, 0x17A
.text:0001E180                 la      $v0, trace_method_enter
.text:0001E184                 move    $t9, $v0
.text:0001E188                 jalr    $t9 ; trace_method_enter
.text:0001E18C                 nop
.text:0001E190                 lw      $gp, 0x30+var_20($fp)
.text:0001E194                 sw      $zero, 0x30+var_18($fp)
.text:0001E198                 la      $v0, showSettingsPage
.text:0001E19C                 move    $t9, $v0
.text:0001E1A0                 jalr    $t9 ; showSettingsPage
.text:0001E1A4                 nop
.text:0001E1A8                 lw      $gp, 0x30+var_20($fp)
.text:0001E1AC                 li      $v0, 1
.text:0001E1B0                 sw      $v0, 0x30+var_18($fp)
.text:0001E1B4                 la      $v0, 0x120000
.text:0001E1B8                 addiu   $a0, $v0, (a__page_show_se - 0x120000)  # "__page_show_settings__"
.text:0001E1BC                 la      $v0, 0x120000
.text:0001E1C0                 addiu   $a1, $v0, (aSD_3 - 0x120000)  # "(%s:%d)"
.text:0001E1C4                 la      $v0, 0x120000
.text:0001E1C8                 addiu   $a2, $v0, (a__SrcJsPage_c - 0x120000)  # "..\\src\\js\\Page.c"
.text:0001E1CC                 li      $a3, 0x182
.text:0001E1D0                 la      $v0, trace_method_leave
.text:0001E1D4                 move    $t9, $v0
.text:0001E1D8                 jalr    $t9 ; trace_method_leave
.text:0001E1DC                 nop
.text:0001E1E0                 lw      $gp, 0x30+var_20($fp)
.text:0001E1E4                 lw      $v0, 0x30+var_18($fp)
.text:0001E1E8                 move    $sp, $fp
.text:0001E1EC                 lw      $ra, 0x30+var_C($sp)
.text:0001E1F0                 lw      $fp, 0x30+var_10($sp)
.text:0001E1F4                 jr      $ra
.text:0001E1F8                 addiu   $sp, 0x28
.text:0001E1F8  # End of function sub_1E11C


which looks like it gets some parameters, calls _mcount, prepares some arguments and calls trace_method_enter, then calls showSettingsPage then calls trace_method_leave. So these functions are actually wrappers around the useful function (showSettingsPage in this case), but they call trace methods before and after the function call...

What we need to do is to skip calling trace_method_* in all of these small functions and we should get a nice speed boost.

Now, how can we do that?

We could add an unconditional jump in each of these functions to skip the trace methods and their parameters (we need to keep track of the stack so that we won't corrupt it - but sadly there doesn't seem to be a calling convention for mips - I don't know if the caller function has to clear the stack or if the callee does this. With a bit more analysis or testing we can probably work it out). This "fix" would bring the most benefit in terms of performance, but it needs to be done in a lot of places. I guess either a jump or replacing the current code with NOPs could do. I think a jump is faster then executing 30 NOPs, but I could be wrong (jumps can invalidate the execution pipeline and add a cost in performance).

Plan b is to skip processing in trace_method_* and just return (or jump near the end). You would still have the penalty of calling trace_method_* a lot of times, but at least it wouldn't execute the 7 functions within. Maybe in the end there can be a combined approach - neuter trace_method_* from within and start to remove calls to it from outside...

Well, that's all for now. I plan on learning more mips assembly for the fun of it (maybe experiment first on a piece of code where I know what the functions do and what/how many parameters they take).

I leave points #3 and #4 for future analysis...
Let me know of your thoughts on this process...
User avatar
mad_ady
Developer
 
Posts: 4529
Joined: Fri Nov 05, 2010 9:08 am
Location: Bucharest, Romania

Re: WDTVExt disassembly 101   

Postby KAD » Fri Nov 28, 2014 2:14 pm

nice bit of research

have you tried getting a hold of pibos or brad, maybe you can get a copy of the source code
might be easier than manually patching things in libext.so
If you like my work please consider a Donation. Donate
Please read the appropriate documentation before posting questions! READ ME FAQ WIKI
PM's are for private matters. Post support questions to the appropriate forum, or they will be ignored.
User avatar
KAD
Global Moderator
 
Posts: 5103
Joined: Mon Apr 12, 2010 4:59 pm
Location: Seattle, WA USA

Re: WDTVExt disassembly 101   

Postby mad_ady » Fri Nov 28, 2014 10:32 pm

I keep begging b-rad to give me whatever source code he has for wdtvext for 1.02, but he keeps having computer problems, so no code yet.
I haven't contacted pibos yet, but I plan to next year. I'm a bit afraid that he might help us and we won't learn anything new, so I plan on trying to figure out things for myself first and learn in this process. Of course without sources or his help, disassembly won't get us too far.
I want to contact pibos again when 1.06 is in a usable state with wdtvext (I'll probably start work on it in january), so that he can help us there.

For now WDTVExt disassembly is mostly a research project.

I plan on doing the same with DMAOSD - from what I've spoken to b-rad I kind of need to, because he added a binary patch to fix the "two level icons bug" a while ago, and as far as I know WD never fixed it, so the experience gained should be valuable. Also, I want to see if I can figure out a way to disable DMAOSD's buffered output - it seems to output about one page of logs at a time, so that might be a fun experiment...

I also plan to document my findings so that others can learn (MIPS ASM isn't that cryptic - especially if you followed a course on x86-64 ASM). By the way, if anybody wants to learn/understand x86 ASM better and GDB, you should have a look at this course: https://www.coursera.org/course/hwswinterface. I found the assignments difficult, but interesting.
User avatar
mad_ady
Developer
 
Posts: 4529
Joined: Fri Nov 05, 2010 9:08 am
Location: Bucharest, Romania

MIPS function calls   

Postby mad_ady » Thu Dec 04, 2014 1:25 am

Ok, time for a new episode regarding MIPS assembly. This time we take things slowly...

We will write this simple C program and disassemble it and understand how function calls are made:

Code: Select all
#include <stdio.h>
int c_function1(void){
   printf("Inside c_function1(void)\n");
   return 5;
}
void c_function2(int a){
   printf("Inside c_function2(%d)\n", a);
}
int c_function3(int a, int b){
   printf("Inside c_function3(%d, %d)\n", a, b);
   return a+b;
}
int c_function4(int a, int b, char c, float d){
   printf("Inside c_function4(%d,%d,%c,%f)\n", a, b, c, d);
   return a+b;
}
int main(void){
   printf("main...\n");
   c_function1();
   int result = 88;
   c_function2(8);
   result = c_function3(result, 12);
   result = c_function4(result, 38, 'M', 3.14159);
   printf("final result: %d\n", result);
   return 0;
}


You can compile this code with the standard mips compiler provided by WD.

Next we will load the binary into IDA Pro and study it (I added comments of what happens for your convenience).

The program flow looks like this:
Image

The main() function is called, and it calls c_function1, c_function2, c_function3 and c_function4 and exits. The other functions call printf() and puts().

Let's take a closer look at them. I'm pasting images here to preserve the highlighting, but you can get the text version here: http://pastebin.com/SDtqEh8N

The main function:
Image

The c_function1():
Image

The c_function2(int arg1)
Image

The c_function3(int arg1, int arg2)
Image

The c_function4(int arg1, int arg2, char arg3, float arg4)
Image

If you follow the comments you can see that all the function have the same pattern:
  • set up private stack space and save the return address and old stack pointer
  • save any arguments we are passed on the stack for safe keeping
  • Now we can start executing actual function code...
  • load any data we need (arguments, string pointers, etc) into $a0 - $a3 registers (for functions with up to 4 arguments; extra arguments are sent via the stack, but I didn't experiemnt with those)
  • put the address of the function to be called in a temporary register (usually $t9)
  • call the function (jal/jalr)
  • Any return values we get will be stored in $v0 or $v1
  • When the function is about to exit it will store its return values in $v0 and optionally $v1
  • Before it exists, the function returns the stack state, restores the return address and the frame pointer and makes an unconditional jump to $ra
Interesting things that I noticed while understanding the assembly code:
  • The mips compiler converted printf() to puts() internally if it had a plain string to display (see c_function1)
  • There are no push/pop commands in the MIPS assembly language to work with stacks. Stack values are accessed as memory space by knowing the offset you want to address.

Now, the good news is we know what the calling convention is:
  • the caller puts the first 4 parameters into $a0 - $a3 and calls the function
  • the callee needs to take care to set up stack space (and restore it on completion), needs to save the return address and parameters and needs to release these resources before finishing the work

I've found out why most jump instructions seem to have an extra "nop" after them - it seems that because of the way the processor is built the next instruction after a jump is also executed (it gets fed into the execution pipeline). So this needs to be taken into account if you want to change instructions to make jumps some place else (see http://en.wikipedia.org/wiki/MIPS_instruction_set#MIPS_assembly_language in the "Jump and link" section).

In order to skip execution of a function we have several alternatives:
  • In the caller function allow it to set up parameters but replace the call to jalr $t9 with nop. Bonus points - replace the parameter setup instructions with nops.
  • In the caller function add an unconditional jump (j/jr) before parameter set up followed by a nop and jump to where the function would have returned.
  • Allow the caller to call the callee, but bypass execution in the callee either by replacing most of the instructions with nop or with an unconditional jump to near the function's end

Now, it looks doable (and for wdtvext I'll probably try #3 first because I need to edit just in a single place instead of 100 function calls), but there's also...

Bad news

It seems Ida Pro is just a disassembler and doesn't allow you to magically add or change instructions and produce a new binary. From what I've looked one needs to manually replace the offending instructions with the correct opcodes. It can generate some binary diffs that can be somehow applied on the target binary. But this needs to be explored/experimented (in my next episode). There are some good news hidden in this bad news however - RISC processors (like MIPS) have fixed instruction length: 32 bits (unlike CISC processors like x86 or x86_64 with have variable length instructions). So, this means we can replace one instruction with any different instruction without having to offset anything around.

Well, I'll see you next time. In the mean time, if you want to read more on how to work with IDA Pro, you can have a look at these links (I haven't read all of it yet):

http://resources.infosecinstitute.com/basics-of-ida-pro-2/
http://resources.infosecinstitute.com/ida-jumping-searching-comments/
http://resources.infosecinstitute.com/ida-idc-sdk-remote-debugging-overview/
http://resources.infosecinstitute.com/applied-cracking-byte-patching-ida-pro/
http://resources.infosecinstitute.com/ida-configuration-options/
http://resources.infosecinstitute.com/ida-program-patching/
http://resources.infosecinstitute.com/applied-reverse-engineering-ida-pro/
User avatar
mad_ady
Developer
 
Posts: 4529
Joined: Fri Nov 05, 2010 9:08 am
Location: Bucharest, Romania

Re: WDTVExt disassembly 101   

Postby mad_ady » Fri Dec 05, 2014 2:23 am

Ok, part two of our tutorial - let's learn how to patch.

We need to change existing instructions into new instructions to jump around in the code as we want (our goal here is to skip the execution of certain functions). We can do this by either replacing the instruction with nop, or (ideally) replace the instruction with a jump to where you want to go to.

Now, the bad news. You can't write new asm code in IDA. There is a "Edit -> Patch program -> Assemble" option that kind of allows you to write asm and generate binary code, but alas MIPS is not supported :(

Image

We can only change things by using "Edit -> Patch program -> Change bytes", but we need to know the machine code to fill in.

Let's first look at the "nop" strategy. We want to replace this "jal c_function1" call (at offset 004007EC) with nop so that the program would skip calling c_function1 when executed. First thing we need to do is find out what op code we need for nop. We can do this by selecting any "nop" instruction and choose "Synchronize with -> Hex-View A". Next, switch to Hex view and it will show you the OP code highlighted. For nop we have "00 00 00 00" as the op code (how convenient).

To change the jump instruction at 004007EC and replace it with nop we need to do the following:
  • click on the instruction you want to change
  • Edit -> Patch program -> Change bytes
  • In the window that appears change the first 4 octets (98 01 10 0C) to 00 00 00 00
  • Check that the jump instruction was replaced with nop in the view.

Image

The changes have been made in IDA's DB. We need to save a diff file and apply the patch manually: File -> Produce file -> Create dif file...

To apply the patch we need a program that can do it (you can get one from here: http://stalkr.net/files/ida/idadif.py)
Code: Select all
adrianp@frost:~/development/c$ cp testmipsfunctions testmipsfunctions-skip-function1
adrianp@frost:~/development/c$ cat testmipsfunctions-skip-function1.dif
This difference file has been created by IDA

testmipsfunctions
000007EC: 98 00
000007ED: 01 00
000007EE: 10 00
000007EF: 0C 00
adrianp@frost:~/development/c$ idadif.py
Usage: /home/adrianp/bin/idadif.py <binary> <IDA.dif file> [revert]
Applies given IDA .dif file to patch binary; use revert to revert patch.
adrianp@frost:~/development/c$ idadif.py testmipsfunctions-skip-function1 testmipsfunctions-skip-function1.dif
Patching file 'testmipsfunctions-skip-function1' with 'testmipsfunctions-skip-function1.dif'
Done


If we test the original binary and the patched one, this is what we get:
Code: Select all
root@Deneb:/tmp# ./testmipsfunctions
main...
Inside c_function1(void)
Inside c_function2(8)
Inside c_function3(88, 12)
Inside c_function4(100,38,M,3.141590)
final result: 138
root@Deneb:/tmp# ./testmipsfunctions-skip-function1
main...
Inside c_function2(8)
Inside c_function3(88, 12)
Inside c_function4(100,38,M,3.141590)
final result: 138
root@Deneb:/tmp#


As you can see, we were successful in skipping function1 (which took no parameters).

Next up, we will try to see if we can learn how to do jumps to arbitrary addresses. Wish me luck!
User avatar
mad_ady
Developer
 
Posts: 4529
Joined: Fri Nov 05, 2010 9:08 am
Location: Bucharest, Romania

Re: WDTVExt disassembly 101   

Postby recliq » Fri Dec 05, 2014 3:43 am

Nice work! Please keep it going ;)
­WDLXTV Project Maintainer
-:] If you like my contributions feel free to donate for a beer or a new flash drive. ...and always remember: RTFM! (README, FAQ, WIKI) [:-
User avatar
recliq
WDLXTV Team
 
Posts: 5513
Joined: Thu Apr 15, 2010 8:09 am
Location: Kiel, Germany

The final jump   

Postby mad_ady » Fri Dec 05, 2014 5:53 am

As promissed, I kept digging and now I know how to jump :D

Image

So, we know how to replace code with nop, but that's not terribly efficient - the processor still wastes ticks executing a bunch of nops. We need to jump to where we need to process. Let's have a look at the standard jump intruction (http://www.mrc.uidaho.edu/mrc/people/jff/digital/MIPSir.html):

Image

The instruction opcode is 0000 10 (= 0x08) followed by the absolute address (encoded in 26 bits) where to jump to. The trick is to find out how to encode the memory address and how to represent it in hex. Without much information on the subject, we can look at a similar jump instruction and try to understand how it's encoded (the jal - jump and link instruction). The difference is just the op code (0000 11 + target address).

Let's analyze the instruction "jal c_function2" at offset 00400800 in the disassembled code. This ASM code translates to the following bytes: "A9 01 10 0C". Let's have a look in binary:
Code: Select all

  A     9    0    1    1    0    0    C
1010 1001 0000 0001 0001 0000 0000 1100


Now, we're looking for the bit sequence 0000 11 and we can find it at the last two octets - 0C. This means the jump instruction is encoded in the least significative byte. The rest are address bits.

So, the function calls c_function2, which is at offset 004006A4. This looks nothing like what the hex representation is - A90110.

Digging around the internet and reading the documentation again, we notice that the memory address is divided by 4 (or left shifted by 2). If we load the data in a hex calculator and do the shift, we get:

Code: Select all
004006A4 >> 2 = 1001A9


Well, now we're cooking . We can identify the octets in the hex string, only they are in reverse order (little endian).

So, to get a jump to address instruction, we need to:
  • divide the address by 4 (and truncate to 26 bits): 004006A4 >> 2 = 1001A9
  • prepend the instruction code (6 bits): 081001A9
  • reverse the order of the octets: A9011008

And now you have an unconditional jump tot that address.

All nice and dandy, so let's do a real world example. We now want to jump over function2 and function3 in main. We will add the jump instruction at offset 004007FC in the code, replacing the li $a0, 8 instruction (which prepares parameters for function2 anyway) and the next instruction must be a nop (because as I've said earlier, the next instruction after a jump is still executed).

Before:
Image

We need to calculate the target address for this jump. We want to reach address 00400814 as our target (right after the call to function3). Using the algorithm above (which I "compiled" into a perl script) we calculate that the instruction will be: 05021008. We go and replace the hex strings at 004007FC and 00400800 (two consecutive instructions) (more specifically, this hex string 08 00 04 24 A9 01 10 0C) with the hex strings for "j 00400814" and "nop" (specifically 05 02 10 08 00 00 00 00).

Image

Now it's time to sit back, sip your cool drink and rejoice! IDA says the jump is correct.

Let's run the program to make sure (you need to export the diff and apply it. Note, it also contains the previous modifications - skipping function1):
Code: Select all
root@Deneb:/tmp# ./testmipsfunctions-skip-function1_2_and_3
main...
Inside c_function4(88,38,M,3.141590)
final result: 126

As you can see, function1, 2 and 3 have been skipped and this also changes the final return value (it used to be 138).

A great success, but it takes a lot of work to do these changes due to IDA's lack of assembly support for MIPS. I was thinking of what I can do to make things easier (to patch wdtvext we need to edit hundreds of functions and add at least 200 jumps), and doing it by hand is a lot of work and error prone. A colleague of mine suggested to generate the patches in IDA format directly from my address calculation script - and that's sensible. I would need to feed two parameters per change: the address where I want to add the jump and the address to jump to. It could work internally and generate the correct instructions (+ the nop we need as well) and generate an IDA patch file. I can then apply the patch, reboot and pray... The work I need to do now (besides the patching script) is to identify the pairs of where to add the jump and where to jump to and write them to a file and then generate the correct code and profit.

Sadly, I'll be at a course next week and won't have time to play with this, but I hope to be able to do the wdtvext mass patching this year.

Thanks for the encouragement. I hope that this work will help others pick up where older contributers left and we can at least fix some annoying bugs.

P.S. Here is the perl code that converts the destination address into a jump instruction:
Code: Select all
#!/usr/bin/perl
use strict;
use warnings;
use bigint;

print "Convert a hex address into a MIPSEL jump Label instruction\n";
print "Input the addresses one per line in hex format: e.g: 004007F0\n";

my $jumpOpCode = 2; # 0b000010
#see op codes encoding here: http://www.mrc.uidaho.edu/mrc/people/jff/digital/MIPSir.html

my $MemoryLast26BitsMask = 0x3FFFFFF;
my $OPCodeFirst6Bits = 0xFC000000;


while(<>){
    my $line = $_;
    chomp $line;
    #process the line as a hex number
    my $address = hex($line);
    print "j $line -> ". convertToJump($address) . "\n";
}


sub convertToJump{
    my $address = shift;
    #divide by 4 (shift by 2)
    $address = $address >> 2;
    my $hexaddress = sprintf("%X", $address);
#    print "Target address is $hexaddress\n";
    #add the jumpOpCode at the beginning
    my $instruction = 0;
    $instruction = $jumpOpCode;
    #move the OP Code to the top 6 bits (MSB)
    $instruction = $instruction << 26;
#    print "Instruction, no address:", sprintf("%08X", $instruction), "\n";;
    #possibly truncate the address to 26 bits
    $address = $address & $MemoryLast26BitsMask;
#    print "Truncated address:", sprintf("%08X", $address), "\n";
    #merge opcode and address
    $instruction = $instruction | $address;
    my $hexInstruction = sprintf("%08X", $instruction);
#    print "Instruction: ", $hexInstruction, "\n";
    #put the bytes in an array and reverse its order
    my @bytes = $hexInstruction=~/([0-9A-F]{2})/g;
    #return the bits in reverse order
    my $reversedInstruction = join("", reverse @bytes);
    return $reversedInstruction;
}
User avatar
mad_ady
Developer
 
Posts: 4529
Joined: Fri Nov 05, 2010 9:08 am
Location: Bucharest, Romania

Re: WDTVExt disassembly 101   

Postby KAD » Fri Dec 05, 2014 8:57 am

this is really great work
If you like my work please consider a Donation. Donate
Please read the appropriate documentation before posting questions! READ ME FAQ WIKI
PM's are for private matters. Post support questions to the appropriate forum, or they will be ignored.
User avatar
KAD
Global Moderator
 
Posts: 5103
Joined: Mon Apr 12, 2010 4:59 pm
Location: Seattle, WA USA

Re: WDTVExt disassembly 101   

Postby PaulF » Sat Dec 06, 2014 11:17 pm

mad_ady wrote:I keep begging b-rad to give me whatever source code he has for wdtvext for 1.02, but he keeps having computer problems, so no code yet.
I haven't contacted pibos yet, but I plan to next year. I'm a bit afraid that he might help us and we won't learn anything new, so I plan on trying to figure out things for myself first and learn in this process. Of course without sources or his help, disassembly won't get us too far.
I want to contact pibos again when 1.06 is in a usable state with wdtvext (I'll probably start work on it in january), so that he can help us there.

For now WDTVExt disassembly is mostly a research project.

I plan on doing the same with DMAOSD - from what I've spoken to b-rad I kind of need to, because he added a binary patch to fix the "two level icons bug" a while ago, and as far as I know WD never fixed it, so the experience gained should be valuable. Also, I want to see if I can figure out a way to disable DMAOSD's buffered output - it seems to output about one page of logs at a time, so that might be a fun experiment...

I also plan to document my findings so that others can learn (MIPS ASM isn't that cryptic - especially if you followed a course on x86-64 ASM). By the way, if anybody wants to learn/understand x86 ASM better and GDB, you should have a look at this course: https://www.coursera.org/course/hwswinterface. I found the assignments difficult, but interesting.
I did a lot of disassembly on DMAOSD and my tools were much cruder than yours. I may have to look at your tools some time. I seem to remember Pibos released the source on an early version. The major issue is the DMAOSD hook addresses changed. Both pibos and brad quit when they got new jobs. I always wondered if someone talked to their bosses about hacking or if their companies had policies against outside work. My old company had such a policy and technically I was supposed always include a disclaimer that I didn't speak for my company when posting here. I thought pibos also lives in Romania. Why don't you knock on his door. :-)
User avatar
PaulF
Developer
 
Posts: 427
Joined: Sat May 08, 2010 8:34 pm
Location: Oregon

Re: WDTVExt disassembly 101   

Postby mad_ady » Sun Dec 07, 2014 11:17 am

I plan on contacting pibos as soon as 1.06 has been wdlxtv'ed. Right now I'm mostly on a quest to learn new things. I'm sure we'll need to do this in dmaosd as well..
User avatar
mad_ady
Developer
 
Posts: 4529
Joined: Fri Nov 05, 2010 9:08 am
Location: Bucharest, Romania

Next

Return to WDTV Live

Who is online

Users browsing this forum: No registered users and 1 guest