1. Overview



Python을 사용하여 'HWP Parser' 제작중 BinData의 내부 Stream에 대해 Decompress를 수행하던 도중 발생한 Trouble에 대해 서술한다.


2. Structure



HWP File Format에 관련하여 한컴 오피스는 홈페이지를 통해 공식 문서를 제공한다.


그 중 BinData 스토리지에는 그림이나 OLE 개체와 같이 문서에 첨부된 바이너리 데이터가 각각의 스트림으로 저장된다.


Parser 제작 과정에서 해당 한글 문서 파일의 악성 유무 판별을 위해 Decompress를 수행해야 했다.


[그림 1] BinData Area



Decompress에는 Python zlib을 활용하였다. 보편적인 zlib의 Decompress 구문은 다음과 같은 에러를 출력했다.


zlib.error: Error -3 while decompressing data: incorrect header check


[그림 2] 에러 확인


3. Trouble Shooting



문제해결을 위해 검색 도중 다음과 같은 글을 확인할 수 있었다.


글에 따르면 메모리에 저장할 수 있는 크기를 초과하는 Stream (또는 파일 입력) 크기 문제로 인해 위의 에러가 발생했을 것이라고 한다. 실제 메모리 크기를 초과한 것이 아닌 버퍼 기본 크기를 초과했기 때문이다.


이를 해결하기 위한 방법은 Stream을 버퍼링으로 처리하고 Decompress를 수행하는 방법이 존재한다. 함께 제공된 솔루션 소스코드는 다음과 같다[1].

 
import zlib
f_in = open('my_data.zz', 'rb')
comp_data = f_in.read()
zobj = zlib.decompressojb()  # obj for decompressing data streams that won’t fit into memory at once.
data = zobj.decompress(comp_data)


위의 방법과 같이 버퍼링을 적용하고 테스트를 진행했을 때 또 다시 동일한 에러가 뜨는 것을 확인했다.


조금 더 찾아보던 도중 다음과 같은 글을 찾을 수 있었다.


[그림 3] wbit 관련 솔루션[2]



위 글에서는 wbit 옵션을 -15로 정의하면 에러가 해결된다고 제시했다. zlib 모듈 공식 홈페이지에서 제공하는 문서에 따르면 WBITS의 의미는 다음과 같다.


[표 1] MAX_WBITS의 의미[3]

The wbits argument controls the size of the history buffer (or the “window size”) used when compressing data, and whether a header and trailer is included in the output. It can take several ranges of values, defaulting to 15 (MAX_WBITS):

  • +9 to +15: The base-two logarithm of the window size, which therefore ranges between 512 and 32768. Larger values produce better compression at the expense of greater memory usage. The resulting output will include a zlib-specific header and trailer.
  • −9 to −15: Uses the absolute value of wbits as the window size logarithm, while producing a raw output stream with no header or trailing checksum.
  • +25 to +31 = 16 + (9 to 15): Uses the low 4 bits of the value as the window size logarithm, while including a basic gzip header and trailing checksum in the output.


MAX_WBIS 값은 15를 가지며 이를 -로 선언하면 -15 값으로 정의되기 때문에 에러를 해결할 수 있다.


이를 적용한 소스코드는 다음과 같다.


 
def bin_data(ole,bin_list):
    print ()
    print ('[+] BinData Information')
    for content in bin_list:
        if content[0] == 'BinData':
            print ('   - File Name : %s' %content[0]+'/'+content[1])
            bin_text = ole.openstream(content[0]+'/'+content[1])
            print ('   - File Size : %s' %ole.get_size(content[0]+'/'+content[1]))
            data2 = bin_text.read()
            print ('   - Hex data ~20bytes(pre-Decompress) : %s' %data2[:20])
            zobj = zlib.decompressobj(-zlib.MAX_WBITS)
            data3 = zobj.decompress(data2)
            print ('   - Hex data ~20bytes(Decompress) : %s' %data3[:20])
            f = open('./'+content[1]+'_Decom.txt','wb')
            f.write(data3)
            f.close
            print ()


결과는 다음과 같다.



[그림 4] 정상적으로 Decompress 완료된 Stream



4. References



[1] https://stackoverflow.com/questions/32367005/zlib-error-error-5-while-decompressing-data-incomplete-or-truncated-stream-in?rq=1


[2] https://daehee87.tistory.com/m/508?category=404227


[3] https://docs.python.org/3/library/zlib.html

포스팅을 시작하기전에 사용한 툴은 다음과 같다.


  1. 파이썬2

  2. XAMPP (Maria DB)

  3. HeidiSQL (DB GUI)



1. 개요



해당 포스팅에는 시나리오가 있다. 악성코드 분석 중 대용량의 자료유출 흔적이 발견되어 대용량의 방화벽 로그를 확보하였다. 해당 로그를 보고 자료유출 흔적을 찾고 공격자의 위치를 찾아내는 것이다.




2. 분석



직접 수집한 데이터가 아니기 때문에 데이터에 대한 많은 정보를 공개할 수는 없다.



▶ 초기 데이터 분석

초기 데이터는 113GB의 텍스트 파일이다.

내부 내용은 [날짜 및 시간], [ID], [severity], [sys], [sub], [name], [action], [fwrule], [src_mac], [dst_mac], [src_ip], [dst_ip], [length], [srcport], [dstport] 로 이루어져있다.  이 로그를 생으로 분석하기에는 불필요한 정보가 너무 많다. 필요 / 불필요를 나누는 기준은 위의 개요에서 언급했듯 목적에 맞춰서 기준을 선정할 것이다.


데이터는 [날짜 및 시간], [src_mac], [dst_mac], [src_ip], [dst_ip], [length], [srcport], [dstport] 를 파싱하여 최적화할 것이다. 우리의 목적은 DB에도 연동하는 것이기 때문에 각 필드 값 또한 최적화를 수행해야 한다.




▶파싱 소스 작성

파싱을 위한 파이썬 소스는 Git에 올려둔 링크로 대신하겠다.
https://github.com/swpose/sw_python2/blob/master/firewall_git.py

코딩 실력이 많이 부족하여 속도는 고려하지 않고 작성하였다. 이후 추가적인 최적화를 위해 수정해보도록 하겠다.



3. DB 연동



DB 연동은 XAMPP를 이용하여 서버를 생성하였다.

해당 도구는 우리가 흔히 사용하는 서버를 쉽게 사용할 수 있도록 해준다. 나는 MySQL을 사용하였다.




HeidiSQL 도구는 데이터를 조회할 때 쿼리가 아닌 GUI로 편리하게 볼 수 있도록 해주는 도구이다. DB를 많이 다뤄보지 않은 사람이나 초심자들이 사용하면서 감을 잡기에는 좋을 것 같다.



위의 두개의 도구는 일반 설치함에 있어서도 별도의 어려움이 존재하지 않기에 설치방안에 대해서는 언급하지 않겠다.

위의 두개의 도구를 활용하면 간단한 쿼리문만으로도 DB를 조회할 수 있다.
우리가 원하는 데이터는 대량유출에 대한 증거를 찾는 것이다.
결과부터 말하면 위의 length의 역순으로 정렬하여 큰 용량을 찾으면 될 것이다. 이를 위해 사용한 쿼리문은 다음과 같다.


DBname = f_log / Table name = firewall 

  1. mysql –uroot;
  2. use f_log;
  3. LOAD DATA LOCAL INFILE "C:/log_output.csv" INTO TABLE f_log.firewall FIELDS TERMINATED BY ",";
  4. select count(*) from firewall;
  5. select * from firewall order by ID desc limit 50;



데이터 연동 중 데이터를 조회하였다. 대충 저런식으로 DB에 연동된다.

4. 결론



초대량의 데이터에서 원하는 정보를 파싱하여 DB에 연동함으로써 원하는 정보를 빠르고 정확하게 찾을 수 있고 침해 사고의 대응하는데 도움이 될 수 있을 것이라는 생각을 하였다.



'개발 > Python' 카테고리의 다른 글

zlib Decompress issue  (0) 2019.02.12
BeautifulSoup vs Scrapy  (0) 2019.01.06
크롤링(Crawling)이란? 또는 파싱(Parsing)이란? 스크래핑이란?  (0) 2019.01.06

* BeautifulSoup

1. html문서에서 원하는 정보를 손쉽게 가져올 수 있는 방법을 제공

2. 자동으로 인코딩을 유니코드로 변환해서 UTF-8로 출력

3. lxml, html5lib 파서를 이용함


* Scrapy

1. web scraper framework

2. 다양한 selector 지원

3. 파이프 라인

4. 로깅

5. 이메일


(혼자 공부하며 참고하기 위해 본문 내용을 그대로 가져왔습니다. 문제시 삭제 조치 하겠습니다.)


크롤링(Craling)이란?

크롤링은 웹 크롤러(web crawling)에서 출발한 말로 무수히 많은 인터넷 상의 페이지(문서, html 등)를 수집해서 분류하고 저장한 후에 나중에 쉽게 찾아볼 수 있도록 하는 역할을 하는 일종의 로봇이다.


그래서 크롤링은 데이터를 수집하고 분류하는걸 크롤링이라고 한다.


다이아몬드 광산에 예를 들면 2만평짜리 광산이 있는데 이 중에 다이아몬드가 주로 나오는 곳이 입구에서 직진해서 200미터 떨어진 부분과, 입구에서 오른쪽으로 꺾어서 400미터 떨어진 부분이라는 이런 정보가 어디에 있는지에 대한 위치를 분류하는 것이라고 보면 된다.


파싱(Parsing)이란?


파싱(Parsing)은 어떤 페이지(문서, html 등)에서 내가 원하는 데이터를 특정 패턴이나 순서로 추출하여 정보로 가공하는 것을 말하는 것이다. 다이아몬드가 많이 나오는 위치로 이동을 일단 한 후에 돌을 많이 캔다음에 다이아몬드만 쏙쏙 뽑아서 보석으로 가공하는 과정하고 비슷하다고 보면 된다.


스크래핑(Scraping) 이란?

데이터를 수집하는 모든 작업을 말한다.



'데이터분석'에 대한 이야기가 많이 나오면서 크롤링, 파싱에 대해 궁금해 하는 사람들이 많이 있고 나도 크롤러, 파서, 스크래퍼를 주로 개발 하다보니 미묘한 차이가 궁금해서 한번 찾아보았다.


요즘은 그냥 인터넷에서 프로그램으로 데이터를 추출해서 사용하는 작업을 크롤링, 파싱 등으로 혼용해서 쓰는 것 같다. 경계가 모호한 말이기도 하고 크롤러를 만들다 보면 파싱 기능이 들어가고 파서를 만들려면 크롤링 하는 기능이 일부 들어가기도 하는 등의 일도 많기 때문.



출처

[1] https://en.wikipedia.org/wiki/Web_crawler#Nomenclature
[2] https://en.wikipedia.org/wiki/Parsing

'개발 > Python' 카테고리의 다른 글

zlib Decompress issue  (0) 2019.02.12
방화벽 로그파일 파싱 및 DB 연동  (0) 2019.01.06
BeautifulSoup vs Scrapy  (0) 2019.01.06

+ Recent posts