Merge branch 'master' of https://github.com/blackorbird/APT_REPORT
This commit is contained in:
@@ -3,3 +3,5 @@ APT donot(APT-C-35) group new test sample
|
||||
KashmirVoice.apk (http://com.update.android .v2.test3)
|
||||
cdf10316664d181749a8ba90a3c07454
|
||||
incode url: https[:]//justin.drinkeatgood.space/api/V1/public/
|
||||
|
||||
password:KashmirVoice
|
||||
|
||||
293
Oceanlotus/APT32-Graph-Deobfuscator.py
Normal file
293
Oceanlotus/APT32-Graph-Deobfuscator.py
Normal file
@@ -0,0 +1,293 @@
|
||||
""" A plugin for Cutter and Radare2 to deobfuscate APT32 flow graphs
|
||||
This is a python plugin for Cutter that is compatible as an r2pipe script for
|
||||
radare2 as well. The plugin will help reverse engineers to deobfuscate and remove
|
||||
junk blocks from APT32 (Ocean Lotus) samples.
|
||||
"""
|
||||
|
||||
__author__ = "Itay Cohen, aka @megabeets_"
|
||||
__company__ = "Check Point Software Technologies Ltd"
|
||||
|
||||
# Check if we're running from cutter
|
||||
try:
|
||||
import cutter
|
||||
from PySide2.QtWidgets import QAction
|
||||
pipe = cutter
|
||||
cutter_available = True
|
||||
# If no, assume running from radare2
|
||||
except:
|
||||
import r2pipe
|
||||
pipe = r2pipe.open()
|
||||
cutter_available = False
|
||||
|
||||
|
||||
class GraphDeobfuscator:
|
||||
# A list of pairs of opposite conditional jumps
|
||||
jmp_pairs = [
|
||||
['jno', 'jo'],
|
||||
['jnp', 'jp'],
|
||||
['jb', 'jnb'],
|
||||
['jl', 'jnl'],
|
||||
['je', 'jne'],
|
||||
['jns', 'js'],
|
||||
['jnz', 'jz'],
|
||||
['jc', 'jnc'],
|
||||
['ja', 'jbe'],
|
||||
['jae', 'jb'],
|
||||
['je', 'jnz'],
|
||||
['jg', 'jle'],
|
||||
['jge', 'jl'],
|
||||
['jpe', 'jpo'],
|
||||
['jne', 'jz']]
|
||||
|
||||
def __init__(self, pipe, verbose=False):
|
||||
"""an initialization function for the class
|
||||
|
||||
Arguments:
|
||||
pipe {r2pipe} -- an instance of r2pipe or Cutter's wrapper
|
||||
|
||||
Keyword Arguments:
|
||||
verbose {bool} -- if True will print logs to the screen (default: {False})
|
||||
"""
|
||||
|
||||
self.pipe = pipe
|
||||
|
||||
self.verbose = verbose
|
||||
|
||||
def is_successive_fail(self, block_A, block_B):
|
||||
"""Check if the end address of block_A is the start of block_B
|
||||
|
||||
Arguments:
|
||||
block_A {block_context} -- A JSON object to represent the first block
|
||||
block_B {block_context} -- A JSON object to represent the second block
|
||||
|
||||
Returns:
|
||||
bool -- True if block_B comes immediately after block_A, False otherwise
|
||||
"""
|
||||
|
||||
return ((block_A["addr"] + block_A["size"]) == block_B["addr"])
|
||||
|
||||
def is_opposite_conditional(self, cond_A, cond_B):
|
||||
"""Check if two operands are opposite conditional jump operands
|
||||
|
||||
Arguments:
|
||||
cond_A {string} -- the conditional jump operand of the first block
|
||||
cond_B {string} -- the conditional jump operand of the second block
|
||||
|
||||
Returns:
|
||||
bool -- True if the operands are opposite, False otherwise
|
||||
"""
|
||||
|
||||
sorted_pair = sorted([cond_A, cond_B])
|
||||
for pair in self.jmp_pairs:
|
||||
if sorted_pair == pair:
|
||||
return True
|
||||
return False
|
||||
|
||||
def contains_meaningful_instructions (self, block):
|
||||
'''Check if a block contains meaningful instructions (references, calls, strings,...)
|
||||
|
||||
Arguments:
|
||||
block {block_context} -- A JSON object which represents a block
|
||||
|
||||
Returns:
|
||||
bool -- True if the block contains meaningful instructions, False otherwise
|
||||
'''
|
||||
|
||||
# Get summary of block - strings, calls, references
|
||||
summary = self.pipe.cmd("pdsb @ {addr}".format(addr=block["addr"]))
|
||||
return summary != ""
|
||||
|
||||
def get_block_end(self, block):
|
||||
"""Get the address of the last instruction in a given block
|
||||
|
||||
Arguments:
|
||||
block {block_context} -- A JSON object which represents a block
|
||||
|
||||
Returns:
|
||||
The address of the last instruction in the block
|
||||
"""
|
||||
|
||||
# save current seek
|
||||
self.pipe.cmd("s {addr}".format(addr=block['addr']))
|
||||
# This will return the address of a block's last instruction
|
||||
block_end = self.pipe.cmd("?v $ @B:-1")
|
||||
return block_end
|
||||
|
||||
def get_last_mnem_of_block(self, block):
|
||||
"""Get the mnemonic of the last instruction in a block
|
||||
|
||||
Arguments:
|
||||
block {block_context} -- A JSON object which represents a block
|
||||
|
||||
Returns:
|
||||
string -- the mnemonic of the last instruction in the given block
|
||||
"""
|
||||
|
||||
inst_info = self.pipe.cmdj("aoj @ {addr}".format(addr=self.get_block_end(block)))[0]
|
||||
return inst_info["mnemonic"]
|
||||
|
||||
def get_jump(self, block):
|
||||
"""Get the address to which a block jumps
|
||||
|
||||
Arguments:
|
||||
block {block_context} -- A JSON object which represents a block
|
||||
|
||||
Returns:
|
||||
addr -- the address to which the block jumps to. If such address doesn't exist, returns False
|
||||
"""
|
||||
|
||||
return block["jump"] if "jump" in block else None
|
||||
|
||||
def get_fail_addr(self, block):
|
||||
"""Get the address to which a block fails
|
||||
|
||||
Arguments:
|
||||
block {block_context} -- A JSON object which represents a block
|
||||
|
||||
Returns:
|
||||
addr -- the address to which the block fail-branches to. If such address doesn't exist, returns False
|
||||
"""
|
||||
return block["fail"] if "fail" in block else None
|
||||
|
||||
def get_block(self, addr):
|
||||
"""Get the block context in a given address
|
||||
|
||||
Arguments:
|
||||
addr {addr} -- An address in a block
|
||||
|
||||
Returns:
|
||||
block_context -- the block to which the address belongs
|
||||
"""
|
||||
|
||||
block = self.pipe.cmdj("abj. @ {offset}".format(offset=addr))
|
||||
return block[0] if block else None
|
||||
|
||||
def get_fail_block(self, block):
|
||||
"""Return the block to which a block branches if the condition is fails
|
||||
|
||||
Arguments:
|
||||
block {block_context} -- A JSON representation of a block
|
||||
|
||||
Returns:
|
||||
block_context -- The block to which the branch fails. If not exists, returns None
|
||||
"""
|
||||
# Get the address of the "fail" branch
|
||||
fail_addr = self.get_fail_addr(block)
|
||||
if not fail_addr:
|
||||
return None
|
||||
# Get a block context of the fail address
|
||||
fail_block = self.get_block(fail_addr)
|
||||
return fail_block if fail_block else None
|
||||
|
||||
def reanalize_function(self):
|
||||
"""Re-Analyze a function at a given address
|
||||
|
||||
Arguments:
|
||||
addr {addr} -- an address of a function to be re-analyze
|
||||
"""
|
||||
# Seek to the function's start
|
||||
self.pipe.cmd("s $F")
|
||||
# Undefine the function in this address
|
||||
self.pipe.cmd("af- $")
|
||||
|
||||
# Define and analyze a function in this address
|
||||
self.pipe.cmd("afr @ $")
|
||||
|
||||
def overwrite_instruction(self, addr):
|
||||
"""Overwrite a conditional jump to an address, with a JMP to it
|
||||
|
||||
Arguments:
|
||||
addr {addr} -- address of an instruction to be overwritten
|
||||
"""
|
||||
|
||||
jump_destination = self.get_jump(self.pipe.cmdj("aoj @ {addr}".format(addr=addr))[0])
|
||||
if (jump_destination):
|
||||
self.pipe.cmd("wai jmp 0x{dest:x} @ {addr}".format(dest=jump_destination, addr=addr))
|
||||
|
||||
def get_current_function(self):
|
||||
"""Return the start address of the current function
|
||||
|
||||
Return Value:
|
||||
The address of the current function. None if no function found.
|
||||
"""
|
||||
function_start = int(self.pipe.cmd("?vi $FB"))
|
||||
return function_start if function_start != 0 else None
|
||||
|
||||
def clean_junk_blocks(self):
|
||||
"""Search a given function for junk blocks, remove them and fix the flow.
|
||||
"""
|
||||
|
||||
# Get all the basic blocks of the function
|
||||
blocks = self.pipe.cmdj("afbj @ $F")
|
||||
if not blocks:
|
||||
print("[X] No blocks found. Is it a function?")
|
||||
return
|
||||
# Have we modified any instruction in the function?
|
||||
# If so, a reanalyze of the function is required
|
||||
modified = False
|
||||
|
||||
# Iterate over all the basic blocks of the function
|
||||
for block in blocks:
|
||||
fail_block = self.get_fail_block(block)
|
||||
# Make validation checks
|
||||
if not fail_block or \
|
||||
not self.is_successive_fail(block, fail_block) or \
|
||||
self.contains_meaningful_instructions(fail_block) or \
|
||||
not self.is_opposite_conditional(self.get_last_mnem_of_block(block), self.get_last_mnem_of_block(fail_block)):
|
||||
continue
|
||||
if self.verbose:
|
||||
print ("Potential junk: 0x{junk_block:x} (0x{fix_block:x})".format(junk_block=fail_block["addr"], fix_block=block["addr"]))
|
||||
self.overwrite_instruction(self.get_block_end(block))
|
||||
modified = True
|
||||
if modified:
|
||||
self.reanalize_function()
|
||||
|
||||
def clean_graph(self):
|
||||
"""the initial function of the class. Responsible to enable cache and start the cleaning
|
||||
"""
|
||||
|
||||
# Enable cache writing mode. changes will only take place in the session and
|
||||
# will not override the binary
|
||||
self.pipe.cmd("e io.cache=true")
|
||||
self.clean_junk_blocks()
|
||||
|
||||
|
||||
if cutter_available:
|
||||
# This part will be executed only if Cutter is available. This will
|
||||
# create the cutter plugin and UI objects for the plugin
|
||||
class GraphDeobfuscatorCutter(cutter.CutterPlugin):
|
||||
name = "APT32 Graph Deobfuscator"
|
||||
description = "Graph Deobfuscator for APT32 Samples"
|
||||
version = "1.0"
|
||||
author = "Itay Cohen (@Megabeets_)"
|
||||
|
||||
def setupPlugin(self):
|
||||
pass
|
||||
|
||||
def setupInterface(self, main):
|
||||
# Create a new action (menu item)
|
||||
action = QAction("APT32 Graph Deobfuscator", main)
|
||||
action.setCheckable(False)
|
||||
# Connect the action to a function - cleaner.
|
||||
# A click on this action will trigger the function
|
||||
action.triggered.connect(self.cleaner)
|
||||
|
||||
# Add the action to the "Windows -> Plugins" menu
|
||||
pluginsMenu = main.getMenuByType(main.MenuType.Plugins)
|
||||
pluginsMenu.addAction(action)
|
||||
|
||||
def cleaner(self):
|
||||
graph_deobfuscator = GraphDeobfuscator(pipe)
|
||||
graph_deobfuscator.clean_graph()
|
||||
cutter.refresh()
|
||||
|
||||
|
||||
def create_cutter_plugin():
|
||||
return GraphDeobfuscatorCutter()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
graph_deobfuscator = GraphDeobfuscator(pipe)
|
||||
graph_deobfuscator.clean_graph()
|
||||
|
||||
|
||||
5
Oceanlotus/README.MD
Normal file
5
Oceanlotus/README.MD
Normal file
@@ -0,0 +1,5 @@
|
||||
# Oceanlotus
|
||||
|
||||
APT32-Graph-Deobfuscator.py come from checkpoint
|
||||
|
||||
https://research.checkpoint.com/deobfuscating-apt32-flow-graphs-with-cutter-and-radare2/
|
||||
20
README.md
20
README.md
@@ -52,6 +52,9 @@ https://unit42.paloaltonetworks.com/unit42-freemilk-highly-targeted-spear-phishi
|
||||
|
||||
### baby related kimsuky
|
||||
|
||||
▶BabyShark Malware Part Two – Attacks Continue Using KimJongRAT and PCRat (April 26, 2019)
|
||||
https://unit42.paloaltonetworks.com/babyshark-malware-part-two-attacks-continue-using-kimjongrat-and-pcrat/
|
||||
|
||||
▶Operation Giant Baby, a giant threat (March 28, 2019)
|
||||
https://blog.alyac.co.kr/2223
|
||||
|
||||
@@ -69,6 +72,7 @@ https://blog.alyac.co.kr/1640
|
||||
|
||||
|
||||
|
||||
|
||||
### kimsuky
|
||||
|
||||
▶ Analysis of "Smoke Screen" in APT campaign aimed at Korea and America (April 17 , 2019)
|
||||
@@ -80,7 +84,23 @@ https://blog.alyac.co.kr/2234
|
||||
▶ Kimsuky Organization, Watering Hole Started "Operation Low Kick"(March 21, 2019)
|
||||
https://blog.alyac.co.kr/2209
|
||||
|
||||
### Jaku
|
||||
|
||||
▶ SiliVaccine: Inside North Korea’s Anti-Virus (May 1, 2018)
|
||||
https://research.checkpoint.com/silivaccine-a-look-inside-north-koreas-anti-virus/
|
||||
|
||||
### Lazarus
|
||||
LAZARUS APT TARGETS MAC USERS WITH POISONED WORD DOCUMENT
|
||||
https://www.sentinelone.com/blog/lazarus-apt-targets-mac-users-poisoned-word-document/
|
||||
|
||||
### Oceanlotus
|
||||
|
||||
▶ Oceanlotus in the first quarter of 2019 for the attack technology of China.(April 24, 2019)
|
||||
https://mp.weixin.qq.com/s/xPsEXp2J5IE7wNSMEVC24A
|
||||
|
||||
▶ Deobfuscating APT32 Flow Graphs with Cutter and Radare2 (April 24, 2019)
|
||||
https://research.checkpoint.com/deobfuscating-apt32-flow-graphs-with-cutter-and-radare2/
|
||||
|
||||
▶ OceanLotus Steganography Malware Analysis White Paper (April 2 , 2019)
|
||||
https://threatvector.cylance.com/en_us/home/report-oceanlotus-apt-group-leveraging-steganography.html
|
||||
|
||||
|
||||
41
baby-kimsuky/Decode-Cowboy.py
Normal file
41
baby-kimsuky/Decode-Cowboy.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import base64
|
||||
|
||||
with open(‘cowboy’, ‘r’) as file_in, open(‘cowboy_clear.bin’, ‘wb’) as file_out:
|
||||
|
||||
EncStr = file_in.read()
|
||||
|
||||
BlkSz = 10
|
||||
|
||||
len_EncStr = len(EncStr)
|
||||
|
||||
NonBlk10_ptr = len_EncStr – (BlkSz -1) * (len_EncStr // BlkSz)
|
||||
|
||||
NonBlk10 = EncStr [:NonBlk10_ptr]
|
||||
|
||||
result = ”
|
||||
|
||||
EncStr = EncStr [NonBlk10_ptr::]
|
||||
|
||||
#print EncStr
|
||||
|
||||
x = range (-1,BlkSz-1)
|
||||
|
||||
Blksize1 = len_EncStr // BlkSz
|
||||
|
||||
for n in x:
|
||||
|
||||
loop_buff1_ptr = n * (len_EncStr // BlkSz)
|
||||
|
||||
loop_buff1 = EncStr [loop_buff1_ptr:loop_buff1_ptr+Blksize1]
|
||||
|
||||
#print loop_buff1
|
||||
|
||||
result = loop_buff1 + result
|
||||
|
||||
result = result + NonBlk10
|
||||
|
||||
clear = base64.b64decode(result)[::-1]
|
||||
|
||||
print clear
|
||||
|
||||
file_out.write(clear)
|
||||
4
baby-kimsuky/README.MD
Normal file
4
baby-kimsuky/README.MD
Normal file
@@ -0,0 +1,4 @@
|
||||
### Baby~~~
|
||||
|
||||
▶ BabyShark Malware Part Two – Attacks Continue Using KimJongRAT and PCRat (April 26, 2019)
|
||||
https://unit42.paloaltonetworks.com/babyshark-malware-part-two-attacks-continue-using-kimjongrat-and-pcrat/
|
||||
29
baby-kimsuky/babyshark-CVE20188174.php
Normal file
29
baby-kimsuky/babyshark-CVE20188174.php
Normal file
@@ -0,0 +1,29 @@
|
||||
if(file_exists($filename))
|
||||
|
||||
{
|
||||
|
||||
if($ff=fopen(“resp”,”a”))
|
||||
|
||||
{
|
||||
|
||||
fwrite($ff, $date . ” ” . $ip . ” “.$useragent.” reopen document.” .”\r\n”);
|
||||
|
||||
fclose($ff);
|
||||
|
||||
}
|
||||
|
||||
header(“location: http://google[.]com”);
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
if($ff=fopen(“resp”,”a”))
|
||||
|
||||
{
|
||||
|
||||
fwrite($ff, $date . ” ” . $ip . ” “.$useragent.” open document.” .”\r\n”);
|
||||
|
||||
fclose($ff);
|
||||
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
### list
|
||||
@@ -2,14 +2,27 @@
|
||||
|
||||
## 20190423
|
||||
|
||||
two relate phishing operation:
|
||||
### Spear Phishing operation:
|
||||
|
||||
Group123, APT attack impersonating Unification Ministry, spread malicious code to Google Drive https://blog.alyac.co.kr/2268 (April 22 , 2019)
|
||||
'group123' group 'survey on the total number of discovery of separated families in North and South' https://blog.alyac.co.kr/1767 (July 28, 2014)
|
||||
Group123, APT attack impersonating Unification Ministry, spread malicious code to Google Drive
|
||||
|
||||
https://blog.alyac.co.kr/2268 (April 22 , 2019)
|
||||
|
||||
|
||||
related:
|
||||
|
||||
'group123' group 'survey on the total number of discovery of separated families in North and South'
|
||||
|
||||
https://blog.alyac.co.kr/1767 (July 28, 2014)
|
||||
|
||||
IOC:
|
||||
|
||||
email_93682646.html
|
||||
|
||||
88107e3c785d3d30e5f6fc191622a157
|
||||
|
||||
memo.utr
|
||||
|
||||
86f83586c96943ce96309e3017a3500c
|
||||
|
||||
email:
|
||||
@@ -19,27 +32,28 @@ Lee Soo-hyun <loveshlee@unikorea.go.kr>
|
||||
info:
|
||||
http://155.138.236.240/sec[.]png?id=
|
||||
|
||||
phishing:
|
||||
input password and login it will redirect to unikorea.go.kr
|
||||
### phishing:
|
||||
|
||||
### input password and login it will redirect to unikorea.go.kr
|
||||
https://unikorea.go.kr/upload/editUpload/20190418/2019041814360535872.png
|
||||
https://unikorea.go.kr/upload/editUpload/20190418/2019041814364795734.png
|
||||
|
||||
The html file is misleading in this two-step process and will connect you to a specific Google Drive address in the background.
|
||||
### The html file is misleading in this two-step process and will connect you to a specific Google Drive address in the background.
|
||||
|
||||
download:memo.utr
|
||||
google drive owner: 한국정치학회
|
||||
Gmail:kpsapress@gmail.com
|
||||
|
||||
decode PE and collect private information
|
||||
post to "pcloud"
|
||||
### post to "pcloud"
|
||||
the authorize email is kcrc1214@hanmail.net ,2018.12.3 join
|
||||
|
||||
|
||||
The attacking organization seems to have registered Russian expressions to intentionally give the analysts a false flag, and when translated into English, it will change to the expression 'Humpty Dumpty'.
|
||||
|
||||
D:\System\Kernel32\Shell32\Sample\Release\Шалтай-Болтай.pdb (Humpty Dumpty)
|
||||
### D:\System\Kernel32\Shell32\Sample\Release\Шалтай-Болтай.pdb (Humpty Dumpty)
|
||||
|
||||
HTML code feature
|
||||
### HTML code feature
|
||||
|
||||
<meta http-equiv ='Content-Type'content ='text / html; charset = UTF-8'/>
|
||||
<meta http-equiv ='Cache-Control'content ='no-cache'/>
|
||||
|
||||
Reference in New Issue
Block a user