Firmware password

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

Get started
How did i find out? Well bg told me :-)
 * First, find out that ls_servd is responsible for decrypting.

ice:~/tera>strings ls_servd|grep unzip /bin/unzip -P %s %s/%s -d %s > /dev/null 2>&amp;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.
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.
 * I have no PPC assembler knowledge.

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

http://www-aix.informatik.uni-tuebingen.de/doc_link/en_US/a_doc_lib/aixassem/alangref/toc.htm

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

We now have about 8k lines assembler code. Where to start?
 * The file is big.

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

10002fec:  48 01 a2 59     bl      1001d244 

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(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  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 

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....

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  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  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  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 
 * 1) Gen ZipPW in r3

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

a0bdb8d5cea1e8c4bedbc1b3dfc8c9c4c5bde0d1dec1dfbcb1dec9e6c3a3bfe9dec4e5bebfc4dfa5db00 d0b0d7e5dbbca0c8dfa6cea1c5c2dca5b1d7d6dadcc3bee1b2bda0b9e8b49fb2a4c0a5d2b1a2deb1b100 c8e5c2b8ddb8c0dedfd4d8dfe7a5a5e3ceb3b2d3d5b4e5d5bfa3a6e0d4c5bfd7bdd7b0e4c2c8dcb0a300 b8d4c8a7dedcb9e6b6dbb6dab8d1b9dca1b5b7cebcc5a3d5bbe2c7b4a7d8d4e49fd6bdc8e6b4a5c3e800

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 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" 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
 * 1) core(r27)
 * 1) return_after_cleanup

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:

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 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
 * 1) Crypto-core, r3 is pointer to binstring
 * 1) repeat

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

The Passwords
We have all ingerdients, now we need to decrypt the contained passwords. Time for a little perl...
 * Decrypt them

@a=qw( a0bdb8d5cea1e8c4bedbc1b3dfc8c9c4c5bde0d1dec1dfbcb1dec9e6c3a3bfe9dec4e5bebfc4dfa5db00 d0b0d7e5dbbca0c8dfa6cea1c5c2dca5b1d7d6dadcc3bee1b2bda0b9e8b49fb2a4c0a5d2b1a2deb1b100 c8e5c2b8ddb8c0dedfd4d8dfe7a5a5e3ceb3b2d3d5b4e5d5bfa3a6e0d4c5bfd7bdd7b0e4c2c8dcb0a300 b8d4c8a7dedcb9e6b6dbb6dab8d1b9dca1b5b7cebcc5a3d5bbe2c7b4a7d8d4e49fd6bdc8e6b4a5c3e800 ); for (@a){ @b=unpack("C*",pack("H*",$_)); print map { $_?chr($_-111):"" } @b; print "\n"; };
 * 1) !/usr/local/bin/perl

Start it

ice:~tera>./crpt 1NIf_2yUOlRDpYZUVNqboRpMBoZwT4PzoUvOPUp6l aAhvlM1Yp7_2VSm6BhgkmTOrCN1JyE0C5Q6cB3oBB YvSInIQopeipx66t_DCdfEvfP47qeVPhNhAuSYmA4 IeY8omJwGlGkIbJm2FH_MV4fLsXE8ieu0gNYwE6Ty

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

Links

 * Firmware Update
 * Examine ARM9 Firmware without Updating