본 게시글은 Windows 10 Pro (버전 1809) x64 에서 실행되는 Powershell을 대상으로 기록하였습니다. Windows OS 의 버전이나 사용자의 세팅 환경에 따라 다를 수 있음을 알려드립니다.
1.1 문제의 발견
서버의 원격 개발을 위해 VScode의 ssh 연결을 위해 {시작 - 실행}을 통해 Powershell을 입력하여 호출하였다.
평소 Powershell을 "CMD"와 유사하게 설정해놓고 사용하고 있다.
Windows에서는 {시작 - 실행}으로 Powershell을 실행하는 방법 이외에 다른 방법으로 호출할 수 있도록 지원한다.
설정(Windows Shortcut : Winkey + i)에서 {개인설정 - 작업표시줄}의 아래 그림에 표시된 토글 버튼의 활성화 여부에 따라 파워유저 메뉴(Windows Shortcut : Winkey + x or 시작버튼 우클릭) 에서 Powershell을 호출할 수 있다.
해당 토글의 활성화를 통해 파워유저 메뉴에서 Powershell을 실행할 수 있다.
파워유저 메뉴를 통해 실행한 Powershell과 실행을 통해 실행한 Powershell이 다른 것을 확인할 수 있었다.
같은 Powershell인데 하나는 설정이 적용되어 있고 하나는 기본 설정으로 되어 있는 것을 확인하였다.
2. Body
2.1 파워유저 메뉴의 Path
앞서 등장한 {Winkey + i}를 통해 실행할 수 있는 파워유저 메뉴는 아래의 경로에서 관리된다.
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 ()
본 포스팅은 MBR 과 EBR에 관해 간략히 서술하며 EBR의 주소 찾아가는 방법에 대해 주로 서술할 것이다.
1. MBR
MBR은
저장 매체 (물리 디스크)의 첫 번째 섹터에 위치하며 512Byte의 크기를 갖는다. 이는 446 Byte의 부트 코드와 64
Byte 의 파티션 테이블, 2 Byte 의 시그니처로 이를 합하면 512 Byte (0x200)의 크기이다.
우리가 주목할 부분은 64 Byte의 파티션 테이블이다. 각 파티션마다 16 Byte로 구성되어 있으며 4개까지의 파티션을 구성할 수 있고 그 이상은 EBR로 관리한다.
지인으로부터 제공받은 이미지 파일을 분석해보겠다. 사용한 도구는 HxD를 이용하였다. 먼저 파일을 불러올 때 드래그 앤 드롭으로 불러오는 것과 기타설정 -> 디스크 이미지 열기와의 차이점이 있다.
(디스크 이미지 열기를 통해 파일을 오픈하면 그림과 같이 섹터를 표시해주기 때문에 주소를 계산하기 더 편리하다.)
위에도 언급했듯 디스크의 첫번째 섹터인 0번섹터에 512 Byte를 확인해보았다. 쉽게 확인하는 방법은 마지막 2바이트를 확인해보면 MBR의 시그니처인 "0xAA55"를 확인할 수 있다.
본 포스팅에서는 부트코드 및 일부 파티션 테이블에 대한 설명은 생략하고 EBR 부분에 집중하겠다.
파티션 테이블에서 EBR을 나타내는 부분을 확인하고 (파란색 강조 부분) 그 중 LBA 시작주소 (붉은 박스)를 확인할 수 있다. LBA 시작주소는 실제 파티션이 시작되는 섹터의 위치를 나타내는 것이다. 리틀 엔디언으로 "0x01E080"이다. EBR의 주소 계산은 기준값이 중요하기 때문에 이 기준값을 잘 참조해야한다.
1.1 주소의 계산
EBR의 실제 파티션 섹터가 위치한 주소는 "0x01E080"이다. 그러나 해당 주소로 바로 이동한다고 되는 것은 아니다.
섹터의 크기만큼을 곱해줘야 우리가 원하는 결과가 출력될 것이다. 16진수 0x01E080은 10진수로 123,008이다. 여기에 섹터의 크기 512를 곱한다. 그리고 그 값의 16진수로 변환하면 EBR의 주소이다.
계산한 결과는 "0x3C10000"이다. 확인해보겠다. HxD에서 Ctrl + G 단축키를 이용하면 오프셋 이동을 할 수 있다.
쉽게 확인하는 방법은 아래 시그니처부분이 "0xAA55" 인것을 확인하면 된다. 아래 그림에서 다음 EBR의 LBA부분을 미리 표시해 두었다. 다음을 찾아가기 전에 조금 더 쉽게 계산하는 방법을 서술하겠다.
앞에서는 [LBA 시작주소의 10진수 * 512 = 결과값 (16진수 변환)]
으로 찾았다. 그러나 HxD로 이미지를 로드할 때 "디스크 이미지 열기"로 오픈한 이유가 여기에 있다. 바로 섹터값을 표시해주는
것인데, 하드 디스크 섹터의 크기인 512 Byte를 설정하고 오픈하면 512 바이트를 곱해줄 필요가 없다. 바로 확인해보겠다.
먼저 첫번째 EBR의 LBA 값은 16진수 0x01E080은 10진수로 123,008이다.
HxD의 기능중에 섹터값으로 찾아가는 방법이 있다. 이를 이용하면 LBA 값의 10진수 입력으로 바로 찾아 갈 수 있다.
주소를 찾아가는 2가지 방법을 정리하고 넘어가겠다.
1. [LBA 시작주소의 10진수 * 512 = 결과값 (16진수 변환)]
2. [LBA 시작주소의 10진수 -> 섹터값으로 주소이동]
첫 번째 EBR의 파티션
테이블에서 알려주는 LBA 시작주소는 "0xA080"이다. 그러나 두번째 EBR에서는 바로 찾아간다고 되는게 아니다. 바로 앞에서
언급했던 "기준값"을 더해주어야 한다. 이유는 EBR의 주소체계구조를 참고하길 바란다. 기준값
"0x01E080"+"0xA080"의 결과값이 다음 EBR의 영역이 될 것이다.
결과값의
10진수인 164,096 섹터로 찾아간 결과에 "0xAA55" 시그니처를 확인할 수 있다. 아래 사진에는 다음 EBR 주소의
LBA 값을 미리 표시해두었다. 바로 찾아가보겠다. "0x01E080"+"0x014100" = "0x32180"
위의
계산법대로 계산하여 다음 EBR에 도착하였다. EBR의 부트코드 영역은 역할이 없기 때문에 0 또는 FF로 채워져 있을 수
있다. 파티션 테이블에서 나타내는 다음 EBR의 LBA 값은 0으로 세팅되어 있다. 이로써 3개의 EBR을 모두 찾은 것을 확인할
수 있다.
2. 또 다른 방법
여기서는 HxD를 이용하여 계산하면서 찾았지만 다른 방법도 많을 것이라고 생각한다. 대표적으로 "010 Editor"의 "Drive.bt" 템플릿을 이용하면 잘 분석해준다.
3. 결론
본
포스팅에서는 EBR의 주소계산법에 초점을 맞추어 서술하였다. 이론적으로 공부할 때도 많이 헷갈렸고 직접 찾아가는 동안에도 상세히
설명된 게시물이 없어서 어려웠다. 그러나 한번만 직접 해보면 이해하기 쉬운것 같다. 이상으로 MBR & EBR 찾기
포스팅을 마치겠다.
해당 포스팅에는 시나리오가 있다. 악성코드 분석 중 대용량의 자료유출 흔적이 발견되어 대용량의 방화벽 로그를 확보하였다. 해당 로그를 보고 자료유출 흔적을 찾고 공격자의 위치를 찾아내는 것이다.
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에도 연동하는 것이기 때문에 각 필드 값 또한 최적화를 수행해야 한다.
(혼자 공부하며 참고하기 위해 본문 내용을 그대로 가져왔습니다. 문제시 삭제 조치 하겠습니다.)
크롤링(Craling)이란?
크롤링은 웹 크롤러(web crawling)에서 출발한 말로 무수히 많은 인터넷 상의 페이지(문서, html 등)를 수집해서 분류하고 저장한 후에 나중에 쉽게 찾아볼 수 있도록 하는 역할을 하는 일종의 로봇이다.
그래서 크롤링은 데이터를 수집하고 분류하는걸 크롤링이라고 한다.
다이아몬드 광산에 예를 들면 2만평짜리 광산이 있는데 이 중에 다이아몬드가 주로 나오는 곳이 입구에서 직진해서 200미터 떨어진 부분과, 입구에서 오른쪽으로 꺾어서 400미터 떨어진 부분이라는 이런 정보가 어디에 있는지에 대한 위치를 분류하는 것이라고 보면 된다.
파싱(Parsing)이란?
파싱(Parsing)은 어떤 페이지(문서, html 등)에서 내가 원하는 데이터를 특정 패턴이나 순서로 추출하여 정보로 가공하는 것을 말하는 것이다. 다이아몬드가 많이 나오는 위치로 이동을 일단 한 후에 돌을 많이 캔다음에 다이아몬드만 쏙쏙 뽑아서 보석으로 가공하는 과정하고 비슷하다고 보면 된다.
스크래핑(Scraping) 이란?
데이터를 수집하는 모든 작업을 말한다.
'데이터분석'에 대한 이야기가 많이 나오면서 크롤링, 파싱에 대해 궁금해 하는 사람들이 많이 있고 나도 크롤러, 파서, 스크래퍼를 주로 개발 하다보니 미묘한 차이가 궁금해서 한번 찾아보았다.
요즘은 그냥 인터넷에서 프로그램으로 데이터를 추출해서 사용하는 작업을 크롤링, 파싱 등으로 혼용해서 쓰는 것 같다. 경계가 모호한 말이기도 하고 크롤러를 만들다 보면 파싱 기능이 들어가고 파서를 만들려면 크롤링 하는 기능이 일부 들어가기도 하는 등의 일도 많기 때문.
Uploaded by Notion2Tistory v1.1.0