IP 난독화
보안 연구원 Osanda Malith 가 작성한 IP주소 난독화 코드입니다. 원본은 여기 서 찾을 수 있습니다. 처음 이 코드를 접했을 때 이렇게도 접속이 되나? 싶었지만 네 실제로 되는군요. 기존에도 SOCK_ADDR 구조체를 직접 갖다 쓰는 악성코드는 정적으로 IP를 탐지할 방법이 없긴 했지만, 단순한 문자열임에도 IP로 인식된단 점에서 해커 입장에선 더 쓰기 편해진게 아닐까 합니다.
C로 작성된 소스코드는 릴리즈된 파일의 모든 기능을 제공하진 않지만 결과값을 본다면 누구나 쉽게 작성할 수 있는 수준입니다. 여기서는 소스코드 상 공개된 하나의 난독화 방법에 대해 다뤄보겠습니다.
IP
IP는 아시다시피 1byte의 옥텟 네 개가 모여 이뤄져 있습니다(IPv4). 예를 들어 192.168.0.1 란 IP주소에서 .으로 구분된 각 숫자는 최대 255의 값을 가질 수 있고, 이는 16진수론 0xFF 가 됩니다. 이와 같은 특성 때문에 IPv4에서는 표현할 수 있는 IP주소의 최대 개수가 2의 32승에 지나지 않습니다.
C언어에서 INT 자료형은 4byte이죠? Osanda Malith 가 공개한 소스코드에서는 IP의 각 1byte 옥텟을 쉬프트 연산하여 하나의 4byte INT로 만들어 접근합니다. 여기서는 C 소스코드를 보고 좀더 읽기 쉽게 다듬어보면 어떨까 해서 작성해봤습니다. (속도가 느려진건 덤입니다)
class IpOpfuscator:
def isValid(self, ip):
ip_pattern = '((0|1[0-9]{0,2}|2[0-9]?|2[0-4][0-9]|25[0-5]|[3-9][0-9]?)\.){3}(0|1[0-9]{0,2}|2[0-9]?|2[0-4][0-9]|25[0-5]|[3-9][0-9]?)'
match = re.match(ip_pattern, ip)
if match is not None:
return True
return False
def opfuscate(self, ip):
octets = ['%02x' % int(octet) for octet in ip.split('.')]
result = '%d' % int(''.join(octets), 16)
return result
코드설명
re는 Regular Expression 의 약자, 파이썬에서 기본 제공하는 정규식 모듈입니다. 느린 속도에 한몫 거들지만 IP 주소가 유효한 값인지 확인하는 용도로 직관적이어서 사용했습니다. 여기서 저 정규식을 일일히 해석하진 않겠습니다. 구글링 잠깐이면 쓸만한 정규식을 많이 찾으실 수 있습니다. (휴대폰번호 라던가, 이메일 주소라던가)
신경쓴 부분은 octets = ['%02x' % int(octet) for octet in ip.split('.')]
입니다. 좀 더 보기 편하게 여러 줄로 나눠쓸 수 있겠지만 최근 배운 문법을 활용해 보고 싶었습니다. 이 코드는 아래의 과정을 담고 있습니다.
octets = ['%02x' % int(octet) for octet in ip.split('.')]
result = '%d' % int(''.join(octets), 16)
'''
>>> "192.168.0.1"
>>> "192", "168", "0", "1"
>>> 192, 168, 0, 1
>>> "C0", "A8", "00", "01"
>>> "C0A80001"
>>> 3232235521
'''
사실 C언어적으로 접근하는게 수백배는 빠를것 같습니다. 하지만 생각하는 과정 그대로 글을 쓰듯이 쓸 수 있는게 파이썬의 장점이 아닐까 생각됩니다. 별로 다를건 없지만 코드 전문을 덧붙입니다.
코드 전문
# -*- coding=utf8 -*-
try:
import re
except ImportError as e:
print('[!] Import Error : %s' % e)
class IpOpfuscator:
def isValid(self, ip):
ip_pattern = '((0|1[0-9]{0,2}|2[0-9]?|2[0-4][0-9]|25[0-5]|[3-9][0-9]?)\.){3}(0|1[0-9]{0,2}|2[0-9]?|2[0-4][0-9]|25[0-5]|[3-9][0-9]?)'
match = re.match(ip_pattern, ip)
if match is not None:
return True
return False
def opfuscate(self, ip):
octets = ['%02x' % int(octet) for octet in ip.split('.')]
return '%d' % int(''.join(octets), 16)
def main():
io = IpOpfuscator()
while True:
ip = input('Enter Ip address : ')
if io.isValid(ip):
print('http://%s' % io.opfuscate(ip))
else:
print('Invalid ip value')
if __name__ == '__main__':
main()