Difference between revisions of "Firmware password"

From NAS-Central Buffalo - The Linkstation Wiki
Jump to: navigation, search
m (Page is also valid for LSPro and LSLive)
(8 intermediate revisions by 5 users not shown)
Line 1: Line 1:
''How to find the password for the encrypted zip containing the Firmware?''
''How to find the password for the encrypted zip containing the Firmware?''
Line 233: Line 235:
Voila! A simple test shows, that the first password is the one actually used. The other ones might come in handy later :)
Voila! A simple test shows, that the first password is the one actually used. The other ones might come in handy later :)<br>
-- [[User:Sec|Sec]]
-- [[User:Sec|Sec]]
<div style="overflow: auto; height: 1px;">
[http://nv2006.com/ nv]
== Links ==
* [[Firmware_update#Firmware_Passwords | Firmware Update]]
* [[Examine ARM9 Firmware without Updating]]

Latest revision as of 19:33, 20 April 2010

How to find the password for the encrypted zip containing the Firmware?

Get started

  • First, find out that ls_servd is responsible for decrypting.

How did i find out? Well bg told me :-)

ice:~/tera>strings ls_servd|grep unzip
/bin/unzip -P %s %s/%s -d %s > /dev/null 2>&1

I had some hope that the password is actually contained in this binary somehow.

However, a brief interlude with a zipcracker and the full result of the "strings" output doesn't find a match, but maybe we just need to dig a little bit deeper...

The %s indicates this is a call to sprintf(3), so we need to check the third parameter of this sprintf call.

How about disassembling the thing?

  • We need a disassembler.

"objdump" usually does the trick...

ice:~/tera>objdump -d ls_servd 
/usr/libexec/elf/objdump: ls_servd: File format not recognized

Huh? Why this?

ice:~/tera>file ls_servd 
ls_servd: ELF 32-bit MSB executable, PowerPC or cisco 4500, version 1 (SYSV), \
  for GNU/Linux 2.4.17, dynamically linked (uses shared libs), stripped

Oh, of course. Its a PPC binary, not an x86 binary.

  • We need an PowerPC disassembler.

Some googling later, I now have an (linux) ppc-cross-objdump binary.

ice:~/tera>./ppc-linux-objdump -s -d ls_servd >DUMP
ice:~/tera>vim DUMP

Good. We have the code, and an hexdump in there. But now we have two more problems

Understanding the code.

  • I have no PPC assembler knowledge.

Well, I know 6510 asm and some x86 asm, which helps with the general mindset, but the details of PPC are a mystery to me.

Nothing, a quick google couldn't fix. A PPC asm reference can be found here:


If you want to look up some mnemonics, be sure to also check "Chapter 6: Extended mnemonics" if you don't find your mnemonic.

  • The file is big.

We now have about 8k lines assembler code. Where to start?

The only "plaintext" part of the listing are the system calls.

10002fec:   48 01 a2 59     bl      1001d244 <wait3@plt>

Objdump nicely annotated them for us. Hey, thanks :)

Parameter passing.

From strings, we know that "/usr/bin/unzip" is being called, that is usually done by using the system(3) system call. Lets check for that. ls_servd uses a lot of system(3) calls, this is the first one:

10005dec:   3c 60 10 01     lis     r3,4097
10005df0:   38 63 b6 44     addi    r3,r3,-18876
10005df4:   48 01 72 01     bl      1001cff4 <system@plt>

system(3) only takes one argument, so we can assume that r3 is that argument. Lets see what it is:

The first line fills the upper 16 bits of r3 with 4097 (hex 1001), resulting in r3=0x10010000 - the second line substracts 18876, resulting in r4=0x1000B644

We can now go and look that up in the hexdump part of the objdump output:

1000b638 613d3078 25303258 0d0a0000 2f736269  a=0x%02X..../sbi
1000b648 6e2f7265 626f6f74 00000000 6c69626c  n/reboot....libl

Which makes the whole thing a call to /sbin/reboot. Yay!

Finding the right place

We could now go, and look up all parameters to system, to find the correct one, but we can also do it the other way round:

1000c478 25303878 0a000000 2f62696e 2f756e7a  %08x..../bin/unz
1000c488 6970202d 50202573 2025732f 2573202d  ip -P %s %s/%s -
1000c498 64202573 203e202f 6465762f 6e756c6c  d %s > /dev/null

okay, the string starts at 0x1000C480 which makes the difference to 0x10010000 = 15232

Only two places refer to that string. The fist one looks like this:

10008c30:   48 00 01 89     bl      10008db8 <_init+0x5f70>
10008c34:   7f 48 d3 78     mr      r8,r26
10008c38:   7c 65 1b 78     mr      r5,r3
10008c3c:   38 9e c4 80     addi    r4,r30,-15232 # unzip -p %s %s/%s -d %s
10008c40:   7f 46 d3 78     mr      r6,r26
10008c44:   38 f8 c3 c4     addi    r7,r24,-15420 # image.zip
10008c48:   38 61 00 08     addi    r3,r1,8
10008c4c:   4c c6 31 82     crclr   4*cr1+eq
10008c50:   48 01 46 4d     bl      1001d29c <sprintf@plt>
10008c54:   38 61 00 08     addi    r3,r1,8
10008c58:   3b ff 00 01     addi    r31,r31,1
10008c5c:   48 01 43 99     bl      1001cff4 <system@plt>

Yay, an sprintf(3) followoed by an system(). The system call parameters are numbered from r3 upwards, so the third parameter to the sprintf is r5, which is copied from r3 a few lines above. It looks like r3 might be the return value of the omnious subroutine at 0x10008db8.

Well the sub starts off a bit lengthy....

# Gen ZipPW in r3 
10008db8:   94 21 ff 10     stwu    r1,-240(r1)
10008dbc:   3c 80 10 01     lis     r4,4097
10008dc0:   93 61 00 dc     stw     r27,220(r1)
10008dc4:   7c 08 02 a6     mflr    r0
10008dc8:   3b 61 00 08     addi    r27,r1,8
10008dcc:   93 e1 00 ec     stw     r31,236(r1)
10008dd0:   38 84 c6 24     addi    r4,r4,-14812 # Binstring1 (41b)
10008dd4:   90 01 00 f4     stw     r0,244(r1)
10008dd8:   7c 7f 1b 78     mr      r31,r3
10008ddc:   93 81 00 e0     stw     r28,224(r1)
10008de0:   38 a0 00 2a     li      r5,42
10008de4:   93 a1 00 e4     stw     r29,228(r1)
10008de8:   7f 63 db 78     mr      r3,r27
10008dec:   93 c1 00 e8     stw     r30,232(r1)
10008df0:   3b 81 00 38     addi    r28,r1,56
10008df4:   48 01 43 d1     bl      1001d1c4 <memcpy@plt>
10008df8:   3c 80 10 01     lis     r4,4097
10008dfc:   38 84 c6 4e     addi    r4,r4,-14770 # Binstring2 (41b)
10008e00:   38 a0 00 2a     li      r5,42
10008e04:   7f 83 e3 78     mr      r3,r28
10008e08:   48 01 43 bd     bl      1001d1c4 <memcpy@plt>
10008e0c:   3b a1 00 68     addi    r29,r1,104
10008e10:   3c 80 10 01     lis     r4,4097
10008e14:   38 84 c6 78     addi    r4,r4,-14728 # Binstring3 (41b)
10008e18:   38 a0 00 2a     li      r5,42
10008e1c:   7f a3 eb 78     mr      r3,r29
10008e20:   48 01 43 a5     bl      1001d1c4 <memcpy@plt>
10008e24:   3b c1 00 98     addi    r30,r1,152
10008e28:   3c 80 10 01     lis     r4,4097
10008e2c:   38 84 c6 a2     addi    r4,r4,-14686 # Binstring4 (41b)
10008e30:   7f c3 f3 78     mr      r3,r30
10008e34:   38 a0 00 2a     li      r5,42
10008e38:   48 01 43 8d     bl      1001d1c4 <memcpy@plt>

which calls memcpy on four different 41 byte long nul terminated binary strings:


Well. This surely had me hooked. I had already suspected this "binary blob" to play some role :)

Then follows some less interesting code.

10008e3c:   2c 1f 00 01     cmpwi   r31,1
10008e40:   41 82 00 ac     beq-    10008eec <_init+0x60a4> # core(r28)
10008e44:   41 81 00 88     bgt-    10008ecc <_init+0x6084> # core(decide)
10008e48:   2c 1f 00 00     cmpwi   r31,0
# core(r27)
10008e4c:   7f 63 db 78     mr      r3,r27
10008e50:   41 82 00 30     beq-    10008e80 <_init+0x6038> # Cryptocore
10008e54:   3d 20 10 02     lis     r9,4098
10008e58:   38 69 cc a8     addi    r3,r9,-13144 # 0x1001CCA8: nul bytes: "result_area"
# return_after_cleanup
10008e5c:   80 01 00 f4     lwz     r0,244(r1)
10008e60:   83 61 00 dc     lwz     r27,220(r1)
10008e64:   83 81 00 e0     lwz     r28,224(r1)
10008e68:   7c 08 03 a6     mtlr    r0
10008e6c:   83 a1 00 e4     lwz     r29,228(r1)
10008e70:   83 c1 00 e8     lwz     r30,232(r1)
10008e74:   83 e1 00 ec     lwz     r31,236(r1)
10008e78:   38 21 00 f0     addi    r1,r1,240
10008e7c:   4e 80 00 20     blr

which deals with deciding which of the 4 binstrings is actually used, and some cleanup on return. Then follows the part we have been hunting for:

# Crypto-core, r3 is pointer to binstring
10008e80:   88 03 00 00     lbz     r0,0(r3)                # r0=r3[0]
10008e84:   39 40 00 00     li      r10,0                   # r10=0
10008e88:   3d 20 10 02     lis     r9,4098                 # r9=0x10020000
10008e8c:   2c 00 00 00     cmpwi   r0,0                    # if r0==0
10008e90:   41 82 00 28     beq-    10008eb8 <_init+0x6070> # Towards "return_after_cleanup"
10008e94:   7c 0b 03 78     mr      r11,r0                  # r11=r0
10008e98:   39 09 cc a8     addi    r8,r9,-13144            # r8=0x1001CCA8: result_area
# repeat
10008e9c:   38 0b ff 91     addi    r0,r11,-111             # r0=r11-111; decrypt
10008ea0:   7c 08 51 ae     stbx    r0,r8,r10               # r8[r10]=r0; store
10008ea4:   39 4a 00 01     addi    r10,r10,1               # r10++
10008ea8:   7c 03 50 ae     lbzx    r0,r3,r10               # r0=r3[r10]; get next
10008eac:   2c 00 00 00     cmpwi   r0,0                    # if not nul byte
10008eb0:   7c 0b 03 78     mr      r11,r0                  # r11=r0
10008eb4:   40 82 ff e8     bne+    10008e9c <_init+0x6054> # To repeat
10008eb8:   39 29 cc a8     addi    r9,r9,-13144            # r9= result_area
10008ebc:   38 00 00 00     li      r0,0
10008ec0:   7c 09 51 ae     stbx    r0,r9,r10               # r9[r10]=0; nul terminate
10008ec4:   7d 23 4b 78     mr      r3,r9                   # RETURN STRING!!!!
10008ec8:   4b ff ff 94     b       10008e5c <_init+0x6014> # return_after_cleanup

Well well. how disappointing. The whole decryption is a bytewise decrement by 111.

The Passwords

  • Decrypt them

We have all ingerdients, now we need to decrypt the contained passwords. Time for a little perl...

for (@a){
  print map { $_?chr($_-111):"" } @b;
  print "\n";

Start it


Voila! A simple test shows, that the first password is the one actually used. The other ones might come in handy later :)
-- Sec