About FLARE 2016 contest

kao

This year's FLARE reverse engineering contest is over. I managed to finish it on time - but I really had to force myself to keep going several times. This graph tells it all:
flare2016-progress

You can get all the challenges from the official site (password: flare) and there are already a number of writeups all over the web.

I'm not going to reinvent the wheel, so here's just my take on few of the challenges. 🙂

#1 - Challenge1

Time spent: 9 mins. Not much to say, it's designed to be a warmup.. Custom Base64, that's it.

#2 - DudeLocker

Time spent: 1h 20mins. The buzzword for year 2016 is "ransomware". Every self-respecting reversing challenge has to have one. It was fun and kinda easy for me - but in my opinion it was too hard for level 2.

#3 - unknown

Time spent: 7h 45mins. Since challenge starts at 3AM in Europe, one needs to get some sleep..

As for the task - reverser has to figure out the correct filename from PDB entry and then the challenge becomes solvable. No, it doesn't have anything to do with actual reversing, just a random crap obstacle thrown into your way.

#4 - flareon2016challenge

Time spent: 1h 20mins. "Play me a song, have me play along.." Given a DLL, one must figure out the correct order in which to call exported functions to play a song. Original idea, kinda refreshing.. 🙂

#5 - smokestack

Time spent: 1h 45mins. And, of course, every challenge has to have custom Virtual Machine. This one was just the right difficulty and offering several ways to break it. Nice.

#6 - khaki

Time spent: 2h 20mins. Obfuscated python bytecode. Considering that it was my first time directly patching python bytecode, I enjoyed it a lot. In my opinion, one of the most interesting of challenges this year.

Fun fact - opcode for nop in Python is 0x09.

#7 - hashes

Time spent: 5days 2h 22mins. This is where I got really annoyed and stopped playing.

Not only they throw in a Linux challenge into the mix, they use Go programming language and compile binary in a way that requires libgo.so.7 - which is not available on certain Linux distributions (like Debian).

So, one has to figure out which bloody Linux distribution has support for it, install that in a Virtual Machine, then find a way to install Go language, then fight with SELinux - and all that just to be able to debug the binary provided by challenge. That's just frigging stupid. How hard it is to tell the OS requirements before the challenge starts? >:(

Once I got over my enrage and disgust, it was actually pretty easy. My "solution" generated ~60GB of data files - just because I let my hash generation code run while still analyzing the challenge code.. 😉

#8 - CHIMERA

Time spent: 2h 10mins. The actual check was hidden in the DOS stub. There were some nice hints and neat and clean code, I loved everything about this challenge! 🙂

The fact that I got to boot up my Windows XP virtual machine and use debug.exe - that was just an icing on the cake.

#9 - GUI

Time spent: 1h 35mins. Mandatory .NET assembly thrown in the mix. Apparently FLARE team was running out of ideas, so they just used ConfuserEx. Three times.

To solve it, all you need to know is that DNSpy exists. I have no idea why this challenge was put as #9, difficulty wise it was more appropriate as #3..#5.

#10 - flava

Time spent: 35.5 days. Difficulty: 9, fun factor: 0.

This was just frigging stupid. I know, it's a final challenge. But it required so much of a very specialized knowledge, that it's insane.

First you need to get through 3 layers of JavaScript obfuscation. Hard but doable. Then you'll arrive at some serious crypto. Normal person can't know this is bugged Diffie-Hellman key exchange which was used by Angler exploit kit and got broken by Kaspersky Labs. So, if you're not a crypto wizard or you don't focus on exploit kit research, you're screwed.

Once you figure out this crap, you still have to get through 3 layers of Flash. First SWF is easy but second one is obfuscated and solving involves a lot of guesswork. You're supposed to guess that RC4 key is reused and decrypting other binary data with the same key stream will get you the required values to get to 3rd layer. How you're supposed to figure that one out? Well, just "do some random stuff and hope it works™".

The final layer has slightly obfuscated ActionScript code but nothing JPEXS can't handle. It is there just to generate the answer.

Conclusion

If I compare this year's FLARE with LabyREnth, LabyREnth wins hands down. In my opinion LabyREnth's Windows track was much more fun and tasks were much more reverse engineering related. But that's just my opinion..

I wish to express my deepest gratitude to fellow reversers who kept me going when I got really angry and upset: Extreme Coders, fasya and Levis. Your hints were invaluable, thank you so much!

Why I’m not using x64dbg

kao

x64dbg is (probably) the most user-friendly x64 debugger right now. It's pretty, it's open-source and it usually works. But I find it very hard to switch from WinDbg to x64dbg for several reasons. Some of them are purely emotional (don't worry, I'm not going to bore you to death explaining those) but most of them are technical and related to the way x64dbg is being developed.

So, here goes slightly exaggerated but still serious list of my grievances. 🙂

Insane system requirements

Both DNSpy and x64dbg suffer from this disease. They love to use the "latest and greatest" of technologies, meaning Visual Studio 2017, .NET 4.6 and what not. That's perfectly fine when you're writing normal software. But debugger is not a normal software.

If I have a customer with a software crashing on his production servers, I can't tell him "You need to install Windows 7 SP2 and 3 different VS redistributables and reboot your machine twice just for me to run my debugger". No, I really can't.

Debugger must run on any and all systems out-of-the box. Olly does that. WinDbg does that. And it wouldn't be hard to link x64bdg with static VS runtime libs and target WinXP while using all the modern goodies. But for some reason it's not done that way.

Updated 30-Sep-2016: Mr. eXoDia let me know that now x64dbg is distributed together with the necessary runtime DLLs. We can remove this grievance off of my list. Hooray! 🙂

Uncertain direction and feature bloat

Antoine de Saint Exupery said:

Perfection is finally attained not when there is no longer anything to add, but when there is no longer anything to take away

These are really wise words and Olly is designed that way. It does all the basic stuff and has stable SDK that enables plugin authors to implement all the extras.

On the contrary, Mr. eXoDia is adding features left and right and the direction of x64dbg development looks more like this:
BrownianMovement

For example, why does a debugger need 3 (yes, three!) different assembler engines?
assemble

Want another example? Let's just look at the latest weekly digest.. How about this:

... change to the info box. Basically the pointer values in the instruction were not resolved (so if the instruction contained qword ptr ds:[rsp+30] it would not show the value of rsp+30). Personally this is quite useless

Yes, Mr. eXoDia, you're right. It is useless for everyone but few people.

And how about:

The commands plugload and plugunload have been added. This is useful for plugin developers who want to test plugins without having to restart x64dbg all the time.

How many people in the entire world will actually benefit from that? 5? 10?

So, why add such bloat? Once you add something, that something must be maintained. And it's very hard to remove stuff later, as it might break something else. So, please don't..

Broken features

When I am on a job and need to debug something, last thing I want to spend my time on, is fighting with debugger bugs. And my customers certainly don't want to pay me for doing that.

Oleh Yuschuk got it exactly right with the OllyDbg. There were few releases - but they were properly tested and rock solid. From what I can see, x64dbg is going the other way:
compiles_ship_it

Frequent commits like "Fixed search for constant references", "Fixed intermodular calls in module", "Fixed FS/GS memory branch destinations" is not something you want to see in any software, let alone a debugger.

Well, it wouldn't matter much, if there was some known-stable version I could put in my tool collection and use it anytime anywhere. But no, Mr. eXoDia thinks that "No more excuses to not update every day!" is a way to go. Instead of using tried-and-tested version, I should use a probably buggy and unstable one? Dafuq?

Conclusion

So, those are my 3 biggest complaints about x64dbg. I'd love to love x64dbg. I'd love to use x64dbg for everything. But right now I just can't.

How about you?

IDA bug in PE export processing

kao

Hi, I'm back from vacation. And now I'm catching up on all the things that have happened during that time. So, here's a short writeup regarding publicly-known IDA bug and how it will (not) affect reversers.

It was supposed to be a long post showing how to use PatchDiff to locate patched code and then backport it. But, as you'll see later, that's not necessary at all. Maybe another time..

Initial research by Palo Alto

When checking my RSS feed, I stumbled upon the article by Palo Alto researchers called "The Dukes R&D Finds a New Anti-Analysis Technique". It stated:

Using the exported functions by ordinal meant the exported function name was unnecessary, which allowed the developer of this DLL to leave the names for the exported functions blank ... The less obvious reason is that it takes advantage of a bug in the popular IDA disassembler that was recently fixed in the latest version of IDA.

Bug in IDA?! How nice, I want to test this!

Testing the bug

Palo Alto report contained most of the information to reproduce the issue. But IDA 6.95 changelog was even more detailed about what was fixed:

BUGFIX: PE: IDA would not detect DLL exports with empty names
BUGFIX: PE: IDA would show no exports if the export directory's DLL name was an empty string

Armed with the detailed description, I used MASM32 package and their Examples to build a DLL file.

Empty DLL name

First, I took hex editor and changed DLL name in export directory.
export_dll_name_1
export_dll_name_2
Now the exported DLL name is 0-length string. Let's see what IDA does..

I started with IDA 6.95 Demo you can download from official site. No surprises here, the bug is fixed:
export_dll_name_IDA695

Then I took legit copy of IDA 6.90. As already demonstrated by Palo Alto, it's buggy:
export_dll_name_IDA690

Naturally, I wanted to see how old this bug is. So, I took a copy of IDA 6.80. Surprise, surprise, it's not buggy!
export_dll_name_IDA680
So, it looks like this bug was introduced in IDA 6.90.

Empty export name

For completeness sake, I repeated the experiment with empty exported API name.
export_api_name_1
export_api_name_2
The results were identical, the bug is only present in IDA 6.90.

How it affects you?

If you're using IDA Free, latest version is 6.95. You're good.
If you're using legit IDA, you have received the updated version 6.95. You're good.
If you're using the latest publicly leaked version of IDA (6.80), it didn't have the bug. So, you're good, too.

To sum it up - it's a fun bit of information but no one is really affected. Good news, I guess. 🙂

Example DLL files if you want to verify your tools: https://www.mediafire.com/?c9t6hm4icd3kk46

Gone for summer vacation

kao

Last few months were quite busy for me.

On the good side: I solved 2 tracks of Labyrenth CTF - Windows and Documents. Unfortunately they still haven't published the Honor Roll, so I have no clue if I placed 1st, 2nd or 44th..

On the bad side: there are lots of changes happening in my office. I don't mind changes per se but the uncertainty of the future of the company.. Well, that's not great.

So, I'm leaving for summer vacation. I'll spend almost 4 weeks on islands with very spotty mobile coverage and almost certainly without Internet access. Will be back in mid-September, relaxed and ready to do some serious reversing again.

Have fun and talk to you all later!

Breaking B0rken ElGamal KeygenMe, part 2

kao

In part 1 of the tutorial, I explained how badly initialized PRNG causes a serious problems and allows us to find the private key. In this part of tutorial, I'll show how to use another weakness in crackme to find private key without using bruteforce.

Looking deeper in the keygenme

If you analyze serial check in more details, you'll notice part of code that processes blacklisted names and keys:

SW0:00401762    mov     [ebp+bad_boy_counter], 0

SW0:00401769    push    offset aLordcarder ; "LordCarder"
SW0:0040176E    push    offset entered_name
SW0:00401773    call    compare_strings
SW0:00401778    add     [ebp+bad_boy_counter], eax
SW0:0040177B    push    offset a5e40b1700252c9 ; "5E40B1700252C909D6050ACDFE0674C0B745B8F"...
SW0:00401780    push    offset entered_sn
SW0:00401785    call    compare_strings
SW0:0040178A    add     [ebp+bad_boy_counter], eax
SW0:0040178D    push    offset aProthief ; "ProThief"
SW0:00401792    push    offset entered_name
SW0:00401797    call    compare_strings
SW0:0040179C    add     [ebp+bad_boy_counter], eax
SW0:0040179F    push    offset a5e40b1700252_0 ; "5E40B1700252C909D6050ACDFE0674C0B745B8F"...
SW0:004017A4    push    offset entered_sn
SW0:004017A9    call    compare_strings
SW0:004017AE    add     [ebp+bad_boy_counter], eax

SW0:004017B1    cmp     [ebp+bad_boy_counter], 0
SW0:004017B5    jnz     blacklisted

It looks like we have 2 blacklisted names & corresponding serials. We can quickly patch crackme and verify that these names and serials really work.

But did you also notice that those serials look a lot alike? That's weird.. Let's take a closer look at them:

SW0:0040518B a5e40b1700252c9 db '5E40B1700252C909D6050ACDFE0674C0B745B8FBAC1D57E38EF982DFDCB1FE9D'
SW0:0040518B                 db '79CA4A7D14DC506A542D661AEF4384554B348FA1A2F8CBB65C5BD16ABA16B6CE',0
..
SW0:00405215 a5e40b1700252_0 db '5E40B1700252C909D6050ACDFE0674C0B745B8FBAC1D57E38EF982DFDCB1FE9D'
SW0:00405215                 db 'C814338D071D56578B2D5CB176E08B56DE1A69A47467312AE32D4D83B46475CF',0

First part of the serial is exactly the same! Let's go back to ElGamal basics and think a bit.

Revisiting ElGamal signing algorithm

I'm sure you already remember the algorithm from my previous blog post. But here it is again.. 🙂

To sign a message M, one would:

  • Make a hash of message, H(M). In this crackme, it's SHA1 of the username
  • Generate a random number K where K<P-1
  • Calculate R=G^K (mod P)
  • Calculate S=(H(M)-RX)*K^(-1) (mod P-1)
  • The signature will be the pair C(R,S).

So, in our case, R is always the same. G and P are hardcoded in the crackme. That means.. K is always the same. And that doesn't sound right! 🙂

Quick look in Wikipedia confirms that it's a really bad idea:

The signer must be careful to choose a different k uniformly at random for each signature and to be certain that k, or even partial information about k, is not leaked. Otherwise, an attacker may be able to deduce the secret key x with reduced difficulty, perhaps enough to allow a practical attack. In particular, if two messages are sent using the same value of k and the same key, then an attacker can compute x directly.[1]

The problem of reusing k and the attack itself is explained in Stinson's "Cryptography Theory And Practice", pages 290-291. Unfortunately, normal person has no chance to understand that "explanation".

Few Google searches later I found 2 writeups from Boston Key Party CTF 2015 which were slightly better:

So, let's try to get something useful out of them.

Reused K and recovering private key

As I said earlier, cryptography is a dark magic - if you don't spend years studying it, you can't understand it. So, I just took the code from those CTF writeups and added few more comments to it. And no, I still don't know why it works. 🙂

import hashlib
import gmpy
from gmpy import mpz
from gmpy import gcd

# hardcoded data from crackme
g = mpz(0x7FB7E340473674B34C7B9BDF338897277CB2A17E0296D5DD08C60D5B3D839219)
p = mpz(0xFE6D5B4400B30374A403F88CFBA3642435FB269AEC2BE5C8C2F331545EF37AB3)
y = mpz(0xCC945009A3E4215D042284F4FE567DFDAAEB906E8A620597FAF4953935F217EC)

# blacklisted key #1
m0 = mpz(hashlib.sha1("LordCarder").hexdigest(), 16)
r0 = mpz(0x5E40B1700252C909D6050ACDFE0674C0B745B8FBAC1D57E38EF982DFDCB1FE9D)
s0 = mpz(0x79CA4A7D14DC506A542D661AEF4384554B348FA1A2F8CBB65C5BD16ABA16B6CE)

# blacklisted key #2
m1 = mpz(hashlib.sha1("ProThief").hexdigest(), 16)
# r1==r0, no need to repeat myself.
s1 = mpz(0xC814338D071D56578B2D5CB176E08B56DE1A69A47467312AE32D4D83B46475CF)


# SOLVING k(s[0] - s[1]) = (m[0] - m[1]) mod p-1
# ka = side2 mod p-1
a = s0 - s1
side2 = m0 - m1

num = gcd(a, p-1)
num = int(num)

k_ = gmpy.divm(side2/num, a/num, (p-1)/num)
for i in range(0,num):
  k = k_ + (i*(p-1)/num)
  r_ = pow(g, k, p)
  if r_ == r0:
      print "FOUND K: "+ k.digits(16)
      break

# solve xr  = (m0 - ks0) mod p -1
side2 = m0 - k * s0
num = int(gcd(r0, p-1))

x_ = gmpy.divm(side2/num, r0/num, (p-1)/num)
for i in range(0, num):
  x = x_ + (i*(p-1)/num)
  y_ = pow(g, x, p)
  if y_ == y:
      print "FOUND x: " + x.digits(16)
      break

# Generate new key just to prove it works. No, we really shouldn't use hardcoded K. :)
userName = "kao"
m = mpz(hashlib.sha1(userName).hexdigest(), 16)
k = 1337
r = pow(g, k, p)
s = gmpy.divm(m - x*r, k, p-1)
print "Serial for " + userName + " is " + r.digits(16) + s.digits(16)

Conclusion

Wise man once said:

Knowledge is of two kinds. We know a subject ourselves, or we know where we can find information upon it.

You don't need to be a crypto wizard to solve crackmes - you just need to know where to find the necessary information. But if you're implementing cryptography in your software, better ask someone who understands those things.

Have fun!

Breaking B0rken ElGamal KeygenMe by SmilingWolf

kao

Some weeks ago I found a nice keygenme on URET forum. The description looked interesting enough:

Yet another company is making wild claims! Your mission: prove that people shouldn't trust companies promoting "revolutionary" crypto algos. Keygen this son of a crypto nightmare and write a DETAILED tutorial!

Rules:
1) The only acceptable solution is a keygen
2) No patching of course

It was not solved for few weeks, so I decided to take a look at it. 🙂

Crash-course in ElGamal signature scheme

I hate cryptography. It's complex, it's confusing and unless you're prepared to study this field for years, you can't really understand why stuff works this or that way.

So, here's a short version, just enough to solve this keygenme. It's based on the explanation in InfoSec Institute's tutorial.

Key generation

  • Generate a random prime number P with chosen length.
  • Generate two random numbers, G and X, with G<P and X<P.
  • Calculate Y=G^X mod P.
  • Public key consists of 3 numbers: (P, G and Y), Private key is X.

All 3 numbers of public key are hardcoded in keygenme and are 256 bits long. We can find them in disassembly:

P = FE6D5B4400B30374A403F88CFBA3642435FB269AEC2BE5C8C2F331545EF37AB3
G = 7FB7E340473674B34C7B9BDF338897277CB2A17E0296D5DD08C60D5B3D839219
Y = CC945009A3E4215D042284F4FE567DFDAAEB906E8A620597FAF4953935F217EC

Private key X is.. well, private. 🙂 Without it we can't generate correct keys for a name of our choice. So, the challenge would be to recover the private key somehow.

Signing

To sign a message M, one would:

  • Make a hash of message, H(M). In this crackme, it's SHA1 of the username
  • Generate a random number K where K<P-1
  • Calculate R=G^K (mod P)
  • Calculate S=(H(M)-RX)*K^(-1) (mod P-1)
  • The signature will be the pair C(R,S).

Verification

To verify a given pair C(R,S), one would:

  • Compute V1=G^M (mod P)
  • Compute V2=Y^R * R^S (mod P)
  • If V1==V2, the signature is valid

Breaking ElGamal

Strength of ElGamal algorithm lies in the Discrete Logarithm Problem (DLP). What it means is that easy to calculate Y=G^X mod P, but it's bloody hard to find out X, if you know P, G and Y.

In the InfoSec Institute's tutorial the author is using figugegl's DLPTool to solve the problem for 128-bit integers. However, it's not even possible to enter 256-bit integers in that tool. And the rest of the suggested tools can't handle such large integers either.

So, we must find another way to break the keygenme. After all, it's called "B0rken ElGamal", so there must be a weakness somewhere!

Inside the keygenme

The keygenme itself is an application that generates ElGamal keys. It would be logical to assume that SmilingWolf used this same application to generate keys for the keygenme. So, let's examine the application and see how ElGamal is implemented in it.

I'll skip the boring "unpack modified UPX part", as it's been explained dozens of times already.

So, here's the relevant code for key generation:

SW0:00401104    push    0FFh
SW0:00401109    push    big_temp
SW0:0040110F    call    bigint_random_1
SW0:00401114
SW0:00401114    push    big_temp
SW0:0040111A    push    big_p
SW0:00401120    call    bigint_copy
SW0:00401125
SW0:00401125    push    2
SW0:00401127    push    big_p 
SW0:0040112D    call    biginit_mul_by_int
SW0:00401132
SW0:00401132    push    big_p
SW0:00401138    call    bigint_find_nearest_prime
SW0:0040113D
SW0:0040113D    push    0FFh
SW0:00401142    push    big_g
SW0:00401148    call    bigint_random_2
SW0:0040114D
SW0:0040114D    push    0FFh
SW0:00401152    push    big_x           ; PRIVATE
SW0:00401158    call    bigint_random_2
SW0:0040115D
SW0:0040115D    push    big_y           ; calculated by powmod
SW0:00401163    push    big_p           ; prime!
SW0:00401169    push    big_x           ; PRIVATE
SW0:0040116F    push    big_g           ; less than p
SW0:00401175    call    bigint_powmod   ; Y=G^x mod P

Looks legit, right? 🙂 Well, not so fast.

Random numbers don't just magically appear out of thin air. They are generated using random number generators. If the generators are flawed, the numbers are not really random. You can read a lot about such attacks on Wikipedia.

So, let's examine random number generator used in this keygenme.

SW0:004018B0 random          proc near
SW0:004018B0
SW0:004018B0 arg_0           = dword ptr  8
SW0:004018B0
SW0:004018B0    push    ebp
SW0:004018B1    mov     ebp, esp
SW0:004018B3    push    ecx
SW0:004018B4    push    edx
SW0:004018B5    mov     eax, random_seed
SW0:004018BA    xor     edx, edx
SW0:004018BC    mov     ecx, 127773
SW0:004018C1    div     ecx
SW0:004018C3    mov     ecx, eax
SW0:004018C5    mov     eax, 16807
SW0:004018CA    mul     edx
SW0:004018CC    mov     edx, ecx
SW0:004018CE    mov     ecx, eax
SW0:004018D0    mov     eax, 2836
SW0:004018D5    mul     edx
SW0:004018D7    sub     ecx, eax
SW0:004018D9    xor     edx, edx
SW0:004018DB    mov     eax, ecx
SW0:004018DD    mov     random_seed, ecx
SW0:004018E3    div     [ebp+arg_0]
SW0:004018E6    mov     eax, edx
SW0:004018E8    pop     edx
SW0:004018E9    pop     ecx
SW0:004018EA    leave
SW0:004018EB    retn    4
SW0:004018EB random          endp

If you search for those constants, you'll see that it's a very standard PRNG which is not great but supposed to be reliable enough. You could bruteforce all 2^32 possibilities but it will take a long time. Another dead end?

Well, not really. Any PRNG must be initialized somehow, see random_seed variable above. So, let's see how SmilingWolf is initializing his PRNG..

SW0:0040108F    mov     eax, [ebp+arg_0]
SW0:00401092    xor     random_seed, eax ; original seed value = 0x37333331

Hmm, that's weird. Normally PRNG is initialized using rdtsc instruction or something even less predictable than that. And what exactly is arg_0? It's a handle of the DialogBox - not random at all!

Finally, we've found a reason why this ElGamal implementation is broken! 🙂

Bruteforcing the private key

Now that we know the weakness, we can write a bruteforcer that will go through all possibilities of random seeds and generate all possible ElGamal keys. Once we generate a key that has the same P, G and Y as in the keygenme, we will also know the correct private key X. But generating all these numbers is a slow process!

Let's look back at the code and see what we can optimize.

1) We don't need to generate all the numbers. It's enough if we find correct P - the rest of numbers will match automatically. So, let's remove the rest of the code.

SW0:0040108F    mov     eax, [ebp+arg_0]
SW0:00401092    xor     random_seed, eax ; original seed value = 0x37333331
...
SW0:00401104    push    0FFh
SW0:00401109    push    big_temp
SW0:0040110F    call    bigint_random_1
SW0:00401114
SW0:00401114    push    big_temp
SW0:0040111A    push    big_p
SW0:00401120    call    bigint_copy
SW0:00401125
SW0:00401125    push    2
SW0:00401127    push    big_p 
SW0:0040112D    call    biginit_mul_by_int
SW0:00401132
SW0:00401132    push    big_p
SW0:00401138    call    bigint_find_nearest_prime

2) First 32 bits of 256 bit integer will almost certainly not change when searching for nearest prime. Therefore, we could get rid of bigint_find_nearest_prime call which is the slowest piece of code. This means we won't be looking for number FE6D5B4400B30374A403F88CFBA3642435FB269AEC2BE5C8C2F331545EF37AB3, but any number starting with FE6D5B44...

SW0:0040108F    mov     eax, [ebp+arg_0]
SW0:00401092    xor     random_seed, eax; original seed value = 0x37333331
...
SW0:00401104    push    0FFh
SW0:00401109    push    big_temp
SW0:0040110F    call    bigint_random_1
SW0:00401114
SW0:00401114    push    big_temp
SW0:0040111A    push    big_p
SW0:00401120    call    bigint_copy
SW0:00401125
SW0:00401125    push    2
SW0:00401127    push    big_p 
SW0:0040112D    call    biginit_mul_by_int

3) Multiplication by 2. Another unnecessary step. Let's just divide P by 2 and look for that number. Instead of looking for FE6D5B44..., we'll look for 7F36ADA2...

SW0:0040108F    mov     eax, [ebp+arg_0]
SW0:00401092    xor     random_seed, eax
...
SW0:00401104    push    0FFh
SW0:00401109    push    big_temp
SW0:0040110F    call    bigint_random_1
SW0:00401114
SW0:00401114    push    big_temp
SW0:0040111A    push    big_p
SW0:00401120    call    bigint_copy

4) Copying bigints. Unnecessary.

SW0:0040108F    mov     eax, [ebp+arg_0]
SW0:00401092    xor     random_seed, eax
...
SW0:00401104    push    0FFh
SW0:00401109    push    big_temp
SW0:0040110F    call    bigint_random_1

There's not much left, I'm satisfied.

Now, how to bruteforce random seed? It's a window handle. Window handles are always even. Also, they consist of 2 parts, high word and low word. Low word is the actual handle. It is almost never higher than 0x2000. High word is the "uniquifier" - just a counter. It's usually quite small - on my PC it's never larger than 0x800.

Taking all these assumptions into account, my pseudo-code for bruteforcer would look like this:

for (UInt16 uniquifier = 0; uniquifier < 0x800; uniquifier++)
{
   for (UInt16 handle = 0; handle < 0x2000; handle += 2)
   {
       UInt32 seed = (uniquifier << 0x10) | handle;
       seed = seed ^ 0x37333331;
       InitPrng(seed);
       bigint_random_1(out bigint_temp, 0xFF);
       if (first 4 bytes of bigint_temp are 0x7F36ADA2)
       {
          we found the correct seed;
       }
   }
}

I implemented the bruteforcer in MASM32 with 95% of code ripped from keygenme and let it run. In less than 2 hours I had one (of the several possible) seed:

Seed = 0x00010968
P = FE6D5B4400B30374A403F88CFBA3642435FB269AEC2BE5C8C2F331545EF37AB3
G = 7FB7E340473674B34C7B9BDF338897277CB2A17E0296D5DD08C60D5B3D839219
X = 7F4BEFC372EED0BA1D4A3543243EE574734C8347459FA21E5BCC5BCF0351812D
Y = CC945009A3E4215D042284F4FE567DFDAAEB906E8A620597FAF4953935F217EC

Once we know X, implementing keygen is a child's play. Again, lots of ripped code and small UI around it. Problem solved!

Keygen for download: https://mega.nz/#!JtgVUDwb!xs3SgCDK3t5h3MabOfoNsDFYCj1mpHcI7GbQT-K1_RE

Further reading

InfoSec Institute's tutorial about ElGamal: http://resources.infosecinstitute.com/breaking-software-protection-elgamal-signature-scheme/
Interactive webpage showing how ElGamal works with small numbers: https://asecuritysite.com/encryption/elgamal

Final thoughts

As SmilingWolf told me after solving his keygenme - there is another way how to break this keygenme. There's one more thing broken in this implementation that makes generating keys really easy. 🙂 So, if you're interested, try to figure out what is it and how to abuse it.

Bugs in Enigma Virtual Box

kao

While working on a new version of my static EnigmaVB unpacker, I tried to generate test files to cover most of the Enigma Virtual Box features. In the process, I ran into quite a few bugs in Enigma Virtual Box v7.40.

So, here's a short list:

Registry virtualization

1. Importing REG file with wrapped lines:

"RootFolder"=hex:01,00,00,00,00,00,00,00,01,00,00,00,04,00,00,00,01,00,00,00,\
  64,00,00,00

Data get truncated at the end of first line.

2. Importing REG file with entry type REG_NONE:

"WMP11.AssocFile.3G2"=hex(0):

It gets virtualized as a string value "hex(0):"

File virtualization

1. If size of any embedded file > 4GB: creates invalid x86 executable;
2. If total size of all embedded files > 4GB: creates invalid x86 executable;
3. If size of main EXE > 2 GB: creates executable that seems to be valid but won't run;
..and that's only for x86 executables. I wonder how many more issue will surface when I start testing x64 executables. 😉

TLS callbacks

Since Enigma Virtual Box uses TLS callbacks to initialize its hooks and handlers, it will (accidentally?) break any executable that also uses TLS callbacks. However, it preserves TLS StartAddressOfRawData, EndAddressOfRawData and AddressofIndex fields. Very weird.. 🙂

Have fun (and remember to test your software properly)!

Six-factor authentication (it’s not)

kao

Today I read an article in The Register called Tor torpedoed! Tesco Bank app won't run with privacy tool installed.

It's a fun read about Tesco's Android banking app and how it refuses to run when Tor application is installed on your mobile. But what really caught my attention, is this comment to the article:

I did a count of my account with a certain bank and when I use a PC which does not store their funky cookies, I get 6 (yes really, 6) steps for authentication.

  • Initial Customer code
  • Security password as there is no cookie so PC is not recognised
  • pre-agreed image
  • pre-agreed phrase
  • Customer Number
  • Security code

and if I use a Windows PC it whinges that I don't have cRapport which would 'improve my security'
So 6-Factor security isn't good enough and you want an extra package to help???????

Sir, if you ever read what a multi-factor authentication is, you wouldn't be stating such nonsense. All six of the steps you mentioned are of the same factor - "something you know". As such, they provide no additional security, as one keylogger/screengrabber will capture them all.

Why your bank insists on you jumping over so many redundant hoops, remains a mystery..

What’s wrong with this file – ASLR is tricky!

kao

I love magic tricks. My absolute favorites are "there's nothing up my sleeve" kind of tricks. You can look at the equipment, you can examine magicians outfit, everything seems fine - yet the rabbit magically appears and disappears.

Here's a similar reversing challenge for you: https://www.mediafire.com/?38evlc6gmyieskn

This EXE file contains relocations. It has all the necessary necessary flags in PE header. And it gets ASLR support in Windows 10, as you can see in picture:
win10_has_aslr
But on Windows 7/8.1 this poor executable will be always loaded at it's preferred imagebase 0x400000, and doesn't get ASLR support:
win7_no_aslr
Can you figure out what's so special about it? 🙂

I will provide the correct answer in one week. Or you can provide your opinions in comments. Extra respect awarded for detailed answers and explaining how you figured that out. Extra extra respect if you knew the answer even before looking at the executable. 🙂

CFF bug in RVA2Offset

kao

Yes, this is yet another post about bugs in CFF Explorer. So far I've described:

Today, I'll describe an issue with CFF Explorer's RVA2Offset function and provide a solution to the problem (patched executable).

And no, I really don't hate CFF Explorer. In fact, it's one of my favorite tools and I use it every day - that's why I keep noticing more and more issues with it. 😉

Introduction

Here is an executable that demonstrates the bug: https://www.mediafire.com/?9ju0cfm36b32ys9

If you open it in CFF Explorer and try to check Import Directory. In this case, CFF will show that it's empty.

cff_bug_empty_imports

That's incorrect, import directory of this executable is present and valid. It contains 2 DLLs and 3 APIs:

actual_imports

In other executables, it can get stuck into eternal loop or - even worse - show incorrect data.

Also, CFF's Address Converter feature is affected. In my demo executable, try convert RVA 0x2000 to file offset. It will return 0:

CFF_address_converter_bug

So, what's happening here?

Background of the bug

To put it simply, bug is triggered when one section in executable has SizeOfRawData much larger than VirtualSize. In my crafted executable it looks like this:

CFF_cause_of_bug

Nitpickers corner: it's actually more complicated. The exact condition is ALIGN_UP(sec.SizeOfRawData, pe.FileAlignment) > ALIGN_UP(sec.VirtualSize, pe.SectionAlignment). But who cares about those small details, anyway?

And the offending pseudo-code in CFF Explorer looks something like this:

foreach (SectionHeader sec in SectionHeaders)
{
   // try to calculate how much data this section actually contains. This goes wrong, if physical size > virtual size.
   dataSize = sec.sizeOfRawData ? align_up(sec.sizeOfRawData, pe.fileAlignment) : sec.virtualSize;

   // check if our RVA falls into this range
   if ( RVA >= sec.virtualAddress && RVA < dataSize + sec.virtualAddress )
   {
      // if so, convert RVA to offset
      return (RVA - sec.virtualAddress + sec.ptrRawData);
   }
}

Fixing the bug

Since I'm doing binary patches to CFF Explorer, I'm quite limited to what I can do and how. In the end, I chose the following pseudocode:

   // calculate how much data this section actually contains. 
   dataSize = align_up(sec.sizeOfRawData, pe.fileAlignment);
   if (dataSize > align_up(sec.virtualSize, pe.sectionAlignment))
   {
      dataSize = align_up(sec.virtualSize, pe.sectionAlignment);
   }

While it doesn't look like much (and it doesn't cover edge cases, for example, when PE file is truncated), in general it should work just fine.

Download link for fixed CFF Explorer: https://www.mediafire.com/?5eg1bs9a9bv39ge
It also includes all my previous fixes.

Conclusion

While writing this post, I noticed that PE viewer in ExeinfoPE v0.0.4.1 has very similar bug. And ProtectionID v6.6.6. And PETools 1.5 Xmas edition. And Stud_PE 2.1. And LordPE. And then I ran out of tools to test. 😀

Obviously, I can't fix them all. All I can say - use PE editing/viewing tools that actually work, for example, HIEW or IDA. And when you're writing your own PE parser library, make sure you test it on weird executables.

Have fun and stay safe!

Further reading