PlaidCTF 2012 – Robot Testing Framework (350) [Pirating] Writeup

NOTE: When there was just 3 hours left to finish PlaidCTF 2012 I started working on the Robot Testing Framework (350) [Pirating] quest. Unfortunately I wasn’t able to finish it on time, but anyways I decided to keep working on it just for the fun. Here’s the writeup. The online service at pwning.net:8009 is no longer online in order to try my solution, but I’m pretty sure that the solution is correct.

The quest was:

We have discovered a robot testing framework that appears to take a robot module and determine whether or not it is acceptable. Can you help us figure out what the criterion for acceptance are? Framework is found at pwning.net:8009.
This challenge was made by our friends at ManTech. If you enjoyed it, you might be interested in working for them.

We downloaded the RobotLoader.exe file and procedeed to do some dynamic analysis with OllyDbg.

The application implements basic anti-debugging protection by reading the IsBeingDebugged field from the TEB:

01004012    55              PUSH EBP
01004013    89E5            MOV EBP,ESP
01004015    C1E0 10         SHL EAX,10
01004018    64:A1 30000000  MOV EAX,DWORD PTR FS:[30]
0100401E    8B40 02         MOV EAX,DWORD PTR DS:[EAX+2]
01004021    25 FF000000     AND EAX,0FF
01004026    5D              POP EBP
01004027    C3              RETN

The server starts listening on port 8009/TCP here:

01002253  |>  68 491F0000   PUSH 1F49                                    ; /Arg1 = 00001F49
01002258  |.  893D 40430001 MOV DWORD PTR DS:[1004340],EDI               ; |
0100225E  |.  E8 14F8FFFF   CALL <RobotLoa.starts_server_8009>           ; \RobotLoa.01001A77

After that, it enters this function:

01002263  |.  50            PUSH EAX                                     ;  socket
01002264  |.  E8 12FFFFFF   CALL RobotLoa.0100217B

That function calls accept() and spawns a child process when a new connection arrives:

010021B3   .  50            PUSH EAX                                     ; /pAddrLen
010021B4   .  8D45 EC       LEA EAX,DWORD PTR SS:[EBP-14]                ; |
010021B7   .  50            PUSH EAX                                     ; |pSockAddr
010021B8   .  FF75 08       PUSH DWORD PTR SS:[EBP+8]                    ; |Socket
010021BB   .  C745 E8 10000>MOV DWORD PTR SS:[EBP-18],10                 ; |
010021C2   .  FF15 AC100001 CALL DWORD PTR DS:[<&WS2_32.#1>]             ; \accept
010021C8   .  8BF0          MOV ESI,EAX
010021CA   .  83FE FF       CMP ESI,-1
010021CD   .^ 74 E1         JE SHORT RobotLoa.010021B0
010021CF   .  56            PUSH ESI                                     ; /Arg1 = socket
010021D0   .  E8 B5FDFFFF   CALL <RobotLoa.spawn_child_process>          ; \RobotLoa.01001F8A

Let’s take a closer look at that function that we named spawn_child_process(). It creates a new process with the same RobotLoader.exe executable, in suspended mode:

0100200A  |.  50            PUSH EAX                                 ; /pProcessInfo
0100200B  |.  8D85 D0FCFFFF LEA EAX,DWORD PTR SS:[EBP-330]           ; |
01002011  |.  50            PUSH EAX                                 ; |pStartupInfo
01002012  |.  A1 40430001   MOV EAX,DWORD PTR DS:[1004340]           ; |
01002017  |.  53            PUSH EBX                                 ; |CurrentDir
01002018  |.  53            PUSH EBX                                 ; |pEnvironment
01002019  |.  6A 04         PUSH 4                                   ; |CreationFlags = CREATE_SUSPENDED
0100201B  |.  6A 01         PUSH 1                                   ; |InheritHandles = TRUE
0100201D  |.  53            PUSH EBX                                 ; |pThreadSecurity
0100201E  |.  53            PUSH EBX                                 ; |pProcessSecurity
0100201F  |.  50            PUSH EAX                                 ; |CommandLine => "C:\RobotTestingFramework\RobotLoader.exe"
01002020  |.  50            PUSH EAX                                 ; |ModuleFileName => "C:\RobotTestingFramework\RobotLoader.exe"
01002021  |.  C785 D0FCFFFF>MOV DWORD PTR SS:[EBP-330],44            ; |
0100202B  |.  FF15 44100001 CALL DWORD PTR DS:[<&KERNEL32.CreateProc>; \CreateProcessA

Shortly after that it creates a new thread on the child process, writes on the child process’ memory, sets the EIP value for that new thread, and finally resumes it:

01002052  |.  53            PUSH EBX
01002053  |.  53            PUSH EBX
01002054  |.  53            PUSH EBX
01002055  |.  68 211A0001   PUSH RobotLoa.01001A21                   ;  Entry address
0100205A  |.  53            PUSH EBX
0100205B  |.  53            PUSH EBX
0100205C  |.  FFB5 20FDFFFF PUSH DWORD PTR SS:[EBP-2E0]
01002062  |.  FF15 3C100001 CALL DWORD PTR DS:[<&KERNEL32.CreateRemo>;  kernel32.CreateRemoteThread

[...]

01002102  |.  53            PUSH EBX                                 ; /pBytesWritten
01002103  |.  6A 0C         PUSH 0C                                  ; |BytesToWrite = C (12.)
01002105  |.  8985 18FDFFFF MOV DWORD PTR SS:[EBP-2E8],EAX           ; |
0100210B  |.  8D85 14FDFFFF LEA EAX,DWORD PTR SS:[EBP-2EC]           ; |
01002111  |.  50            PUSH EAX                                 ; |Buffer
01002112  |.  FFB5 F4FDFFFF PUSH DWORD PTR SS:[EBP-20C]              ; |Address
01002118  |.  899D 1CFDFFFF MOV DWORD PTR SS:[EBP-2E4],EBX           ; |
0100211E  |.  FFB5 20FDFFFF PUSH DWORD PTR SS:[EBP-2E0]              ; |hProcess
01002124  |.  FF15 34100001 CALL DWORD PTR DS:[<&KERNEL32.WriteProce>; \WriteProcessMemory
0100212A  |.  85C0          TEST EAX,EAX
0100212C  |.^ 74 B8         JE SHORT RobotLoa.010020E6
0100212E  |.  8D85 30FDFFFF LEA EAX,DWORD PTR SS:[EBP-2D0]
01002134  |.  50            PUSH EAX                                 ; /pContext
01002135  |.  FFB5 24FDFFFF PUSH DWORD PTR SS:[EBP-2DC]              ; |hThread
0100213B  |.  C785 30FDFFFF>MOV DWORD PTR SS:[EBP-2D0],10007         ; |
01002145  |.  FF15 30100001 CALL DWORD PTR DS:[<&KERNEL32.SetThreadC>; \SetThreadContext
0100214B  |.  FFB5 24FDFFFF PUSH DWORD PTR SS:[EBP-2DC]              ; /hThread
01002151  |.  FF15 2C100001 CALL DWORD PTR DS:[<&KERNEL32.ResumeThre>; \ResumeThread

Following that, the data from the network will be recv()‘d on the child processes. In order to debug the child processes, I set up a breaking on the call to SetThreadContext. When the breakpoint is hit, we can see that the new value for the EIP register is 0x01001E37, so we patch the child process’ memory at that address in order to set up an infinite loop. I’m using Virtual Section Dumper from my teammate +NCR/CRC! [ReVeRsEr] to do the job:

We resume the execution of the server and now we can attach to the child process, which is locked in an infinite loop. After attaching we need to restore the two original bytes at 0x01001E37 (68 1C).

The application reads 4 bytes from the network, which indicate the size of the following data. By snooping around I’ve realized that the RobotLoader is expecting a Windows DLL, so I compiled a simple one using Visual C++ Express 2008.

The DLL we are sending to the service must meet some requirements in order to be accepted. The initial checks are performed within the function that starts at 0x01001844. First of all, a new buffer  is allocated using VirtualAlloc, being the size the value of the SizeOfImage field from the Optional Header of the DLL:


01001844 >/$  8BFF          MOV EDI,EDI                              ;  check_dll
01001846  |.  55            PUSH EBP
01001847  |.  8BEC          MOV EBP,ESP
01001849  |.  83EC 28       SUB ESP,28
0100184C  |.  53            PUSH EBX
0100184D  |.  56            PUSH ESI
0100184E  |.  57            PUSH EDI
0100184F  |.  8BD8          MOV EBX,EAX
01001851  |.  0FB673 3C     MOVZX ESI,BYTE PTR DS:[EBX+3C]           ;  Offset to PE
01001855  |.  6A 04         PUSH 4                                   ; /Protect = PAGE_READWRITE
01001857  |.  68 00100000   PUSH 1000                                ; |AllocationType = MEM_COMMIT
0100185C  |.  03F3          ADD ESI,EBX                              ; |
0100185E  |.  FF76 50       PUSH DWORD PTR DS:[ESI+50]               ; |Size = OptionalHeader.SizeOfImage
01001861  |.  33FF          XOR EDI,EDI                              ; |
01001863  |.  57            PUSH EDI                                 ; |Address => NULL
01001864  |.  897D E4       MOV DWORD PTR SS:[EBP-1C],EDI            ; |
01001867  |.  FF15 10100001 CALL DWORD PTR DS:[<&KERNEL32.VirtualAll>; \VirtualAlloc

[...]

It’s important to note that this function, when reading the e_lfanew (a.k.a offset to PE) field from the DOS Header, interprets it as a single byte, when it’s actually 4 bytes long. I don’t know if it was a bug from the developer of the challenge or if it was a limitation on purpose. Anyways, our DLL must have an offset to PE < 0x100, otherwise this function will not work as expected.

After that, the function traverses the table of section headers, copying the sections data to the buffer:


0100188E  |>  FF77 FC       /PUSH DWORD PTR DS:[EDI-4]               ; /n = D5E00 (876032.)
01001891  |.  8B07          |MOV EAX,DWORD PTR DS:[EDI]              ; |
01001893  |.  03C3          |ADD EAX,EBX                             ; |
01001895  |.  50            |PUSH EAX                                ; |src
01001896  |.  8B47 F8       |MOV EAX,DWORD PTR DS:[EDI-8]            ; |
01001899  |.  0345 FC       |ADD EAX,DWORD PTR SS:[EBP-4]            ; |
0100189C  |.  50            |PUSH EAX                                ; |dest
0100189D  |.  E8 220D0000   |CALL <JMP.&msvcrt.memcpy>               ; \memcpy
010018A2  |.  83C4 0C       |ADD ESP,0C
010018A5  |.  83C7 28       |ADD EDI,28
010018A8  |.  FF4D F4       |DEC DWORD PTR SS:[EBP-C]
010018AB  |.^ 75 E1         \JNZ SHORT RobotLoa.0100188E

Then it checks a couple of conditions that our DLL must meet:

  1. Is the number of exported functions == 0xbeef?
  2. Is the number of functions exported by name == 2?

010018AF  |>  8B56 78       MOV EDX,DWORD PTR DS:[ESI+78]
010018B2  |.  0355 FC       ADD EDX,DWORD PTR SS:[EBP-4]
010018B5  |.  817A 14 EFBE0>CMP DWORD PTR DS:[EDX+14],0BEEF          ;  number of exported functions == 0xbeef?
010018BC  |.  0F85 EA000000 JNZ RobotLoa.010019AC
010018C2  |.  837A 18 02    CMP DWORD PTR DS:[EDX+18],2              ;  number of functions exported by name == 2?

If we succeed, then more checks follow.

This function holds 4 flags; all of them must have value 1 if we want our DLL to be accepted by the service. Those flags are hold at [EBP-0x10], [EBP-0x14], [EBP-0x18] and [EBP-0x1C].

[EBP-0x14] will have value 1 if the first exported by name function is called “asdfqwer“.

[EBP-0x10] will have value 1 if the second exported by name function is called “robots_fear_squatch“.


010018F5  |>  8B75 F8        MOV ESI,DWORD PTR SS:[EBP-8]
010018F8  |.  8B0CB1        |MOV ECX,DWORD PTR DS:[ECX+ESI*4]
010018FB  |.  8B75 FC       |MOV ESI,DWORD PTR SS:[EBP-4]
010018FE  |.  03F1          |ADD ESI,ECX
01001900  |.  3BF7          |CMP ESI,EDI
01001902  |.  8975 DC       |MOV DWORD PTR SS:[EBP-24],ESI
01001905  |.  74 20         |JE SHORT RobotLoa.01001927
01001907  |.  6A 09         |PUSH 9
01001909  |.  BF 4C110001   |MOV EDI,RobotLoa.0100114C               ;  ASCII "asdfqwer"
0100190E  |.  59            |POP ECX
0100190F  |.  33DB          |XOR EBX,EBX
01001911  |.  F3:A6         |REPE CMPS BYTE PTR ES:[EDI],BYTE PTR DS>
01001913  |.  74 05         |JE SHORT RobotLoa.0100191A
01001915  |.  1BDB          |SBB EBX,EBX
01001917  |.  83DB FF       |SBB EBX,-1
0100191A  |>  33FF          |XOR EDI,EDI
0100191C  |.  85DB          |TEST EBX,EBX
0100191E  |.  75 07         |JNZ SHORT RobotLoa.01001927
01001920  |.  C745 EC 01000>|MOV DWORD PTR SS:[EBP-14],1
01001927  |>  8B75 DC       |MOV ESI,DWORD PTR SS:[EBP-24]
0100192A  |.  3BF7          |CMP ESI,EDI
0100192C  |.  74 20         |JE SHORT RobotLoa.0100194E
0100192E  |.  6A 14         |PUSH 14
01001930  |.  BF 58110001   |MOV EDI,RobotLoa.01001158               ;  ASCII "robots_fear_squatch"
01001935  |.  59            |POP ECX
01001936  |.  33DB          |XOR EBX,EBX
01001938  |.  F3:A6         |REPE CMPS BYTE PTR ES:[EDI],BYTE PTR DS>
0100193A  |.  74 05         |JE SHORT RobotLoa.01001941
0100193C  |.  1BDB          |SBB EBX,EBX
0100193E  |.  83DB FF       |SBB EBX,-1
01001941  |>  33FF          |XOR EDI,EDI
01001943  |.  85DB          |TEST EBX,EBX
01001945  |.  75 07         |JNZ SHORT RobotLoa.0100194E
01001947  |.  C745 F0 01000>|MOV DWORD PTR SS:[EBP-10],1
0100194E  |>  FF45 F8       |INC DWORD PTR SS:[EBP-8]
01001951  |.  837D F8 02    |CMP DWORD PTR SS:[EBP-8],2
01001955  |.^ 72 9B         \JB SHORT RobotLoa.010018F2

[EBP-0x1C] will have value 1 if the 0xbeefth exported function is not null:


0100195A  |.  B9 EFBE0000   MOV ECX,0BEEF
0100195F  |.  8BF1          MOV ESI,ECX
01001961  |.  2BF2          SUB ESI,EDX
01001963  |.  393CB0        CMP DWORD PTR DS:[EAX+ESI*4],EDI
01001966  |.  74 08         JE SHORT RobotLoa.01001970
01001968  |.  33F6          XOR ESI,ESI
0100196A  |.  46            INC ESI
0100196B  |.  8975 E4       MOV DWORD PTR SS:[EBP-1C],ESI

And [EBP-0x18] will have value 1 if the 1337th exported function is not null:


01001970  |>  33F6          XOR ESI,ESI
01001972  |.  46            INC ESI
01001973  |>  BB 39050000   MOV EBX,539
01001978  |.  2BDA          SUB EBX,EDX
0100197A  |.  393C98        CMP DWORD PTR DS:[EAX+EBX*4],EDI
0100197D  |.  74 03         JE SHORT RobotLoa.01001982
0100197F  |.  8975 E8       MOV DWORD PTR SS:[EBP-18],ESI

As you may imagine, I needed a DLL with a huge (0xbeef == 48879) number of exported functions.

In order to achieve that, I created a simple Python script that spits the needed number of functions declarations, and then I pasted its output into my Visual C++ project.


template = """EXTERN_DLL_EXPORT DWORD zz%d(PVOID generic){
*((DWORD *)generic) = 0x41414141;
return 0x42424242;
}
"""

buffer = ""

for i in range(0xbeef-2):
print "%d/%d..." % (i, 0xbeef-2)
buffer += template % i

f = open("output.txt", "w")
f.write(buffer)
f.close()

Finally, the function checks that all the exported functions are null’ed, except the four ones we’ve mentioned, that’s: “asdfqwer”, “robots_fear_squatch”, export #1337, export #0xbeef.

01001982  |>  8D0490        LEA EAX,DWORD PTR DS:[EAX+EDX*4]
01001985  |>  3938          /CMP DWORD PTR DS:[EAX],EDI
01001987  |.  74 03         |JE SHORT RobotLoa.0100198C
01001989  |.  FF45 F4       |INC DWORD PTR SS:[EBP-C]
0100198C  |>  83C0 04       |ADD EAX,4
0100198F  |.  49            |DEC ECX
01001990  |.^ 75 F3         \JNZ SHORT RobotLoa.01001985
01001992  |.  837D F4 04    CMP DWORD PTR SS:[EBP-C],4
01001996  |.  8BC6          MOV EAX,ESI
01001998  |.  74 03         JE SHORT RobotLoa.0100199D

If we succedded we’ll exit this function having EAX == 1. That means that we’ll reach the following call:

01001F2D   .  FF75 E4       PUSH DWORD PTR SS:[EBP-1C]               ;  SizeOfImage
01001F30   .  FF75 E0       PUSH DWORD PTR SS:[EBP-20]               ;  Buffer
01001F33   .  E8 5AFDFFFF   CALL <RobotLoa.dump_DLL>                 ;  01001C92

That function starting at 0x01001C92 dumps our DLL to a file in the current directory, and loads it by calling LoadLibraryA. After loading our library, the function starting at 0x010016D2 is called. This function will resolve the addresses of our 4 exported functions ( “asdfqwer”, “robots_fear_squatch”, export #1337, export #0xbeef) it will call them using different calling conventions, and it will check the returned values against expected values.
The summary is written below:

  • asdfqwer“: it is resolved by name. Receives 4 parameters, using stdcall calling convention. It must return 0x12345678.

01001432    68 4C110001     PUSH RobotLoa.0100114C                   ; ASCII "asdfqwer"
01001437    FF75 08         PUSH DWORD PTR SS:[EBP+8]                ; hModule
0100143A    FF15 00100001   CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress
[...]

0100145B    68 EFCD0000     PUSH 0CDEF
01001460    68 AB890000     PUSH 89AB
01001465    68 67450000     PUSH 4567
0100146A    68 23010000     PUSH 123
0100146F    FFD1            CALL ECX                                 ; call asdfqwer()
01001471    8BC8            MOV ECX,EAX                              ; stdcall
[...]

0100148C    81F9 78563412   CMP ECX,12345678        ; must return 0x12345678
01001492    75 10           JNZ SHORT RobotLoa.010014A4
  • export #0xbeef: resolved by ordinal (#48879). Receives 4 parameters, using cdecl calling convention. It must return 0xDDDDDDDD.

010014E8    68 EFBE0000     PUSH 0BEEF
010014ED    FF75 08         PUSH DWORD PTR SS:[EBP+8]
010014F0    FF15 00100001   CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress

[...]

01001511    BE DDDDDDDD     MOV ESI,DDDDDDDD
01001516    56              PUSH ESI
01001517    68 CCCCCCCC     PUSH CCCCCCCC
0100151C    68 BBBBBBBB     PUSH BBBBBBBB
01001521    68 AAAAAAAA     PUSH AAAAAAAA
01001526    FFD1            CALL ECX                                 ; call #oxBEEF
01001528    83C4 10         ADD ESP,10                               ; cdecl calling convention
[...]

01001546    3BCE            CMP ECX,ESI                              ; esi = expected value, 0xDDDDDDDD
01001548    75 10           JNZ SHORT RobotLoa.0100155A

  • robots_fear_squatch“: resolved by name. Receives 4 parameters, using stdcall calling convention. It must return 0xABCDABCD.

0100159E    68 58110001     PUSH RobotLoa.01001158                   ; ASCII "robots_fear_squatch"
010015A3    FF75 08         PUSH DWORD PTR SS:[EBP+8]                ; hModule
010015A6    FF15 00100001   CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress
[...]

010015C7    6A 04           PUSH 4
010015C9    6A 03           PUSH 3
010015CB    6A 02           PUSH 2
010015CD    33F6            XOR ESI,ESI
010015CF    46              INC ESI
010015D0    56              PUSH ESI
010015D1    FFD1            CALL ECX                                 ; call robots_fear_squatch()
010015D3    8BC8            MOV ECX,EAX                              ; stdcall
[...]

010015EE    81F9 CDABCDAB   CMP ECX,ABCDABCD
010015F4    75 0C           JNZ SHORT RobotLoa.01001602
  • export #1337: resolved by ordinal. It receives 4 parameters, using cdecl calling convention. It must return 0xFEDCBA98.

01001646    68 39050000     PUSH 539                                 ; 1337
0100164B    FF75 08         PUSH DWORD PTR SS:[EBP+8]                ; hModule
0100164E    FF15 00100001   CALL DWORD PTR DS:[<&KERNEL32.GetProcAdd>; kernel32.GetProcAddress
[...]

0100166F    68 20030000     PUSH 320
01001674    68 22030000     PUSH 322
01001679    68 21030000     PUSH 321
0100167E    68 23344223     PUSH 23423423
01001683    FFD1            CALL ECX                                 ; call #1337
01001685    83C4 10         ADD ESP,10                               ; cdecl
[...]

010016A3    81F9 98BADCFE   CMP ECX,FEDCBA98
010016A9    75 10           JNZ SHORT RobotLoa.010016BB

Finally (really, finally :)), the function starting at 0x01001b28 is invoked. This function calls CreateFileA in order to read an existing file named “KeyFileN.ame”:

01001BA0  |.  899D E8FBFFFF MOV DWORD PTR SS:[EBP-418],EBX           ; |
01001BA6  |.  FF15 48430001 CALL DWORD PTR DS:[1004348]              ; \CreateFileA

CreateFileA is invoked with the following arguments:

0006F86C   0006FC9C  |FileName = "KeyFileN.ame"
0006F870   80000000  |Access = GENERIC_READ
0006F874   00000001  |ShareMode = FILE_SHARE_READ
0006F878   00000000  |pSecurity = NULL
0006F87C   00000003  |Mode = OPEN_EXISTING
0006F880   00000000  |Attributes = 0
0006F884   00000000  \hTemplateFile = NULL

The content of the KeyFileN.ame file is read, and it’s interpreted as the name of another file containing the key to this challenge. So one more call to CreateFile is performed in order to read the contents of the file which name is specified within the KeyFileN.ame file, and its content (the key to this challenge) is sent through the socket to the connected client.

This is the Python script I’m using to connect to the server:


import socket

f = open('RobotTestingFramework.dll', 'rb')
dll = f.read()
f.close()

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('localhost', 8009))

data = s.recv(4096)
print data

s.send(struct.pack('<L', len(dll)))
print "Sending 0x%X bytes..." % len(dll)
s.send(dll)

print "Waiting for the key..."
data = s.recv(4096)
print "Received key: %s" % data
s.close()

I hope you liked it! If you were able to finish this challenge on time, please leave a comment!

Advertisements

9 thoughts on “PlaidCTF 2012 – Robot Testing Framework (350) [Pirating] Writeup

  1. Really nice write-up dude ! I think you are the first one to publish your result ! This was the only Windows challenge on pCTF12 but what a challenge !!!

  2. Perfect write-up! And, you’re right, that was a bug, great catch! Look me up at defcon and I’ll buy you a beer. The key: The Int3rdimensional Sasqu@tch crush3s r0bots!1!1!

  3. Pingback: Plaid CTF 2012 ~ Writeups collection | TechBrunch

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s