This is one of those ones that will work under the right circumstances, Steve. May I call you Steve? Before you take to twitter complaining about a broken challenge, think about how you can be the change you want to see in the world, Steve.
7-zip password: flare
Challenge file: mypassion
This is one of the harder challenges this year. It requires some work in deciphering shellcodes.
This challenge is all about understanding how the first argument gets parsed within the application so that you can give it an input that fits the expected value within the context.
In the main function, you can see that all the juicy stuff occur when the length of the argc
is 2
:
Initially, we see in main
that the program exits and logs off the session (logic in exit
function) if the following conditions are not met.
- the first letter of the first argument
argv[1]
is not0
(ASCII value 0x30). - the second letter shifted left by two + third letter (i.e.,
ord(argv[1][2]) + (ord(argv[1][1]) << 2) != 0x127
)
If any of the above conditions is is true, the app will exit.
To find some possible combinations of ASCII letters that meet this criteria, we can write this simple program:
combinations = []
for letter2 in range(0x20, 0x7f):
for letter3 in range(0x20, 0x7f):
if letter3 + (letter2 << 2) == 0x127:
combinations.append((chr(letter2), chr(letter3)))
for combo in combinations:
print("letter2: {} letter3: {}".format(combo[0], combo[1]))
I'll choose the combination (letter2: 5
letter3:S
).
Now we place a breakpoint at base+0x109D
and run the program with the argument 05Sabcdefghijklmnopqrstuvwxyz
, we notice that we pass the check, which means we do not exit:
There's another check at 0x10fe
that compares the 6th letter with R
, so we'll modify our argument to be 05SabRdefghijklmnopqrstuvwxyz
.
Now that we've passed the initial checks, it's time to dive deeper!
Now if we place a breakpoint at the VirtualAlloc
call, we'll notice that there's something that is not right.
0:000> bp sym + 11b0
0:000> g
ModLoad: 00007ff8`a3900000 00007ff8`a3930000 C:\Windows\System32\IMM32.DLL
Breakpoint 0 hit
sym+0x11b0:
00007ff7`da2611b0 ff15121f0100 call qword ptr [sym+0x130c8 (00007ff7`da2730c8)] ds:00007ff7`da2730c8={KERNEL32!VirtualAllocStub (00007ff8`a3948810)}
0:000> p
sym+0x11b6:
00007ff7`da2611b6 488bf8 mov rdi,rax
0:000> r rax
rax=0000000000000000
0:000> !gle
LastErrorValue: (Win32) 0x57 (87) - The parameter is incorrect.
LastStatusValue: (NTSTATUS) 0xc0000045 - The specified page protection was not valid.
VirtualAlloc
's flProtect
is not correct. Why?
Breakpoint 0 hit
sym+0x11b0:
00007ff7`da2611b0 ff15121f0100 call qword ptr [sym+0x130c8 (00007ff7`da2730c8)] ds:00007ff7`da2730c8={KERNEL32!VirtualAllocStub (00007ff8`a3948810)}
0:000> r r9
r9=0000000000000064
According to MSDN, 0x64
does not represent a valid protection value. If we look closer at the main
function, we'll notice that our input affects this parameter. 0x64
is the ASCII value of d
, which is the 7th letter in our input. We'll modify the 7th letter to be @
, which represents PAGE_EXECUTE_READWRITE
.
We'll rerun the binary with the argument 05SabR@efghijklmnopqrstuvwxyz
, and we'll notice that VirtualAlloc
executes successfully this time.
0:000> bp sym + 11b0
0:000> g
Breakpoint 0 hit
sym+0x11b0:
00007ff7`da2611b0 ff15121f0100 call qword ptr [sym+0x130c8 (00007ff7`da2730c8)] ds:00007ff7`da2730c8={KERNEL32!VirtualAllocStub (00007ff8`a3948810)}
0:000> r r9
r9=0000000000000040
0:000> p
sym+0x11b6:
00007ff7`da2611b6 488bf8 mov rdi,rax
0:000> r rax
rax=000001822f990000
Next, we notice that there's a call to sub_140003f10
, which is essentially a memset
function that Binary Ninja failed to recognize. It zeros a region within the stack.
After executing memset
, the VirtualAlloc
allocated chunk gets overwritten by a shellcode which is then executed. Notice that the 13th letter of our argument overwrites the offset 0x41
of the shellcode.
A better look of that code is provided by IDA:
If we continue the execution of the binary with the current argument, we get an exception:
(26d8.1c8c): Illegal instruction - code c000001d (first chance)
(26d8.1c8c): Illegal instruction - code c000001d (!!! second chance !!!)
00000290`773c0040 c6 ???
If we inspect the shellcode before executing it, we'll notice that it has a bad instruction:
0:000> u rdi L14
000001d9`6dad0000 4055 push rbp
000001d9`6dad0002 488bec mov rbp,rsp
000001d9`6dad0005 4883ec10 sub rsp,10h
000001d9`6dad0009 c6451874 mov byte ptr [rbp+18h],74h
000001d9`6dad000d 4c8d55f0 lea r10,[rbp-10h]
000001d9`6dad0011 c6451965 mov byte ptr [rbp+19h],65h
000001d9`6dad0015 4c8d5df0 lea r11,[rbp-10h]
000001d9`6dad0019 c6451a6e mov byte ptr [rbp+1Ah],6Eh
000001d9`6dad001d 33c0 xor eax,eax
000001d9`6dad001f c6451b00 mov byte ptr [rbp+1Bh],0
000001d9`6dad0023 4c8bc1 mov r8,rcx
000001d9`6dad0026 8945f0 mov dword ptr [rbp-10h],eax
000001d9`6dad0029 4533c9 xor r9d,r9d
000001d9`6dad002c 668945f4 mov word ptr [rbp-0Ch],ax
000001d9`6dad0030 c645f016 mov byte ptr [rbp-10h],16h
000001d9`6dad0034 c645f117 mov byte ptr [rbp-0Fh],17h
000001d9`6dad0038 c645f23b mov byte ptr [rbp-0Eh],3Bh
000001d9`6dad003c c645f317 mov byte ptr [rbp-0Dh],17h
000001d9`6dad0040 c6 ???
000001d9`6dad0041 6af4 push 0FFFFFFFFFFFFFFF4h
As expected, the letter j
in our argument overwrote the shellcode, which resulted in a bad instruction.
We also note that the first argument when this call happens is our argument as well:
0:000> dc rcx
00000091`57effa50 68676665 00000069 00000000 00000000 efghi...........
To help through the shellcode debugging, I used CyberChef.
SIDE NOTE
Don't depend entirely on CyberChef disassembler. It sometimes gives incorrect instructions. Always check instructions in the debugger.
For now, we just want to continue the execution of the binary, so we'll modify the letter 13th j
to any value that fits within the context. For example, let's use E
, which results into this. This should continue the program execution without having an invalid instruction.
The updated argument is 05SabR@efghiEklmnopqrstuvwxyz
.
0:000> bp sym + 1299
0:000> g
Breakpoint 0 hit
sym+0x1299:
00007ff7`da261299 ffd7 call rdi {0000016b`60910000}
0:000> p
sym+0x129b:
00007ff7`da26129b 4c8b4308 mov r8,qword ptr [rbx+8] ds:0000016b`609962d8=0000016b6099632e
As shown, now the call to the shellcode isn't failing.
Next, I'll list the behavior of the unrecognized functions (shown in the figure above) is as follows:
sub_140001000
: a wrapper around strnlen
.
sub_140002F00
: retrieves a segment separated by /
. First argument is the source string and the second is an integer representing the segment to be retrieved.
sub_140002C00
: does sha256sum of the provided input.
sub_140002D20
: decrypts data.
sub_140002DB0
: Writes an HTML file and then opens it.
sub_1400018B0
: requires further discussion, but it mainly contains the binary's logic.
sub_1400013E0
: is the runner function. It takes data within the Destination
and performs the required functions according to the binary's logic.
We'll next focus on the call to sub_1400013E0
function at 0x13c6
as it is the interesting one that gets executed after the initial VirtualAlloc
.
sub_1400013E0
starts with checking if /
is present within the supplied argument. If not, then it exits.
In order to pass those checks and not exit, we'll modify the argument to be 05SabR@efghiEklmnopqrstuvwxyz/test/test
. If we let the program executes with this argument, it'll create a new file called test
(from our argument) within the current folder.
Checking the the entropy of the file indicates that it's probably encrypted.
The program continues all the way until the end of sub_1400013E0
, but it calls another shellcode at 0x1892
.
This shellcode is decrypted using the sha256 hash of the word turnitup
as shown in the following figure:
We'll try to dump the shellcode and analyze it:
Breakpoint 0 hit
sym+0x1892:
00007ff7`da261892 ffd0 call rax {00000174`70800000}
0:000> db rax L0n176
00000174`70800000 48 89 5c 24 10 48 89 74-24 18 57 48 83 ec 20 48 H.\$.H.t$.WH.. H
00000174`70800010 8b 81 08 01 00 00 48 8b-d9 c7 40 08 03 00 00 00 ......H...@.....
00000174`70800020 48 8b 81 08 01 00 00 8b-50 08 ff 91 90 03 00 00 H.......P.......
00000174`70800030 41 b8 04 00 00 00 48 c7-44 24 30 00 00 00 00 48 A.....H.D$0....H
00000174`70800040 8b c8 48 8d 54 24 30 48-8b f0 ff 93 80 03 00 00 ..H.T$0H........
00000174`70800050 48 8b 4c 24 30 ba 20 00-00 00 8b f8 ff 93 88 03 H.L$0. .........
00000174`70800060 00 00 3b c7 74 12 48 8b-8b 08 01 00 00 8b 49 08 ..;.t.H.......I.
00000174`70800070 ff 93 58 03 00 00 eb 0c-69 cf e8 03 00 00 ff 93 ..X.....i.......
00000174`70800080 50 03 00 00 48 8b ce ff-93 68 03 00 00 48 8b cb P...H....h...H..
00000174`70800090 ff 93 b0 03 00 00 48 8b-5c 24 38 48 8b 74 24 40 ......H.\$8H.t$@
00000174`708000a0 48 83 c4 20 5f c3 46 9d-81 02 07 00 c7 db 37 8c H.. _.F.......7.
0:000> dc rcx
000000d1`1d4ff930 61533530 65405262 69686766 6d6c6b45 05SabR@efghiEklm
000000d1`1d4ff940 71706f6e 75747372 79787776 65742f7a nopqrstuvwxyz/te
000000d1`1d4ff950 742f7473 00747365 00000000 00000000 st/test.........
Within this shellcode at shellcode1+2a
, it calls the function sub_140002F00
, which gets a segment (separated by /
):
000002c2`926a002a ff9190030000 call qword ptr [rcx+390h] ds:000000e9`b278f740=00007ff7da262f00
0:000> da rcx
000000e9`b278f3b0 "05SabR@efghiEklmnopqrstuvwxyz/te"
000000e9`b278f3d0 "st/test"
0:000> r rdx
rdx=0000000000000003
0:000> p
000002c2`926a0030 41b804000000 mov r8d,4
0:000> r rax
rax=0000000000000000
As shown, the return value is NULL
because we have only provided 2 /
. We'll update the argument include another /
at the end 05SabR@efghiEklmnopqrstuvwxyz/test/test2/
.
000002f0`4296002a ff9190030000 call qword ptr [rcx+390h] ds:000000f4`0c8ffbf0=00007ff7da262f00
0:000> p
...
0:000> da rax
000002f0`42a3a860 "test2"
Right after this call, this shellcode calls strtol
at shellcode1+0x4a
with the extracted segment test3
as its argument. This obviously means that this segment should be a number.
000002f0`4296004a ff9380030000 call qword ptr [rbx+380h] ds:000000f4`0c8ffbe0=00007ff7da2671a0
0:000> da rcx
000002f0`42a3a860 "test2"
We'll need to update this segment to be a number 05SabR@efghiEklmnopqrstuvwxyz/test/10test/
.
The test
at the end is for strlen
call that comes after at shellcode1+0x5c
.
shellcode1+0x90
calls the function sub_1400018B0
with our argument in RCX
00000218`33660090 ff93b0030000 call qword ptr [rbx+3B0h] ds:000000af`819df940=00007ff7da2618b0
0:000> da rcx
000000af`819df590 "05SabR@efghiEklmnopqrstuvwxyz/te"
000000af`819df5b0 "st/10test/"
sub_1400018B0
is a pretty large function, and it includes many other function calls within it.
In this function, we notice this check
The disassembled code is as follows:
Our input is held at r14
. This is another segment we need to add in our argument. Our input is being checked against a scattered word, so we'll need to read the letters from memory:
sym+0x199c:
00007ff7`da26199c 410fb64603 movzx eax,byte ptr [r14+3] ds:00000000`00000003=??
0:000> da rcx+1e L1
000000af`819df56e "p"
0:000> da rcx+1d L1
000000af`819df56d "i"
0:000> da rcx+1f L1
000000af`819df56f "z"
0:000> da rcx+1c L1
000000af`819df56c "z"
0:000> da rcx+20 L1
000000af`819df570 "a"
It checks the provided input with pizza
starting from index 1
of the segment. We'll update the argument accordingly 05SabR@efghiEklmnopqrstuvwxyz/test/10test/?pizza/
.
Then we face the following check:
This is a comparison with the first four bytes of in the encrypted file. With the current argument, this check fails and the program exits.
It turns out that we missed some logic in sub_1400013E0
, and we can control the header through one of the filename segment. This logic is listed in the following figure:
What comes after 1337
in the second segment will be the file name. Next, we notice two checks:
v33
is responsible for checking if the file name submitted is correct. If we set the second segment to 1337test
and run the program, we get the following:
0:000> u RIP L2
sym+0x1c40:
00007ff7`da261c40 0fb710 movzx edx,word ptr [rax]
00007ff7`da261c43 0fb70c18 movzx ecx,word ptr [rax+rbx]
0:000> du rax
00000052`21ad4b08 "pr.ost"
0:000> du rax+rbx
000001ac`5ef1a6b0 "test"
The second check calls GetTickCount
and compares it with a previously calculated value + 8000
.
It turns out that the Sleep
function executed in the previous shellcode has an effect on it. To pass this check, we'll need to set the third segment to 30testtesttest
.
In the last shellcode, we had the following
shellcode1+00 48895c2410 mov qword ptr [rsp+10h],rbx
shellcode1+05 4889742418 mov qword ptr [rsp+18h],rsi
shellcode1+0a 57 push rdi
shellcode1+0b 4883ec20 sub rsp,20h
shellcode1+0f 488b8108010000 mov rax,qword ptr [rcx+108h]
shellcode1+16 488bd9 mov rbx,rcx
shellcode1+19 c7400803000000 mov dword ptr [rax+8],3
shellcode1+20 488b8108010000 mov rax,qword ptr [rcx+108h]
shellcode1+27 8b5008 mov edx,dword ptr [rax+8]
shellcode1+2a ff9190030000 call qword ptr [rcx+390h] // get segment 3
shellcode1+30 41b804000000 mov r8d,4
shellcode1+36 48c744243000000000 mov qword ptr [rsp+30h],0
shellcode1+3f 488bc8 mov rcx,rax
shellcode1+42 488d542430 lea rdx,[rsp+30h]
shellcode1+47 488bf0 mov rsi,rax
shellcode1+4a ff9380030000 call qword ptr [rbx+380h] // strtol
shellcode1+50 488b4c2430 mov rcx,qword ptr [rsp+30h]
shellcode1+55 ba20000000 mov edx,20h
shellcode1+5a 8bf8 mov edi,eax
shellcode1+5c ff9388030000 call qword ptr [rbx+388h] // strlen
shellcode1+62 3bc7 cmp eax,edi // check
shellcode1+64 7412 je 00000254`d83a0078
shellcode1+66 488b8b08010000 mov rcx,qword ptr [rbx+108h]
shellcode1+6d 8b4908 mov ecx,dword ptr [rcx+8]
shellcode1+70 ff9358030000 call qword ptr [rbx+358h]
To put simply, the string 30testtesttest
is passed to strtol
to convert. This will convert 30
to a base 4 number. This call returns 0xC
when 30testtesttest
is passed. The check at shellcode1+62
compares the resulted number from strtol
with the length of the string testtesttest
. This obviously will result into passing this check and also passing the GetTickCount
comparison that comes after checking the file name.
That said, our updated argument now should be 05SabR@efghiEklmnopqrstuvwxyz/1337pr.ost/30testtesttest/?pizza/
.
Next in sub_1400018B0
, we see a call to VirtualAllocEx
to reserve a relatively large space.
From the allocated size, we assume that this space is likely to be used for the buffer read from the encrypted file as the reserved space matches the size of the encrypted file. This can be confirmed through the memmove
call.
We also see a call to sub_140002170
. This is the function we'll analyze next.
Within sub_140002170
, we notice calls to the functions sub_140001DA0
and sub_140001F80
. These two functions simply prepare and return shellcodes. Also, sub_140002170
prepares another shellcode. We can see three shellcodes in total:
shellcode2
takes Beep
as its second argument. Beep is a function that simply generates a beep sound. Could shellcode2
be resolving a function name (in this case Beep
) based on its argument? If that's the case, then the first argument must be a module handle?
It turns out that this is the case for shellcode2
. It simply acts as GetProcAddress.
Since shellcode2
takes v5
that is obtained from shellcode3
, could that mean that shellcode3
resolves some type of module handle? It turns out that this is the case for shellcode3
.
For shellcode1
, we'll dump and analyze it:
Breakpoint 0 hit
sym+0x21cc:
00007ff7`da2621cc ffd0 call rax {00000264`94de0000}
0:000> .writemem C:\\Windows\\Temp\\shellcode_21cc.bin rax L72
Writing 72 bytes.
Upon analyzing this shellcode, it turns out that it just acts exactly as sub_140002F00
, which extracts a segment separated by /
based on the second argument.
The second argument to this is 5
, which simply means that it'll extract segment 5 from the argument, so we'll modify the argument to 05SabR@efghiEklmnopqrstuvwxyz/1337pr.ost/30testtesttest/?pizza/ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ/
.
We can verify this
Breakpoint 0 hit
sym+0x21cc:
00007ff7`da2621cc ffd0 call rax {000001a9`49360000}
0:000> da rcx
00000022`54fef6a0 "05SabR@efghiEklmnopqrstuvwxyz/13"
00000022`54fef6c0 "37pr.ost/30testtesttest/?pizza/Z"
00000022`54fef6e0 "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
00000022`54fef700 "/"
0:000> r rdx
rdx=0000000000000005
0:000> p
sym+0x21ce:
00007ff7`da2621ce 488d4de7 lea rcx,[rbp-19h]
0:000> da rax
00000022`54fc4740 "ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ"
00000022`54fc4760 "Z"
If we continue the execution, we face a crash in shellcode3
:
0:000> g
(1c7c.3264): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
000001a9`49380001 488b042560000000 mov rax,qword ptr [60h] ds:00000000`00000060=????????????????
Obviously, the instruction is invalid as it is dereferencing an invalid memory address. The reason for this crash is that the fifth segment of our input (i.e., ZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZZ
) modifies the shellcode opcodes (both shellcode3
and shellcode2
).
To understand this, we'll dump shellcode3
as it is where we crash:
Breakpoint 0 hit
sym+0x2354:
00007ff7`da262354 ffd2 call rdx {000001df`6a440000}
0:000> db rdx L65
000001df`6a440000 5a 48 8b 04 25 60 00 00-00 48 8b 48 18 48 8b 51 ZH..%`...H.H.H.Q
000001df`6a440010 20 48 83 ea 10 48 8b 42-5a 66 83 78 10 5a 75 2e H...H.BZf.x.Zu.
000001df`6a440020 66 83 78 0e 32 75 27 66-83 78 0c 33 75 20 66 83 f.x.2u'f.x.3u f.
000001df`6a440030 78 0a 4c 74 07 66 5a 78-08 6c 75 12 0f b7 40 08 x.Lt.fZx.lu...@.
000001df`6a440040 b9 df ff 00 00 66 83 e8-45 66 85 c1 74 12 48 8b .....f..Ef..t.H.
000001df`6a440050 5a 10 48 83 ea 10 48 83-7a 30 00 75 b8 33 c0 c3 Z.H...H.z0.u.3..
000001df`6a440060 48 8b 42 5a c3 H.BZ.
As can be seen in CyberChef disassembler, this shellcode has invalid instructions. If we look at the opcodes, we see several instances of 0x5a
(ASCII value of Z
).
After further analysis, I came up with the following list of indexes at which our input overwrites the shellcode:
Unknown letters indexes in shellcode3: 12, 5, 8, 9, 7, 6
[12] 48 8b 04 25 60 00 00 00 48 8b 48 18 48 8b 51
20 48 83 ea 10 48 8b 42 [5] 66 83 78 10 [8] 75 2e
66 83 78 0e 32 75 27 66 83 78 0c 33 75 20 66 83
78 0a 4c 74 07 66 [9] 78 08 6c 75 12 0f b7 40 08
b9 df ff 00 00 66 83 e8 45 66 85 c1 74 12 48 8b
[7] 10 48 83 ea 10 48 83 7a 30 00 75 b8 33 c0 c3
48 8b 42 [6] c3
This means that the letter at index 12
in segment 5 will affect the first byte (byte index 0
), letter at index 5
will affect the byte at index 24
, ... and so on.
Note that the first letter (index 0
) in the fifth segment must equal the 11th letter (index 10
) due to the comparison at 0x22e8
. If not, the program exits.
Beep
is in KERNEL32.DLL
. This means that this shellcode must get a handle to KERNEL32.DLL
module. We need to modify the shellcode (using our entry points supplied by 5th argument) in a way that the shellcode behaves as expected.
To do this, I used the following set of letters:
[12] = 0x65 = e
[5] = 0x60 = `
[8] = 0x2e = .
[9] = 0x43 = C
[7] = 0x52 = R
[6] = 0x30 = 0
OUr updated argument is 05SabR@efghiEklmnopqrstuvwxyz/1337pr.ost/30testtesttest/?pizza/ZZZZZ`0R.CZZeZZZZZZZZZZZZZZZZZZZZ/
.
Breakpoint 0 hit
sym+0x2354:
00007ff7`da262354 ffd2 call rdx {000002ad`21a10000}
0:000> p
sym+0x2356:
00007ff7`da262356 4885c0 test rax,rax
0:000> r rax
rax=00007ff8a3930000
0:000> lm m kernel32
Browse full module list
start end module name
00007ff8`a3930000 00007ff8`a39ed000 KERNEL32
With this argument, the shellcode works as expected and returns the base address of KERNEL32.DLL
.
Next, we'll take a look at shellcode2
that is supposed to resolve the address of Beep
. We'll set a breakpoint and dump the shellcode:
Breakpoint 1 hit
sym+0x236c:
00007ff7`da26236c ffd6 call rsi {000002ad`21a00000}
0:000> db rsi La1
000002ad`21a00000 48 89 5c 24 08 48 89 7c-24 18 48 89 54 24 10 4c H.\$.H.|$.H.T$.L
000002ad`21a00010 8b c1 48 85 c9 74 63 b8-5a 5a 00 00 66 39 01 75 ..H..tc.ZZ..f9.u
000002ad`21a00020 59 48 63 41 3c 81 3c 08-50 5a 00 00 75 4c 44 8b YHcA<.<.PZ..uLD.
000002ad`21a00030 8c 08 88 00 00 00 4c 03-c9 45 8b 59 20 4c 03 d9 ......L..E.Y L..
000002ad`21a00040 33 c9 41 39 49 18 76 32-41 8b 04 8b 48 8b 5c 24 3.A9I.v2A...H.\$
000002ad`21a00050 10 49 03 c0 48 2b d8 8b-f9 0f b6 10 44 0f b6 14 .I..H+......D...
000002ad`21a00060 18 41 2b d2 75 08 48 ff-c0 45 85 d2 5a eb 85 d2 .A+.u.H..E..Z...
000002ad`21a00070 74 15 ff c1 41 3b 49 18-72 ce 33 c0 48 8b 5c 24 t...A;I.r.3.H.\$
000002ad`21a00080 08 48 8b 7c 24 18 c3 41-8b 49 5a 49 03 c8 0f b7 .H.|$..A.IZI....
000002ad`21a00090 14 79 5a 8b 49 1c 49 03-c8 8b 04 91 49 03 c0 eb .yZ.I.I.....I...
000002ad`21a000a0 db
Again, we'll do the same exact process. After analyzing the shellcode, I noticed that our argument affects the shellcode at the following indexes:
Unknown letters indexes in shellcode2: 1, 11, 4, 2, 3, 0 (index 0 must equal index 10)
48 89 5c 24 08 48 89 7c 24 18 48 89 54 24 10 4c
8b c1 48 85 c9 74 63 b8 [1] [11] 00 00 66 39 01 75
59 48 63 41 3c 81 3c 08 50 [4] 00 00 75 4c 44 8b
8c 08 88 00 00 00 4c 03 c9 45 8b 59 20 4c 03 d9
33 c9 41 39 49 18 76 32 41 8b 04 8b 48 8b 5c 24
10 49 03 c0 48 2b d8 8b f9 0f b6 10 44 0f b6 14
18 41 2b d2 75 08 48 ff c0 45 85 d2 [2] eb 85 d2
74 15 ff c1 41 3b 49 18 72 ce 33 c0 48 8b 5c 24
08 48 8b 7c 24 18 c3 41 8b 49 [3] 49 03 c8 0f b7
14 79 [0] 8b 49 1c 49 03 c8 8b 04 91 49 03 c0 eb
db
We need to modify the shellcode (using our entry points supplied by 5th argument) in a way that the shellcode behaves as expected. This discussed earlier, this shellcode is similar to what GetProcAddress
does.
After spending sometime analyzing the shellcode, I found that the following set of letters will make the shellcode works as expected:
[1] = 0x4d = M
[11] = 0x5a = Z
[4] = 0x45 = E
[2] = 0x75 = u
[3] = 0x24 = $
[0] = [10] = 0x41 = A
With this, our updated argument should be 05SabR@efghiEklmnopqrstuvwxyz/1337pr.ost/30testtesttest/?pizza/AMu$E`0R.CAZeZZZZZZZZZZZZZZZZZZZZ/
. Let's run with the updated argument:
Breakpoint 0 hit
sym+0x236c:
00007ff7`da26236c ffd6 call rsi {000001ec`138c0000}
0:000> p
sym+0x236e:
00007ff7`da26236e 4885c0 test rax,rax
0:000> u rax
KERNEL32!BeepImplementation:
00007ff8`a3966980 48895c2418 mov qword ptr [rsp+18h],rbx
00007ff8`a3966985 57 push rdi
00007ff8`a3966986 4881ecf0030000 sub rsp,3F0h
00007ff8`a396698d 488b058cb80700 mov rax,qword ptr [KERNEL32!_security_cookie (00007ff8`a39e2220)]
00007ff8`a3966994 4833c4 xor rax,rsp
00007ff8`a3966997 48898424e0030000 mov qword ptr [rsp+3E0h],rax
00007ff8`a396699f 8bd9 mov ebx,ecx
00007ff8`a39669a1 8bfa mov edi,edx
We can confirm that shellcode2
works as expected.
At the end of sub_140002170
, there's another call to sub_140002910
that we'll need to analyze next. Don't worry, the suffering will end soon :'D!
Within sub_140002910
, there's a call to sub_140002F00
to extract yet another segment at index 6.
We'll update our argument to 05SabR@efghiEklmnopqrstuvwxyz/1337pr.ost/30testtesttest/?pizza/AMu$E`0R.CAZe/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA/
to see what it exactly does with this new segment. We deleted the extra Z
at segment 5 as they do not get processed anywhere, so we do not need them anymore.
Next, we see some operations being performed on our segment then then a call to memcmp
:
Let's investigate the call to memcmp
0:000> bp sym + 2AAD
0:000> g
...
Breakpoint 0 hit
sym+0x2aad:
00007ff7`da262aad e89ef10000 call sym+0x11c50 (00007ff7`da271c50)
0:000> r r8
r8=0000000000000019
0:000> da rcx L19
00000010`66374590 "VVVVVVVVVVVVVVVVVVVVVVVVV"
0:000> da rdx L19
00000010`66374570 "RUECKWAERTSINGENIEURWESEN"
It compares a specific string with our segment after it did some operations on it. Instead of figuring out what operations it exactly does on it, we'll make our lives easier by supplying ABCDEFGHIJKLMNOPQRSTUVWXYZ
as our 6th segment and then pick the letters it wants.
Breakpoint 0 hit
sym+0x2aad:
00007ff7`da262aad e89ef10000 call sym+0x11c50 (00007ff7`da271c50)
0:000> da rcx
00000087`566d42d0 "VPWLCJSFTXKHINGUBYZDOMQERA"
The result of ABCDEFGHIJKLMNOPQRSTUVWXYZ
is VPWLCJSFTXKHINGUBYZDOMQERA
. Now we can just map the letters to match RUECKWAERTSINGENIEURWESEN
.
We can now write a simple Python script to automate the mapping
table = str.maketrans("VPWLCJSFTXKHINGUBYZDOMQERA", "ABCDEFGHIJKLMNOPQRSTUVWXYZ")
key = "RUECKWAERTSINGENIEURWESEN"
segment = key.translate(table)
print(segment) # YPXEKCZXYIGMNOXNMXPYCXGXN
To confirm, we'll set the 6th segment to YPXEKCZXYIGMNOXNMXPYCXGXN
and check memcmp
call.
0:000> bp sym + 2AAD
0:000> g
...
Breakpoint 0 hit
sym+0x2aad:
00007ff7`da262aad e89ef10000 call sym+0x11c50 (00007ff7`da271c50)
0:000> da rcx L19
0000003a`16ed4420 "RUECKWAERTSINGENIEURWESEN"
0:000> da rdx L19
0000003a`16ed4400 "RUECKWAERTSINGENIEURWESEN"
As shown, the two words match.
At the end of sub_140002910
, there's a call to sub_140002670
.
With our current argument 05SabR@efghiEklmnopqrstuvwxyz/1337pr.ost/30testtesttest/?pizza/AMu$E`0R.CAZe/YPXEKCZXYIGMNOXNMXPYCXGXN/
, the flow continues all the way to sub_140002670
, which we will discuss next.
At the top of sub_140002670
, the function attempts to extract another:
We'll set this new segment to test1234
as a testing value.
At 0x26e1
, there's a comparison with the new segment:
sym+0x26e1:
00007ff7`da2626e1 3b03 cmp eax,dword ptr [rbx] ds:0000018e`0c11fd10=74736574
0:000> r eax
eax=6835656f
If any of these comparisons fails, the program exits.
As shown, test
is compared with 0x6835656f
, which is oe5h
. e
and h
come from our first segment.
If we set our 7th segment to oe5h12345
, this first check passes, and another comparison is performed:
sym+0x26e9:
00007ff7`da2626e9 663b4304 cmp ax,word ptr [rbx+4] ds:00000190`b7b60f34=3231
0:000> r ax
ax=7267
0x7267
(gr
) is being compared with 12
. We'll update the segment to oe5hgr345
to pass this check.
Next, another comparison is performed:
sym+0x26ef:
00007ff7`da2626ef 3a4b06 cmp cl,byte ptr [rbx+6] ds:0000022a`0ce8fd16=33
0:000> r cl
cl=69
We'll update the segment to oe5hgri
and remove any other letter as it is the last comparison.
Then we notice a call to sub_1400023D0
at 0x27B6
. According to ChatGPT, this is an RC4 decryption function. Let's inspect this call:
Breakpoint 0 hit
sym+0x27b6:
00007ff7`da2627b6 e815fcffff call sym+0x23d0 (00007ff7`da2623d0)
0:000> da rcx
000000f4`850d45d0 "REVERSEENGINEER"
0:000> db r8
000002ab`b4680000 01 a1 2f 2b e7 73 be 47-40 bb 8c 7f 4f 32 ce b1 ../+.s.G@...O2..
000002ab`b4680010 cf 30 4b 65 36 1d 14 31-d9 b7 c0 3a f0 10 7c 79 .0Ke6..1...:..|y
000002ab`b4680020 73 b7 5a c3 ca 36 79 1d-3d ca 8b ba 3c 72 53 ee s.Z..6y.=...<rS.
000002ab`b4680030 2a 30 3b ba 41 20 1a fa-63 fc 86 11 ee 2f 2f e0 *0;.A ..c....//.
000002ab`b4680040 5b 73 33 40 b8 37 e5 c6-d8 cc ff 7c 28 28 7a 98 [s3@.7.....|((z.
000002ab`b4680050 7c 45 ec c0 fc f3 59 63-dc 6e da 4a 62 0f 0c 76 |E....Yc.n.Jb..v
000002ab`b4680060 fd aa 20 08 e3 a3 62 77-07 17 be 78 1f 6d 63 a8 .. ...bw...x.mc.
000002ab`b4680070 6f ea ec dd c6 bc 7c 80-c9 91 1e 8e a7 d6 57 b0 o.....|.......W.
If we continue the execution with the updated segment, the program exits. If we inspect the call stack, we can observe where ExitProcess
was called from:
0:000> g
ntdll!NtTerminateProcess+0x14:
00007ff8`a568d3d4 c3 ret
0:000> k
# Child-SP RetAddr Call Site
00 000000dc`1a0d4608 00007ff8`a564da98 ntdll!NtTerminateProcess+0x14
01 000000dc`1a0d4610 00007ff8`a394e3bb ntdll!RtlExitUserProcess+0xb8
02 000000dc`1a0d4640 00007ff7`da262817 KERNEL32!ExitProcessImplementation+0xb
03 000000dc`1a0d4670 00007ff7`da262be2 sym+0x2817
The exit was caused by the comparison at 0x27FF
. Looking at the comparison, it seems that the program exits because of a CRC32 check failure (dword_14001A8C0
points to a CRC32 table).
If this check passes, the sha256 hash of our 7th segment will be calculated. Once hash is calculated, the decryption function is called. This made me think that the sha256 hash of the 7th segment is used as a key in the decryption function.
The main problem right now though is that the CRC32 checksum fails.
The value *(_DWORD *)(a1 + 992)
in the CRC32 comparison is obtained from the file at 0x1D1D-0x1D2C
.
After spending a lot of time trying to see how this check can be passed, I switched my way to brute forcing. Our current argument 05SabR@efghiEklmnopqrstuvwxyz/1337pr.ost/30testtesttest/?pizza/AMu$E`0R.CAZe/YPXEKCZXYIGMNOXNMXPYCXGXN/oe5hgri/
must have an incorrect segment (or character) that's causing this issue, so I tried to see what values affect edx
in the check:
Breakpoint 0 hit
sym+0x27ff:
00007ff7`da2627ff 3997e0030000 cmp dword ptr [rdi+3E0h],edx ds:00000088`01f5f9e0=92a7a888
0:000> r edx
edx=5d9ef227
The calculated value is in edx
, so to pass this check, edx
must equal 0x92a7a888
.
It turns out that the character before the word pizza
(?
) is responsible this value. In my case, this value should be *
to pass this comparison, so the 4th segment should be *pizza
:
Breakpoint 0 hit
sym+0x27ff:
00007ff7`da2627ff 3997e0030000 cmp dword ptr [rdi+3E0h],edx ds:000000b4`7f94f880=92a7a888
0:000> r edx
edx=92a7a888
Next, our 7th segment gets SHA256 hashed as expected:
Breakpoint 0 hit
sym+0x2828:
00007ff7`da262828 ff9798030000 call qword ptr [rdi+398h] ds:0000003e`728ff898=00007ff7da262c00
0:000> da rcx
00000187`230af150 "oe5hgri"
If we continue the execution, we reach invalid instructions in what appears to be a region for a another shellcode:
Breakpoint 0 hit
sym+0x290a:
00007ff7`da26290a 48ffe0 jmp rax {000002c2`84e00000}
0:000> t
000002c2`84e00000 86f6 xchg dh,dh
0:000> u RIP
000002c2`84e00000 86f6 xchg dh,dh
000002c2`84e00002 07 ???
000002c2`84e00003 7e93 jle 000002c2`84dfff98
000002c2`84e00005 52 push rdx
000002c2`84e00006 af scas dword ptr [rdi]
000002c2`84e00007 a2f77707d3686e288d mov byte ptr [8D286E68D30777F7h],al
000002c2`84e00010 ae scas byte ptr [rdi]
000002c2`84e00011 ec in al,dx
This means that the key we supplied is incorrect, so it's time to fix it.
Recall that the key is in the 7th segment. There are few letters that are combined from the first segment that we are not sure about. The letters obtained from the first segment are shown in red in following figure:
These are the letters we're not sure about.
In this case, I attempted to brute force the key until valid instructions appeared in the decrypted shellcode. There are many ways to do it, but I created a list of potential keys and then narrowed it down until I had this final list:
ob5cure
ob5curE
ob5cur3
ob5cUre
ob5cUrE
ob5cUr3
ob5Cure
ob5CurE
ob5Cur3
ob5CUre
ob5CUrE
ob5CUr3
oB5cure
oB5curE
oB5cur3
oB5cUre
oB5cUrE
oB5cUr3
oB5Cure
oB5CurE
oB5Cur3
oB5CUre
oB5CUrE
oB5CUr3
o85cure
o85curE
o85cur3
o85cUre
o85cUrE
o85cUr3
o85Cure
o85CurE
o85Cur3
o85CUre
o85CUrE
o85CUr3
The correct key turned out to be ob5cUr3
.
We'll update our argument accordingly 05SabR@bfUc3Eklmnopqrstuvwxyz/1337pr.ost/30testtesttest/*pizza/AMu$E`0R.CAZe/YPXEKCZXYIGMNOXNMXPYCXGXN/ob5cUr3/
.
Let's run the program:
0:000> bp sym + 290A
0:000> g
Breakpoint 0 hit
sym+0x290a:
00007ff7`da26290a 48ffe0 jmp rax {000001a6`2afa0000}
0:000> u rax
000001a6`2afa0000 48895c2418 mov qword ptr [rsp+18h],rbx
000001a6`2afa0005 4889742420 mov qword ptr [rsp+20h],rsi
000001a6`2afa000a 55 push rbp
000001a6`2afa000b 57 push rdi
000001a6`2afa000c 4156 push r14
000001a6`2afa000e 488bec mov rbp,rsp
000001a6`2afa0011 4881ec80000000 sub rsp,80h
000001a6`2afa0018 488b8108010000 mov rax,qword ptr [rcx+108h]
As shown, the instructions seem to have been decrypted successfully. Next, we'll dump the shellcode and analyze it:
The shellcode starts by extracting yet another segment and then performing some comparison.
Let's add a dummy segment test
and rerun the program:
Breakpoint 0 hit
sym+0x290a:
00007ff7`da26290a 48ffe0 jmp rax {0000027c`a2d80000}
0:000> t
0000027c`a2d80000 48895c2418 mov qword ptr [rsp+18h],rbx ss:000000ef`b0ad4640=0000027ca13c0000
...
0000027c`a2d80033 ff9190030000 call qword ptr [rcx+390h] ds:000000ef`b0affb50=00007ff7da262f00
0:000> da rcx
000000ef`b0aff7c0 "05SabR@bfUc3Eklmnopqrstuvwxyz/13"
000000ef`b0aff7e0 "37pr.ost/30testtesttest/*pizza/A"
000000ef`b0aff800 "Mu$E`0R.CAZe/YPXEKCZXYIGMNOXNMXP"
000000ef`b0aff820 "YCXGXN/ob5cUr3/test/"
0:000> r rdx
rdx=0000000000000008
0:000> p
0000027c`a2d80039 4c8b8b08010000 mov r9,qword ptr [rbx+108h] ds:000000ef`b0aff8c8=000000efb0aff780
0:000> da rax
0000027c`a13efd90 "test"
We can see that it compares test
with fin
:
0000027c`a2d80043 0fbe08 movsx ecx,byte ptr [rax] ds:0000027c`a13efd90=74
0:000> p
0000027c`a2d80046 410fb65133 movzx edx,byte ptr [r9+33h] ds:000000ef`b0aff7b3=66
0:000> p
0000027c`a2d8004b 3bca cmp ecx,edx
0:000> da r9+33h
000000ef`b0aff7b3 "fin"
0:000> da rax
0000027c`a13efd90 "test"
If we change test
to fin
, we FINALLY get the flag :)
The final updated argument is 05SabR@bfUc3E/1337pr.ost/30testtesttest/*pizza/AMu$E`0R.CAZe/YPXEKCZXYIGMNOXNMXPYCXGXN/ob5cUr3/fin/