05 December 2018

GandCrab "Anti-Disassembly"

I won't do an analysis on what GandCrab is, or how it works, there are some posts/papers related.
What I am going to assist, is the annoying "Anti-Disassembly" technique they are using, which breaks the IDA Graph view. This technique is used at least in the versions 5.x.x, I saw some post mentioning it also in the 4.x.x.


Below the "problem"(OEP function of GandCrab)


Same situation, we are going to find in the most GandCrab functions.


IDA doesn't disassembly this technique correctly, below is manually how the assembly should look.



What exactly is happening is:
  1. With "call $+5" is jumping to EIP + 0x5 = 0x406231, but when using call the pointer to return is pushed to stack, so [esp] = 0x406231.
  2. Adds to the first stack value 0x11. In other words, adds to the return pointer of "call $+5" 0x11.
  3. Then depending on the "ZF" flag value it will jump to loc_40623D address, either by jnz or by jz instruction. Similar to those two jumps would be to have "jmp 40623D"
  4. With "pop  eax", eax is receiving the previous addition 0x406231 + 0x11 = 0x406242 and then jumps to this address.



By replacing those hex values "E9281458FFE000E9", with "909090E900000000" we manage to fix the code to be viewable in IDA Graph view as well as usable.

1. Change Example (OEP function)

2. Change Example

 3. Change Example

Code to fix this "problem".
1:  # GandCrab anti-dissasembly fix  
2:  import os  
3:  import sys  
4:    
5:  import os.path  
6:    
7:  def pre_checks():  
8:      # pre-checks & returns in & out file  
9:      if len(sys.argv) not in [2,3]:  
10:          error_log('Too few or too many arguments, --help')  
11:      in_file = sys.argv[1]  
12:      try:  
13:          out_file = sys.argv[2]  
14:      except IndexError:  
15:          out_file = get_output(in_file)  
16:      if not os.path.isfile(in_file):  
17:          error_log("Input file doesn't exist")  
18:          exit()  
19:      return in_file, out_file  
20:    
21:  def error_log(msg):  
22:      print msg  
23:      exit()  
24:    
25:  def get_output(fname):  
26:      if not os.path.isfile(fname):  
27:          error_log("Input file doesn't exist")  
28:      path = os.path.dirname(os.path.realpath(fname))  
29:      return path + "/output.bin"  
30:        
31:  def main(in_, out_):  
32:      bytes1 = "\xE9\x28\x14\x58\xFF\xE0\x00\xE9"  
33:      bytes2 = "\x90\x90\x90\xE9\x00\x00\x00\x00"  
34:      fp1 = open(in_, 'rb')  
35:      fd1 = fp1.read()  
36:      fp1.close()  
37:      fp2 = open(out_, 'wb')  
38:      fd2 = fd1.replace(bytes1, bytes2)  
39:      fp2.write(fd2)  
40:      print out_, " successfully created!!"  
41:    
42:  if __name__ == "__main__":  
43:      in_file, out_file = pre_checks()  
44:      main(in_file, out_file)