LLVM 프로젝트는 IR (Intermediate Representation) 수준에서 구현되어 프로그래밍 언어 (C, C ++, Objective-C, Ada 및 Fortran)와 CPU (x86, x86-64, PowerPC, PowerPC-64, ARM, Thumb, SPARC, Alpha, CellSPU, MIPS, MSP430, SystemZ, XCore)등이 호환된다.
여기서 bogus control-flow, control-flow flattening, code tamper-proofing 등을 적용한 것이 OLLVM이고 이 프로젝트는 OpenSource이다.
2018년에 OLLVM이 적용된 놈을 android 보안 솔루션에서 처음 접하여 분석했었는데... PC 악성코드에서 만나니 반가웠다.
히오스 폭리 큐 기다리는 동안 작성 할 수 있는 것이 String Obfuscation 까지라... 여기까지만 정리하겠다.
분석 대상은 Dropper이며 뱅킹 타겟인 Emotet 악성코드이다.
MD5 | SHA-256 | File size |
d0621ab41e352e3b73401169da6453e1 | 4096c2e62a6cc2dad5ad11e6fb77579de331ec545fb40144b1870133546694d6 | 66.50 KB (68096 bytes) |
ollvm이 적용된 function의 control flow graph를 보면 다음과 같다.

ollvm으로 추가된 코드 동작은 prologue에서 상태 변수를 확인한 뒤 main dispatcher에서 미리 지정한 상수에 따라 움직인다.

각 relevant blocks의 끝에 다음으로 이동 하는 relevant blocks 상태 변수가 존재한다. (참고)

0x00405CBF-> 0x00407456 -> 0x00402E00 으로 진행.

처음엔 당황스럽지만 아주 간단한 xor 연산이고 이 알고리즘으로 구현된 함수가 3개 존재한다.

첫 4바이트가 키고 다음 4바이트는 길이고 그다음 바이트가 실제 문자열이다.
Key | Length | Data | |
90 EF 5B 6D | 82 EF 5B 6D | B5 9C 61 37 | FF 81 3E 43 |
이 암호문의 문자열 길이는 18다.
enc_str | B5 9C 61 37 | FF 81 3E 43 |
xor key | 90 EF 5B 6D | 90 EF 5B 6D |
dec_str | 25 73 3A 5A | 6F 6E 65 2E |
%s:Z | one. |
dec_str : %s:Zone.Identifier
이제 알고리즘도 알았고 일일이 복호화하기 귀찮으니 idapython으로 짜 보자
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | import struct def getFunction(curHead): curHead = idc.PrevHead(curHead) while not (GetMnem(curHead) = = "mov" and "ecx" in GetOpnd(curHead, 0 )): curHead = idc.PrevHead(curHead) return GetOperandValue(curHead, 1 ) def getString(addr): curAddr = addr enc = "" check = 0 while Byte(curAddr) ! = 0 or len (enc) < = 8 : curAddrByteVal = Byte(curAddr) enc + = chr (curAddrByteVal) check + = curAddrByteVal curAddr + = 1 return enc, check def decrypt(encStr): paddedStr = encStr + ( "\x00" * ( 4 - ( len (encStr) % 4 ))) info = [paddedStr[i:i + 4 ] for i in range ( 0 , len (paddedStr), 4 )] unpackedInfo = list ( map ( lambda x : struct.unpack( 'I' , x), info)) key = unpackedInfo[ 0 ][ 0 ] length = unpackedInfo[ 1 ][ 0 ] ^ key decStr = "" for ui in unpackedInfo: decStr + = struct.pack( "I" ,ui[ 0 ] ^ key) decStr = decStr[ 8 :length + 8 ] return decStr def decryptXrefStrings(addr): xrefs = XrefsTo(addr, flags = 0 ) for addr in xrefs: ref = getFunction(addr.frm) encStr, check = getString(ref) if check ! = 0 : decStr = decrypt(encStr) MakeComm(addr.frm, decStr) MakeComm(ref, decStr) print ( "Address: %x Enc string %s.." % (addr.frm, encStr.encode( 'hex' )[: 10 ])) print ( "Dec string: %s" % decStr) #Main routine targetAddrs = [ 0x00402D40 , 0x00402C60 , 0x00406730 ] for targetAddr in targetAddrs: decryptXrefStrings(targetAddr) |

잘 풀린다.