블로그 이미지
자유로운설탕

calendar

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

Notice

2020. 5. 10. 10:30 보안

  이번 시간에는 잘 아는 분야는 아니라고 생각하지만 악성코드라는 분야를 어떻게 바라보면 될지에 대해서 나름의 관점에서 얘기해 보려 한다.

 

 

 

[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 

 

 

 

1. 들어가면서

  앞의 글에서도 한 얘기지만 소프트웨어 보안에 마술 같은 부분은 없다. 모든 건 컴퓨터와 연결된 환경내에서 일어나는 일이며, 결국은 코드에 기반하여 논리적으로 설명이 가능해야 방어나 절충이 가능하게 된다. 그건 악성코드와 같은 분야에서도 마찬가지로 적용된다. 악성코드는 비 기술 적이거나 보안을 잘 모르는 사람들에게는 이해할 수 없는 어려운 현상일 수 있다. 악성코드가 컴퓨터에 설치되는 과정에 대해서는 하나하나 취약점의 기술적 측면을 살펴보며 이해해야 하는 문제(물론 이걸로 밥 먹고 사는 사람들이 있는 어려운 분야이긴 하지만)겠지만, 일단 설치된 후에 하는 행동은 일반 프로그램의 경우와 크게 다른 부분이 없다.

 

  예로서 요즘 많은 화제가 되고 있는 우리에게 익숙한 랜섬웨어의 행동을 살펴보자. 걸리면 특별한 경우가 아님 무조건 포맷을 해야 해결이 가능한 악명 높은 이런 악성코드도 사실 동작은 아주 단순하다고 볼 수 있다. 한번 랜섬웨어의 행동을 사람이 따라해 본다고 해보자.

 

 

  누군가 골탕을 먹이고 싶은 사람이 컴퓨터를 로그인 해 놓은채로 자리를 비웠다 하자. 해당 컴퓨터로 재빨리 다가가서 그 사람이 소중하게 생각할 만한 문서 파일이나, 데이터 파일들을 찾아 선택하여 zip으로 압축을 해본다. 확장자도 zip 보다는 다른 누군가가 악의로 했다는 것을 명확히 알아챌 수 있게 “lupine” 이라고 만들어 보자. 내가 아니면 압축을 풀 수 없도록 압축 파일에 나만 아는 복잡한 암호를 걸어서 압축한다(Brute force 공격에 의해서 쉽게 뚫어지지 않도록). 내가 암호를 알려주지 않는다면 아마 영원히 해당 파일의 원본을 찾을 순 없을 것이다. 이후 압축이 안된 원본 파일을 삭제하는데 가능한 복원이 안 되도록 단순히 지우지(delete) 말고, wiping 을 해서 원복 하기 어렵게 만든다. 주의할 점은 시스템에서 사용하는 중요한 파일들은 압축하거나 삭제하면 안된다. 운영체제 동작 자체가 망가져 켜지지 않는다면 상태가 골탕을 먹었는지도 모르게 되니까 말이다. 메모장을 열어 패스워드가 적힌 종이가 적힌 장소를 알려주는 퀴즈를 적은 후, 압축된 파일과 같은 폴더에 복구방법.txt” 라는 이름으로 저장해 놓는다. 뭐 운이 좋음 암호를 찾을 수도 있겠지

 

  위에 사람이 했던 것과 같은 행동을 랜섬웨어는 기존의 악성코드들과 동일한 방법으로 컴퓨터에 설치된 후, 프로그램 코드를 통해서 자동으로 수행한다. 그리고 최종 목표는 상태의 멸망(모든 것을 새로 설치하고 데이터를 날려버리게 하는)이나, 패스워드를 인질로 하고 비트 코인 등을 송금하도록 요구를 한다.

 

 

 

 

2. 백신 프로그램의 입장에서 상상해 보기

  그럼 반대로 이러한 악성코드를 문제가 일어나기 전에 적절히 찾아야 하는 백신 프로그램의 입장은 어떨까? 총기가 허용된 사회에서 테러를 저지를 수 있는 위험한 사람을 찾아내야 하는 것 같은 모호한 입장에 있다고 생각한다.

  왜 모호할까? 겉에서 보이는 행동이외에 사람의 마음속이나 행동의 의도를 알아내긴 힘들기 때문이다. 우선 총을 가지고 있다고 위험한 사람일까? 물론 가능성은 높을 것이다. 하지만 테러를 일으킬 수 있는 사람일 수도 있지만, 단순히 사복을 입은 경찰관일 수도 있고, 사회 분위기가 어수선해서 자신을 보호하기 위해 총기를 휴대한 사람일 수도 있다. 그럼 총기 허가증이 있거나, 경찰이라면 안심할 수 있을까? 반대로 테러를 위해서 치밀하게 준비된 시나리오일 수도 있다. 그럼 어떻게 해야 할까? 1시간 정도만 그 사람의 행동을 관찰하면 될까? 아니 어쩌면 그 사람의 작전 D-day10일 뒤이기 때문에 하루 종일 살펴봐도 이상한 징후는 없을지도 모른다. 총을 주머니 바깥으로 빼내서 겨눈다고 나쁜 사람일까? 아니 무언가 수상한 범죄자를 보고 총을 겨누고 있는 형사일수도 있다.

 

  마이너리티 리포트 영화 같은 대상의 악의성을 판단하는 이런 판단의 문제는 백신 업체를 무척 머리 아프게 만드는 측면일 것이다. 그래서 백신이 멀쩡히 깔려 있는 컴퓨터에서 새로운 랜섬웨어가 걸리는 것 같은 이해할 수 없는 일들도 일어 날테고 말이다. 위의 테러범을 찾는 문제로 간다면, 검출이 안되는 플라스틱 총이나, 케익 상자로 위장 할 수도 있고, 경찰이 테러범과 비슷한 형태로 행동할 수도 있는 상식적인 선을 넘는 여러 시도가 일어날 수 있기 때문이다. 심지어는 테러범을 수색하는 사람으로 위장한 사람이 나타날 수도 있다. 영화나 부패한 나라에서 일어나는 일이지만 아래와 같이 현실 상에서도 소프트웨어 세계에 비슷한 일이 발생할 수가 있다.

 

[PC PC 검색어 조작 해킹 심은 일당 검거 - 아이러브피씨방]

http://www.ilovepcbang.com/news/articleView.html?idxno=73794

 

[사설 보안 연구소장이 해킹.. 국내 PC방 절반 감염 - YTN]

https://www.ytn.co.kr/_ln/0115_201611142210438443

 

[허위 백신 - 나무위키]

https://namu.wiki/w/%ED%97%88%EC%9C%84%EB%B0%B1%EC%8B%A0

 

 

 

 

3. 백신 프로그램이 할 수 있는 전략을 상상해 보기

  최신의 백신 프로그램이 어떤 무기와 전략을 가지고 돌아가는 지는 해당 회사에 있지 않은 이상은 모를테지만, 책이나 기사에서 본 것을 토대로 상식적으로 접근해 볼 순 있을 것이다. 먼저 정적 분석이라고 하는 분야가 있다. 상식적으로 이해하기 위해 위의 테러범을 찾는 문제의 과점에서 생각하면 이해가 좀더 쉬울 것이다.

 

  먼저 의심이 가는 사람의 외모적 특징을 본다. 그 담에 지문이나 신분증을 확인하며 이상한 이력이 없는지 전산을 조회해 본다. 이후 소지품 검사를 해서 총이나 수상한 물품을 소지했는지 체크하고, 총기 허가증을 체크한다. 상황에 따라 일반적으로 차별이 금지되어 있지만 테러 의심자를 조사하는 특수한 경우에만 허용된 여러 편향적인 부분 또한 체크할 수도 있을 것 같다(국적, 종교, 지인, 직업 등).

 

  그 다음은 동적 분석인데, 해당 부분으로 통과된 사람이라도 뭔가 의도적으로 정상적으로 위장한 사람일 수도 있기 때문에 여러 동적인 행동도 체크해 본다. 위험하다고 분류된 특정한 행동을 한다든지, 불안한 패턴을 보인다든지, 특정한 사람과 연락이나 대화를 한다든지 하는 부분 말이다. 데이터 분석 및 머신러닝 등을 이용해 해당 패턴들을 과거의 테러범들의 데이터에 비교하거나 특이한 부분을 찾아 수상한 패턴을 찾을 수도 있을 것이다.

 

  비슷하게 백신 프로그램도 실행이 가능한 파일들에 대해서 같은 조사를 할 수 있다. 파일내의 특정 이미지나 텍스트를 검사하거나(외모), 파일의 해시 값(지문, 주민번호)을 알려진 악성코드의 해시 값과 비교하거나, 특정 바이트의 특징(신체적, 사회적 특징)을 찾거나, 프로그램 코드를 따라가며 위험한 행동을 하는 코드를 찾거나 할 수 있다. 조금 더 나아가 사용하는 라이브러리나 패커 등의 패턴 등을 기존에 구축된 악성코드 데이터베이스를 기반으로 비교해 수도 있을 것이다.

 

  나아가 샌드박스 등으로 제한된 환경에서 프로그램을 실행 시켜 이상한 행동을 하는지 지켜보거나, 수상한 외부 사이트와 연결해 데이터를 주고 받거나 하는지도 체크할 수 있을 것이다.

 

 

 

 

4. 프로그램 행동 분석의 명암

  이상적으로는 위의 액션들을 통해 위험한 것을 전부 발견할 수 있겠다고 하고 싶겠지만, 여기에는 몇 가지 제약들이 얹어지게 된다. 분석을 방해하는 암호화된 팩커들이 존재하고(이건 뭐 실행 시점엔 원래대로 돌아오니 본질적으론 괜찮다고 싶다하고), 분석시간에 제한도 생긴다(백신 파일이 내가 실행하려는 파일을 한없이 잡고 있는 걸 바라는 사용자는 없다), 감시 당한 다는 것을 알고 감시 안 당할 때 행동을 시작하려는 악성코드도 있을 수 있고, 특정 조건 하에서만 특정한 코드를 실행하는 경우도 많을 것이다. 앞의 피씨방 프로그램처럼 대부분의 기능은 정상적인 프로그램이고 그 안에 숨어 못된 행동을 할 수도 있다.

 

  결국 위의 정적인 분석, (세미) 동적인 분석, 디버거, 디스어셈블리 툴 같은 것을 이용한 파일 분석 등이 필요할 텐데 디버거나 디스어셈블리 툴을 통한 분석은 가장 해당 프로그램의 진실에 가깝게 접근하겠지만 아무리 자동화로 구축을 하더라도 분석 하는 사람의 경험&재능 및 어느 정도의 인력, 시간 등의 제약요소를 가진 노동 집약적인 특징을 피할 수는 없을 것 같다. 머신러닝 등의 데이터를 기반한 분석을 하더라도, 수많은 악성코드 데이터를 모두 모으고 신규 생성되는 코드 또한 실시간으로 수집해 데이터베이스를 재 구축하는 부분도 역시 쉽지 않은 도전이 될 것 같고, 그렇게 하더라도 앞으로 일어날 새로운 패턴의 공격을 막을 수 있을지는 확실히 자신하긴 힘들 것 같고 말이다. 그래서 아무래도 방어하는 쪽 보다는 공격하는 쪽이 맘이 많이 편하긴 할 듯 싶다.

 

 

 

 

5. 동적 분석의 예 및 회피 시도 해보기

  파일내의 문자열이 패턴 분석 같은 정적 분석에 대한 예제 보다는, 동적 분석의 예를 보이면 좋을 것 같아서 처음엔 Cuckoo Sandbox 같은 오픈소스 샌드박스를 구축해 파일을 실행해 볼까 했지만, 찾아보니 원하는 결과를 얻기까지 가야 될 길이 꽤 복잡해 보이고, Virus Total 사이트를 보다 보니 기본적인 동적 분석을 제공 해주는 것 같아서 해당 사이트를 이용해 가볍게 시연을 해보려고 한다.

 

  시연에 사용할 악성 파일 샘플의 경우도 굳이 백신을 잠시 꺼야 할지도 모르는 실제 덜 위험한 악성코드나 자바나 C언어 등을 이용한 실행 파일을 만들기는 번거로울 듯 해서, 12교시 리버싱과 포렌식 편에서 만들었던 엑셀의 최근 열린 파일을 알아내기 위해 레지스트리 키를 읽어왔던 아래의 파이썬 샘플을 파이썬 인터프리터가 포함된 하나의 exe 파일로 만들어 해당 파일을 이용하도록 해본다 (실행 파일이 레지스트리를 읽는 동작도 동적 동작에 포함되기 때문에 샌드박스에서 찾아낼 것이다).

1
2
3
4
5
6
7
8
9
10
11
import winreg
# 키를 정의 한다.
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\16.0\\Excel\\File MRU")
 
# 키 안에 담김 value 숫자를 얻어 그 숫자 만큼 루프를 돈다.
for i in range(0, winreg.QueryInfoKey(hKey)[1]):
    name, value, type = winreg.EnumValue(hKey, i)
 
    # Item 이라는 문자열로 시작하는 이름일 때 프린트 한다.
    if name.startswith("Item"):
        print (name + ": " + value)
cs

[show_excel_recently.py]

 

  현재 진행을 하고 있는 버전이 python 3.6.4 버전이라는 것을 잊지 말고(다른 버전에서는 아래 방식이 지원이 안되면 다른 방법으로 exe 를 만들어야 할 수도 있다), 우선 pyinstaller 를 설치해 보자.

c:\Python\code\>pip install pyinstaller

 

  이후 13교시에 만든 show_excel_recently.py 파일을 아래 명령어를 통해서 하나의 exe 파일로 만들어 보자

c:\Python\code>pyinstaller --onefile -w show_excel_recently.py

 

  해당 명령어가 잘 돌아가게 되면, "c:\python\code\dist" 폴더에 “show_excel_recently.exe” 파일이 생성되어 있을 것이다(해당 파일을 실행한다고 커맨드 창에 결과를 뿌리진 않는다. 아마 그렇게 보여주려면 파이썬 파일 수정이 좀 필요할 듯 보이는데 금번 시연 과정에서 굳이 그럴 필요는 없는 것 같아서 그냥 안 되는채로 두려고 한다)

 

 

  이제 바이러스 토탈 사이트로 이동해 보자. “Choose File” 버튼을 클릭해 방금 전에 만든 exe 파일을 업로드해 보자. 분석이 시작되고 조금 후에 결과 화면이 나온다.

https://www.virustotal.com/gui/home

 

  기본적인 정적 분석 결과가 나오고, 판단 이유는 모르겠지만, 69개 중 4개의 바이러스 엔진이 이 파일이 좀 위험한 거 같다고 얘기했다고 한다. “BEHAVIOR” 탭을 클릭해 본다(처음 올렸다면 분석 후 결과가 표시되는데 조금 시간이 걸릴 수도 있다)

 

  그럼 해당 exe 프로그램이 생성 수정한 파일들 이라든지, 레지스트리 키, 프로세스, 사용한 DLL, 특이한 액션 등 여러 정보 들이 표시되게 된다. 이 파일이 뭔가 특별한 일은 하는게 아니고 레지스트리만 읽어 왔기 때문에, “Registry Actions” 파트 쪽을 보자. 그럼 두 번째 줄에 우리가 코드를 통해 접근한 엑셀 키가(HKCU\…\Excel\File MRU) 보이게 된다.

 

 

 그럼 앞의 테러범 얘기로 돌아가서 한번 더 역으로 악성코드 입장에서 생각해 보자. 내가 동적 검사를 당했을 때, 거기에 검문당하지 않고 순진한 파일로 인식되려면 어떻게 해야 할까? 가장 좋은 방법은 지금 감시당하고 있다는 것을 인지하고 발톱을 숨기는 것일 테고, 아주 단순한 방법은 적당히 단속이 끝날 시간까지 몸조심하면서 아무 일도 하지 않는 것일 것이다.

 

  파일을 올린 후를 생각해 보면 우리가 동적 분석 결과를 얻기까지 그렇게 오랜 시간이 걸리지 않았고, 그렇게 오랫동안 관찰하는 방식은 검사하는 프로그램의 ROI 나 파일이 실행되기를 기다리는 사용자의 불편을 초래하기 때문에 힘들듯 싶으니, 우리가 원하는 레리스트리 키를 읽는 동작을 하기 전에 일정 시간 쉬었다 읽게 됨 동적 검사를 빠져나갈 수 있지 않을까 싶다.

 

  그럼 그 가설을 한번 체크해 보기 위해서 파이썬 코드를 조금 수정하여, 1분 동안 잠시 쉰 후(sleep), 동작을 하게 하도록 만들어 보자. show_excel_recently_sleep_1m.py 이라는 새 이름으로 저장해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import winreg
import time
 
# 10초를 쉰다.
time.sleep(60
 
# 키를 정의 한다.
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\16.0\\Excel\\File MRU")
 
# 키 안에 담김 value 숫자를 얻어 그 숫자 만큼 루프를 돈다.
for i in range(0, winreg.QueryInfoKey(hKey)[1]):
    name, value, type = winreg.EnumValue(hKey, i)
 
    # Item 이라는 문자열로 시작하는 이름일 때 프린트 한다.
    if name.startswith("Item"):
        print (name + ": " + value)
cs

[show_excel_recently_sleep_1m.py]

 

  이후 다시 exe 파일을 만들어 본다. show_excel_recently_sleep_1m.exe 라는 이전 파일과 하는 동작은 같지만 1분 정도 멈췄다 레지스트리 키를 읽는 파일을 만들어 본다.

c:\Python\code>pyinstaller --onefile -w show_excel_recently_sleep_1m.py

 

  같은 방식으로 바이러스 토탈 사이트에 해당 exe 파일을 업로드 후 결과를 보면, 우리가 기대했던 바와 같이 레지스트리의 엑셀 정보를 접근한다는 정보는 보이지 않는다. 이렇듯 검사하는 쪽은 시간에 쫒길테지만 공격하는 쪽은 1분후에 공격한다고 뭐 특별히 문제가 있지 않는 상황이 된다. 그래서 공격하는 쪽이 압도적으로 회피를 하기엔 유리하다고 본다.

 

 

 

6. 마무리 하면서

  생각하면 할수록 악성코드에 대한 전쟁은 방어하는 쪽이 압도적으로 불리한 영역 같다는 생각이 든다. 그래서 자동화 파트에서도 비슷한 얘기를 했었지만 이제는 작은 개인은 백신과 같은 지식 집약적 영역에서 빛을 발하기는 힘들다. 백신 프레임워크를 개발하는 것에 대한 실력의 문제가 아니라 앞서 얘기한 방대한 데이터들과, 악성 행동들에 대한 정의의 모호함, 순진한 프로그램과 유사하게 보이는 사회공학적, 현실적 측면(점유율 높은 PC방 프로그램 회사를 인수해 악성코드를 심으리라고 누가 쉽게 생각하겠는가?)을 추가해 점점 정교해지는 공격 패턴 등에 대한 집단 지성에 가까운 다양성 및 양적 범위를 커버하기 힘들 것 같기 때문이다. 어찌 보면 기술과 돈과 전략을 겸비한 거대한 단체들의 물량과 물량의 싸움으로도 보인다.

 

  악성코드에 대한 안전함은 현실적으로는 OS 개발 사의 취약점 패치와, 잘 알려진 공격 패턴들에 대해 빠르고 정확하게 방어하는 백신, OS 단에서 악성코드와 정상코드의 행동 구분을 원활하게 만드는 보안 설계 등에 의존할 수밖에 없는 것 같다. 그래서 OS 나 프로그램 패치를 열심히 하고, 백신 패턴을 최신으로 유지하며, 불법적인 무료 이익을 주는 프로그램이나 사이트를 이용하지 말고, 랜섬웨어 같은 단순하지만 강력한 무기들을 대비해 백업을 안전하게 해놓는 것이 개인으로선 최선인 것 같다(여러가지 예외 상황 때문에 백신만으로는 100% 못 막을 것이라는 것을 가정하는 게 더 현명한 방어 전략일 것 같다). 방어를 하는 입장에서의 여러 기술들의 최적화와 자동화에 대해서는 우리가 모르는 각 보안 회사들만의 여러 노하우들이 많이 있을 것 같고 말이다.

 

  소극적인 편법의 측면으로는 공격자가 가치를 모르게 데이터를 잘 위장해 놓는 것도 하나의 방법이 될 것 같기도 하다. 공격자 입장에서는 은폐와, ROI를 위해 최소한의 타겟팅 공격을 하는 편이고, 한편으로는 어차피 속고 속이는 싸움 같아 보이기 때문이기 말이다. 방어자 또한 꼭 정직하게 행동해야 할 필요는 없어 보인다. 뭐 하지만 위장 방법의 깊이가 깊지 않다면, 또는 위장이라는 것은 zip 파일의 복잡한 패스워드처럼 (현실적으로) 완벽한 노출에 대한 대응 수단은 아니기 때문에, 수법이 드러나는 순간 당한지도 모르고 당하게 되는 리스크가 있기 때문에 기본적인 정책에 추가해 상대를 어렵게 만드는 보조적인 수단으로만 생각해야 할 듯도 싶다. 원숭이도 나무에서 떨어지는 법 이니까 말이다. 자기확신만큼 무서운 건 없다.

 

 

  또한 악성 코드에 대한 자동이나 수동 판단에 대한 모호성 문제는 사실 보안의 많은 분야에서 마찬가지로 발생한다. 그래서 이상적인 관점만큼 100% 안전한 것은 보안 분야에 걸쳐 사실상 드문거 같다. 그래서 이쪽 분야에 대해 많이 알게 될수록 그 한계 또한 알게 되며, 겸손하게 되는게 일반적인 성장 패턴일것 같다.

 

 

 

2020.5.10 by 자유로운설탕
cs

 

 

 

 

 

 

posted by 자유로운설탕
2020. 5. 4. 19:29 Japan Pop

  중국어를 공부하다 일본어 학원을 다시 잠시 다녔을 때 만난 고등학생 애한테 요즘 듣는 가수들 추천을 해달랬더니, 정리해준 가수들이 모두 락 쪽이였다. 락은 나한테 조금 힘든데 하는 맘은 있었지만, 정리해 준 것에 대한 고마움과 예의 때문에 유투브를 통해 하나하나 가수를 찾아 들어봤다(요즘은 사람들이 좋아하는 취향들에 대해서는 내가 이해를 못하더라도 나름 시간을 들여 이해할수 있다면 그들이 좋아하게 되는 특별한 매력이 있겠지 하는 생각이 든다). 그 중 조금 맘에 드는 노래가 있어서 마침 코인 노래방에도 있고 해서 도전. 막상 불러보니 들을 때 잔잔한 느낌과는 다르게, 후렴에서 뭔가 가볍게 지르는 느낌에 부르는 재미도 있었다. 그래서 곡이 영화의 OST 인것 같아 좀 더 노래의 느낌을 이해하고 싶어서 영화를 찾아 보게 됬다.

 

ぼくは明日、昨日のきみとデートする(나는 내일 어제의 너와 만난다)

https://namu.wiki/w/%EB%82%98%EB%8A%94%20%EB%82%B4%EC%9D%BC%2C%20%EC%96%B4%EC%A0%9C%EC%9D%98%20%EB%84%88%EC%99%80%20%EB%A7%8C%EB%82%9C%EB%8B%A4(%EC%98%81%ED%99%94)

 

  영화는 라이트 노벨을 원작으로 한 거라 해서 그닥 거슬리진 않는 약간의 판타지가 섞여 있다. 다만 조금 일본 드라마, 영화 특유의 미약한 사람의 손이 닿지 않는 커다란 운명의 열려있는 결말로 끝나서, 보고나면 마음 한쪽이 시큰거리는 허무함이 있다. 아마 왠지 이 노래는 영화를 보고 만든건 아니였을 것도 같은데, 가사랑 영화의 내용이 은근 잘 매치가 된다. 다만 영화쪽이 좀 더 깊고 섬세한 감정이라 노래가 좋다면 영화도 봐보는 것을 추천 한다. 아는 지인은 반대로 영화를 보고 엔딩 부분에 나오는 이 노래가 좋아 찾아봤다는 경우도 있다. 코인 노래방을 좋아하는 사람이라면 코로나 끝나면 꼭 가서 도전해 보기를(요즘 지역 경제엔 미안하지만 주변의 안전을 위해 코인 노래방을 몇달째 못 가서 금단 현상이 가득하다)...

 

  중국 노래는 성조 때문에 노래로 언어를 배우는 것에 찬반이 있는 것 같다. 하지만 개인적으로는 아무거라도 해당 언어의 교과서 공부 틀을 벗어나는 경험을 하는 것은 나쁘진 않은거 같다. 노래 안에서도 성조가 들린다는 것은 아직 이해가 잘 안가지만 말이다. 경험상 노래엔 필요없어 보여서 성조를 같이 안외고 발음만 외어버리면 나중에 엉터리로 각인되버린 성조들을 다시 교정하느라 돌 엄청 맞게되니 모르는 한자가 많이 나오고 노래속에선 성조를 표현을 못하더라도 성조를 같이 외자. 다른 한편으로는 회화는 형편없는 편이라 해당 가설이 맞나 싶기도 하다.

 

  하지만 일본어의 경우는 노래를 보다보면 동사의 변형, 원형 등도 반복해 유추하게 되고 교과서엔 안 나오는 단어나 어휘도 많이 보게되서 장점이 더 많은 듯 싶다. 또 빠른 노래를 연습 하다보면 한자 읽기에 대한 순발력이 길러지지 않나도 싶다. 노래할 때 도움을 주는 히라가나로 된 한자 발음이 오히려 귀찮게 눈에 자꾸 거슬리게 된다면 나름 성공이다. 중국 노래와 마찬가지로 나름 해석 해보고 애매하거나 자신 없는 부분은 지인한테 물어서 보충했다. 다른 일도 마찬가지 겠지만 언어를 공부할 때 편하게 물어볼 수 있고, 귀찮아 하지 않고 알려주고 싶어하는 사람이 있는 건 정말 좋은일 같다.

 

 

 

さよならが喉の つっかえてしまって

안녕이라는 말이 목 안에 걸려 버리고,

咳をするみたいに ありがとうって言ったの

기침을 하는 것처럼 고맙다고 말했어

次の言葉はどこかと ポケットを探しても

다음엔 어떤 말을 할까 하며 주머니를 뒤져봐도

見つかるのはあなたを好きな私だけ

찾을 수 있는 건 너를 좋아하는 나 자신 밖에

 

よ大丈夫だよ

아무렇지 않아, 괜찮아

優しくなれたと思って 願いにわって最後はになって

상냥하게 되었다고 생각하고, 소원으로 변해, 마지막엔 거짓말이 되어

(뭔가 이별을 예고되 맘이 아프지만 상대방이 알아채지 못하게 친절하고, 다정하게 대하려고 노력했지만 결국엔 모든 게 현실의 무게에 치여 거짓말이 되어버렸다 정도로 이해하면 어떨까 싶다)

いまま枯れてゆく あなたを好きなままで消えてゆく
푸르른 채로 시들어 가, 당신을 좋아하는 채로 사라져 가

(이게 정말 푸르다는 건지 아직 다 익지 못한 설익은 단계라는 건지는 알송달송)

私みたいと手に取って

나 처럼 손에 쥐고

にあった想いと一に握り潰したの

안에 있던 마음과 함께 쥐어 부셔 버렸어
(위의 두 줄은 어떤 이미지를 표현하는 가사인지 잘 모르겠음)

大丈夫 大丈夫

괜찮아 괜찮아

今すぐに抱きしめて

지금 바로 껴안고 싶은데

私がいれば何もいらないと それだけ言ってキスをして

내가 있으면 아무 것도 필요 없다고 그렇게 말하며 키스를 해줘

なんてね だよ ごめんね

농담이야 거짓말이거든 미안해

 

こんな時思い出す事じゃ ないとは思うんだけど

이런 때에 생각이 날 일은 아니라고 생각하지만

一人にしないよって あれは嬉しかったよ

혼자 있지 않아도 되서 그거 사실은 기뻤어

(しないよって 해석이 애매함)

あなたが勇を出して 初めて電話をくれた

당신이 용기를 내서 처음으로 전화해 줬던

あの夜の私と何が違うんだろう

그 밤의 나와는 무엇이 다른 것  일까?

どれだけ離れていても  どんなにえなくても

얼마나 멀리 떨어져 있더라도, 아무리 못 만나게 되더라도

持ちがわらないから ここにいるのに

마음이 변하지 않기 때문에 여기에 있는 거니까

いまま枯れてゆく あなたを好きなままで消えてゆく

푸르른 채로 시들어 가, 당신을 좋아하는 채로 사라져 가

私をずっとえていて

나를 계속 기억해 줘

なんてね だよでいてね ああ

농담이야 거짓말이거든 건강하게 잘 지내줘

ララララ ララララ

ララララ ララララ

 

泣かない私に少し ほっとした顔のあなた

울지 않는 나에게 조금은 안심한 얼굴의 너

わらず暢 そこも大好きよ

여전히 느긋한 거 같아 그 것도 너무 좋아

が付けばにいて 別に君のままでいいのになんて

맘에 걸린 다면 옆에 있어줘, 그냥 당신 그대로 괜찮으니까

勝手に 拭いたくせに

제멋대로 눈물을 훔쳤던 주제에

見える全部こえる全て 色付けたくせに

보이는 것 전부, 들리는 것 전부 색칠해 버린 주제에

(아마 실제와는 다르게 상대방의 맘이 편하게 괴롭지 않은 척 연극했다는 것 같음)

いまま枯れてゆく あなたを好きなままで消えてゆく
푸르른 채로 시들어 가, 당신을 좋아하는 채로 사라져 가

私みたいと手に取って

나 처럼 손에 쥐고

にあった想いと一に握り潰したの

안에 있던 마음과 함께 쥐어 부셔 버렸어

大丈夫 大丈夫

괜찮아 괜찮아

今すぐに抱きしめて

지금 바로 안고 싶은데

私がいれば何もいらないと そう言ってもう離さないで

내가 있으면 아무 것도 필요 없다고 그렇게 말하며 더 이상 떠나지 말아줘

なんてね だよ さよなら

농담이야 거짓말이거든 그럼 안녕

 

 

 

 

[서비스] 한자 발음 및 원형

(のど), (おく), (せき), 言う(いう)

(つぎ), 言葉(ことば), 探す(さがす), 見つかる(みつかる), 好き(すき), (わたし)

 

(へいき), 大丈夫(だいじょうぶ)

優しい(やさしい), 思う(おもう), 願い(ねがい), わる(かわる), 最後(さいご), (うそ)

(あおい), 枯れる(かれる), 消える(きえる), (), 取る(とる)

想い(おもい), (いっしょ), 握り潰す(にぎりつぶす)

 

(いま), 抱きしめる(だきしめる), (なに), (うそ)

 

(とき), 思い出す(おもいだす), (こと), 一人(ひとり), (じつ), 嬉しい(うれしい)

(ゆうき), 初め(はじめ), 電話(でんわ), (よる), 違う(ちがう)

 

離れる(はなれる). (あう), (きもち), える(おぼえる), (げんき)

 

泣く(なく), 少し(すこし), (かお), わらず(あいかわらず), (のんき), 大好き(だいすき)

(よこ), 別に(別に), (きみ), 勝手(かって), (なみだ) 拭く(ふく)

全部(ぜんぶ), (きく), 全て(すべて), 色付ける(いろづける), 離す(はなす)

 

posted by 자유로운설탕
2020. 4. 18. 23:34 China Pop

  역사적인 관계가 얽혀있는 다른 나라의 언어를 배운다는 것은 종종 묘한 부분이 있다. 특히 두 나라 사이의 감정이 격해 질때면, 관련된 언어를 공부하고, 해당 언어로 만들어진 여러 문화적인 부분을 경험하고 노출하는 것 조차 괜히 눈치가 보일때가 있다(개인적으로는 코인 노래방에서 중국-일본 노래 부르기, 지하철에 앉아 해당 언어 펼치고 공부하기 등). 뭐 살아가면서 역사적 관계나 이익의 충돌이 전혀 없는 나라의 언어를 배우게 될 일이 있을지는 모르겠지만, 하나의 언어를 배우면서 그 나라 사람들을 만나게 되면 추상적으로 생각해 오던 해당 나라가 구체적인 모습으로 다가 오게 되는 측면도 있는 것 같다.

 

  이 노래는 강사분이 추천해 줬던 노래로 이미 여러 블로그에서 소개되어 있지만, 처음으로 스스로 번역해서(이상한 곳 체크를 받고) 올렸다는 데에 의미가 있을 듯 싶다^^. 워낙 노랫말처럼 가랑비에 옷 젖듯 천천히 공부해온 탓에 그렇긴 하지만, 노래 가사는 시와 비슷하게 생략된 문장 형태인 경우가 많고, 중국어 특유의 함축적이고 중의적인 한자의 느낌 때문에, 원래 쓴 사람의 의도를 잘 파악하기 어려운 때가 많은 듯 싶다. 뭐 그것은 이 글의 해석의 어딘가가 매끄럽지 않은데에 대한 변명이기도 하다--;

 

  개인적으로 중국은 고전적인 가치관이 여전히 사회의 많은 부분을 감싸고 있는 것으로 보인다. 현재의 시대를 살아감에 있어 고전적이라는 것은 구닥다리 느낌의 부정적인 측면도 있지만, 나아가는 것이 꼭 좋아지는 것만은 아니라는 측면에서는 긍정적인 부분도 동시에 존재하는 것같다. 물론 한 개인이 사회에서 둘 사이에 적당한 균형을 이루고 살긴 많이 어려운 것 같지만 말이다. 이 노래는 중국 사회의 고전적인 감정에 담긴 잔잔한 우아함을 한자에 담아 표현하고 있다고 생각한다. 참고로 한국 노래방 기계에는 없는 듯해서 많이 아쉽다^^~

 

 

 

 

书里总爱写到喜出望外的傍晚
Shūlǐ zǒng àixiědào xǐchūwàngwài de bàngwǎn

책속에는 항상 뜻밖의 기쁨을 주는 저녁 무렵의 풍경이 담겨 있죠.

 

骑的单车还有他和她的对谈
de dānchē háiyǒu tā hé tā de duìtán
자전거를 타거나 그와 그녀가 이야기를 나누는 모습

(책 속에 담긴 여러 사랑 이야기 들의 장면을 떠올리는 듯...)

 

女孩的白色衣裳男孩爱看她穿
hái de báishang nánhái àikàn tā chuān

하얀 옷을 입은 여자아이를 바라 보는 것을 좋아하는 남자아


好多桥段    好多都浪漫
hǎoduō qiáoduàn   hǎoduō dōu làngmàn
얼마나 많은 일들이,  얼마나 많은 낭만이

(桥段 은 보통 영화 연극 등의 scene 같은 것을 말하는 듯. 특정한 갈등이 있는 상황이나 계기 등? 적당한 표현할 단어가 생각 안나 함축적으로? "일"이라고 해석. 연인들 끼리의 친해지는 과정이나, 다툼에 대한 에피소드라고 보면 어떨까 싶다)

 

好多人心酸    好聚好散
hǎoduō rén xīnsuān    hǎo jù hǎo sàn
얼마나 많은 사람이 가슴이 아프고, 모였다 흩어졌을까요

 

好多天都看不完
hǎoduō tiān dū kànwán

여러 날이 지나도 다 못볼 만큼 많죠

(위와 같은 많은 감정의 편린들이 있어서 여러 날을 봐도 못 볼만큼 많다는 의미)

 

 

 

 

刚才吻了你一下你也喜欢对吗
gāngcái wěn le nǐ yīxià nǐ yě xǐhuān duì ma
방금 내가 입맞췄을 때 당신도 좋았던 맞죠? 

 

不然怎么一直牵我的手不放
rán zěnme yīzhí qiān wǒ de shǒu bù fàng

그렇지 않다면 내손을 왜 줄곧 잡고 있겠어요.

 

你说你好想带我回去你的家乡
nǐ shuō nǐ hǎoxiǎng dài wǒ huíqù nǐ de jiāxiāng
당신은 나를 고향으로 데리고 가고 싶다 말했죠

 

绿瓦红砖    柳树和青苔

wǎ hóng zhuān    liǔshù hé qīng tái
녹색기와와 붉은 벽돌, 버드나무와 푸른 이끼

 

过去和现在    都一个样
guòqù hé xiànzài    dōu yī gè yàng
과거와 현재, 모두 한결같죠 

 

你说你也会这样
nǐ shuō nǐ yě huì zhè yàng

당신은 당신도 그렇게 변하지 않을거라고 말했죠

 

 

 

 

慢慢喜欢你    慢慢的亲密
mànman xǐhuān nǐ    mànman de qīnmì
천천히 당신을 좋아하고, 천천히 가까워지고

 

慢慢聊自己    慢慢和你走在一起
mànman liáo zìjǐ    mànman hé nǐ zǒu zài yīqǐ
천천히 자신에 대해 얘기하고,  천천히 당신과 걸어가기

 

慢慢我想配合你    慢慢把我给你
mànman wǒ xiǎng pèihé nǐ   mànman bǎ wǒ gěi nǐ

천천히 당신과 어울리고, 천천히 당신에게 나를 주고 싶죠

 

慢慢喜欢你    慢慢的回忆
mànman xǐhuān nǐ    mànman de huíyì
천천히 당신을 좋아하고, 천천히 추억하고

 

慢慢的陪你慢慢的老去
mànman de péi nǐ mànman de lǎoqù

천천히 당신과 함께 있고, 천천히 나이를 먹어가기

 

因为慢慢是个最好的原因

yīnwèi mànman shì gè zuì hǎo de yuányīn

왜냐하면 느리다는 건 아주 좋은 이유 중 하나기 때문이죠

(原因 은 "원인"이라는 뜻이긴 하지만, "느리게" 무언가를 쌓아가는게 무언가 하나의 두 사람 사이의 완전한 사랑을 만들게 된다는 의미로 생각하면 어떨까 싶다. 같은 완전함 이래도 불 같은 사랑과 반대라고 할까? 그래서 사랑이 이루어지게 되는 "이유" 중 하나라고 옮김)  

 

 

 

晚餐后的甜点就点你喜欢的吧
wǎncān hòu de tiándiǎn jiù diǎn nǐ xǐhuān de ba
저녁 식사 후의 디저트는 당신이 좋아하는 걸로 주문해요

 

今晚就换你去床的右边睡吧
jīnwǎn jiù huàn nǐ qùchuáng de yòubiān shuì ba

오늘 밤엔 당신이 오른 쪽에서 잠을 자요

(아마 침대 오른쪽이 벽이고, 평소에는 바깥 쪽에서 남편이 자다가 뒤에 나오 듯 내일은 여행을 가는 일에 설레는 부인이 먼저 일어나서 준비하며 기다리기 위해서 바깥 쪽인 왼쪽에서 자겠다는 의미로 추측) 


这次旅行我还想去上次的沙滩
zhècì lǚxíng wǒ hái xiǎngqù shàngcì de shātān
이번 여행 때 지난번 갔던 그 해변에 다시 가보고 싶어요.

 

球鞋手表    袜子和衬衫都已经烫好    放行李箱
qiúxié shǒubiǎo    wàzi hé chènshān dōu yǐjīng tànghǎo    fàng xínglǐxiāng
운동화와 시계, 이미 다려놓은 양말과 셔츠는, 트렁크에 넣어놨어요.

早上等着你起床
zǎo shang děng zhe nǐ qǐ chuáng

아침에 당신이 일어나기만 기다리고 있어요.

 

 

 

[이 후는 반복]

慢慢喜欢你    慢慢的亲密
mànman xǐhuān nǐ    mànman de qīnmì
천천히 당신을 좋아하고, 천천히 가까워지고

 

慢慢聊自己    慢慢和你走在一起
mànman liáo zìjǐ    mànman hé nǐ zǒu zài yīqǐ
천천히 자신에 대해 얘기하고,  천천히 당신과 걸어가기

 

慢慢我想配合你    慢慢把我给你
mànman wǒ xiǎng pèihé nǐ   mànman bǎ wǒ gěi nǐ

천천히 당신과 어울리고, 천천히 당신에게 나를 주고 싶죠

 

慢慢喜欢你    慢慢的回忆
mànman xǐhuān nǐ    mànman de huíyì
천천히 당신을 좋아하고, 천천히 추억하고

 

慢慢的陪你慢慢的老去
mànman de péi nǐ mànman de lǎoqù

천천히 당신과 함께 있고, 천천히 나이를 먹어가기

 

因为慢慢是个最好的原因
yīnwèi mànman shì gè zuì hǎo de yuányīn

왜냐하면 느리다는 건 아주 좋은 이유 중 하나기 때문이죠

 

 

书里总爱写到喜出望外的傍晚
shū lǐ zǒng ài xiě dào xǐ chū wàng wài de bàng wǎn

책속에는 항상 뜻밖의 기쁨을 주는 저녁 무렵의 풍경이 담겨 있죠.

 

 

posted by 자유로운설탕
2019. 10. 6. 18:05 보안

  이번 시간에는 모니터링 이라는 주제에 대해서 얘기해 보려 한다. 사실 모니터링은 보안 뿐만 아니라 모든 IT 영역에서 관심을 가지는 부분이기도 하지만, 간단한 예제와 함께 보안에서의 모니터링이란 무엇일까에 대해서 가볍게 생각해 보려 한다.



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터


 


 


1. 들어가면서

  흔히 물리적 보안을 소프트웨어 적인 보안 분야보다 명확하고 쉬운 분야로 취급하는 경향이 많은 것 같긴하지만, 두 개의 분야는 무척 유사하고 구분 하긴 힘든 연결 고리를 가지고 있다고 생각한다. 예로서 특정한 가게를 도난에 안전하게 지키려고 노력한다고 생각해 보자. 가장 기본적으로는 CCTV, 동작 감지기 등의 가게 주변의 환경의 변화를 모니터링 하고 알려주는 센서들을 설치 할텐데 왜 그러한 센서들을 설치하려 하는 걸까? 결국은 특정한 상황을 알려주는 데이터를 얻기 위해서 라고 볼수 있을 것이다.

 

  만약 CCTV 가 적절하지 않은 장소에 설치된다면 어떤 일이 벌어지게 될까? 잘못되거나 의미 없는 데이터를 가지고 판단(또는 모니터링) 하는 결과를 가지게 될 것 이다. 또한 현실적으로 사람이 잠시도 빼먹지 않고(또는 지치지 않고) 모니터링 하는 모든 데이터들을 100% 보고 있을 순 없기 때문에 여러가지 데이터에 여러 소프트웨어 적인 요소들을 적용하여 이상 현상을 찾으려고 한다. CCTV 를 예로 들면 화면의 변화, 화면안의 객체의 움직임, 해당 움직임의 의미 등을 프로그래밍(나아가 데이터의 통계나 구조적인 의미를 알려주는 머신러닝)을 통해 해석해서, 침해가 일어난 다고 의심되는 특정 이벤트 만을 알람으로 발생하게 하여 효율적인 모니터링을 하려고 한다. 이러한 관점을 생각하게 되면 물리적 모니터링은 소프트웨어 적인 모니터링과 문제의 성격으로 보아 별 차이는 없어 보이게 된다.

 

  또한 이러한 데이터 부분은 현실적인 행동이 있어야만 의미가 있을 수도 있다. 경험상 가게 자체적인 CCTV 의 구축이 효율이 적은 이유 중 하나는 누군가 계속 제대로 모니터링을 해야하며, 사건이 발생했을 때 조치할 방법이나 즉각적으로 행동할 사람이 없다면 효용성이 많이 떨어지기 때문이다(몇일 치의 CCTV 를 뒤지면서 원하는 장면을 찾아본 경험이 있다면 사후 조치란 의미로 원인을 찾기 것이 얼마나 피곤하게 만드는 일인지 알수 있을 것이다). 이러한 부분이 보호해야할 자산을 가진 사람들이 중앙 집중적인 관제 및 조치하는 사람이 있는 서비스를 제공하고 있는 **원 같은 보안 서비스 들을 사용하는 이유라고 생각된다. 해당 서비스에서 이상적으로 영상 및 이벤트 데이터 들은 실시간으로 원격에 저장되어 안전하게 보존, 백업 되며, 각 이벤트 들은 관제사 및 프로그램들에 의해서 체크 되며, 문제가 있을 시 물리적 조치를 행사할 수 있는 인원들이 움직이기 때문일 것 같다. 물론 실제로는 상징적인 효과일 수도 있고 비용이나 프라이버시 문제가 있을 수도 있을 것이다.

 

 

 

 

2. 결국 중요한 것은 데이터

  이 글의 맨 처음에서 보안의 주요한 부분중 하나는 데이터의 흐름을 따라다니는 일 이라고 얘기 했었는데, 모니터링도 크게 그 범위를 벗어나지는 않다고 본다. 앞의 리버싱과 포렌식 글에서 컴퓨터는 0과 1로 이루어진 세계로 얘기 했는데, 점점 시간이 지날 수록 현실의 많은 데이터는 이 0과 1로 이루어진 형태로 등가적으로 변형되어(디지털 화) 컴퓨터 안에 들어가고 있다.

 

 

  CCTV 의 영상도, 다른 여러 센서의 데이터도, 사람들의 행동들도 모두 디지털화 됨으로서, 어떻게 보면 현실의 많은 부분들이 이젠 컴퓨터내의 문제로 등가적으로 치환되었다고 볼수 있을 듯하다. 그렇게 컴퓨터 내의 데이터 문제가 되어, 그 동안 사람들이 고안해낸 여러 컴퓨터 내의 데이터 문제를 해결하는 기법들을 사용할 수 있게 됬지만, 그렇게 됨으로서 몇 가지의 새로운 차원의 문제도 발생하게 되었다고 본다.

 

 

  첫째로 정확하게 현실의 특징을 충분히 반영하여 디지털로 변환 되었냐는 문제가 있다. 예를 들어 예전의 해상도가 낮았던 시절의 CCTV는 나중에 해당 데이터를 가지고 100% 정확한 판단을 하기 힘든 문제가 있었다. CCTV 위치가 잘못 되어 태양 빛이 너무 밝게 들어와 영상을 제대로 인지 못했을 수도 있고, 센서가 고장날 수도 있으며, 센서의 설계가 처음부터 적합치 않았을 수도 있다.

 

  이건 소프트웨어 보안 쪽에서도 마찬가지 라고 보는데, 우리는 모든 데이터가 로그나 데이터베이스의 형태로 쌓였다고 생각하지만, 실제 그 데이터를 일으키게 한 대상은 컴퓨터 바깥 세상의 존재일 가능성이 높다. 그럼 그 데이터를 만들어낸 대상이 가진 특징을 올 해당 데이터를 기준으로 올바르게 판단 가능할 정도로 정확히 가지고 있는가를 우선적으로 따져봐야 한다. 영화에서 나오는 CCTV 나 센서를 보고 있는 감시 요원들이 주인공을 놓치게 되는 이유는 이 판단을 위한 원천 데이터 자체가 왜곡되는 경우라고 볼수 있을 것 같다. 

 

  혹은 아예 처음 부터 소프트웨어에서 자체에서 생겨난 데이터일 수도 있겠지만 그 경우도 우리가 최종 모니터링에 사용할 기반 데이터를 제대로 만들어낸 것인 지에 대해서 항상 여러 측면에서 고민을 해봐야 하는것 같다. 그래서 데이터를 만들어낸 도메인을 제대로 이해한 상태에서, 데이터의 수집부터 판단에 필요한 데이터를 만들어 내고, 위의 소프트웨어 적인 해결 도구들을 적절히 사용했는지를 잘 검토해야 하는 것 같다.

 

 

  둘째로 첫번째와 비슷하지만 우리가 현재 데이터라고 믿고있는 기준이 실제 우리가 원하는 현상을 모니터링 하기에 충분하냐는 문제가 있다. 세상이란게 실제로 많은 부분 근사와 추정으로 이루어져 있긴 한듯 하지만, 가능한 현재 모니터링 하고 싶은 부분에 대해 적절한 이벤트를 만들어 낼 수 있는 데이터를 수집하고 있는지는 체크해 봐야 한다.

 

  보안이나 QA가 좀 그런 면이 있긴 하지만, 시스템을 움직이는 데이터와 시스템을 모니터링 하는 데이터는 겹칠때도 많지만 별개일 수도 있다. 시스템의 여러 동작 간에 모니터링을 위한 데이터를 일부러 쌓아야 하는 경우도 있고, 뒤에서 천천히 집계하여 효율성 있게 모니터링 하기 위한 기반 데이터를 만들어야 하는 경우도 많아 보인다. 이러한 부분은 저장 비용, 퍼포먼스와도 밀접히 연관되어 있는 경우도 많으므로, 뻔한 얘기긴 하지만 새로운 시스템이 만들어지고 적절한 보안적인 모니터링이 필요하다면 설계 단계 부터 여러 측면에서 설계나 예산 측면에서 고려를 해야 하는 부분 같다. 일단 시스템이 본 궤도에서 돌아가게 되면 모니터링을 끼워넣기는 엄청 힘들어지는 것 같다.

 

 

  셋째로 과거의 데이터를 기반으로 만들어낸 규칙이 새로운 데이터에 얼마나 적합한지에 대한 부분이다. 머신러닝에서도 학습된 모델이 현재 얼마나 유효한 지에 대해 항상 고민을 하긴 하지만, 굳이 그렇게 복잡한 상황이 아니더라도 기본적인 모니터링 판단에서도 마찬가지다.

 

  만약 CCTV 를 열심히 설치해 놨는데 새로운 문이 생기면 어떻게 될까? 새로운 권한을 가진 사람들이 같이 근무하게 될수도 있을 것이다. 회사의 근무시간이 고정된 시간에서 자율 출퇴근 제로 바뀌어도 마찬가지 일것이다. 또한 재택근무가 된다면 등등... 현재의 고정된 판단을 가졌던 데이터의 규칙이 언제라도 특정 시점에 바뀔 수 있을 것이다. 기존 모니터링 시스템에서 받아들이 데이터 들의 그런 변화를 적절히 2차 모니터링 하여 데이터의 대상 및 룰에 대한 변화를 알려 줄수 있는 부분도 필요할 수 있다. 물론 그렇게 변할 가능성이 있는 데이터를 판단의 기준으로 삼지 않는 접근 방법도 있지만, 당장 효과가 있는 판단 기준들을 쉽게 빼는 것은 쉽지 않은 일이다. 애시당초 부터 적절한 판단을 할 수 없을 지도 모르고 말이다.

 

   여러 현실의 트랜드나 구조의 변화 때문에 생성되는 데이터의 성격이 충분히 변할 수 있기 때문에 이러한 변화를 모니터링 하는 부분도 역설적으로 모니터링을 구축하는데 또 다른 숙제가 되는 듯하다. 

 

 

  넷째로는 데이터가 조작에 얼마나 취약하느냐를 따져봐야 한다. 보안 쪽 모니터링을 하면서 데이터가 항상 주어진 그대로의 특성을 유지할 것이라고 믿는 것은 피해야할 시각이다. 앞에서 얘기한 인젝션 같은 문제로 기대하지 않은 데이터가 들어와 순진한 프로그램을 악용하는 것처럼, 순진한 모니터링 프로그램은 조작된 데이터를 그대로 놓쳐버릴 수 있다. 특히 외부의 움직임에 의해 생성되어 들어오는 데이터를 기반으로 모니터링을 할때는 항상 이런 부분을 더 신경써야 하는 것 같다. 

 

 

  다섯째로 만들어진 모니터링 프로그램은 대부분 명확한 답이 없이 "임계점"을 기준으로 Alert 을 띄우는 경우가 많으므로, 해당 임계점을 어떻게 조정하느냐도 민감한 주제가 될것 이다. 좀더 확실히 놓치지 않기위해 임계점을 낮추게 되면 수많은 Alert 의 늪에 빠지게 될것이고, 운영의 효율을 위해서 높이게 되면 삶의 질은 나아지겠지만 문제를 놓치게 될 가능성이 높아지기 때문이다. 

 

  마지막으로 결국은 모니터링은 사람의 문제로 귀결된다는 거다. 그건 모니터링 하는 입장이나 해당 처리를 하는 입장에 모두 해당 된다. 아무리 잘 만들어진 시스템이 많이 이벤트와 좋은 대시보드 화면을 보여준다고 해도 결국 최종적으로 오탐인지, 위험이 있는지 판단하는 부분은 사람일 수 밖에 없다. 또한 많은 부분에서 주어진 데이터에 대해 알고있는 도메인 지식을 기반으로 해석해야만 정상적인 판단을 할 수 있는 경우도 많다. 

 

  사람이란게 필연적으로 먹고 자는 존재이며, 기분에 따라 컨디션이 많이 달라질 수 있는 존재이기 때문에, 그러한 부분에 따른 판단 오차 및 대응 지연을 최소화 시킬 수 있는 시스템 및 모니터링 리소스의 배치도 마찬가지로 필요한 듯 싶다. 시스템 적인 입장만 생각하자면 최종 결과를 메일, SMS, 메신저 등의 연락처로 보내고 관련된 데이터와 그래프를 보여주면 된다고 생각할 수 있지만 그 메시지와 데이터를 보고 판단하는 사람들이 처한 환경을 간과 해서는 안된다. 물론 그러한 판단을 모니터링 하는 사람들이 힘을 덜 들이면서도 정확하고, 오차없게 하게 하는 여러 노력들은 필요하겠지만 말이다.

 

  때로는 100% 자동으로 이루어지는 선처리도 있긴 할테지만(예를 들어 과열시의 차단), 어쨋거나 해당 원인을 밝혀 개선하거나, 적절한 차단인지 되돌아 검토하거나, 룰을 수정하는 등의 일은 사람의 판단이 결국 필요하게 된다. 그래서 모니터링은 어찌보면 데이터의 생성 방식부터 최종 판단 까지 전체를 물리적, 디지털 적으로 잘 케어해야 하는 종합적인 분야같다.  

 

 

 

 

3. 간단한 모니터링 예제 보기

  어떤 예제를 보여줄까 고민하다가, 결국은 데이터의 한 측면을 보고 판단을 하는 예제를 간단히 보여주는 것으로 결정했다.

 

  우선 모니터링 대상은 파이썬으로 웹페이지를 하나 만들며 해당 페이지는 호출 할때마다 1부터 하나씩 숫자가 증가하면서 해당 숫자를 <td> 태그 안에 넣어 보여준다. 다음은 모니터링을 하는 프로그램인데, 해당 페이지를 1초 마다 한번 호출 하면서 5가 발견되면 SQLite 디비에 해당 결과를 저장 후, 화면에 읽어 표시한다.

 

 

3.1 모니터링 대상 페이지 만들기

  우선 숫자를 표시해 주는 flask 웹 페이지를 보자. 내용을 보면 global 변수를 이용하여 계속 0부터 1을 증가 시키면서 monitoring.html 을 랜더링 하면서 해당 값을 웹페이지에 표시해준다(코드가 이해 안가는 경우는 파이썬 플라스크 편을 보고 오자).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
from flask import Flask, make_response, render_template
 
# flask 웹서버를 실행 합니다.
app = Flask(__name__)
 
num_count = 1
 
@app.route("/monitoring", methods=['GET'])
def xss():
    global num_count 
    count_string = str(num_count)
    num_count = num_count + 1
    return render_template('monitoring.html', count = count_string)
 
# 이 웹서버는 127.0.0.1 주소를 가지면 포트 5000번에 동작하며, 에러를 자세히 표시합니다 
if __name__ == "__main__":
    app.run(host='127.0.0.1',port=5000,debug=True)
cs

 

   해당 파일을 c:\python\code 폴더에 "flask_monitoring.py" 이름으로 "UTF-8" 포맷으로 저장한다.

 

 

  다음은 해당되는 템플릿 파일을 보자. 정말 간단하게 <td> 태그 하나만 하나 있다. class 이름으로 check_point 를 지정한 이유는 나중에 beautifulsoup 라이브러리로 가져올때 td 태그가 여러개 있을 때 기준을 가지고 쉽게 가져오기 위해서이다.

1
<td class="check_point">{{ count|safe }}</td>
cs

 

   c:\python\code\templates 폴더에 "monitoring.html" 이름으로 "UTF-8" 포맷으로 저장한다.

 

 

  이제 커맨드 창에서 실행 시켜 페이지가 잘 동작하나 본다.

c:\Python\code>python flask_monitoring.py
....
 * Running on
http://127.0.0.1:5000/ (Press CTRL+C to quit)

 

 

  브라우저로 웹페이지를 띄워 리프레시를 해보면 호출할 때마다 1씩 숫자가 증가됨을 볼 수 있다.

http://localhost:5000/monitoring

 

 

 

3.2 모니터링 대상 페이지 만들기

  이제 해당 페이지를 모니터링 하는 프로그램을 만들어 보자. 해당 프로그램은 http://localhost:5000/monitoring 페이지를 30번까지 호출 하며, class 가 check_point 인 <td> 태그의 내용을 가져와 5를 발견하게 되면 로컬 SQLite 디비에 이벤트와 시간을 저장하고 for 루프를 먼춘 후, 해당 디비에 저장한 내용을 읽어와 화면에 표시한다.  

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
import sqlite3
import requests
from bs4 import BeautifulSoup
import datetime
import time
 
 
# sqlite 파일을 열음
conn = sqlite3.connect("monitoring.db", isolation_level=None)
cursor = conn.cursor()
 
# BookInfo 테이블을 생성한다.
cursor.execute("""CREATE TABLE IF NOT EXISTS SiteInfo(checkDate timestamp, checkNum text, result text)""")
 
# url 요청할 세션 만들기
= requests.session()
 
# URL 만들기
searchurl = 'http://127.0.0.1:5000/monitoring'
 
# 30번 루프를 돈다. 
for i in range(30):
    # URL 호출
    con = s.get(searchurl)
 
    # html 파서 사용
    soup = BeautifulSoup(con.text, 'html.parser')
 
    # 숫자가 들어있는 태그 가져오와 표시해 보기
    check_num = soup.find("td", class_="check_point")
    print(check_num.string)
 
    # 5가 발견 됬다면
    if check_num.string == "5":
 
        # 발견했다 출력하고 테이블에 저장하기
        print("find it: " + check_num.string)
 
        sql = "INSERT into SiteInfo(checkDate, checkNum, result) values (?, ?, ?)"
        cursor.execute(sql, (datetime.datetime.now(), check_num.string, "find it"))
        break
    else:
        # 못 찼았다 출력 하기
        print("no event")
    
    # 1 초 쉰다
    time.sleep(1)
 
# 테이블에 저장한 값 불러오기
sql = "select checkDate, checkNum, result from SiteInfo"
 
cursor.execute(sql)
row = cursor.fetchone()
 
if row: 
   while row:
      print(row)
      row = cursor.fetchone()
cs

 

  c:\python\code 폴더에 "flask_monitoring.py" 이름으로 "UTF-8" 포맷으로 저장 후 커맨드 창에서 실행해 보자(아래와 같이 1부터 다시 나오게 하려면 앞의 웹 서버 실행을 취소했다 다시 시작하면 된다)

 

c:\Python\code>python monitoring_job.py
1
no event
2
no event
3
no event
4
no event
5
find it: 5
('2019-10-13 20:48:09.088506', '5', 'find it')

 

 

 

 

4. 마무리 하면서

  사실 예제처럼 웹 호출을 기반으로 데이터를 가져와 모니터링 하는 것은 마이너한 경우일 테지만, 꼭 가져오는 방식이 중요한 것은 아니다. 웹에서든, 디비든, 장비든, 로그 파일이든 결국은 파이썬 1교시의 프로그래밍의 입력과 같이 형태만 다를뿐 결국 파싱해 가져오고 싶어하는 건 최종적으로 데이터라는 데엔 변함이 없다. 모니터링은 과감히 간략화 하자면 컴퓨터 내에 적재한 여러 데이터를 기반으로 적절한 체크 포인트를 찾아서, 특정한 데이터의 변화가 일어났을때 알려주며, 종종 그래프 같은 시각적인 형태로 모니터링하는 사람에게 데이터의 추이를 설명해주는 단순한 작업이다.

 

 

  생각보다 모니터링 업무를 하는 사람에게는 지루하거나 스트레스를 받는 일이며, 발생하는 이벤트를 수동적으로 계속 따라가게 됨으로서, 그다지 생산적이지도 않게 느껴진다. 일적으로도 대상을 지키는 업무라는 인식이 강하기 때문에 하는 일의 중요도에 비해 많은 인정을 받지도 못하는 경향도 있다. 때로는 자신의 메인 업무에 부록처럼 따라오는 시간을 조금씩 갏아가는 귀찮은 일일 수도 있다. 또한 운영 업무의 대부분은 이런 모니터링과 일부 또는 전부 연관되어 있다.

 

  반면에 모니터링을 정확히 잘하려면 대상 데이터를 정확히 이해해야 하며, 데이터를 정확하게 이해하기 위해서 데이터를 만들어내는 전반적인 도메인과 관련된 시스템, 사물, 사람의 행동을 이해해야 하기 때문에, 깊이 들어가고자 하면 생각보다 복잡한 일이기도 하다(예를 들어 SQL 을 모니터링 하려면 기본적인 SQL 문법과 사용자 들이 사용하는 패턴의 이해, 해당 SQL의 대상이 되는 서버, 디비, 테이블, 컬럼의 성격을 이해해야만 한다). 뭐 하지만 이런 부분은 IT를 포함한 어떤 분야의 일이나 마찬가지인듯 하다. 다들 익숙하게됨 지루하게 보자면 무척 지루한 일일 수도 있지만, 의미를 가지고 데이터의 원천에 관심을 가진다면 다른 차원의 일로도 볼수도 있다. 스스로 모니터링 시스템을 설계하거나 개선하는 업무의 롤이라면 더 더욱 그럴 것이고 말이다.

 

  모니터링 업무를 도메인의 데이터를 이해하고 적절하게 데이터를 해석할수 있는 툴을 적용하는 일이라고 정의해 보면 어떨까 싶다. 그러면 보안의 다른 분야와 마찬가지로 무척 공부할 것이 많아지는 듯 싶다.

 

 

2019.10.14 by 자유로운설탕
cs

 

 

 

 

 

 

 

 

 

 

 

 

posted by 자유로운설탕
2019. 8. 25. 17:34 보안

  이번 시간에는 리버싱(리버스 엔지니어링: Reverse Engineering)과 포렌식(Forensic)에 대해서 이야기 해보려 한다. 시작하기 전에 먼저 양해를 구하고 싶은 부분은 이 두 분야에 대한 실무를 해본적이 없기 때문에 그다지 자신이 없다. 다만 어떻게 이 두 분야를 바라봐야 할까에 대해서 간단한 예제들를 중심으로 이야기를 풀어보려 한다.



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터


 


 


1. 들어가면서

  위의 두 개의 큰 주제를 하나의 파트로 묶은 이유는, 결과적으로 생각했을 때 두 분야의 연결점이 무척 많다고 생각하기 때문이다. 둘 중 한 쪽 분야에 대한 탄탄한 지식은 다른 분야를 접근 할때 허들을 많이 낮출테고, 일을 함에 있어서도 공통이 되는 지식 분야가 분리하기 힘들 정도로 많이 겹친다고 본다. 비유하자면 포렌식이 생태계과 생물들이 살아갔던 흔적에 주로 관심이 있다면, 리버싱은 생태계 안에서 살아가는 생물들의 행동 관찰에 관심이 있는 분야라고 본다. 생물을 이해하려면 생물들이 살아가는 환경을 이해해야 하고, 환경의 변화를 추적하려면 환경안에서 살아가는 존재들의 상호작용을 이해해야 할 것이다.

 

  사실 그것은 보안 전체에 대해서도 마찬가지인 부분 같다. 편의상 여러 분야로 나누어서 보안 지식을 분류하긴 하지만, 크게 보면 모든 분야가 유기적으로 얽혀 있다고 볼 수 있기 때문에, 어디나 분위기는 비슷비슷 하다고 본다. 반대로 여러 분야의 지식이 어느 정도 골고루 쌓여있지 않다면 뭔가 구멍이 뚫려 있듯이 어설픈 상태에 놓였다는 느낌이 들게 된다. 개인적으로 이 두 부분은 스스로 그렇게 느끼고 있는 구멍나 있는 부분들이기도 하다^^ 

 

 

 

 

2. 리버싱 예제 만들어 보기

  어딘가에서 만들어 놓은 샘플을 하나 가져와도 좋겠지만, 좀 더 간단하지만 통제가 되고 이해가 되는 예제를 예로 들기 위해서, 직접 C++ 로 프로그램을 하나 만들어서 비주얼 스튜디오의 정적 환경(뭐 디버깅 중이라서 동적이라고 볼 수도 있겠다)과 디버거 상에서 동적으로 실행되는 2개의 환경에서 코드를 비교해 볼까 한다. 윈도우즈 환경이기 때문에 요즘은 무료로 제공되고 있는 비주얼 스튜디오 커뮤니티(Visual Studio Community) 버전을 설치해 보도록 한다.

 

 

  구글에 "visual studio community" 라고 적으면 아래의 다운로드 링크를 찾을 수 있다.

 

[visual studio community - microsoft 홈페이지]

https://visualstudio.microsoft.com/ko/vs/community/

 

 

  다운로드 하여 실행하면 설치 화면이 나오는데, 설치를 진행하면서 Workload 탭 에서 "desktop development with c++" 기능을 체크해서 설치한다(이미 설치한 상태라서 스크린 샷은 못찍었는데 아래의 블로그를 참조하면 비슷한 듯 싶다). 뭐 다른 언어도 이것저것 공부할때 필요할 듯 하다면 설치해도 좋다.

 

[비주얼 스튜디오 2017 커뮤니티에서 c/c++ 프로그래밍 하기 - 모두의 코드님 블로그]

https://modoocode.com/220

 

 

 

2.1 리버싱 예제 만들기

  이후 프로그램을 실행을 시키고, "새 프로젝트 만들기" 를 선택한다. 위쪽 검색 메뉴에서 "c++" 를 입력하면, 아래와 같이 c++ 로 콘솔 앱을 만드는 항목이 보이게 된다. 해당 항목을 선택하고, "다음" 버튼을 누른다.

 

 

  프로젝트 이름을 "ReversingSample" 로 위치를 매번 파이썬 파일을 만들던 "c:\Python\Code" 로, 솔루션 이름을 "ReversionSample" 로 한 후, "만들기" 버튼을 누른다(나중에 디버거 툴로 선택할 exe 파일을 잘 찾을 수 있다면 기본값으로 설정 하거나 임의로 생성해도 된다)

 

 

  아래와 같이 정말 간단한 샘플 코드가 만들어져 나오는데, 간단히 기본 커맨드 화면에 "hello world" 를 뿌려주는 코드다(파이썬 코드의 print("hello world\n") 문이 하나 있다고 보면 된다)

 

코드 부분만을 분리해 보면 아래와 같다.

1
2
3
4
5
6
#include <iostream>
 
int main()
{
    std::cout << "Hello World!\n";
}
cs

 

 

 

  위의 코드 자체에서 바로 살펴봐도 되지만, 그러기에는 뜯어 볼 게 너무 없는 코드이므로, 해당 내용을 모두 지우고 아래와 같이 for 문을 돌면서 Hello World 문구를 4번 입력하는 코드로 대체해 보자. 마지막에는 출력후 커맨드 창이 바로 종료되지 않도록 getchar() 함수를 넣어 사용자가 글자를 하나 입력해야 종료되도록 만들었다.

1
2
3
4
5
6
7
8
9
#include <stdio.h>
int main()
{
    for (int count = 0; count < 4; count++)
       printf("hello, world\n");
 
    getchar();
    return 0;
}
cs

 

 

 

2.2 정적인 코드 보기

  정적인 분석을 하기 위해서 이제 화면에서 c++ 코드에 해당 하는 어셈블리 코드를 봐보자. 원래는 cl.exe 라는 명령어를 사용해서 컴파일을 해서 보는 방법을 많이 쓰는 거 같은데, 구글을 찾다보니 IDE UI 에서 편하게 볼 수 있는 기능이 있어서 해당 방식으로 보려 한다.

 

  아래의 그림과 같이 for 문 코드의 왼쪽 공간의 영역에서 마우스 왼쪽 버튼을 클릭해 빨간 브레이크 포인트를 찍는다. 이후 위쪽 Debug 드롭박스 옆의 환경을 64비트 어셈블리 코드를 보기위해서 "x86 -> x64" 로 바꾼다. 이후 그 옆의 "로컬 Windows 디버거" 버튼을 눌러서 디버깅 모드로 들어간다.

 

 

  그러면 컴파일 및 빌드가 진행 된 후, 아무것도 표시되지 않는(브레이크 포인트 땜에 아직 for 문이 돌아기지 못했으므로... 피들러에서 브레이크 포인트를 건 것과 비슷하다) CMD 창이 하나 뜨면서, 비주얼 스튜디오 창의 브레이크 포인트를 찍은 for 문 코드에서 멈춰 있게 된다.

 

  이 후, 해당 for 문 코드 위에서 마우스 오른쪽 버튼을 누르면, "디스어셈블리로 이동" 이라는 메뉴가 보이게 된다. 해당 메뉴를 클릭하면, 탭이 하나더 열리면서 현재 코드에 해당하는 어셈블리 코드가 보이게 된다(마치 브라우저에서 요소검사를 했다고 생각해도 될듯 싶다).

 

 

  그럼 위와 같은 처음 보게 된다면, 조금은 암호와 같이 느껴질수 있는 코드가 보여지게 된다. 화면을 보면 코드를 비교해 편하도록 기존 c++ 코드를 흰색 글자로 보여주고, 그에 해당하는 어셈블리 코드를 회색 코드로 보여주고 있다. 어셈블리 코드 쪽을 문법을 보면 크게 3가지의 섹션으로 나누어 지는데, 첫번째 섹션이 메모리 주소(00007FF... 같은거), 두번째 섹션이 명령어(mov, jmp 같은거), 세번째 섹션이 인자(eax)라고 보면 된다.

 

  컴파일을 하게 되면 0과 1로만 이루어진 기계어로 된 코드가 만들어 지는데 해당 코드가 CPU 쪽으로 전달되게 되어 정해진 명령어들을 수행하면서 컴퓨터가 동작하게 된다. CPU 를 0과 1로 이루어진 코드를 이용해 움직이게 하는 부분이 상상이 안간다면 파이썬 글에서도 추천했던 아래와 같은 책을 한 권 읽어보길 권한다.

  • CODE 코드 : 하드웨어와 소프트웨어에 숨어 있는 언어

 

  그런 0과 1로 된 코드를 보면서 동작을 상상하는 것은, 사람들에게는 낯설기도 하고 코드도 너무 복잡해 져서 하기 힘든 일이기 때문에, 조금 더 CPU 와 상호 작용하는 부분을 동작에 따라 분류해서 사람이 읽기 쉽도록 정리한 언어가 어셈블리어이다. 지금은 일반적으로는 고대어 처럼 취급되긴 하지만, 처음 컴퓨터가 개발됬을 때에는 기계어가 지금의 c 언어 정도이고, 어셈블리어가 자바나, Python, NET 같은 사람에게 가독성과 프로그래밍을 쉽게 하도록 만들어주는 언어 였을 것 같다. 프로그래머들은 아마도 기계어를 안봐도 되는, 어셈블리 언어가 있는게 무척 고마웠을 것이고 말이다. 그런 어셈블리 언어를 다시 한번 감싼게 c, c++ 같은 언어이고, 그 것을 한번 더 추상화 한게, Java 나 Python 같은 언어일 것이다.

 

  현재는 프로그래밍 분야에서는 컴파일러의 동작을 배우거나, 임베디드 프로그래밍을 하지 않는 이상 어셈블리어를 배울일은 거의 없을 것이다(위키를 보다보니 요즘은 사물인터넷 때문에 작은 하드웨어에 최적화된 프로그래밍을 하기위해서 어셈블리어가 다시 사용이 늘고 있다고는 한다). 파이썬의 numpy 같은 특정 모듈이 성능을 위해 c 언어로 개발됬듯이, 종종 c 프로그램의 성능과 효율성을 높이기 위해서 특정 모듈을 직접 어셈블리어로 개발하는 경우도 있다곤 하지만, 꽤 드문 일일 것같다. 그래서 특정한 분야에 일하지 않고는 해당 언어를 사용하거나 볼 수 있는 기회가 드물다는게 어셈블리어를 배우는데 동기부여가 힘든 부분 중 하나인것 같다. 예를 들면 어떤 사람들에게는 평소에 전혀 안 쓰는 예전의 라틴어 같은 언어를 배우는 거와 비슷한 일이 되버릴 수 있다.

 

 

  또한 어셈블리어와 기계어는 1:1 의 매칭이 될테지만, 상위 수준의 언어와는 1 대 多, 또는 多 대 1의 관계가 될 수도 있다. 예를 들면 고 수준 언어로 갈수록 객체 지향 같은 구조적인 프로그램밍이나 코딩의 편의를 위해서 생긴 여러가지 추상적인 요소가 있을 수 있겠지만, 컴파일이 되어 실제 실행을 하는 기계어 코드 입장에서는 그런 사람을 위한 머리 꼬리들은 모두 띄어내고 실행에 필요한 요소들만 코드화 하게 할 것이다. 그렇게 됨으로서 한번 컴파일 된 언어는 다시 돌아갔을때, 원래 언어의 모양 그대로 복원하기 힘들게 되는 일이 생겨버리게 된다(아주 예쁘게 데코한 케잌을 일하면서 한 손으로 효율적으로 먹기위해 꽁꽁 뭉쳐버렸다고 생각해 보자. 원래 모양에 대한 정보가 없기 때문에 완전한 복원은 힘들다--;).

 

  또 자바나 Python 같은 언어를 사용해 보면 c 같은 언어에 비해서 사용자 코드만 만들면, 나머지 모든 것은 자동으로 연결해 움직이게 해주는 경향이 있다. 그러다 보니 실제 코딩을 한 코드가 빌드 될때, 기계어로 바뀐 후, 여러 미리 만들어진 외부 코드를 링크 하여 사용하기 때문에, 실제 기계어 입장에서 실행되는 코드는 처음 코드보다 몇 배는 부풀려져 있다. 보통 디버거를 돌릴때, 우리가 찾으려는 코드에 닿기 전에, 앞에 OS에서 움직이기 위해서 필요한 여러 코드(또는 디버깅을 방해하기 위한 방어코드)의 숲을 빠져나오는 여러가지 요령을 배우게 된다. 

 

  IDE 의 Hex-Rays 라는 플러그인을 사용하면 어셈블리어가 좀더 가독성이 높은 c++ 로 보여진다고는 하지만, 밑의 블로그에서 볼수 있듯이, 사람이 만든 변수명 등의 추상적인 정보가 다 기계어 쪽에 남아있진 않기 때문에 복원이라기 보단 어셈블리어가 문법만 c++ 로 보이도록 재구성 했다고 보는게 맞을 듯 싶다. 그리고 어째거나 c++ 도 엄청 잘해야 해당 코드를 잘 볼수 있을 테고 말이다.

 

[Reversing C++ programs with IDA pro and Hex-rays - ARIS's Blog]

https://blog.0xbadc0de.be/archives/67

 

  자바같이 여러 환경에서 자바 가상환경만 설치하면 그 위에서 호환되어 돌아가는 언어는, 컴파일을 하면 기계어가 아닌 가상환경에서 해석되는 공통 코드로 만들어지기 때문에 원래의 자바 언어로 디컴파일이 좀더 쉬운 편이다. 하지만 뭐 항상 그렇지만 코드는 점점 복잡해 지고 있고, 해당 언어를 아주 잘하지 않는 이상 남이 만든 코드를 보고 주어진 시간내에 이해하는 건 쉬운일은 아닌 것 같다.

 

[컴파일러 - 나무위키]

https://namu.wiki/w/%EC%BB%B4%ED%8C%8C%EC%9D%BC%EB%9F%AC

 

 

 

  사족이 길었지만 다음 이야기를 진행하기 전에 프로그램의 리버싱에서 왜 어셈블리어를 사용해야 하는지를 이해하고 넘어갔음 해서 그랬다. 그럼 비주얼 스튜디오에서 만든 코드를 보면서 이미 알고 있는 c++ 코드에 기반해서 어셈블리 코드의 동작방식을 해석해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
    for (int count = 0; count < 4; count++)
00007FF799ED184A  mov         dword ptr [rbp+4],0  
00007FF799ED1851  jmp         main+3Bh (07FF799ED185Bh)  
00007FF799ED1853  mov         eax,dword ptr [rbp+4]  
00007FF799ED1856  inc         eax  
00007FF799ED1858  mov         dword ptr [rbp+4],eax  
00007FF799ED185B  cmp         dword ptr [rbp+4],4  
00007FF799ED185F  jge         main+4Fh (07FF799ED186Fh)  
        printf("hello, world\n");
00007FF799ED1861  lea         rcx,[string "hello, world\n" (07FF799ED9C28h)]  
00007FF799ED1868  call        printf (07FF799ED11D1h)  
00007FF799ED186D  jmp         main+33h (07FF799ED1853h)  
    getchar();
00007FF799ED186F  call        qword ptr [__imp_getchar (07FF799EE02F0h)]  
    return 0;
00007FF799ED1875  xor         eax,eax  
}
cs

 

  실제 편집창을 보면 저 잘라낸 코드 말고도 전체 코드는 엄청 길지만 앞뒤의 코드는 앞서 말한 "윈도우즈 환경에서, 커맨드 창을 통해서 해당 코드가 실행하기 위한 행사코드(행사 코드는 개발자인 임백준씨의 표현인데 이 경우 적절한 표현같다)" 라고 생각하면 될것 같다.

 

  다음 부터 나오는 모르는 어셈블리 키워드 들은 구글에서 "어셈블리 eax" 등으로 찾아 퍼즐을 맞춰보면 된다. 코드를 보면 rbp, eax, rcx같은 애들이 나타나는데, 이들은 모두 레지스터를 나타낸다. 레지스터는 CPU 입장에서 보면 프로그램의 변수와 같다. 이 변수들에 메모리에 있는 주소 번지, 숫자들과 잘 조합해서 명령을 실행한다.

 

  rbp, rcx 같은 경우는 64비트에서만 해당 표현을 볼수 있는 레지스트로 만약 앞에서 x86 으로 드랍박스를 선택했다면 나오지 않을 레지스트 이름이다(x86에서는 ebp, ecx등으로 e 로 시작되게 지칭한다). 앞으로 코드를 보면 알겠지만 64비트에서는 rcx 도 32비트에서의 동급인 레지스트인  ecx 표현으로도 같이 사용한다(코드 흐름상 추측하면 64비트에서는 rcx 사용 공간을 반만 사용하면 ecx 같다). 각 변수들은 미리 잘 쓰는 용도를 정해 놓은 편인데, 그건 어셈블리 코드를 많이 보면서 패턴상에서 감으로 익혀야 될 문제 같다.

 

   2번째 라인을 보면 "mov  dword ptr [rbp+4],0" 이 있다. 대충 살펴보면 rbp 라는 메모리 스택의 주소를 관리하는 레지스트가 가르키는 스택 위치에 0을 넣는다. 위에서 보면 c++ 코드의 "for (int count = 0; count < 4; count++)" 의 count=0 에 해당하는 주소 같아 보인다. 이후 3번째 줄의 "jmp   main+3Bh (07FF799ED185Bh)" 의 점프(jump=jmp) 명령을 통해 해당 주소인 7번째 줄(07FF799ED185B 주소)의 "cmp   dword ptr [rbp+4],4" 로 간다. 보면 아까 넣어놨던 0을 4와 비교(compare=cmp) 하는 라인이다.

 

  그 다음 8번째 줄을 보면 "jge main+4Fh (07FF799ED186Fh)" 로 앞 줄에서 비교한 결과가 크거나 같으면 루프를 벗어나 다음 코드(07FF799ED186F 주소 - c++ 코드로 보면 getchar())로 점프하는(jump if great or equal = jge) 코드가 있다. 지금은 0과 4를 비교했기 때문에 당연히 만족이 안 될 것이므로 점프 하지 않고 9번 줄로 넘어 갈것이다.

 

  9 번째 줄로 가면, 문자열 "Hello World\n" 가 담긴 주소를 rcx 레지스터로 담는다(Load Effective Address = lea). 이후 10번째 줄에서 "call  printf (07FF799ED11D1h)" 를 통해 윈도우즈의 printf api 를 호출해서 화면에 "Hello World" 를 뿌린다. 이후 11번째 줄에서 다시 루프의 시작에서 실행이 안됬던 4번째 줄로 점프한다(jmp  main+33h (07FF799ED1853h)

 

  4번째 줄을 보면, 아까 스택에 넣었던 0값을 eax 레지스터로 옮긴 후(mov  eax,dword ptr [rbp+4]), 5번째 줄 "(inc  eax)" 에서 1을 증가(increment = inc) 시키고, 6번째 줄에서 다시 1이 더해진 eax 값을 아까는 0이 들어있던 스택 주소로 보내 값을 0->1로 업데이트 한다(mov  dword ptr [rbp+4],eax)

 

  이후 7번째 줄로 가면 다시 위에서 설명했던 비교 명령을 통해서 1과 4를 비교하게 된다. 이렇게 계속 반복되다보면 스택의 0 값은 0, 1, 2, 3, 4, ... 가 되고, 0~3까지는 계속 Hello World 를 4번 표시하다가 4가 담기면서 4보다 크거나 같다는(jge) 조건을 만족하게 되서, 비로서 루프를 빠져나가서 14번 줄의 "call  qword ptr [__imp_getchar (07FF799EE02F0h)]" 을 통해 getchar() API 를 호출하게 된다. 이후 16번 줄의 "xor  eax,eax" 을 통해(자기자신을 xor 하면 0이 되니까 뭐 대충 c++ 의 "retrun 0" 의 느낌이 난다) retrun 0 을 통해 메인 프로그램 함수를 종료하게 된다. 이렇게 저렇게 꼬인 점프 루프 부분만을 빼면 아래와 같은 그림으로 정리함 어떨까 싶다.

 

  이렇게 어셈블리 코드를 보게 되면 기존 고수준 언어랑 많이 다르다는 느낌이 든다. 절대 쓰지 말라고 권고하는 점프도 마음것 사용 하고, 무언가 빠른 실행 속도만을 위해 최적화된 코드의 느낌이다.

 

 

 

  그럼 마지막으로 한가지만 더 생각해 보자. 아래와 같이 다른 코드를 그대로 유지하면서, for 문의 2번째 "<" 만 "<=" 로 바꾸면 어떻게 될까?

1
2
3
4
5
6
7
8
#include <stdio.h>
int main()
{
    for (int count = 0count <= 4; count++)
        printf("hello, world\n");
    getchar();
    return 0;
}
cs

 

 

  유추해 보자면, 어셈블리어에는 jg (jump if greater than)라는 명령어가 있으므로, 기존 코드는 그대로 유지되면서 jge 가 jg 로만 바뀌면 한번 더 루프를 돌게 됨으로서 같아지지 않을까 싶다. 실제 코드를 수정하고 디버그 모드에서 어셈블리 코드를 보면 아래와 같이, jge 가 jg 로만 바뀐 어셈블리 코드가 나온다. 상황에 따라 두꺼운 책을 지루하게 보는 것보다는 이렇게 c 코드와 비교해 가면서 스스로 원본 코드의 난이도를 조정하면서 어셈블리 공부를 하는 것도 나쁘진 않을 것 같다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
00007FF7046C184A  mov         dword ptr [rbp+4],0  
    for (int count = 0; count <= 4; count++)
00007FF7046C1851  jmp         main+3Bh (07FF7046C185Bh)  
00007FF7046C1853  mov         eax,dword ptr [rbp+4]  
00007FF7046C1856  inc         eax  
00007FF7046C1858  mov         dword ptr [rbp+4],eax  
00007FF7046C185B  cmp         dword ptr [rbp+4],4  
00007FF7046C185F  jg          main+4Fh (07FF7046C186Fh)  
        printf("hello, world\n");
00007FF7046C1861  lea         rcx,[string "hello, world\n" (07FF7046C9C28h)]  
00007FF7046C1868  call        printf (07FF7046C11D1h)  
00007FF7046C186D  jmp         main+33h (07FF7046C1853h)  
    getchar();
00007FF7046C186F  call        qword ptr [__imp_getchar (07FF7046D02F0h)]  
    return 0;
00007FF7046C1875  xor         eax,eax  
cs

 

 

 

2.3 동적으로 보기

  이번에는 여러 리버싱 책에서 하는 것처럼, 디버기를 이용해서 같은 코드(다만 앞에 본것은 디버깅 빌드 버전이고, 이번엔 릴리즈 버전으로 만들어 본다. 디버깅에 필요한 사족이 다 제거된 버전이라고 보면 될 듯 하다)를 봐보자.

 

  우선 디버깅을 할 exe 파일을 만들어 내보자. 위쪽의 네모난 정지 버튼이나 "Shift+F5" 를 눌러 디버깅을 중지한다. 이후 아까 Debug 상태의 드롭박스를 "Release" 로 바꾸고, "빌드>솔루션 빌드" 명령어를 통해 빌드를 한다.

 

1>코드를 생성했습니다.
1>ReversingSample.vcxproj -> C:\Python\Code\ReversingSample\x64\Release\ReversingSample.exe

 

 

 

  이제 디버거 프로그램을 다운받도록 해보자. 구글을 찾아보니 요즘은 64비트 환경에서는

x64dbg 라는 프로그램을 많이 쓰는 것 같다. 아래의 페이지에서 zip 파일을 다운로드 받아서 적당한 폴더에 압축 해제 한다.

 

[x64dbg]

https://x64dbg.com/#start

 

 

  이후 release\x64 폴더내에 있는 x64dbg.exe 파일을 실행 시킨다. 디버거 창이 뜨면 "파일 > 열기" 명령어를 통해서 아까 만들어 놓은 C:\Python\code\ReversingSample\x64\Release 폴더에 있는, "ReversingSample.exe" 을 선택한다. 아래와 같이 디버깅이 시작되는 화면이 열린다.

 

 

  디버거는 프로그램이 시작되면서 cpu 에서 실행되는 명령어 들에 대해서 어셈블리 레벨에서 가로채서 보여주는 프로그램이다. 그러다 보니 특정 명령어를 찾아 건너 뛰거나, 프로그램에서 사용하는 여러 값을 변경하거나 하는 일을 자유롭게(잘 이해만 한다면) 할수 있다. 앞에서 얘기한 웹에서의 피들러로 조작했던 클라이언트 코드와 비슷하지만, 보다시피 어셈블리와 그 어셈블리와 상호작용하는 시스템 자체의 동작을 이해해야 그것이 가능하기 때문에 자유롭게 구사하기 위해서는 브라우저로 실행 공간을 추상화 시킨 웹보다는, 배우서 익숙해야 할 부분이 더 많이 필요하다고 생각한다. 다만 그 더 많이 필요한 부분은 보안 기술 자체의 난이도 라기 보다는 앞 시간들에서 얘기했던 대상을 이해하기 위한 난이도인것 같다. 어셈블리나 해당 어셈블리가 돌아가는 환경들을 이해하는 부분은 보안의 측면이라기 보다는 기본적인 IT 기술의 이해쪽에 좀 더 가깝다고 본다. 다시 한번 강조하지만 보안 쪽은 대상의 기술적 측면을 이해하지 못한다면 할수 있는 것이 아주 적어진다.

 

 

  아까의 비주얼 스튜디오 환경에서는 원하는 코드에 대응하는 어셈블리 코드를 바로 볼수 있었지만(브라우저에서 요소 검사를 통해 해당 코드 위치로 이동한 것과 비슷하다고 볼 수 있다), 실행 파일에서는 우리가 작성한 코드가 실행되기 전에, 윈도우 환경에서 프로그램이 실행되기 위한 여러가지 행사코드들이 먼저 실행 될 것이기 때문에 한줄 한줄을 실행하게 되면 한참을 따라 가야한다. F8(건너서 단계 진행) 같은 키를 눌러 나름 큰 발자국으로 한 단계씩 이동하다가 원하는 코드를 만날 수도 있겠지만, 그러다 보면 원하는 지점을 놓쳐 몇번 시행 착오도 겪을수 있으므로 디버거에는 특정한 문자열이나 명령어, 패턴 등의 어셈블리 코드 요소를 기준으로 원하는 위치를 찾을 수 있게 검색 기능이 제공되어 있다. 해당 코드 화면에서 마우스 오른쪽 버튼을 누르고 "다음을 찾기 > 모든 모듈 > 문자열 참조" 를 선택하면 아래와 같은 프로그램이 가지고 있는 문자열들을 리스팅 하는 화면이 나온다.

 

  오른쪽 문자열 팬에 보면 우리가 뿌려주는 "hello, world\n" 문자열이 보인다. 해당 항목을 더블 클릭 해보자. 자 그럼 아까 비주얼 스튜디오에서 봤던 코드와 비슷하지만 조금은 모양이 다른 코드가 보이게 된다.

 

 

  그럼 한번 코드를 살펴보자. 비주얼 스튜디오와 비슷한 모양이 되도록 16진수 코드가 나오는 중간 부분은 편집해 제거했다.

1
2
3
4
5
6
7
8
9
10
11
12
00007FF62C5F1070 | push rbx                               | 
00007FF62C5F1072 | sub rsp,20                             |
00007FF62C5F1076 | mov ebx,5                              | 
00007FF62C5F107B | nop dword ptr ds:[rax+rax],eax         |
00007FF62C5F1080 | lea rcx,qword ptr ds:[7FF62C5F2220]    | 00007FF62C5F2220:"hello, world\n"
00007FF62C5F1087 | call <reversingsample.printf>          |
00007FF62C5F108C | sub rbx,1                              |
00007FF62C5F1090 | jne reversingsample.7FF62C5F1080       |
00007FF62C5F1092 | call qword ptr ds:[<&_fgetchar>]       | 
00007FF62C5F1098 | xor eax,eax                            | 
00007FF62C5F109A | add rsp,20                             | 
00007FF62C5F109E | pop rbx                                |
cs

 

  뭔가 첨에는 조금 달라 보였지만, 루프 코드를 보면 3번째 라인에서 ebx 레지스터에 5 값을 넣는다(mov ebx,5). 그리고 4, 5번 라인의 앞에서 이미 봐서 익숙한 패턴의 코드를 통해서 "hello world" 를 출력해 준다. 그리고 7번 라인을 보면 rbx(ebx 와 동일한 레지스트의 64비트 이름)에 1씩 빼준다(sub rbx,1). 이후 jne(jump not equal) 가 호출 되어 rbx 가 0인지 체크하여 0일 때까지 다시 5번째 라인인 7FF62C5F1080 주소로 점프하며 프린트를 반복한다.

 

  결국 실제 릴리즈 되어 실행되는 코드는 아까 디버깅에서 봤던 어셈블리와 모양은 다르지만 하는 일은 같은 등가의 코드라는 것을 알 수 있다.

 

 

 

 

3. 리버싱에 대해 생각해 보기

  앞의 예제를 통해서 짧게 격투기 선수의 초보 줄넘기 같은 짧은 리버싱 과정을 살펴 봤다. 그럼 다시 근본적인 문제로 돌아가서 왜 우리가 저렇게 귀찮은 과정을 거치면서 리버싱을 해야할까? 가장 큰 이유는 프로그램 소스가 없기 때문이다. 소스가 있는 프로그램을 굳이 리버싱을 하는 것는 KTX 열차 표가 있는데 뛰어서 서울에서 부산까지 가려하는 행동하고 비슷할 것이다.

 

  보통 리버싱 작업의 유용성을 예로 드는 데 사용하는 악성 코드는 소스를 모르는 채로 시스템을 공격하는 대상이고, 해당 실행 코드의 동작을 예측하고 방지하기 위해서는 내부 동작을 알아야 하기 때문에 궁극적으로는 리버싱 밖에는 방법이 없다(물론 여러가지 정적인이거나 해시 값을 체크하는 등의 히스토리에 기반한 방법도 있겠지만 그 쪽은 운이 좋은 편이라고 봐도 될듯 하다). 물론 악성 코드 분석과 같이 일정한 가상환경에 넣어놓고 동작을 관찰하는 보조적인 방법도 충분히 의미는 있을 것이다.

 

 

  리버싱 책을 보면 보면 보통 간단하게 어셈블리 코드의 동작 원리에 대해 설명한 후, 코드에서 원하는 위치를 찾아 로직을 이해하거나, 값이나 기능을 원하는 대로 패치하는 부분을 설명하고, 이후 PE파일의 구조, DLL 인젝션이나, API 후킹, 리버싱을 방해하는 방어수단에 대처하는 방법들을 설명하곤 한다.  그런데 사실 그러한 측면에서의 리버싱은 합법과 불법을 넘나들기도 한다. 같은 기술을 이용하여 타사의 프로그램의 동작 원리를 이해하여, 카피하고자 하는 용도로도 마찬가지로 사용될 수 있기 때문이다. 또한 긍정적인 측면으로는 많은 백신이나, 보안 툴들이 시스템 사용자의 허가(설치와 동의)를 기반으로 시스템을 보호하는데도 사용되며, 반대로 악성코드들은 그런 기술을 이용하여 사용자가 인지 못하는 사이에 동의를 얻어(택배문자를 클릭해 가짜 택배 프로그램을 설치한다던가) 자신을 숨기거나 사용자가 모르는 사이에 사용자의 자료나 행동을 기반으로 나쁜 행동을 벌이곤 한다.

 

  나아가 파이썬 시간에도 얘기했지만, 프로그램은 혼자서 움직이지 못하는 존재이다. 사용자가 직접 만든 코드 로직은 정말 적은 핵심 부분에 불과하고, 나머지는 타인이 만들어 놓은 모듈을 사용하고 있고, 해당 모듈도 해당 모듈의 언어에서 제공하는 라이브러리나, OS에서 제공하는 API(이것도 마찬가지로 코드라고 볼수 있다)를 사용해 해당 행동을 하게된다. 이 먹이사슬과도 같은 관점에서 보게 되면 여러분이 어셈블리를 잘 이해하고 미지의 프로그램을 잘 이해하려 한다면 해당 프로그램을 구성하는 언어 및 OS 의 API, OS의 동작 원리에 대해 잘 이해해야 한다는 결론이 나오게 된다. "Windows Internal" 같은 OS 동작을 설명하는 책부터, 시스템 프로그래밍 책 들과 같은 부분이 그런 시도의 출발점이 아닐까 생각한다. 좀 더 낮은 레벨에서 시스템을 안전하게 하려고 하면 할 수록, 이러한 평소에는 상위 언어에 감싸여 신경쓰지 못했던 부분들에 익숙해 져야 하는게 어쩔수 없는 면 같다.

 

  추가로 많은 악성 코드의 같은 경우는 외부와의 통신을 통해서 주요 로직 모듈을 다운 받거나 데이터를 받거나 전달하는 일도 많기 때문에, "통신" 이라는 개념에서 웹이나 다른 소켓 프로토콜과도 연관이 되어 버리게 되는 부분 같다. 그러다 보면 데이터 은닉(data hiding) 이라는 정상적인 것으로 위장해 하는 행동을 숨기려고 하는 쪽과도 주제가 만나게 된다.

 

 

 

 

4. 다른 측면의 리버싱

  위에는 코드에 기반한 얘기만 했지만, 블랙박스 테스트를 잘하면 화이트박스 기법과는 다른 측면에서 적절하게 주요 코드의 동작을 예측할 수 있고, 자바스크립트를 잘 분석하면 서버 사이드의 로직을 어느 정도 예측할 수 있듯이, 외부에서 관찰을 하여 분석하는 리버싱 영역도 있을것 같다.

 

  종종 만나는 부분이 게임 커뮤니티 같은데에서 사용자가 여러 실험을 통해서, 데미지 규칙을 유추 한다는지, 하는 부분이다. 사실 이런 부분은 서버 사이드 로직이기 때문에, 클라이언트를 아무리 분석해 봤자, 코드가 없을 테니 알 수가 없다.

["랩이 깡패다" 분석 실험 - 인벤]

http://www.inven.co.kr/board/lineagem/5056/4500

 

  뭔가 잘 짜여진 시나리오로 이런 반복적인 테스트를 하는 부분은 충분히 다른 측면의 리버싱이기도 하고, 클라이언트의 기반의 리버싱의 한계를 넘을 수 있게 하는 부분이기도 하다. 마치 별의 움직임을 관찰해서 행성들의 움직임의 규칙에 대한 가설을 세우는 일과 비슷하다고 보면 너무 거창한가는 싶지만 말이다.

 

 

 

 

5. 포렌식 예제 만들어보기

  뭐 예제라고 그러긴 좀 민망하긴 하지만, 포렌식의 측면을 살짝 엿볼수 있는 2개의 예제를 파이썬 코드로 만들어 살펴 보려고 한다.

 

 

5.1 특정 파일에 대한 생성, 수정, 접근 시간 보기

   첫번째는 파일의 생성일과 수정일 히스토리를 살펴보려고 한다. 우선 메모장을 열어서 "test" 라고 적고 c:\python\code 폴더에 test.txt 라고 저장한다(create). 이후 다시 해당 파일을 열어 "modify" 라고 다음 줄에 적고 다시 저장을 한다(modify). 이후 다시 해당 파일을 열어서 본다(access). 우리는 파일을 수정해서 저장하면 해당 파일만 남는 다고 생각하지만, NTFS 시스템은 해당 파일에 대해 생성, 수정, 접근에 대한 기록을 저장해 놓는다.

 

 

 

  "test.txt" 파일에 대한 해당 속성을 보기 위해 아래와 같은 파이썬 코드를 생성해 보자.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import os, time, datetime
import stat
 
file = "test.txt"
 
# 파일 생성일 출력
created = os.path.getctime(file)
created_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(created))
print("생성 시간: " + created_time)
 
 
# 파일 수정일 출력
modified = os.path.getmtime(file)
modified_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(modified))
print("수정 시간: " + modified_time)
 
# 파일 접근일 출력
accessed = os.path.getatime(file)
accessed_time = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(accessed))
print("접근 시간: " + accessed_time)
cs

 

  내용을 보면 os모듈을 이용해서, 각 속성을 얻어와서 화면에 뿌린다. c:\python\code 폴더에 "UTF-8" 포맷으로 "show_timestamp.py" 라고 저장한다(잘 모르겠으면 파이썬 2교시를 참조). 이후 해당 파일을 실행 한다.

 

C:\Python\code>python show_timestamp.py
생성 시간: 2019-09-01 13:23:50
수정 시간: 2019-09-01 13:24:00
접근 시간: 2019-09-01 13:24:00

 

  근데 내용을 보면 이상하게 맨 마지막으로 파일을 열어본 시간이 안 찍히고 있다. 구글을 찾아보면 윈도우즈 7부터 성능을 위해서 파일이나, 디렉토리가 접근되었을때는 해당 정보를 업데이트 안한다고 한다. 이를 수정하기 위해서는 NtfsDisableLastAccessUpdate 레지스트리 키를 0으로 하면 된다고 한다. 뭐 해보고 싶으면 해봐도 좋을듯 싶다.

 

[Enable last access time - Open Tech Guide 사이트]

https://www.opentechguides.com/how-to/article/windows-10/129/enable-last-access-time.html

 

 

 

5.2 엑셀의 최근 항목을 레지스트리에서 찾아보기

   두 번째는 엑셀의 최근 접근 문서를 찾아보자. 엑셀을 열어보게 되면, 아래와 같이 최근 열었던 문서가 표시된다(오피스 16버전 기준).

 

 

  구글을 찾아보면, "HKEY_CURRENT_USER\Software\Microsoft\\Office\16.0\Excel\\File MRU" 경로에 있다고 나온다. 여러 전문적인 포렌식 툴도 있겠지만, 실행 창에서 "regedit" 를 입력하여 레지스트리 편집기를 열어 해당 경로로 이동해 보면, 아래와 같이 엑셀 창에서 봤던 최근 문서들이 키/값 으로 저장되어 있다.

 

  그대로 끝내긴 좀 그래서, 파이썬으로 해당 레지스트리 키를 가져와서, 최근 문서 값 만을 뿌려주는 코드를 하나 작성했다.

1
2
3
4
5
6
7
8
9
10
11
import winreg
# 키를 정의 한다.
hKey = winreg.OpenKey(winreg.HKEY_CURRENT_USER, "Software\\Microsoft\\Office\\16.0\\Excel\\File MRU")
 
# 키 안에 담김 value 숫자를 얻어 그 숫자 만큼 루프를 돈다.
for i in range(0, winreg.QueryInfoKey(hKey)[1]):
    name, value, type = winreg.EnumValue(hKey, i)
 
    # Item 이라는 문자열로 시작하는 이름일 때 프린트 한다.
    if name.startswith("Item"):
        print (name + ": " + value)
cs

 

  역시 c:\python\code 폴더에 "UTF-8" 포맷으로 "show_excel_recently.py" 라고 저장 후 실행해 보자. 아래와 같이 원하는 결과가 나온다.

 

C:\Python\code>python show_excel_recently.py
Item 1: [F00000000][T01D415FA193618D0][O00000000]*C:\Python\code\07교시\result.xlsx
Item 2: [F00000000][T01D37D59BFAC7C70][O00000000]*C:\Backup\code\result2.xlsx
Item 3: [F00000000][T01D37D59BBFEB6B0][O00000000]*C:\Backup\code\result.xlsx

 

 

 

 

6. 포렌식에 대해 생각해 보기

  예제를 보았으니, 앞의 리버싱 얘기에 연결해서 전혀 다른 분야 같이 보이는 포렌식(forensic)에 대해 생각을 해보자

 

  포렌식의 가장 기본이 되는 형태는 삭제된 파일을 복구하는 undelete 작업일 것이다. 속도의 문제 때문에 컴퓨터에서 파일을 삭제할때(휴지통에 남기지 않더라도), 시스템에 등록된 파일에 대한 정보 위주로 삭제하고, 실제 파일 내용이 저장된 디스크의 자기 공간 부분은 그대로 남겨두게 된다. 

 

  왜 전체 내용을 다 지우지 않냐고 생각할 수도 있겠지만, 파일이라는 것은 디스크 내에 0과 1로 저장된 정보의 형태고, 그것을 완전하게 지우기 위해서는 랜덤한 형태로 (여러번) 덮어 씌워야 한다. 그렇다는 것은 몇 기가 정도의 파일을 완전히 삭제하려면 적어도 해당 파일을 디스크 사이에서 복제하는 시간만큼 동일하게 소요되게 된다는 것이다.

 

  매번 파일을 지울때마다(이건 사용자의 파일도 있을 수 있고, 시스템이 사용하는 파일일 수도 있다) 그렇게 번거로운 작업이 일어난다면 지금의 컴퓨터는 휠씬 더 많이 느려질 것이다(큰 파일 복사를 할때나 큰 용량의 파일을 여러개 다운로드 받을때 컴퓨터가 느려지는 현상을 생각함 될듯 하다). 그래서 파일을 지운 후 딱히 컴퓨터를 더 사용되지 않아 해당 파일이 저장되 있던 디스크 영역이 덮어써 지지만 않는다면, 원래의 파일로 복구가 가능할 가능성이 높게 된다. 물론 삭제를 해도 기존에 저장되었던 잔여 자기장에 기반하여 복구가 가능하다고는 하지만, 현실적으로는 가용 가능한 정보가 얼마나 복구될까는 싶다. 

 

[소거 프로그램 - 나무위키]

https://namu.wiki/w/%EC%86%8C%EA%B1%B0%20%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%A8

 

  앞의 어셈블리(또는 c++같은 낮은 수준의 프로그래밍 언어) 코드를 보면 알수 있지만, 컴퓨터는 의외로 생각보다 단순한 동작의 반복으로 우리가 보는 이 운영체체와 프로그램의 동작들을 구현한다. 단순하게 본다면 레지스트, 메모리, 디스크에 데이터를 썼다 지우거나 하는게 전부라고 봐도 될지 모른다. 그러한 과정에서 파일 삭제의 경우와 같이 무언가 완전히 덮어지지 않고 디스크 내에 남거나, 운영체제나 어플리케이션 들이 모니터링이나, 디버깅, 사후 추적의 목적으로 사용자 몰래 남기거나, 사용자가 부주의 해서 또는 필요해서 남긴 부분들이 존재하게 될 것이다. 또한 특수한 경우를 빼고는 메모리 상에는 대부분의 보호장치가 풀어지고 실행가능한 코드만 남게 되기 때문에 접근이 가능하다면 의미 있는 정보를 추출하는 것이 가능하다. 이런 부분들에 대해서 살펴보고, 복구하고, 시간의 측면에 따라 재구성 하여 사용자의 행동을 이해하고 증명하는 작업이 포렌식이란 영역이 아닐까 싶다.

 

  조금 공식적으로 얘기하자면, 그러한 데이터들을 이쪽에서는 아티팩트(artifact) 라고 부르는 것 같은데 그 아티팩트들이 존재할 수 있는 장소는 메모리 상의 여러 요소(실행되는 프로세스, 문서들), 평문으로 저장된 패스워드, 채팅 등 사용자간에 전달되는 데이터, 네트워크 커넥션 로그, MBR , 레지스트리, 로그 및 컨피그 파일, 프로그램 파일, 임시 파일, 데이터 파일, 삭제 후 유지되는 데이터 공간 등이 있을 것이다.

 

  앞에서 보안은 데이터의 흐름을 따라가는 일이라고 생각한다고 말했었는데, 모든 보안의 분야가 그렇겠지만 포렌식 분야야 말로 데이터에 포커스를 두고 흔적을 따라가는 일이라고 본다. 이상적으로는 데이터가 담긴 메모리, 디스크에 모든 필요한 정보가 담겨있다고 볼수 있지만, 해당 데이터들이 구체적인 사용 주체들과 연결되지 않는다면 0과 1로 만들어진 숫자에서서 쉽게 의미를 찾을 순 없을 것이다. 그렇게 하기 위해 OS 에 따른 디스크 및 메모리의 사용 방식, 프로그램이 동작하는 방식, 사용자가 생각하거나 움직이는 방식(사용자가 생성하는 데이터를 이해하거나, 사용자가 컴퓨터를 사용하는 용도나 스타일에 따라서도 접근이 달라질 수 있다고 본다)까지도 이해할 필요가 있다고 생각한다.

 

  나아가 명시적이진 않은 어플리케이션의 데이터를 추적하고 찾기 위해서는(역시 앞의 리버싱과 마찬가지로 소스를 모르니까) 앞 단에서 다르게 느껴졌던 리버싱 영역의 스킬들이 필요하게 될 가능성이 높아진다. 여기서에 두 개의 영역이 결국 만나게 된다고 생각하는데, 프로그램이나 운영체제의 행동을 쫓던 리버싱은 대상이 만들어내거나 접근하는 데이터에 관심을 가지게 되고, 데이터를 쫓아가던 포렌식은 데이터를 만들어 내는 프로그램이나 운영체제의 행동에 관심을 가지게 되어버린다. 결국 두 개의 분야의 지식이 만나 상호 작용하며 균형을 이루어야만 각 분야가 완전하게 될수 있는 것 같다.

 

 

 

 

5. 마무리 하면서 

  처음부터 많이 돌아오긴 했지만 리버싱과 포렌식은 서로 거리가 있는 분야가 아니며 서로의 영역의 지식이 필요한 연결되어 있는 분야라는 얘기를 하고 싶었다. 더 나아가면 보안의 다른 분야에서도 이러한 낮은 레벨의 지식이 필요한 분야가 전체적인 보안의 균형과 응용을 만들어 주는데 꼭 필요하다는 생각을 한다.

 

  그리고 현실적으로는 포렌식 같은 증명을 하는 작업은 객관적이고 표준적인 방법이 필요하므로(법적으로 증명해야 되는 경우도 점점 더 늘어나고 있을테니), 개인의 특별한 지식 보다는 공인된 기관에서 보안적으로 인증된 툴을 사용하여 증적을 만들어야 하는 경우도 많을 것 같다(회사가 인증이나 감사를 받을 때의 기준을 생각하면 이해가 될 것이다).

 

  다만 해당 툴의 결과를 올바르게 해석하거나 툴의 한계를 극복하는 부분은 앞의 스캐너 부분에서 얘기한 스캐너와 수동 테스트의 관계와 마찬가지라고 본다. 또 다른 측면에서는 취미나, 새로운 방법론을 연구하거나, 자동화 때처럼 공식적인 툴들이 지원하지 않는 마이너한 영역에 대해서 연구하는 측면도 있을 것 같다. 이렇게 보면 보안은 결국 컴퓨터라는 세상을 이해하는 일인 것 같기도 하다.

 

 

2019.9.1 by 자유로운설탕
cs

 

 

 

 

 

 

 

 

 

 

posted by 자유로운설탕
2019. 7. 7. 19:32 보안

  이번 시간에는 자동화에 대해서 얘기하려 한다. 보안은 많은 부분이 자동화를 기반으로 구축되어 있는 분야기도 한것 같긴 하지만, 그러한 부분을 조망하는 얘기를 하려는 것은 아니고, 한 개인의 입장에서 생각 할 수 있는 부분이 어떤가에 대해서 이야기 하려 한다. 



[목차]

1. 보안을 바라보는 방법

2. 보안에서의 코드 읽기

3. 인젝션(Injection) 살펴보기 #1, #2

4. 암복호화

5. 클라이언트 코드

6. 업로드, 다운로드

7. 스크립트 문제

8. API

9. 하드닝(Hardening)
10. 보안 설계 문제
11. 스캐너 vs 수동 테스트

12. 자동화 잡

13. 리버싱과 포렌식

14. 모니터링 문제

15. 악성코드

16. 보안과 데이터

 


 


1. 들어가면서

  자동화에 대해서는 이전의 파이썬 글에서도 많이 언급이 된 주제이다. 웹 자동화에 대해서는 8교시 정규표현식, 11교시 웹 페이지 파싱, 12교시 웹 자동화편을 걸쳐서 얘기를 했고, 13교시에서 윈도우즈 자동화를, 14교시에서 작업에 대한 자동화를 이야기 했다. 10교시의 Whois API 이용도 API 를 호출해 자동화 한다는 측면에서 마찬가지이다(마음의 여유가 있다면 이번 시간을 보기전에 해당 글들을 가볍게 보고 왔음 한다)

 

  파이썬 글에서는 그렇게 나눠 놓긴 했지만, 지금와서 드는 생각은 실제 자동화는 저 부분들이 서로 섞여서 진행되는 것이라는 것이다. 조금 더 나아가면 우리가 프로그래밍 한 줄을 실행하는 시점 부터 자동화를 수행하고 있다고 봐도 될 듯 하다. 지금 이 글을 쓰고있는  컴퓨터 자체나, OS, 브라우저, 엑셀 포토샵, 일러스트레이트 같은 디자인, 편집 프로그램, 매일 재미있게 하고 있는 게임 등도 결국은 자동화의 결정체라고 보는게 맞지 않을까 싶다. 사람은 항상 효율성을 추구하는 방식으로 생각하기 때문에(어쩌면 사회적 훈련의 결과일지는 모르겠지만), 사람이 하는 많은 일들은 점점 성숙이 되다보면 자동화로 귀착되어 버리는 것 같다.

 

  보안쪽에서 흔히 솔루션이라고 얘기하는 많은 하드웨어(보통 소프트웨어가 기반이긴 하지만)와 소프트웨어도 결국은 보안 업무의 자동화가 구현된 결과라고 볼수 있다. 우리가 당연한게 사용하고 있는 백신이나, 각종 제어 솔루션, 모니터링 시스템, 방화벽, IPS, 여러 회사들의 보안 솔루션들도 결국은 보안적 지식을 자동화 하여(결과적인 판단 부분에서는 아직 사람들이 많이 관여해야 하겠지만) 구현한 결과라고 볼수 있다. 이 부분에 대해서는 파이썬 21교시의 "The five orders of ignorance" 에서 소개한 지식을 담는 매체의 관점으로 이해하면 어떨까 싶다.

 

 

 

 

 

 

 

2. 보안에서의 자동화 

  맨 처음 부분에서 "한 개인의 입장에서"라고 범위를 좁혀놓은 이유는 이젠 보안 쪽에 있는 많은 자동화의 부분이 개인이 감당하기에는 범위 및 정보가 너무 커졌다고 생각하기 때문이다. 백신 같은 분야만 봐도 예전에는 한명의 개인이 만들어 유지보수가 가능한 부분이였다면, 현재는 수많은 사람들이 해당 회사에 속해서 24시간 돌아가면서 새로운 악성코드 들에 대응을 해야되는 분야가 된것 같다. 또한 백신이 최신의 악성코드 공격을 따라기기 위해서 수집 해야하는 다양한 데이터의 양도 이제 한 개인이 수집하기 에는 너무 다양한 환경과 분석해야 될 데이터의 양이 존재한다(데이터를 준다해도 저장한 서버를 살 돈이 없어서라도 못할듯 싶다--;). 백신 회사들이 개인에게는 대부분 무료 백신을 제공하는 부분도 점유율을 높인다거나, 위험한 PC 들이 너무 늘어나게 되는 것을 기본적으로 방지하는 목적도 있겠지만, 다양한 사용자들의 환경과 그곳에서 발견되는 악성코드를 수집하기 위한 목적도 큰 부분을 차지할 것 같다. 그렇게 보면 세상에 공짜는 없어 보인다. 

 

  이러한 부분은 네트워크, 스캐너, 디비 접근 제어 및 모니터링, 매체제어 솔루션, 패치 등의 관리도 그렇고, 포렌식이나 리버싱 툴도 마찬가지인것 같다. 이젠 작은 개인이 이러한 부분에 대해서 무언가 의미있는 성과를 내기에는 관련된 환경과 데이터가 감당할 수준을 넘어버렸고 ROI와 계속적인 유지보수 측면에서 어려운 시기가 된것 같다. 또한 만들어 놓은 부분에 대해서 잘 동작하느냐를 증명하는 QA 부분도 더더욱 해당 부분을 어렵게 만든다. 또한 필요한 기술의 광범위 함도 한 몫 한다.

 

  예로서 만약 디비 접근 모니터링 솔루션을 만들고 싶다면 여러 데이터베이스의 통신 패킷들을 파싱하고 원하는 값들을 뽑을 수 있어야 한다. 물론 현재에 딱 맞는 오픈 라이브러리가 있을지는 모르지만 그 라이브러리가 적용 후, 새로운 데이터베이스 버전이 나왔을 때 더이상 업데이트가 안된다면 어떻게 하겠는가?(다른 많이 사용되는 오픈소스 라이브러리들도 마찬가지라고 생각될지 모르지만, 수많은 사람에게 필요한 데이터베이스에 쿼리를 날리는 범용의 목적을 가진 라이브러리와, 데이터베이스 패킷을 파싱하는 특수한 라이브러리는 유지보수가 계속 될 확률이 많이 차이가 난다고 본다). 물론 커다란 인력 풀을 가진 회사들에게는 직접 개발 및 유지보수에 대한 제약이 적을지는 모르겠지만, 일반적으로는 성숙된 분야의 툴을 스스로 만드는 것은 힘든 부분 같다.

 

 

 

  그렇다면 작은 개인은 무엇을 자동화 해야할까? 이 부분은 틈새 시장이라는 표현으로 생각함 어떨까 싶다. 아직 변동성이 크거나 시장성이 없어서 정식의 외부 솔루션들이 개발되지 않거나, 솔루션이 있더라도 많은 투자를 하지 않아서 어설픈 분야들이 있다. 또는 솔루션을 들여놓기에는 너무 부담이 되서 부족하게 나마 유사한 효과를 가진 프로그램을 만들고 싶을 때도 있을 것이다. 또는 솔루션의 기능이 부족해서, 뭔가 추가적인 보충을 해보고 싶을 때, 솔루션 개발사나 오픈 소스에서는 보통 해당 부분의 개선이 여러 이유로 쉽지가 않다(수많은 요청을 하나하나 들어주다가는 수 백개의 유지해야할 소스 트리가 생길 수도 있다). 아님 최악의 경우 예산이 없어서 몸으로 때울 수는 없고 비슷하게 라도 만들어 땜빵을 해야 할지도 모르겠다.

 

 

  또 다른 측면에서는 일상의 모든일이 해당된다고 봐도 괜찮을 듯 싶다. 엑셀 작업을 돕는 수많은 매크로 및 함수들이 있듯이, 파이썬 같은 간편한 언어를 가지고 일상의 작은 일들을 자동화 하는 것도 괜찮아 보인다.  처음부터 거창한 무언가를 만들려 하는 것보다는 이러한 작은 조각 조각들을 구현 하다보면, 언제가 꽤 큰 퍼즐에 대해서도 그 동안 모은 조각들을 기반으로 처음 생각보다 어렵지 않게 만들어 낼 수도 있다. 개인적으로 생각하기엔 이런 접근이 프로그램을 주 직업이 아닌 보조 기술로 접근하는 사람들에게는 현실적일 듯도 싶다.

 

  예를 들어 데이터베이스의 모든 테이블에 대해서 특정 쿼리를 날려서 원하는 정보를 가져오는 일을 해야 하는 경우, 쿼리 분석기에서 일일히 쿼리를 만들어낼 수도 있겠지만(SQL 이나 텍스트 편집기들을 잘 쓰는 사람들은 여러 팁을 통해서 모든 테이블에 대한 쿼리를 일괄로 만들어 낼수도 있긴 하겠지만), 파이썬으로 데이터베이스에서 테이블 목록을 얻어낸 후, 적당히 조건에 맞는 문법과 조합하여 쿼리를 만든 후, 각각의 쿼리로 조회하여 결과를 엑셀 등에 정리하면 좀더 편할 것이다. 추가로 해당 작업이 매일매일 일어나거나, 쿼리가 자주 특정 패턴으로 변경 되거나, 대량으로 일어난다면 시간을 아끼게 된다고 느껴 매우 만족하게 될 수도 있다.

 

 

  여러 오픈 소스로 제공되는 대시보드 같은 프로그램 들도 결국은 이러한 자동화에 대한 장벽을 해당 분야에 익숙한 전문가들이 낮춰주려 하는 노력인것 같다. 그렇게 보면 Redis 나 Elastic Search 같은 데이터베이스도 키, 값을 쉽고, 효율적으로 저장하게 해주는 많은 노력들이 자동화의 산물로 만들어져 제공 된다고 보면 된다. 파이썬 같은 프로그래밍 언어나 거기에 속해져 전세계 사람들에 의해 무료로 개발되어 제공되고 있는 모듈들도 마찬가지 인것 같다. 우리 중 일부는 가끔 자동화의 1차 생산자가 되기도 하지만, 대부분의 사람들은 2차 생산자나 3차 생산자인 경우가 많은 것 같다.

 

  이렇게 보면 특정 분야의 자동화라는 말은 별 의미는 없는것 같기도 하다. 적용되는 형태는 다를지 몰라도, 적용되는 기술의 배경은 많은 부분들이 서로 겹치게 되는 것 같다. 결국은 자동화는 프로그래밍 또는 프로그래밍으로 감싸진 좀더 사용자 친화적인 2차 기술 들을 이용하여 일을 하는 방식 같다.

 

 

 

 

3. 자동화 예제 구현해 보기 - 데이터베이스에서 개인정보 찾기

  보안 쪽에서 가장 많이 쓰인다고 생각되는 자동화 프로그래밍 요소중 하나는 패턴에 대한 해석인것 같다. 그 중에서도 으뜸은 파이썬 8교시에 얘기한 정규 표현식 이라고 본다. 나중에 얘기할 모니터링과도 연결될 내용이긴 하지만, 결국 데이터를 기반으로 모든걸 자동으로 검사할 수 밖에 없기 때문에, 결국 통계든 Raw 데이터 자체든 패턴을 찾아야 한다. 물론 특정한 데이터베이스 안의 숫자의 변화를 체크하는 경우도 있긴 하겠지만, 그 숫자가 만들어지는 배경을 살펴 보게 되면 특정한 조건이 발생할때 쌓이는 경우라고 볼 수 있고, 그런 특정한 조건이 주어지는 상황 또한 패턴이라고 볼수 있다고 본다.

 

  여하튼 현재 보여줄 예제는 데이터베이스 안의 모든 테이블에 대해서 10건씩 데이터를 조회해서, 모든 컬럼에서 개인정보를 검사한 후, 결과를 반환하는 프로그램이다.

 

1) 데이터베이스내의 테이블을 가져오는 쿼리는 구글에서 "mssql get tables" 로 조회해 얻었다.

 

2) 기본적으로 테이블에 조회 쿼리를 만들고 행을 가져오는 부분은 파이썬 7교시의 내용을 pyodbc 모듈을 사용하는 것으로 변환해 적절히 만들었다(책에는 pyodbc 로 진행되게 됬어서 해당 샘플을 가져왔다). 예전 댓글이 생각나서 가능한 인자가 호출되는 부분은 parameterized query 로 만들었긴 했는데, 사실 어플리케이션 관점에서 보면 작업의 시작인 테이블 리스트를 가져오는 함수에 인자 자체가 없고, 테이블 이름이나, 컬럼 이름은 데이터베이스 내부에서 가져온 값이기 때문에 조작 가능성은 거의 없다고 봐도 될듯하다.

 

3) 개인정보 패턴을 찾는 정규 표현식은 구글에서 찾다가 행정 자치부에서 배포한 "홈페이지 개인정보 노출 가이드라인"의 83페이지를 참조했다(개인적으로 언어별로 최신 패턴으로 유지보수 되는 라이브러리를 제공하면 더 좋을 것 같다).

https://www.privacy.go.kr/nns/ntc/selectBoardArticle.do?nttId=5952

 

4) 마지막으로 어떻게 테이블, 컬럼, 10개 행을 어떤 자료구조에 넣어서 적절히 루프를 돌리면서 개인정보를 찾은 후 결과를 정리할 수 있을까 생각하다보니, Pandas의 Dataframe 을 사용하면 어떨까 싶었다. 개인적으로 dataframe 은 메모리에 떠있는 디비라고 생각하며 사용하고 있는데, 파이썬에서 간단하게 세로 열을 순서대로 가져와 검사할 수 있기 때문이다(예: A 테이블의 10개 행 중에 메모 컬럼에 해당하는 10개 데이터). 왠지 RDB 에서 가져온 결과를 담은 결과 리스트를 인덱스를 통해 조작하는 것보다는, openpyxl 같은 엑셀 형태의 객체나 pandas 같은 데에 넣는 것이 좀 더 편하게 원하는 값을 선택을 할 수 있을 것 같았다.

 

 

  위에 얘기한 것을 그림으로 정리하면 아래와 같다. 

 

 

  코드를 보기전에 개인정보가 담긴 테이블이 있어야 결과가 나올 것이기 때문에, 우선 MSSQL Management Studio 를 실행하여 기존 데이터베이스에 테이블을 2개 추가하고 데이터를 넣자)쿼리를 실행할 줄 모른다면 3교시를 다시 참고한다)

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
create table Secret_A
(
    MemberNo int,
    MobileTel varchar(20),
)
 
go
 
insert into Secret_A
values (10'010-1111-2222')
 
insert into Secret_A
values (20'010-3333-4444')
 
go
 
create table Secret_B
(
    MemoNo int,
    Memo varchar(100),
)
 
go
 
insert into Secret_B
values (2000'제 전화번호는 011-1234-5678입니다.')
 
insert into Secret_B
values (2001'이건 개인정보가 아니예요 010-2222')
cs

 

 

  테이블을 생성하고 조회하면 아래와 같이 조회 데이터가 나온다. 내용을 보게 되면 Secret_A 테이블에는 2개의 행에 모두 핸드폰 번호가 있고, Secret_B 테이블에는 1개의 핸드폰 번호가 있다. 파이썬과 보안 글을 모두 실습했다면 이 두개의 테이블을 포함해 7개의 잡다한 테이블이 있을 것이다. 

 

  그럼 파이썬으로 만든 코드를 봐보자. 위에 그린 그림과 비슷하게 get_table_names 함수에서 테이블 이름들을 가져오고, get_column_names, make_column_query 함수를 이용해 make_dataframe 함수가 데이터베이스에서 조회한 내용을 dataframe 에 담는다. 이후 check_personal_pattern 를 이용해 각 컬럼별 데이터들에 대해서 루프를 돌리면서 개인정보를 찾아 반환한다.

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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
import pyodbc
import pandas as pd
import re
 
 
# 연결 문자열을 세팅
server = 'localhost'
database = 'mytest'
username = 'pyuser'
password = 'test1234'
 
# 데이터베이스에 연결
conn = pyodbc.connect('DRIVER={ODBC Driver 13 for SQL Server};SERVER='+server+' \
;PORT=1433;DATABASE='+database+';UID='+username+';PWD='+ password)
 
# 커서를 만든다.
cursor = conn.cursor()
 
 
# 테이블 이름을 얻어옴
def get_table_names():
    table_names = []
    
    sql_get_tables = "SELECT name FROM sysobjects WHERE xtype='U'"
    cursor.execute(sql_get_tables)        
    rows = cursor.fetchall()
    
    for row in rows:
        table_names.append(row[0])
 
    return table_names   
    
 
# 테이블의 컬럼이름을 얻어옴
def get_column_names(table_name):
    column_names = []
    
    sql_get_columns = "SELECT column_name FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = ?"
    cursor.execute(sql_get_columns, table_name)        
    rows = cursor.fetchall()
    
    for row in rows:
        column_names.append(row[0])
 
    return column_names
 
 
# select 컬럼 쿼리 제작(a, b, c 이런 식으로 만들어진다)
def make_column_query(column_names):
    column_query = ''
 
    for name in column_names:
        column_query = column_query + name + ','
    column_query = column_query[:-1]
    return column_query
 
# 테이블의 내용을 읽어서 Panda Dataframe 에 저장한다.
def make_dataframe(table):
    columns = get_column_names(table)
    column_query = make_column_query(columns)
    
    query1 = "SELECT top 10 " + column_query + " FROM " + table + "(nolock)"
    df = pd.read_sql(query1, conn)
 
    return df
 
# 핸드폰 패턴을 찾아서 반환한다
def check_personal_pattern(table, column, check_items):
    # 결과를 반환할 배열
    detected_list = []
    # 핸드폰 정규 표현식
    mobile_tel_pattern = "(01[016789][-~.\s]?[0-9]{3,4}[-~.\s]?[0-9]{4})"
 
    for check_item in check_items:
        check_item = str(check_item)
        match_mobile_tel = re.search(mobile_tel_pattern, check_item)
 
        if  match_mobile_tel:
            matched_word = match_mobile_tel.group(1)
            matched_type = "핸드폰번호"    
            
            # 이미 다른 row가 걸린게 있다면 발견한 정보를 추가해 업데이트 하자 
            if detected_list:
                detected_list[0][3= detected_list[0][3+ " / " + matched_word
            # 처음 걸린 거라면 그냥 넣자
            else:
                detected_list.append([table, column, matched_type, matched_word])
             
    return detected_list
 
 
################
# 메인코드 시작
################
 
# 데이터베이스에서 테이블 이름을 얻어온다
tables = get_table_names() 
 
# 테이블 하나하나에 대해서, 10개의 row 를 불러와 dataframe에 넣은 후,
# 개인정보 검사를 하여 결과를 반환 한다.
for table in tables:
    print("[" + table + "]"
    
    my_dataframe = make_dataframe(table)
    
    # 컬럼 이름을 가져와서
    for column in my_dataframe.columns: 
        # 컬럼 이름에 해당하는 수직 row 데이터를 가져온다.
        lists = my_dataframe[column].tolist()
        
        # 개인정보가 있는지 검사한다
        for item in lists:
            personal_infos = check_personal_pattern(table, column, lists)
        
        # 찾은 개인정보를 출력한다
        for personal_info in personal_infos:
            print(personal_info)
cs

 

 

  c:\python\code 폴더에 인코딩은 UTF-8 로 해서 dbsearch.py 란 이름으로 저장 후 실행해 본다.

c:\Python\code>python dbsearch.py
[supermarket]
[order_record]
[escape_test]
[play]
[sms_cert]
[Secret_A]
['Secret_A', 'MobileTel', '핸드폰번호', '010-1111-2222 / 010-3333-4444']
[Secret_B]
['Secret_B', 'Memo', '핸드폰번호', '011-1234-5678']

 

  결과를 보면 개인정보 패턴이 없는 위의 supermarket 같은 테이블들은 그냥 넘어갔고, 개인정보가 들어있는 Secret_A, B 테이블에서만 개인정보를 찾아서 결과를 보여준다.

 

 

 

 

4. POC 이상의 것

  위의 코드는 어찌보면 POC(Proof of Concept) 레벨이라고 볼수 있다. 실제 환경에서는 몇가지 추가적인 확장점들이 필요하다.

 

  첫 째, 위에는 핸드폰 번호 하나밖에 없지만, 여러 개인정보 패턴을 넣도록 해야한다. 가이드에서 제시된 패턴이 실제 필드의 환경에 맞는 적절한 패턴인지도 검증도 해야된다. 특정 필드에서 쓰는 개인정보 패턴은 가이드에도 없을 수 있으므로, 정규 표현식으로 찾을 수 있는지 고민하고 구현을 해야할지도 모른다. 다른 개인정보 패턴을 넣게 되면 핸드폰 패턴을 검사하는 코드가 아마 더 복잡하게 되어버리게 될것 이다. 추가로 파이썬에서 쓰는 정규표현식 표현으로 일부 조정해야할지도 모른다.

 

  둘 째, 범위의 확장도 해야된다. 지금은 데이터베이스 하나밖에 없다고 가정하지만, 수많은 서버가 있고 해당 서버내에 데이터베이스가 복수개 있다면, 서버 목록을 관리하고, 해당 서버에서 데이터베이스를 찾는 부분이 확장되서 만들어져야 한다.

 

  셋 째, 아마 실제 필드에서 위의 프로그램을 돌리게 되면 수많은 false postive 들이 나올 것이다. 해당 부분이 패턴으로 찾는 방식의 맹점중 하나인데 그러한 부분들을 감소시켜 최종적으로 봐야할 데이터를 줄이는 여러 노력들이 필요하다. 안전한 컬럼명의 제외라든지, 특정 패턴의 예외라든지, 패턴들의 통계적 분포 라든지 등등, 패턴이 찾았지만 실제 개인정보가 아닌 데이터들을 자동으로 예외처리하는 여러가지 로직이 있어야 해당 툴을 사용하는 사람이 현실적으로 스트레스를 안 받고 유용하게 쓸수 있게 된다.

 

  넷 째, 결과를 어떻게 관리하고 보여줄지도 고민해 봐야 된다. 엑셀로 담을지 데이터 베이스로 담아 웹 페이지에서 관리하게 할지 등등, 히스토리나 결과에 대한 관리 및 검증에 대해서도 생각해 보면 좋을 것이다.

 

  다섯 째, 오라클, MySQL 같은 RDB 라면 위의 MSSQL 에 대한 코드를 비슷하게 수정해 쓸 수 있겠지만, Redis, MongoDB 같은 NoSQL 데이터베이스라면 앞의 데이터를 가져와 개인정보 검출 함수에 던지는 부분에 대한 정리를 해당 데이터베이스의 형태에 맞게 고민해 봐야한다. 쿼리 형태도 다를 뿐아니라, 넘어오는 데이터도 Json 베이스의 키, 값 데이터가 여러 깊이로 쌓여있는 경우가 많기 때문에 어떻게 파싱을 해야할지도 고민해야 한다. 또한 위의 테이블, 컬럼, 값에 대한 개념에 NoSQL 데이터베이스의 요소들을 어떻게 매칭 시킬지, 아니면 분리해 다룰지를 고민해야 한다. 인코딩, 디코딩에 관련된 데이터 깨짐 문제도 발생해 해결해야 할 수 있다.

 

  여섯 째, 검사해야 되는 대상자체를 유사도에 따라서 제외해야 할수도 있다. Elestic Search 의 날짜 별로 쌓이는 인덱스 라든지, Redis 의 캐시 성격의 키 같은 경우는 모든 것을 검사안하고 샘플링 하여 검사하는게 현실적일 수도 있다.

 

  일곱 째, 만들어진 프로그램에 대해서 어떻게 동작을 보장할 수 있을지에 대해서 검증 계획도 세워야 한다. 버그가 있을지도 모르는 프로그램이라면 안심하고 있다 뒷통수를 맞게 되거나, 프로그램의 효과를 자신하기 힘들게 된다.

 

  이렇게 열거하고 보면 할일이 엄청 많이 보이지만, 기본이 되는 POC코드가 만들어 졌으므로 하나하나 살을 붙여 보는 것도 꽤 재밌는 과정이 되리라 생각한다. 파이썬 글에서 얘기했던 여러 잡다한 주제들이 이런 과정에서 다들 현실화가 되서 하나의 툴로 동작하게 되는 것을 볼 수 있을지도 모른다.

 

 

 

 

5. 마무리 하면서

  자동화라는 망망한 바다 같은 주제를 짧은 예제하나로 요약해 소개해 좀 그렇긴 하지만, 앞에서 얘기했듯이 프로그래밍 자체가 자동화 이고, 이전 파이썬 글들도 결국은 자동화에 대해서 얘기한 것이라고 봐도 될듯 하다. 무언가 대단한 결과를 꿈꾸기 보다는 자신 주변에 있는 작은 자동화 요소들을 찾아서 조금씩 연습을 하면서 자동화의 씨앗을 키워보는게 어떨까 싶다. 

 

 

 

2019.7.13 by 자유로운설탕
cs

 

 

 

posted by 자유로운설탕