티스토리 뷰

# IDAPython 공부하면서 엄청 막 정리한 것


ea = idc.ScreenEA()

print "0x%x %s" % (ea, ea)


ea = here() 


# 위 2개는 같은 명령어로써 현재 위치의 주소를 가져올 수 있다.


hex(MinEA()) # 제일 작은 주소를 불러옴


hex(MaxEA()) # 제일 큰 주소를 불러옴


idc.SegName(ea) # get text(세그먼트 이름의 문자열 표현을 얻음)


idc.GetDisasm(ea) # get disassembly


idc.GetMnem(ea) # get mnemonic


idc.GetOpnd(ea,0) # get first operand


idc.GetOpnd(ea,1) # get second operand


idaapi.BADADDR # 주소가 존재하는지 확인할 수 있다.


hex(idaapi.BADADDR)


if BADADDR != here(): print "valid address"


# Segments


for seg in idautils.Segments():

 print idc.SegName(seg), idc.SegStart(seg), idc.SegEnd(seg)


idautils.Segments()는 iterator type object를 return한다.


# idautils.Functions()는 알려진 함수 목록을 반환한다. 인수로는 (start_addr, end_addr)같이 전달할 수 있고 idc.GetFuncName()를 통해 함수 이름을 얻을 수 있다.


# IDAPython에는 함수 작업을 위한 대규모 API 세트가 포함되어 있다.


for func in idautils.Functions():

 print hex(func), idc.GetFunctionName(func)


func = idaapi.get_func(ea)

type(func)

print "Start: 0x%x, End: 0x%x" % (func.startEA, func.endEA)


idaapi.get_func(ea)는 idaapi.func_t 클래스를 리턴한다. 


dir(func)


ea = here()

start = idc.GetFunctionAttr(ea, FUNCATTR_START)

end = idc.GetFunctionAttr(ea, FUNCATTR_END)

cur_addr = start

while cur_addr <= end:

 print hex(cur_addr), idc.GetDisasm(cur_addr)

 cur_addr = idc.NextHead(cur_addr, end)


# idc.GetFunctionAttr(ea, attr)은 함수의 시작과 끝을 가져온다. idc.GetDisasm(ea)를 사용하여 현재 주소와 디스어셈블리를 출력할 수 있다.


다음 명령의 시작을 얻고 도달할 때까지 계속하려면 idc.NextHead()를 사용하면 된다.


idc.GetFunctionAttr(ea, attr)과 비슷한 또 다른 수집 기능 함수에 대한 정보는 GetFunctionFlags(ea)이0다. 


import idautils

for func in idautils.Functions():

 flags = idc.GetFunctionFlags(func)

 if flags & FUNC_NORET:

  print hex(func), "FUNC_NORET"

# FUNC_NORET, FUNC_FAR, FUNC_LIB, FUNC_STATIC, FUNC_FRAME, FUNC_USERFAR, FUNC_HIDDEN, FUNC_THUNK, FUNC_BOTTOMBP


idautils.Functions()를 사용하여 알려진 모든 함수의 주소 목록을 얻은 다음 주소 idc.GetFunctionFlags(ea)를 사용하여 플래그를 가져온다.


# FUNC_NORET

이 플래그는 리턴 명령을 실행하지 않는 함수를 식별하는 데 사용된다. 내부적으로 1로 표시된다.


# FUNC_FAR

This flag is rarely seen unless reversing software that uses segmented memory. It is

internally represented as an integer of 2.


# FUNC_USERFAR

This flag is rarely seen and has very little documentation. HexRays describes the flag as

“user has specified far-ness of the function”. It has an internal value of 32.


# FUNC_LIB

이 플래그는 라이브러리 코드를 찾는 데 사용된다. 라이브러리 코드를 식별하는 것은 코드이므로 매우 유용하다. 일반적으로 분석할 때 무시할 수 있는데, 그것은 내부적으로 정수 4로 표현됩니다.


for func in idautils.Functions():

 flags = idc.GetFunctionFlags(func)

 if flags & FUNC_LIB:

  print hex(func), "FUNC_LIB", GetFunctionName(func)


# FUNC_STATIC

이 플래그는 정적 함수로 컴파일된 함수를 식별하는데 사용된다. C 함수에서 기본적으로 전역이다. 작성자가 함수를 정적으로 정의하면 이 함수는 다음과 같이 액세스할 수 있다.


# FUNC_FRAME

이 플래그는 함수가 프레임 포인터 ebp를 사용함을 나타낸다. 포인터는 일반적으로 스택 설정을 위한 표준 함수 프롤로그로 시작한다.


# FUNC_BOTTOMBP

FUNC_FRAME과 마찬가지로 이 플래그는 프레임 포인터를 추적하는데 사용된다. 


# FUNC_HIDDEN

FUNC_HIDDEN 플래그가 있는 함수는 숨김 상태이므로 확장해야 함을 나타낸다.숨겨진 것으로 표시된 함수의 주소로 이동한다면 자동으로 확장된다.


# FUNC_THUNK

이 플래그는 THUNK 함수들을 식별한다. THUNK 함수들은 다른 함수로 점프하는 간단한 함수이다.


# 함수는 여러 개의 플래그로 구성될 수 있다.


### Instructions ###


idautils.FuncItems(ea)를 사용하여 지정한 모든 주소의 리스트를 가져올 수 있다.


dism_addr = list(idautils.FuncItems(here()))

type(dism_addr)

print dism_addr


for line in dism_addr: print hex(line), idc.GetDisasm(line)


idautils.FuncItems(ea)는 실제로 iterator type을 리턴하지만 list로 cast된다. 리스트는 연속된 순서로 각 명령어의 시작 주소를 포함한다. 때로는 압축된 코드를 리버싱할 때 동적 호출이 발생하는 위치만 알기 위해 사용합니다. 동적 호출은 호출하거나 call eax 또는 jmp edi와 같은 레지스터인 operand로 점프합니다.


for func in idautils.Functions(): # 알려진 모든 함수의 목록을 가져옴

 flags = idc.GetFunctionFlags(func)

 if flags & FUNC_LIB or flags & FUNC_THUNK:

  continue

 dism_addr = list(idautils.FuncItems(func)) 함수 내에서 모든 주소를 가져온다.

 for line in dism_addr:

  m = idc.GetMnem(line)

  # mnemonic이 call이거나 jump인 경우 GetOpType 호출하여 피연산자 유형을 얻는다.

  if m == 'call' or m == 'jump':

   op = idc.GetOpType(line, 0)

   if op == o_reg: # 레지스터인지 확인

    print "0x%x %s" % (line, idc.GetDisasm(line))



ea = here()

len(idautils.FuncItems(ea))  # 이와 같이 출력하면 오류가 난다.

# TypeError: object of type 'generator' has no len()

len(list(idautils.FuncItems(ea)) # 이와 같이 리스트로 묶어줘야 길이를 알 수 있다.


모든 주소를 포함하는 목록을 반복하여 다음 명령 주소(instruction address)로 이동하기 위해 idc.NextHead(ea)를 사용할 수 있다. 또한 idc.PrevHead(ea)를 통해 이전 명령 주소를 얻을 수 있다.


next address를 얻기 위해서는 idc.NextAddr(ea)와 previous address를 얻기 위해 idc.PrevAddr(ea)를 사용할 수 있다.


ea = here()

print hex(ea), idc.GetDisasm(ea)

next_instr = idc.NextHead(ea)

print hex(next_instr), idc.GetDisasm(next_instr) # 다음 instruction을 얻어옴

prev_instr = idc.PrevHead(ea)

print hex(prev_instr), idc.GetDisasm(prev_instr)

print hex(idc.NextAddr(ea)) # 다음 address를 얻어옴

print hex(idc.PrevAddr(ea)) # 이전 address를 얻어옴


### Operands ###


idc.GetOpType(ea, n)을 사용하여 피연산자 유형을 얻을 수 있으며, ea는 해당 주소이고 n은 색인이다. 8가지의 유형의 피연산자 유형이 존재한다.


# o_void

명령어에 피연산자가 없으면 0을 반환한다.


print hex(ea), idc.GetDisasm(ea)

#0xa09166 retn

print idc.GetOpType(ea,0) # 피연산자 없이 retn만 존재하므로 0이 리턴된다.


# o_reg

피연산자가 일반 레지스터이면 해당 유형을 반환한다. 이 값은 내부적으로 1로 표시된다.


print hex(ea), idc.GetDisasm(ea)

# 0xa09163 pop edi

print idc.GetOpType(ea,0) # 피연산자가 edi(일반 레지스터)이므로 1을 반환한다.


# o_mem

피연산자가 직접적인 메모리 참조라면 해당 유형을 반환한다. 이 값은 내부적으로 2로 표시된다. 이 유형은 DATA를 참조하는데 사용된다.

print hex(ea), idc.GetDisasm(ea)

0xa05d86 cmp ds:dword_A152B8, 0

print idc.GetOpType(ea,0)


# o_phrase

피연산자가 기본 레지스터인 and/or 인덱스 레지스터로 구성된 경우 피연산자가 반환되며, 이 값은 내부적으로 3으로 표시된다.


0x1000b8c2 mov [edi+ecx], eax

print idc.GetOpType(ea,0)


# o_displ

피연산자가 레지스터와 변위 값으로 구성된 경우 이 피연산자가 반환된다. 변위는 0x18과 같은 정수 값이며, 이것은 일반적으로 명령어가 구조의 값에 액세스 할 때 나타난다. 이 값은 내부적으로 4로 표시된다.


0xa05dc1 mov eax, [edi+18h]

print idc.GetOpType(ea,0)


# o_imm

0xC의 정수와 같은 값인 피연산자는 이 유형이며 내부적으로 5로 표시된다.


print hex(ea), idc.GetDisasm(ea)

0xa05da1 add esp, 0Ch

print idc.GetObType(ea,1)


#### 예제 ####


import idautils

import idaapi

displace = {}


# for each known function

for func in idautils.Functions():

 flags = idc.GetFunctionFlags(func)

 # skip library & thunk functions

 if flags & FUNC_LIB or flags & FUNC_THUNK: # 라이브러리와 썽크는 무시

  continue

 dism_addr = list(idautils.FuncItems(func))

 for curr_addr in dism_addr:

  op = None

  index = None

  # same as idc.GetOptype, just a different way of accessing the types

  idaapi.decode_insn(curr_addr)

  # 피연산자 1 또는 피연산자 2가 o_displ 유형인지 확인한다.

  if idaapi.cmd.Op1.type == idaapi.o_displ:

   op = 1

  if idaapi.cmd.Op2.type == idaapi.o_displ:

   op = 2

  if op == None:

   continue

  if "bp" in idaapi.tag_remove(idaapi.ua_outop2(curr_addr, 0)) or "bp" in idaapi.tag_remove(idaapi.ua_outop2(curr_addr, 1)):

   # ebp will return a negative number 

   # 문자열 "bp"를 포함하며, 레지스터 bp, ebp 또는 rbp가 피연산자에 있는지 확인하는 빠른방법이다.

   if op == 1: # 변위 값이 음수인지 확인

    index = (~(int(idaapi.cmd.Op1.addr) - 1) & 0xFFFFFFFF)

   else:

    index = (~(int(idaapi.cmd.Op2.addr) - 1) & 0xFFFFFFFF)

  else:

   if op == 1:

    index = int(idaapi.cmd.Op1.addr)

   else:

    index = int(idaapi.cmd.Op2.addr)

  if index:

   if displace.has_key(index) == False:

    displace[index] = []

   displace[index].append(curr_addr)


# idautils.Functions() 및 GetFunctionFlags(ea)를 사용하여 적용 가능한 모든 기능을 가져온다. idautils.FuncItems() 함수가 호출됨으로써 함수 내부의 instruction을 가져올 수 있다.


idaapi.decode_insn() 함수는 디코드하길 원하는 명령의 주소를 가져온다. 일단 디코드가 되면 idaapi.cmd를 통해 액세스하여 instruction의 다른 속성에 액세스할 수 있다.


dir(idaapi.cmd) 명령을 통해 출력물을 확인하면 많은 속성이 존재한다. 피연산자 유형은 idaapi.cmd.Op1.type을 사용하여 액세스할 수 있다. GetOpType(ea,n)에서 피연산자 인덱스는 0이 아니라 1에서부터 시작되는 것에 유의해야 한다. 


idaapi.tag_remove(idaapi.ua_outop2(ea,n))를 사용하여 피연산자의 문자열 표현을 가져온다. idc.GetOpnd(ea,n)를 호출하면 읽기가 더 쉽다.

ex) mov ebp, esp 라면 idc.GetOpnd(ea, 0) = ebp, idc.GetOpnd(ea, 1) = esp가 된다.


# idaapi.ua_outop2(ea,n)을 수행하면 저수준 문자를 가져오며, idaapi.tag_remove(res)를 통해 저수준 문자를 없애고 가시적인 문자열만 가져온다.


min = MinEA() # 최소 및 최대 주소를 얻는다.

max = MaxEA()

# for each known function

for func in idautils.Functions():

 flags = idc.GetFunctionFlags(func)

 # skip library & thunk functions

 if flags & FUNC_LIB or flags & FUNC_THUNK:

  continue

 dism_addr = list(idautils.FuncItems(func))

 for curr_addr in dism_addr: # 각 명령어에 대해 피연산자 유형이 o_imm인지 확인

  if idc.GetOpType(curr_addr, 0) == 5 and (min < idc.GetOperandValue(curr_addr,0) < max): # 값이 정의되면 idc.GetOperandValue(ea, n)를 호출하여 값을 읽고나서 최소 및 최대 주소의 범위에 있는지 확인하기 위해 값을 검사한다.

   idc.OpOff(curr_addr, 0, 0) # idc.OpOff(ea, n, base) ea는 주소, n은 피연산자 인덱스, base는 기본주소

  if idc.GetOpType(curr_addr, 1) == 5 and (min < idc.GetOperandValue(curr_addr,1) < max):

   idc.OpOff(curr_addr, 1, 0)


##### xref #####

# 외부 참조는 특정 데이터가 사용되는 위치 또는 기능이 호출되는 위치를 제공하므로 중요하다.


wf_addr = idc.LocByName("WriteFile") # API WriteFile의 주소를 얻으며, API의 주소를 반환한다.

print hex(wf_addr), idc.GetDisasm(wf_addr)


# CodeRefsTo(ea, flow) ea는 상호 참조가 필요한 주소이며, flow는 bool이다.

for addr in idautils.CodeRefsTo(wf_addr, 0):

 print hex(addr), idc.GetDisasm(addr)


IDB에서 모든 이름이 바뀐 함수와 API는 idautils.Names()를 호출하여 액세스할 수 있다.


[x for x in Names()]


idautils.CodeRefsTo(ea, flow) 및 idautils.CodeRefsFrom(ea, flow)는 교차 참조를 검색하는데 사용된다.


데이터를 검색하거나 상호 참조를 검색하려면 idautils.DataRefsTo(ea), idautils.DataRefsFrom(ea)를 사용할 수 있다.


idc.MakeName(ea, name)으로 이름을 지정할 수 있다.


print hex(ea), idc.GetDIsasm(ea)

#0x1000e3ec db 'vnc32',0

for addr in idautils.DataRefsTo(ea): print hex(addr), idc.GetDisasm(addr)


for xref in idautils.XrefsTo(ea, 1): # 문자열에 대한 모든 참조를 가져온다.

 print xref.type, idautils.XrefTypeName(xref.type), hex(xref.frm), hex(xref.to), xref.iscode

# xref.type은 xrefs type value를 출력한다. 아래는 type value값이다.


0 = 'Data_Unknown'

1 = 'Data_Offset'

2 = 'Data_Write'

3 = 'Data_Read'

4 = 'Data_Text'

5 = 'Data_Informational'

16 = 'Code_Far_Call'

17 = 'Code_Near_Call'

18 = 'Code_Far_Jump'

19 = 'Code_Near_Jump'

20 = 'Code_User'

21 = 'Ordinary_Flow'


# xref.iscode => 외부 참조가 코드 세그먼트에 존재하면 출력한다.


print hex(hxref.frm), idc.GetDisasm(xref.frm)


for xref in idautils.XrefsTo(ea, 1):

 print xref.type, idautils.XrefTypeName(xref.type), hex(xref.frm), hex(xref.to), xref.iscode


for xref in idautils.XrefsTo(ea, 0):

 print xref.type, idautils.XrefTypeName(xref.type), hex(xref.frm), hex(xref.to), xref.iscode


def get_to_xrefs(ea):

 xref_set = set([])

 for xref in idautils.XrefsTo(ea, 1):

  xref_set.add(xref.frm)

 return xref_set


def get_frm_xrefs(ea):

 xref_set = set([])

 for xref in idautils.XrefsFrom(ea, 1):

  xref_set.add(xref.to)

 return xref_set


print hex(ea), idc.GetDisasm(ea)

0xa21138 extern GetProcessHeap:dword


get_to_xrefs(ea)

set([10568624, 10599195])


[hex(x) for x in get_to_xrefs(ea)]


idaapi.get_inf_structure().procName


idautils.XrefsFrom(ea, 1) - ea에 해당하는 주소를 선언하는 곳을 가리킴

ex) GetAdaptersInfo 함수가 있다고 한다면, 이 함수가 정의된 곳을 가리킨다.


idautils.XrefsTo(ea, 1) - ea에 해당하는 주소를 사용하는 곳을 가리킴

ex) GetAdaptersInfo 함수가 있다고 한다면, 이 함수를 사용하는 주소를 가리킨다.


######### Searching ###########


바이트 또는 바이너리 패턴을 검색하기 위해 idc.FindBinary(ea, flag, searchstr, radix=16)를 사용할 수 있습니다. ea는 검색하고 싶은 주소이며, 플래그는 방향 또는 조건이다. 


SEARCH_UP = 0

SEARCH_DOWN = 1

# UP, DOWN은 검색을 수행 할 방향을 선택하는 데 사용된다.


SEARCH_NEXT = 2

# SEARCH_NEXT는 다음 개체를 가져오는데 사용된다.


SEARCH_CASE = 4

# 대소문자 구분을 지정하는데 사용된다.


SEARCH_REGEX = 8

SEARCH_NOBRK = 16


SEARCH_NOSHOW = 32

# 검색 진행률을 표시하지 않는다.


SEARCH_UNICODE = 64 **

# 모든 검색 문자열을 유니코드로 처리하는데사용된다.


SEARCH_IDENT = 128 **

SEARCH_BRK = 256 **

** Older versions of IDAPython do not support these


pattern = '55 8B EC'

addr = MinEA()

for x in range(0,5):

 addr = idc.FindBinary(addr, SEARCH_DOWN, pattern);

 if addr != idc.BADADDR:

  print hex(addr), idc.GetDisasm(addr)


pattern = '55 8B EC'

addr = MinEA()

for x in range(0,5):

 addr = idc.FindBinery(addr, SEARCH_DOWN|SEARCH_NEXT, pattern);

 if addr != idc.BADADDR:

  print hex(addr), idc.GetDisasm(addr)



# ALT + F12 이용한 문자열 찾기를 스크립트로

cur_addr = MinEA()

end = MaxEA()

while cur_addr < end:

 cur_addr = idc.FindText(cur_addr, SEARCH_DOWN, 0, 0, "Accept")

 if cur_addr == idc.BADADDR:

  break

 else:

  print hex(cur_addr), idc.GetDisasm(cur_addr)

 cur_addr = idc.NextHead(cur_addr)


주소 형식을 결정하는 데 사용할 수 있는 is로 시작하는 API의 하위 집합이 있다. API는 True 또는 False의 부울 값을 반환한다.


idc.isCode(f)

- IDA가 주소를 코드로 표시하면 True를 반환한다.


idc.isData(f)

- IDA가 주소를 데이터로 표시하면 True를 반환한다.


idc.isTail(f)

- IDA가 주소를 tail로 표시하면 True를 반환한다.


idc.inUnknown(f)

- IDA가 주소를 알 수 없음으로 표시하면 True를 반환하며, 주소가 코드 또는 데이터인 경우 식별되지 않는다.


idc.FindCode(ea, flag)

- 코드로 표시된 다음 주소를 표시하는 데 사용된다. ea가 이미 코드로 표시된 주소이면 다음 주소를 반환한다.


print hex(ea), idc.GetDisasm(ea)

#0x4140e8 dd offset dword_4140EC

addr = idc.FindCode(ea, SEARCH_DOWN|SEARCH_NEXT)

print hex(addr), idc.GetDisasm(addr)

#0x41410c push ebx


idc.FindData(ea, flag)

- 데이터 블록으로 표시된 다음 주소의 시작을 반환한다는 점을 제외하고는 idc.FindCode와 동일하게 사용된다.

print hex(ea), idc.GetDisasm(ea)

0x41410c push ebx

addr = idc.FindData(ea, SEARCH_UP|SEARCH_NEXT)

print hex(addr), idc.GetDisasm(addr)

0x4140ec dd 49540E0Eh, 746E6564h .....


idc.FindUnexplored(ea, flag)

- 이 기능은 IDA가 코드 또는 데이터로 식별하지 못한 바이트의 주소를 나타내기 위해 사용된다.


Python>print hex(ea), idc.GetDisasm(ea)

0x406a05 jge short loc_406A3A

Python>addr = idc.FindUnexplored(ea, SEARCH_DOWN)

Python>print hex(addr), idc.GetDisasm(addr)

0x41b004 db 0DFh ; ?


idc.FindExplored(ea, flag)

- IDA가 코드 또는 데이터로 식별한 주소를 표시하는데 사용된다.


0x41b900 db ? ;

Python>addr = idc.FindExplored(ea, SEARCH_UP)

Python>print hex(addr), idc.GetDisasm(addr)

0x41b5f4 dd ?


idc.FindImmediate(ea, f lag, value)

- 유형을 검색하는 대신 특정 값을 검색하려고 할 수 있다. 예를 들어, 코드에서 임의의 숫자를 생성하기 위해 rand를 호출하지만, 코드를 찾을 수 없는 경우가 있다. rand가 값 0x323FD를 시드로 사용한다는 것을 알았다면 그 숫자를 검색할 수 있다.


Python>addr = idc.FindImmediate(MinEA(), SEARCH_DOWN, 0x343FD )

Python>addr

[268453092, 0]

Python>print "0x% x % s % x" % (addr[0], idc.GetDisasm(addr[0]),

addr[1] )

0x100044e4 imul eax, 343FDh 0


MinEA()를 통해 최소 주소를 전달하고 검색한 다음 값 0x343FD를 검색한다. 이전 FInd API에서와 같이 주소를 반환하는 대신 idc.FindImmediate는 tupple을 반환한다. 튜플의 첫 번째 항목은 주소, 두 번째 항목은 피연산자가 된다. idc.GetOpnd의 반환과 마찬가지로 첫 번째 피연산자는 0에서 시작된다.


addr = MinEA()

while True:

 addr, operand = idc.FindImmediate(addr, SEARCH_DOWN|SEARCH_NEXT, 0x7a )

 if addr != BADADDR:

  print hex(addr), idc.GetDisasm(addr), "Operand ", operand

 else:

  break


0x402434 dd 9, 0FF0Bh, 0Ch, 0FF0Dh, 0Dh, 0FF13h, 13h, 0FF1Bh, 1Bh

Operand 0

0x40acee cmp eax, 7Ah Operand 1

0x40b943 push 7Ah Operand 0

0x424a91 cmp eax, 7Ah Operand 1

0x424b3d cmp eax, 7Ah Operand 1

0x425507 cmp eax, 7Ah Operand 1


대부분의 코드는 비슷하지만, 여러 값을 검색할 때 while 루프와 SEARCH_DOWN|SEARCH_NEXT 플래그를 사용하여 검색한다.


######### Selecting Data ###########


선택된 데이터의 시작을 얻기 위해 idc.SelStart()를 사용하며, 끝을 얻기 위해 idc.SelEnd()를 사용할 수 있다.


Python>start = idc.SelStart()

Python>hex(start)

0x408e46

Python>end = idc.SelEnd()

Python>hex(end)

0x408e58


idaapi.read_selection()을 사용할 수 있으며, 반환 값으로 첫 번째는 bool인 튜플을 반환하고 두 번째는 시작 주소, 세 번째는 마지막 주소이다. 주목해야 할 것은 끝이 마지막으로 선택된 주소가 아니라 다음 주소의 시작이라는 것이다.


Python>Worked, start, end = idaapi.read_selection()

Python>print Worked, hex(start), hex(end)

True 0x408e46 0x408e58


######### Comments&Renaming ###########


주석을 추가하려면 idc.MakeComm(ea, comment)을 사용하고 반복 가능한 주석에는 idc.MakeRptCmt(ea, comment)를 사용합니다. ea는 주소이고 comment는 추가하고자하는 문자열입니다. 아래 코드는 명령어가 XOR로 레지스터나 값을 0으로 만들 때마다 주석을 추가합니다.


for func in idautils.Functions():

 flags = idc.GetFunctionFlags(func)

 # skip library & thunk functions

 if flags & FUNC_LIB or flags & FUNC_THUNK:

  continue

 dism_addr = list(idautils.FuncItems(func))

 for ea in dism_addr:

  if idc.GetMnem(ea) == "xor":

   if idc.GetOpnd(ea, 0) == idc.GetOpnd(ea, 1):

    comment = "% s = 0" % (idc.GetOpnd(ea,0))

    idc.MakeComm(ea, comment)


0040B0F7 xor al, al ; al = 0

0040B0F9 jmp short loc_40B163


주석을 얻으려면 GetCommentEx(ea, repeatable)을 사용하면 된다. ea는 주석이 들어있는 주소이며, repeatable은 bool 값이다.

Python>print hex(ea), idc.GetDisasm(ea)

0x40b0f7 xor al, al ; al = 0

Python>idc.GetCommentEx(ea, False)

al = 0


주석이 반복 가능한 경우 idc.GetCommentEx(ea, False)를 idc.GetCommentEx(ea, True)로 바꿉니다. 함수에 주석을 추가할 수도 있으며, idc.SetFunctionCmt(ea, cmt, repeatable)를 사용하고 함수 주석을 얻기 위해 idc.SetFunctionCmt(ea, cmt, repeatable)를 호출합니다. ea는 함수의 시작과 끝 경계 내에 있는 주소이며, cmt는 우리가 추가하고자 하는 문자열 주석이고, 주석을 반복하지 않으려면 0, False를, 반복할려면 1, True로 나타낼 수 있다.


Python>print hex(ea), idc.GetDisasm(ea)

0x401040 push ebp

Python>idc.GetFunctionName(ea)

sub_401040

Python>idc.SetFunctionCmt(ea, "check out later", 1)

True


주소의 이름을 바꾸려면 idc.MakeName(ea, name) 함수를 사용할 수 있다. ea는 주소이고 name은 문자열 이름입니다. 

Python>print hex(ea), idc.GetDisasm(ea)

0x10005b3e push ebp

Python>idc.MakeName(ea, "w_HeapAlloc")

True


피연산자 값을 얻으려면 GetOperandValue(ea, n)를 사용할 수 있다.


.text:004047AD lea ecx, [ecx+0]

.text:004047B0 mov eax, dword_41400C

.text:004047B6 mov ecx, [edi+4BCh]


Python>print hex(ea), idc.GetDisasm(ea)

0x4047b0 mov eax, dword_41400C

Python>op = idc.GetOperandValue(ea,1)

Python>print hex(op), idc.GetDisasm(op)

0x41400c dd 2

Python>idc.MakeName(op, "BETA")

True

Python>print hex(ea), idc.GetDisasm(ea)

0x4047b0 mov eax, BETA[esi]


현재 작업중인 주소를 출력하고 idc.GetOperandValue(ea, n)를 호출하여 두 번째 피연산자 값 dword_41400C를 op에 할당합니다. 피연산자의 주소를 idc.MakeName(ea, name)에 전달한 다음 새로 이름을 변경한 피연산자를 출력합니다.


## example ##


import idautils


def rename_wrapper(name, func_addr)

 if idc.MakeNameEx(func_addr, name, SN_NOWARN):

  print "Function at 0x%x ranamed %s" % ( func_addr, idc.GetFuctionName(func))

 else:

  print "Rename at 0x%x failed. Function %s is being used." % (func_addr, name)

  return


def check_for_wrapper(func):

 flags = idc.GetFunctionFlags(func)

 # skip library & thunk functions

 if flags & FUNC_LIB or flags & FUNC_THUNK:

  return

 dism_addr = list(idautils.FuncItems(func))

 # get length of the function

 func_length = len(dism_addr)

 # if over 32 lines of instruction return

 if func_length > 0x20:

  retn

 func_call = 0

 instr_cmp = 0

 op = None

 op_addr =None

 op_type = None

 # for each instruction in the function

 for ea in dism_addr:

  m = idc.GetMnem(ea)

  if m == 'call' or m == 'jmp':

   if m == 'jmp':

    temp = idc.GetOperandValue(ea, 0)

    # ignore jump conditions within the function boundaries

    if temp in dism_addr:

     continue

   func_call += 1

   # wrappers should not contain multiple function calls

   if func_call == 2:

    return

   op_addr = idc.GetOperandValue(ea, 0)

   op_type = idc.GetOpType(ea, 0)

  elif m == 'cmp' or m == 'test':

  # wrappers functions should not contain much logic

   instr_cmp += 1

   if instr_cmp == 3:

    return

  else: continue

 # all instructions in the function have been analyzed

 if op_addr == None:

  return

 name = idc.Name(op_addr)

 #skip mangled function names

 if "[" in name or "$" in name or "?" in name or "@" in name or name == "":

  return

 name = "w_" + name

 if op_type == 7:

  if idc.GetFunctionFlags(op_addr) & FUNC_THUNK:

   rename_wrapper(name, func)

   return

 if op_type == 2 or op_type == 6:

  rename_wrapper(name, func)

  return


for func in idautils.Functions():

 check_for_wrapper(func)


기존 코드와 차이점으로 rename_wrapper의 idc.MakeNameEx(ea, name, flag)를 사용하는 것인데, 함수 이름이 중복일 시에 경고 창을 던지는 idc.MakeName과 다르다.


SN_NOWARN 또는 256의 플래그 값을 전달하면 대화 상자가 표시되지 않는다. 




####### Accessing Raw Data #######


데이터에 액세스하려면 먼저 단위 크기를 결정해야합니다. 바이트에 접근하기 위해 idc.Byte(ea)를 호출하거나 idc.Word(ea) 등으로 접근해야 한다.


idc.Byte(ea) # 예를 들어 push ebp(55 8B)를 대상으로 한다면, 55를 가져온다.

idc.Word(ea) # 위와 동일한 조건에서 8B55를 가져온다(리틀 엔디언으로 안되나봄...)

idc.Dword(ea)

idc.Qword(ea)

idc.GetFloat(ea)

idc.GetDouble(ea)


Python>print hex(ea), idc.GetDisasm(ea)

0xa14380 mov ecx, hHeap

Python>hex( idc.Byte(ea) )

0x8b

Python>hex( idc.Word(ea) )

0xd8b

Python>hex( idc.Dword(ea) )

0x6d0c0d8b

Python>hex( idc.Qword(ea) )

0x6a5000a26d0c0d8bL

Python>idc.GetFloat(ea) # Example not a float value

2.70901711372e+27

Python>idc.GetDouble(ea)

1.25430839165e+204


주소에서 지정된 크기의 바이트를 읽으려면 idc.GetManyBytes(ea, size, use_dbg=False)를 사용할 수 있습니다. 마지막 인수는 선택적이며 디버거 메모리를 원할 경우에만 필요합니다.


for byte in idc.GetManyBytes(ea, 6):

 print "0x%X" % ord(byte),

# 0x8B 0xD 0xC 0x6D 0xA2 0x0


idc.GetManyBytes(ea, size)는 char 표현을 반환한다는 것에 유의해야한다. 그러므로, ord(문자를 아스키코드로)를 사용해야 한다.



####### Patching #######


악성코드를 리버싱할 때 샘플에 인코딩된 문자열이 존재한다. 이 패치와 같은 상황에서는 IDB가 유용하다. 주소의 이름은 바꿀 수 있지만 이름 변경은 제한적이다. 값으로 주소를 패치하려면 아래 3가지 함수를 사용할 수 있다.


idc.PatchByte(ea, value)

idc.PatchWord(ea, value)

idc.PatchDword(ea, value)


ea는 주소이고 value는 IDB를 패치하고자하는 정수 값이다. 값의 크기는 우리가 선택한 함수 이름으로 지정된 크기와 일치해야한다. 예를 들어 다음과 같이 인코딩된 문자열이 존재한다고 가정해보자.

.data:1001ED3C aGcquEUdg_bUfuD db 'gcqu^E]~UDG_B[uFU^DC',0

.data:1001ED51 align 8

.data:1001ED58 aGcqs_cuufuD db 'gcqs\_CUuFU^D',0

.data:1001ED66 align 4

.data:1001ED68 aWud@uubQU db 'WUD@UUB^Q]U',0

.data:1001ED74 align 8


아래는 디코딩 함수이다.


100012A0 push esi

100012A1 m ov esi, [esp+4+_size]

100012A5 xor eax, eax

100012A7 test esi, esi

100012A9 jle short _ret

100012AB m ov dl, [esp+4+_key] ; assign key

100012AF m ov ecx, [esp+4+_string]

100012B3 push ebx

100012B4

100012B4 _loop: ;

100012B4 m ov bl, [eax+ecx]

100012B7 xor bl, dl ; data ^ key

100012B9 m ov [eax+ecx], bl ; save off byte

100012BC inc eax ; index/count

100012BD cmp eax, esi

100012BF jl short _loop

100012C1 pop ebx

100012C2

100012C2 _ret: ;

100012C2 pop esi

100012C3 retn


start = idc.SelStart()

end = idc.SelEnd()

def xor(size, key, buff):

 for index in range(0,size):

  cur_addr = buff + index

  temp = idc.Byte(cur_addr) ^ key

  idc.PatchByte(curr_addr, temp)


xor(end - start, 0x30, start)

idc.GetString(start) # Print decoded string


####### Input and Output #######


파일 이름을 가져오거나 저장하려면 AskFile(forsave, mask, prompt)을 사용합니다. forsave는 만약 dialog box를 열려면 값 0(import)을, save dialog box를 열기 위해서는 1(export)을 입력합니다. mask는 extension(확장자) 도는 pattern(패턴)입니다. 만약 확장자 dll 파일만 열기 위해서는 "*.dll" mask를 사용합니다. prompt는 윈도우의 제목이다. 입출력 및 데이터 선택의 좋은 예는 아래 코드의 IO_DATA 클래스이다.


import sys

import idaapi


class IO_DATA():

 def __init__(self):

  self.start = SelStart()

  slef.end = SelEnd()

  self.buffer = ''

  self.ogLen = None

  self.status = True

  self.run()


 def checkBounds(self):

  if self.start is BADADDR or self.end is BADADDR:

   self.status = False


 def getData(self):

  #시작과 끝 사이에 데이터를 가져 와서 object.buffer에 넣습니다.

  self.ogLen = self.end - self.start

  self.buffer = ''

  try:

   for byte in idc.GetManyBytes(self.start, self.ogLen): # 처음부터 끝까지 데이터의 Byte를 읽음

    self.buffer = self.buffer + byte

  except:

   self.status = False

  return

 

 def run(self):

  # basically main

  self.checkBounds()

  if self.status == False:

   sys.stdout.write('ERROR: Please select valid data\n')

   return

  self.getData()


 def patch(self, temp=None):

  '''patch idb with data in object.buffer'''

  if temp != None:

   self.buffer = temp

   for index, byte in enumerate(self.buffer):

    idc.PatchByte(self.start+index, ord(byte))


 def importb(self):

  '''import file to save to buffer'''

  filename = idc.AskFile(0, "*.*", 'Import File')

  try:

   self.buffer = open(filename, 'rb').read()

  except:

   sys.stdout.write('ERROR: Cannot access file')


 def export(self):

  '''save the selected buffer to a file'''

  exportFile = idc.AskFile(1, "*.*", 'Export Buffer')

  f = open(export, 'wb')

  f.write(self.buffer)

  f.close()


 def status(self):

  print "start: %s" % hex(self.start)

  print "end: %s" % hex(self.end)

  print "len: %s" % hex(len(self.buffer))


이 클래스를 사용하여 데이터를 버퍼로 저장하고 파일에 저장할 수 있습니다. 이는 IDB에서 인코딩되거나 암호화된 데이터에 유용합니다. IO_DATA를 사용하여 파이썬에서 버퍼를 디코딩한 다음 IDB를 패치할 수 있습니다. 아래 코드는 IO_DATA Class를 사용하는 예입니다.


Python>f = IO_DATA()

Python>f.stats()

start: 0x401528

end: 0x401549

len: 0x21


obj.start

선택한 오프셋의 시작 주소를 포함합니다.


obj.end

선택한 오프셋의 끝 주소를 포함합니다.


obj.buffer

이진 데이터가 들어 있습니다.


obj.ogLen

버퍼의 크기를 포함합니다.


obj.getData()

obj.start와 obj.end 사이의 바이너리 데이터를 obj.buffer obj.run()에 복사합니다.

선택된 데이터는 바이너리 형식으로 버퍼에 복사됩니다.


obj.patch()

obj.start의 IDB를 obj.buffer의 데이터로 패치하십시오.


obj.patch(d)

obj.start에서 IDB를 인수 데이터로 패치하십시오.


obj.importb()

파일을 열고에 데이터를 저장합니다.


obj.buffer. obj.export ()

obj.buffer의 데이터를 파일로 저장합니다.


obj.stats()

obj.start, obj.end 및 obj.buffer 길이의 16 진수를 출력합니다.


####### Intel Pin Logger ########


Pin은 IA-32 및 x86-64용 동적 바이너리 계측 프레임워크입니다. PIN의 동적 분석결과와 IDA의 정적 분석을 결합하면 강력한 조합이 됩니다. 아래의 단계는 실행 파일을 추적하고 IDB에 실행된 주소를 추가하는 Pintool을 설치하기 위한 30초 가이드입니다.


Notes about steps

* Pre-install Visual Studio 2010 (vc10) or 2012 (vc11)

* If executing malware do steps 1,2,6,7,8,9,10 & 11 in an

analysis machine

1. Download PIN

* https://software.intel.com/en-us/articles/pintool-downloads

* Compiler Kit is for version of Visual Studio you are

using.

2. Unzip pin to the root dir and renam e the folder to "pin"

* example path C:\pin\

* There is a known but that Pin will not always parse the

arguments correctly if there is spacing in the file path

3. Open the following file in Visual Studio

* C:\pin\source\tools\MyPinTool\MyPinTool.sln

- This file contains all the needed setting for Visual

Studio.

- Useful to back up and reuse the directory when starting

new pintools.

4. Open the below file, then cut and paste the code into

MyPinTool.cpp (currently opened in Visual Studio)

* C:\pin\source\tools\ManualExamples\itrace.cpp

- This directory along with ../SimpleExamples is very

useful for example code.

5. Build Solution (F7)

6. Copy traceme.exe to C:\pin

7. Copy compiled MyPinTool.dll to C:\pin

* path C:\pin\source\tools\MyPinTool\Debug\MyPinTool.dll

8. Open a com m and line and set the working dir to C:\pin

9. Execute the following com m and

* pin -t traceme.exe -- MyPinTool.dll

- "-t" = name of file to be analyzed

- "-- MyPinTool.dll" = specifies that pin is to use the

following pintool/dll

10. While pin is executing open traceme.exe in IDA.

11. Once pin has completed (com m and line will have returned)

execute the following in IDAPython

* The pin output (itrace.out) must be in the working dir of

the IDB. \


1. PIN 다운로드

* https://software.intel.com/en-us/articles/pintool-downloads

* 컴파일러 키트는 사용중인 Visual Studio 버전 용입니다.

2. 핀을 루트 디렉토리에 압축을 풀고 폴더를 "핀"으로 바꿉니다.

* 예제 경로 C : \ pin \

* 알려진 바는 있지만 파일 경로에 간격이 있으면 Pin이 항상 인수를 올바르게 구문 분석하지 않습니다.

3. Visual Studio에서 다음 파일을 엽니 다.

* C : \ pin \ source \ tools \ MyPinTool \ MyPinTool.sln

-이 파일에는 Visual Studio에 필요한 모든 설정이 포함되어 있습니다.

- 새로운 pintools를 시작할 때 디렉토리를 백업하고 다시 사용할 때 유용합니다.

4. 아래 파일을 열고 MyPinTool.cpp에 코드를 잘라 붙여 넣습니다 (현재 Visual Studio에서 열림).

* C : \ pin \ source \ tools \ ManualExamples \ itrace.cpp

- ../SimpleExamples와 함께이 디렉토리는 예제 코드에 매우 유용합니다.

5. 솔루션 빌드 (F7)

6. traceme.exe를 C : \ pin에 복사하십시오.

7. 컴파일 된 MyPinTool.dll을 C : \ pin에 복사합니다.

* 경로 C : \ pin \ source \ tools \ MyPinTool \ Debug \ MyPinTool.dll

8. 명령행을 열고 작업 디렉토리를 C:\pin으로 설정하십시오.

9. 다음 명령을 실행하십시오.

* pin -t traceme.exe - MyPinTool.dll

- "-t"= 분석 할 파일의 이름

- "- MyPinTool.dll"= 핀이 다음 pintool / dll을 사용하도록 지정합니다.

10. 핀이 IDA에서 열린 traceme.exe를 실행하는 동안.

11. 일단 핀이 완료되면 (com 및 행이 반환됩니다) IDAPython에서 다음을 실행합니다.

* 핀 출력 (itrace.out)은 IDB의 작업 디렉토리에 있어야합니다. \


itrace.cpp는 itrace.out에 실행 된 모든 명령어의 EIP를 인쇄하는 pintool입니다. 데이터는 다음과 같은 출력으로 보입니다.


pintools가 실행되면 모든 실행 된 주소에 주석을 추가하기 위해 IDAPython 코드를 실행할 수 있습니다. itrace.out 파일의 출력은 IDB의 작업 디렉토리에 있어야합니다.


f = open('itrace.out', 'r')

lines = f.readlines()


for y in lines:

 y = int(y,16)

 idc.SetColor(y, CIC_ITEM, 0xfffff)

 com = idc.GetCommentEx(y,0)

 if com == None or 'count' not in com:

  idc.MakeComm(y, "count:1")

 else:

  try:

   count = int(com.split(':')[1],16)

  except:

   print hex(y)

  tmp = "count:0x%x" % (count + 1)

  idc.MakeComm(y, tmp)

f.close()


먼저 itrace.out을 열고 모든 줄을 목록으로 읽습니다. 그런 다음 목록의 각 행을 반복합니다. 출력 파일의 주소는 16 진수 문자열 형식이므로 정수로 변환해야합니다.


############# Batch File Generation #############


때로는 디렉토리의 모든 파일에 대해 IDB 또는 ASM을 작성하는 것이 유용 할 수 있습니다. 이렇게하면 동일한 맬웨어 패밀리의 일부인 샘플 세트를 분석 할 때 시간을 절약 할 수 있습니다. 대용량 파일 세트를 수동으로 수행하는 것보다 배치 파일 생성이 훨씬 쉽습니다.


배치 분석을 수행하려면 -B 인수를 텍스트 idaw.exe에 전달해야합니다. 그만큼 아래 코드는 파일을 생성하고자하는 모든 파일이 들어있는 디렉토리에 복사 할 수 있습니다.


import os

import subprocess

import glob

paths = glob.glob("*")

ida_path = os.path.join(os.environ['PROGRAMFILES'], "IDA", "idaw.exe")


for file_path in paths:

 if file_path.endswith(".py"):

  continue

 subprocess.call([ida_path, "-B", file_path])


glob.glob("*")를 사용하여 디렉토리의 모든 파일 목록을 가져옵니다. 인수는 특정 정규식 패턴이나 파일 유형만 선택하려는 경우 수정되었습니다. 확장자가 .exe 인 파일 만 얻으려는 경우 glob.glob ("*.exe")를 사용합니다. 


os.path.join(os.environ['PROGRAMFILES'], "IDA", "idaw.exe")은 idaw.exe에 대한 경로를 얻는 데 사용됩니다. IDA의 일부 버전에는 버전 번호가 표시된 폴더 이름이 있습니다. 이 경우 "IDA"인수를 폴더 이름으로 수정해야합니다. 또한 명령을 사용하도록 선택하면 전체 명령을 수정해야 할 수도 있습니다. 


IDA의 설치 경로가 C:\Program\IDA라고 가정합니다. 경로를 찾았으면 .py 확장자를 포함하지 않은 디렉토리의 모든 파일을 반복 후 IDA로 전달합니다. 개별 파일의 경우 C:\ProgramFiles\IDA\idaw.exe -B bad_file.exe와 같습니다. 일단 실행하면 파일에 대한 ASM 및 IDB가 생성됩니다.



############### Executing Scripts ################


IDAPython 스크립트는 명령 행에서 실행할 수 있습니다. 다음 코드를 사용하여 IDB의 각 명령을 계산한 다음 특정 파일에 코드를 쓸 수 있습니다.


import idc

import idaapi

import idautils


idaapi.autoWait()


count = 0

for func in idautils.Functions():

 # Ignore Library Code

 flags = idc.GetFunctionFlags(func)

 if flags & FUNC_LIB:

  continue

 for instru in idautils.FuncItems(func):

  count += 1


f = open("instru_count.txt", "w")

print_me = "Instruction Count is %d" % (count)

f.write(print_me)

f.close()


idc.Exit(0)


명령 행 관점에서 가장 중요한 두 기능은 idaapi.autoWait() 및 idc.Exit(0)입니다. IDA가 파일을 열면 분석이 완료 될 때까지 기다리는 것이 중요합니다. 이를 통해 IDA는 IDA의 분석 엔진을 기반으로 하는 모든 함수, 구조 또는 다른 값을 IDA에 채울 수 있습니다. 분석이 완료되기를 기다리려면 idaapi.autoWait()를 호출합니다.


IDA가 분석 될 때까지 대기/일시 중지가 됩니다. 일단 분석이 완료되면 제어권을 다시 스크립트로 되돌릴 것입니다. 분석에 의존하는 IDAPython 함수를 호출하기 전에 스크립트 시작 부분에서 이를 실행하는 것이 중요합니다. 스크립트가 실행되면 idc.Exit(0)을 호출해야 합니다. 그러면 스크립트 실행이 중단되고 데이터베이스가 닫히고 스크립트의 호출자에게 반환됩니다. 그렇지 않으면 IDB가 제대로 닫히지 않습니다.


IDAPython을 실행하여 IDB의 모든 줄을 계산하고자 한다면 다음 명령 줄을 실행합니다.

C:\Cridix\idbs>"C:\Program Files (x86)\IDA 6.3\idaw.exe" -A -Scount.py cur-analysis.idb


-A는 Autonomous 모드이고 -S는 IDA가 IDB를 열면 스크립트를 실행하도록 IDA에 알립니다. 작업 디렉토리에서 우리는 instru_count.txt라는 파일에 모든 명령어가 포함된 것을 볼 수 있습니다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함