일본 여행을 대비해서 여러가지 준비를 하다가 이번 여행은 사진이 중요하다는걸 깨닫고, 기타 치는 영상도 좀 찍어보고 싶어서 카메라를 사기로 결심했습니다. 몇 가지 조건을 설정해 놓고 카메라를 잘 아는 지인들께 물어물어 구매하게 되었는데요, 조건은 다음과 같았습니다.


- 카메라에 대해 아는게 하나도 없으므로 간단할 것

- 렌즈 교체가 가능할 정도의 커다란 카메라의 경우 여행 시 불편할 수 있으니 가볍고 작을 것

- 영상 촬영도 큰 목적 중 하나이므로 영상 촬영 성능도 뛰어날 것

- 영상 촬영 시 화면을 직접 보면서 찍고 싶으니 화면을 뒤집거나 돌릴 수 있을 것


 여러 가지 제안이 나오기는 했지만, 소니의 RX 100MK5가 좋다고 하길래 여러모로 검색해 보다가 RX 100MK5A로 구매하기로 했습니다. 이름이 RX100 MK5A라고도 하고 RX100 M5A라고도 하는데 둘 다 같은 모델이라 좀 귀찮았습니다. 어쨌든 여기저기 돌아보다가 한 소셜커머스가 가장 가격이 쌌기 때문에 그곳에서 구매했습니다. 소셜커머스에서 가격은 대충 80만원대였네요. 카메라의 상세 스펙은 소니의 공식 홈페이지를 참조해주세요.



 요 박스가 뽁뽁이로 칭칭 감겨서 다른 박스 안에 담겨 왔습니다. 저 상자 위의 스크래치를 긁어서 나오는 일련번호를 소니 공식 홈페이지에서 입력하는 것으로 정품 등록을 할 수 있는데요, 현재 정품등록시 여행용 배터리와 충전기 키트를 추가로 주는 이벤트를 하고 있으므로 이벤트 기간 내라면 등록하고 이벤트에 참여해보는 것도 좋아 보입니다.



 내용물을 전부 꺼내 보면 여러 나라의 언어로 작성된 사용설명서와 보증서, 충전기와 케이블, 스트랩, 배터리, 카메라 본체가 등장합니다. 따로 렌즈 교환식 카메라도 아니고 큰 편도 아니기 때문에 생각보다 심플한 구성입니다.



 카메라 본체 전면부입니다. 심플한 블랙의 메탈재질인데 정확히 어떤 재질인지는 잘 모르겠네요. 만져보니 생각보다 손기름이 잘 묻어나오고 들었을때 미끄러질 것 같은 불안감이 좀 생깁니다. 어차피 케이스 끼워서 사용할거라 별로 상관없긴 하지만 기름이 잘 묻어나는 건 좀 걸립니다.



 카메라 본체 후면부입니다. 커다란 화면 하나랑 작고 큰 버튼들, 그리고 엄지손가락을 올려둘 수 있는 그립 부분이 있습니다. 카메라는 전혀 알지 못하기 때문에 자세한 기능은 잘 모르겠지만 대충 픽토그램으로 의미는 파악됩니다.



 충전기에 연결할 경우 전원버튼의 가운데에 이렇게 불이 들어옵니다. 저 가운데 네모난 부분은 위쪽의 번개 모양 버튼을 당기면 펼쳐지는데 플래시를 터뜨릴때 사용합니다. 그 옆의 SONY 로고 위는 뷰파인더로, 옆의 손잡이를 내리면 툭 튀어나오고 렌즈 반대 방향으로 당겨서 사용 가능합니다.



 카메라가 도착하고 나서 케이스가 따로 없다는걸 깨닫고 그대로 들고다니긴 무서워서 LCJ-RXF 라고 하는 케이스를 추가로 구매했습니다. 목에 걸수 있는 스트랩도 주기는 하지만 오히려 거치적거려서 그냥 케이스만 끼웠고, 워낙 크기가 작다 보니 평소에는 주머니에 넣고 다닐 것 같네요. 



 케이스를 열면 이렇게 앞쪽 절반과 뒤쪽 전체가 아래로 벗겨지며, 바로 전원버튼을 눌러 사진을 찍을 수 있습니다. 떨어져나온 부분이 불편하다면 밑의 단추를 당겨 아예 떼어낼수도 있습니다.



 카메라에 대해 잘 아는 분께 부탁해서 회사 옥상에서 찍은 사진입니다. 비싼 녀석이라 그냥 자동으로 놓고 찍기만 해도 멋스럽게 찍어줄 것 같지만 여러가지 조작을 하시더라구요.



 추가로 한컷입니다. 이정도로 작고 가벼운데도 성능은 정말 좋네요. 연속촬영도 테스트해봤는데, 버퍼가 견디는 최대까지 대략 300장 연사정도 되었습니다. 카메라에 또 취미가 생겨버리면 상당히 돈이 많이 들 것 같네요. 앞으로 들고 다니면서 이것저것 찍어서 올리도록 해봐야겠습니다. :D

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

 이번 2월 말에 계획한 일본 여행을 준비하며 저렴한 호텔을 찾아보다가 토요코인이라는 호텔을 발견하여 예약하였습니다. 토요코인은 일본의 비즈니스 호텔로, 지역에 따라 다르지만 도쿄 내에서는 6천엔~7천엔대에 1박이 가능한 저렴한 호텔입니다. 캡슐호텔이나 게스트하우스는 불편하지만 자금에 압박이 있으신 분께 딱 좋다고 생각합니다.


 토요코인에는 여러 가지 할인 제도가 있는데요, 그중에서도 여기서는 토요코인의 클럽카드 시스템에 대해 소개합니다. 간단하게 말하면 회원 카드인데, 물론 비회원일 경우에도 숙박이 가능하지만 회원으로 등록하면 다음과 같은 서비스를 제공하고 있습니다.


- 10포인트(10박)를 채울 시 싱글룸 1박 무료 투숙

- 평일 및 토요일은 5%, 일요일 및 공휴일은 20% 요금 할인

- 비회원(16:00)에 비해 1시간 빠른 15:00부터 체크인이 가능

- 비회원(3개월)에 비해 3개월 빠른 6개월 전부터 예약이 가능

 (다만, 처음 가입 시 1회에 한해 1,500엔(국내에선 15,000원)을 가입비로 받습니다.)


[토요코인 서울 강남점]


 국내에도 토요코인은 여러 군데에 점포가 있는데, 대표적으로 서울 및 인천, 부산에 위치해 있습니다. 국내에서 클럽카드를 만들어서 해외에서 사용이 가능하므로 여행 전에 미리 만들어 두는 것이 여러 모로 편합니다. 홈페이지에서 클럽카드 사전 정보 등록 및 신청을 할 수 있으며, 신청 후 알려주는 신청 번호를 가져가서 호텔 프런트에 전달하면 즉석에서 웹캠으로 사진을 찍어서 카드에 박아줍니다. (즉석이므로 좋은 퀄리티의 사진이 나올거라고 기대해선 안됩니다...)


 호텔에서 체크인 시에도 회원카드 제시만으로 가능하다고 하니 발급받아 두면 여러 모로 편리할 것으로 예상됩니다. 실제로 이번 도쿄 여행에도 회원카드 미제시시 3박 22,700엔이지만 회원이라 20,470엔으로 예약하였습니다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,



 스파이에어 월드 투어 하면서 무려 단독으로는 2년만에 내한 공연을 왔습니다. 마침 2년 전, 2016년 12월 4일에 했던 스파이에어 내한 공연도 티켓팅에 성공하여 인생 첫 라이브로 맛봤기 때문에 후기글을 올린 적이 있었습니다. (링크



 전에 공연했던 Yes24 라이브홀에 이어 이번에는 KBS 아레나홀에서 공연을 하기로 했고, 날짜는 바로 오늘, 2018년 12월 8일이었습니다. 공연 끝나고 집 들어와서 바로 글 쓰는거라 피곤해 죽겠네요. 어쨌든 저는 VIP 티켓팅을 성공했습니다! VIP는 무대와 무척 가까운 스탠딩석에서 관람이 가능함은 물론, 스파이에어 멤버들과 공연 끝난 후 하이터치 및 특별 사인 포스터와 카드를 받을 수 있습니다. 그 내용은 밑에서 작성하겠습니다.



 하필이면 오늘 올해 들어 가장 강한 한파가 닥쳐버렸기 때문에(영하 12도...) 안그래도 목이 부어있는 상태라 무척 걱정이 되긴 했습니다만 일단은 일찍 출발하는게 좋겠다고 생각해서 1시쯤에 도착하도록 집에서 출발했습니다.



 아레나홀쪽으로 가는 방향에 표지판 잘 세워져있길래 한컷 찍어봤습니다. 정확히는 아레나홀로 바로 모이는게 아니라 바로 옆에 있는 제 2체육관에 일단 모여서 VIP는 팔찌를 배부 받고, 굿즈 구매를 먼저 한 다음 5시쯤부터 번호대로 줄 서서 한번에 아레나홀로 들어갔습니다.





 저는 VIP 스탠딩 1층 2구역이었고, 번호는 그렇게 빠르지 않아서 VIP 입장 중후반쯤에 들어갔습니다. 





 공연 시작 전 아레나홀 내부입니다. 광각으로 찍어서 좀 멀어보일수 있는데, 위에서 미리 보여드린 구역표 보시면 아시다시피 바로 2m 옆은 T자형 무대였고 실제 무대와도 6~7m밖에 떨어져있지 않아서 그냥 다 보인다고 생각하시면 됩니다. 실제로 공연 시작했을때 거리감이 엄청 가까워서 놀랐었습니다.


 공연 전에 스파이에어 노래도 아니고 왠지 마룬파이브나 린킨 파크같은 다른 밴드 노래를 작은 볼륨으로 틀어놓더니 입장이 완료되고 준비가 끝나자 노래가 끊기고, 기타 및 드럼을 테스트하더니 소리가 커지면서 멤버들이 무대에서 갑자기 등장했습니다. 첫 시작은 분명 현상파괴였고, 그 다음곡은 Firestarter였는데 이후 곡 순서는 좀 기억이 희미하네요. 내한 공연 코멘트로 한국 팬들의 특징이 떼창이다 보니 떼창을 하기 좋은 곡들로 구성하겠다고 했었는데 정말로 Just one life와 Rockin' out을 연속으로 불러제낄줄은 몰랐습니다. 중간중간 잠시 쉬면서 얘기도 하고 했는데 이케 목 상태가 별로 좋지 않다고 하더라구요. 왠지 계속 마스크를 쓰고다니더니 그 이유일지도 모르겠습니다. 무서운 한국 한파...


 일단 이번 공연에는 2016년도에는 나오지 않았던 Beautiful days와 Wendy - It's you 가 등장했고, 신곡인 I Wanna be... 도 (페이크)마지막 곡으로 등장했습니다. I Wanna be... 이후 멤버들이 퇴장하고 앵콜 외칠때 Singing의 떼창 부분을 사용했고, 이후 사진으로 보여드릴 I★SPYAIR 피켓도 들었습니다. 앵콜은 Overload->Japanication->Singing 순으로 진행되고 공연은 막을 내렸습니다. 2016년에도 분명히 Singing으로 마지막을 장식했던 것 같네요. 


 저도 목 상태가 별로였지만 최선을 다해서 뛰고 흔들고 떼창했고, 다행히 이번 공연에서는 굿즈 판매시간에 맞춰 가서 사무라이 하트에서 타올 돌리는것도 어찌저찌 해봤습니다. 이케가 중간에 내한 공연이 언제 있을지 모르고 앞으로는 오기 힘들 수도 있다고 했었지만 마지막에 다시 오겠다고 해줬으니까 희망을 가져보려고 합니다. 안오면 내가 일본 가야지 뭐.


 공연 끝나고 나서 VIP는 퇴장하면서 멤버들이랑 하이터치...를 하는데 그냥 일렬로 나가면서 멤버들이랑 눈마주치고 지나가면서 손바닥 마주치고 가는거 뿐이라 사실 그렇게 큰 의미는 없고, 지나가면서 한마디씩 해주거나 하는데 그거 듣는거랑 '아, 난 스파이에어 멤버들이랑 하이터치도 해봤다' 라는 걸로 자랑은 할 수 있을 것 같습니다.


요 밑에서부턴 굿즈 내용입니다.







'Hobby' 카테고리의 다른 글

일렉기타 연습할 곡 리스트업  (0) 2018.07.01
Schecter C-1 FR S Apocalypse 구매  (1) 2018.04.19
일렉기타 셋업 - 닥터에이 방문  (0) 2018.04.06
Line6 POD HD500X 구매  (0) 2018.02.24
Fender Mustang MG-69 MH CAR  (1) 2017.12.27
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

 악성코드 하나를 분석하던 중, 아주 흔하면서도 지금까지 짜증나게 만들었던 루틴이 하나 등장했습니다.

단순하게 LoadLibrary와 GetProcAddress를 통해 모듈 핸들과 함수 주소를 가져오고, 이를 전역변수에 대입하여 다른 코드들에서 사용하는 것으로 IAT에 자신이 사용하는 함수명을 표시하지 않게 하는 흔한 기법이지만, 몇가지 문제점이 있었습니다.



[그림 1. 첫번째 문제]


 첫째, 양이 많았습니다. 한 함수 내에서 40개가 넘어가는 함수들의 주소를 가져오는데, 이는 다음 문제점과 더해 미친듯한 시너지를 발휘합니다.



[그림 2. 두번째 문제]


 둘째, 변수명이 스택에 저장되어있습니다. 바이트 단위로 할당되어있기 때문에 보통이라면 이걸 전부 아스키 문자로 바꿔서 하나하나 읽고 무슨 함수인지를 파악해야 합니다. 함수가 엄청나게 많다 보니 1500바이트가 넘어가는 양입니다.




[그림 3. 세번째 문제]


 셋째, 변수 값이 암호화되어있습니다. 정확히는 0x08이라는 고정된 값으로 XOR되어있는 것이지만, 어쨌든 그냥 봐서는 어떤 함수인지 알기가 힘듭니다. 셋중 하나라도 문제가 없었으면 그냥 복사해서 파이썬 스크립트로 복호화하거나 해서 하나하나 네이밍해줬겠지만, 도저히 그럴 양이 아니었습니다.



 여기에서 잠시 현자타임이 왔고, 이걸 어떻게 해결할까 하다가 지금까지 너무 무식하게 분석해왔다는 생각이 들어서 IDAPython을 이용하여 자동 스크립트를 짜기로 결심했습니다.




[그림 04. 스크립트 동작 전]


 작성한 스크립트를 돌리기 전의 모습입니다. 



[그림 05. 스크립트 로그]


 스크립트를 동작시키면 IDAPython 콘솔창에 로그가 남습니다.




[그림 06. 스크립트 동작 후]


 스크립트를 동작한 후 새로고침해주면 위와 같이 GetProcAddress를 이용하여 대입한 값들이 네이밍됩니다. 예전 BoB 4기에서 잠깐 한 적이 있기는 했는데 사실 기억에는 전혀 남아있지 않아서 혼자 여기저기 검색하면서 조잡하게 짰습니다.


이 스크립트에는 아직 개선할 점이 몇 가지 있습니다.


- GetProcAddress의 결과를 전역변수가 아닌 지역변수나 Struct의 멤버변수같은것에 대입하는 경우는 네이밍이 되지 않습니다.

- 특정 함수 내에 커서를 둔 채로 실행해야 하며, 해당 함수 내에서의 GetProcAddress에 대해서만 네이밍을 진행합니다.

- GetProcAddress함수의 주소를 직접 구해서 RenameGetProcAddrVars 함수에 인자로 넣어야 합니다.


스크립트를 사용하면서 나중에 조금씩 업데이트해갈텐데, 그러면서 하나씩 해결해갈 예정입니다.


소스코드 및 정리한 내용은 아래에 있습니다.


IDAPythonUtils.py




 소스코드 내에서 사용한 idaapi의 함수들은 다음과 같습니다.


※ ea라는 용어는 Effective Address의 약자로, 주소값을 의미합니다.

- Byte : 인자로 ea를 받으며 해당 주소의 1바이트 값을 반환합니다. 리터럴 상수처럼 정적으로 값이 확인 가능해야 합니다.
- GetFunctionAttr : 첫번째 인자로 ea, 두번째 인자로 받아올 함수의 속성을 입력받아 해당 함수의 속성을 가져옵니다. 여기에서는 함수 시작 주소와 끝 주소를 받아오는데에 사용했습니다.
- GetDisasm : 인자로 ea를 받아 해당 주소의 값을 디스어셈블하여 문자열로 리턴합니다.
- GetOpnd : 첫번째 인자로 ea를, 두번째 인자로 가져올 오퍼랜드를 0-Base로 입력받습니다. 디스어셈블된 오퍼랜드 자체를 넘겨줍니다. (예:  lea eax, [esp+674h+var_617]의 경우 :[esp+674h+var_617]: 리턴)
- GetOperandValue : 첫번째 인자로 ea를, 두번째 인자로 가져올 오퍼랜드를 0-Base로 입력받습니다. 오퍼랜드에 넘어간 값을 강제로 int형으로 바꿔서 반환하는 것 같습니다. (예: lea eax, [esp+674h+var_617]의 경우 93 리턴)
- here : 현재 커서가 가리키는 주소 반환
- PrevHead : ea를 인자로 받아 바로 이전 명령의 주소 반환
- NextHead : ea를 인자로 받아 바로 이후 명령의 주소 반환
- XrefsTo : ea를 인자로 받아 해당 주소를 참조하는 주소들을 iterable하게 반환




블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,


 해당 프로그램은 악성코드 분석 연구 도중 흥미 위주로 만들어본 프로그램이며, 꼭 사용 전 파일을 백업해주세요. 필자는 이 프로그램을 사용함에 있어 생긴 피해에 대해 어떠한 책임도 지지 않습니다.


PyCLDecryptor.exe


 


 테스트를 위해 실제로 가상머신 내에서 PyCL 랜섬웨어를 돌려 일부러 감염시킨 테스트 파일들입니다.


 

 프로그램 실행 후 복호화 할 파일/폴더의 경로와 키 값을 입력하면 자동으로 해당 경로 하위를 전부 돌면서 감염된 확장자를 확인하고 입력받은 키로 복호화를 시도합니다. 복호화에 실패하더라도 원본 파일을 수정하거나 지우지는 않지만 혹시 모르니 항상 백업은 하고 사용해주시기 바랍니다. 랜섬웨어의 키를 찾는 방법은 이전 포스트의 마지막에 작성해두었습니다.


 소스코드는 gist로 첨부하겠습니다.

PyCLDecryptor.cpp 

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

 



 

1. 개요

[그림 1. 분석 대상 샘플]

 PyCL이라는 특이한 이름의 랜섬웨어가 등장했길래 재밌어보여서 분석해봤습니다.

 

 

[그림 2. 흐름도]

 

 

2. 샘플 정보

파일명

sample1

파일 크기

10,192,467 Bytes (9.71MB)

파일 타입

PE32+ executable for MS Windows (GUI) Mono/.Net assembly

MD5

0d9e127186433171791aaf2820be9575

SHA1

cb1f60268ff2146f3970d2afa40f8f07e5481e81

SHA256

115c02efdacb18478be9a92b81f2d9eba622189aa9f60ecdbde522c4d3111d38

SSDeep

196608:KJMSrtRIcI2tVHhEVSL2v8hp1yjAOHR3f1gu8N150IrE7N3sO5JvVWsj4Pz:rj27aVSLo8p323f4750jIsU

 

파일명

sample2

파일 크기

10,192,572 Bytes (9.71MB)

파일 타입

PE32+ executable for MS Windows (GUI) Mono/.Net assembly

MD5

eab2c08156d5372c34f02b486d70c2f6

SHA1

69a1da7bea8c49fb10e15f57e76dffdce7a7b801

SHA256

5b9cd4e6fd0d309a8a7c6fa9181b372d1ce85532f4c5181ba1bb9e38495f2c5e

SSDeep

196608:KJMSrtR8cI2tVHhEVSL2v8hp1yjAOHR3f1gu8N150IrE7N3sO5JvVWsj4Pz:pj27aVSLo8p323f4750jIsU

 

 그림 1의 아이콘과 위의 메타데이터들을 보면 알 수 있듯, 파일 크기가 클 뿐만 아니라 두 샘플 모두 비슷한 크기의 같은 타입, 거의 동일한 SSDeep 해쉬값을 갖고 있습니다. 패커 혹은 래퍼로 감싸져서 그런 것이라고 의심이 가능합니다.


 

3. 분석

[그림 3. Tkinter 아이콘 확인]

 랜섬웨어가 실행되고 나면 위와 같은 창과 메시지박스가 등장하는데, 메시지박스의 아이콘은 파이썬 내장 GUI 라이브러리인 Tkinter의 기본 아이콘입니다. 이름에서 이미 예상했지만 파이썬으로 작성된 프로그램이라는 것을 알게 되었고, PE 파일로 래핑되어 있으므로 이를 해제하기 위해 무슨 툴을 사용했는지를 알 필요가 있었습니다.

 

[그림 4. PyInstaller 문자열 확인]

 파이썬을 EXE로 래핑하기 위해 가장 많이 사용하는 툴인 PyInstallerPy2Exe를 체크해볼 생각이었는데 찾아보니 바로 PyInstaller로 확인되었습니다.

 

[그림 5. pyinstxtractor]

 PyInstaller로 래핑된 파일을 래핑 해제하기 위해 pyintxtractor라고 하는 오픈소스 라이브러리(https://sourceforge.net/p/pyinstallerextractor/tickets/5/attachment/pyinstxtractor.py)를 사용하였습니다.

 

[그림 6. scriptedhind 파일]

 래핑 해제된 파일을 아무리 찾아도 메인 소스코드로 보이는 파일이 보이지 않아서 찾아보다 보니 scriptedhind라는 파일 내부에 랜섬웨어의 소스코드로 보이는 내용이 작성되어 있는 것을 볼 수 있었습니다. 파이썬 소스코드가 원형을 유지하고 있지는 않았지만 하드코딩된 문자열 등은 그대로 나타나 있었기 때문에 대충 어떤 랜섬웨어인지는 파악이 가능했고, 이것만으로는 정보가 불충분하다고 느껴서 파일명인 scriptedhind로 검색을 해봤습니다.

 

[그림 7. Ransomware Builder Github]

 검색 결과 scriptedhind라는 문자열을 포함하는 Github 주소가 등장했고, 접속해보니 자동으로 랜섬웨어를 만들어 주는 툴을 배포하고 있었습니다.

 

[그림 8. 책임 회피용 문구]

 작성자는 Github에 교육적인 목적으로만 제작된 것으로, 프로그램의 사용에 의한 책임을 지지 않겠다고 작성해놓았습니다. 하지만 이전에 오픈소스 원격관리도구라며 .Net기반 RATNanocore를 개발한 개발자가 유죄를 선고받은 사례도 있으므로 이게 합법이라고 판단하긴 힘들어 보입니다.



[그림 9. 해당 Github 유저]

 ScRipt1337이라는 닉네임을 사용하는 유저는 이외에도 직접 제작한 것처럼 보이는 여러 악성 프로그램을 교육적인 목적이라는 명목으로 공개해놓고 있습니다.

 


[그림 10. Ransomware Builder]

 아까 발견한 Ransomware Builder에 존재하는 두 개의 exe 파일은 둘 모두 랜섬웨어와 같이 PyInstaller로 래핑된 파이썬 기반 프로그램이었습니다. Builder.exe 파일을 실행하고 Github에 작성되어 있는 패스워드를 입력하면 위와 같은 창이 등장하고, 원하는 내용을 채워넣으면 이대로 랜섬웨어가 작성됩니다. 결과물은 Python 스크립트로, 단순히 위 항목들을 포맷팅하여 넣은 것에 지나지 않습니다. 드랍된 스크립트를 분석해보겠습니다.

 


[그림 11. 권한 체크]

 생성된 파이썬 파일은 실행된 직후 Admin 권한을 가졌는지 체크하고, 아니라면 UAC 컨트롤로 관리자 권한 획득을 시도합니다.

 

[그림 12. AntiVM?]

 이후 “WMIC BIOS GET SERIALNUMBER” 명령을 이용해 하드웨어 정보를 가져옵니다. VMware 에서 생성한 VM 내부에서 사용할 경우 stdout으로 “VMware-56 4d 05 69 52 1c d8 fa-86 9e 1c 72 28 bd f5 46”라는 값이 출력되었는데, 저기에서 받는 result stdout을 받는 것이 아니라 프로그램의 종료 시 리턴 값을 받는 것이기 때문에 VM 여부에 관계없이 명령어가 성공한 이상 무조건 0이 반환되었습니다.

 

[그림 13. 작업 관리자 차단]

 그림 12 disabletask 함수 내부에서는 특정 레지스트리 값을 추가하는 것으로 작업 관리자의 사용을 차단합니다.

 

[그림 14. 실수?]

 실수인지 고의인지는 모르겠지만, 이렇게 반환된 0 int로 형변환한 후 문자열 “0”과 비교하는 어이없는 일을 수행합니다. 이미 타입 자체가 다르기 때문에 무조건 False가 될 수밖에 없고, main이 실행됩니다. 여기서 strongKey는 위의 Builder.exe에서 입력한 암호화 키 값입니다.

 

[그림 15. main]

 main 함수 내에서는 “C:\” 하위의 파일들을 대상으로 총 145개의 확장자를 검색하여 일치하는 파일에 대해 암호화를 수행합니다. 여기서 문제는 파일이 존재할 때마다 바탕화면에 랜섬노트로 추정되는 파일을 계속해서 덮어쓴다는 것입니다. 이 랜섬웨어는 작동하는 속도가 비정상적으로 느려 전부 암호화가 될 때까지 10분 이상이 소요되는데, 이 작업이 거기에 한몫 하는 것으로 보입니다. (그러면서 자기가 작성한 랜섬노트마저 확장자가 .txt라는 이유로 암호화해버립니다;)

 

[그림 16. 암호화 루틴]

 암호화에는 AES CBC를 사용하며, Initialization Vector는 그때그때 랜덤한 값을 뽑아서 사용합니다. 파일의 암호화 전에 파일의 크기와 IV를 총 32바이트 버퍼에 담아서 파일 앞부분에 저장하고, 이후 특정 크기씩 읽어와 블록 암호화를 진행합니다. 입력한 key는 키 그대로가 아니라 sha256으로 한번 해싱하여 AES의 키로 사용합니다. 암호화된 파일의 확장자는 .impect가 추가로 붙습니다.

 

[그림 17. 복호화 루틴]

 바로 밑에 복호화 루틴도 있었는데, 복호화 완료된 파일명을 제대로 복원시켜주는게 아니고 뒤에서 offset -3까지만 가져오기 때문에 [원본파일명].imp 로 변환될 것으로 예상이 가능합니다.

 

[그림 18. 실수 2?]

 이렇게 암호화가 전부 완료되고 나면 try~except 문으로 진입하는데, try문 내에서 특정 파일을 읽어와서 str형태로 변환한 다음 6을 더하는 행위를 하고 있습니다. Javascript도 아니고 str 타입 변수에 int형 상수를 더하는 것이 허용될 리 없으므로 무조건 except로 빠지게 되고, 결국 현재 시간을 작성하는 용도로밖에 쓰이지 않습니다. except에서 현재 시간을 포맷팅한 후 offset -6까지를 저장하는 것으로 보아 현재의 시간을 작성하는 것이고, 가져온 시간에 6을 더해서 일치할 경우(있을 수가 없는 일이지만) “C:\Users\유저네임이하의 모든 파일을 삭제합니다. 결코 발생할 수 없는 루틴이지만 일정 시간 내에 금전을 지불하지 않을 경우 해당 파일들을 전부 삭제하겠다는 류의 협박성 루틴인 것 같습니다.

 

 

[그림 19. 작업 표시줄 숨기기]

 위 작업이 끝나고 나면 이유는 모르지만 작업 표시줄을 먼저 숨깁니다. 작업 표시줄과 함께 시작 버튼도 같이 숨기려고 하는 것 같은데, Windows 7에서 테스트 해본 결과 제대로 숨겨지지 않았습니다.

 


[그림 20. 바탕화면 변경 시도]

 Builder.exe에서 입력했던 url을 이용하여 이미지 파일을 다운로드 받고, 그것을 이용하여 바탕화면을 변경하려고 시도합니다. 여기서는 64비트인지 체크하여 맞을 경우 SystemParametersInfoW를 사용하고 아닐 경우 SystemParametersInfoA를 사용하는데, 왜 굳이 저렇게 하는지는 잘 모르겠습니다. 윈도우 API에 익숙하지 않은 제작자라는 생각이 듭니다.

 

 

[그림 21. GUI 루틴]

 위 작업까지 끝나고 나면 그림 3에서 볼 수 있듯이 Tkinter를 이용하여 GUI로 윈도우 하나를 띄우고, 랜섬웨어 감염 사실을 (욕설과 함께)알리고 키를 입력할 수 있는 Entry 칸을 하나 만들어둡니다. Decrypt 버튼을 누르면 runthefuckup 함수가 실행됩니다. 교육적인 목적으로 공개했다고 했는데 전혀 교육적이지 않아 보입니다.

 


[그림 22. runthefuckup 함수]

 입력받은 키 값이 저장된 키 값과 일치할 경우 C:\ 하위를 돌면서 암호화된 파일에 대해 복호화를 수행하고, 다를 경우 욕설이 씌여 있는 메시지 박스를 띄웁니다.


4. 복호화 방법

 pyinstxtractor를 이용하여 PyInstaller를 래핑 해제했을 때 메인 소스코드가 정상적으로 나오면 좋은데, 어째선지 제대로 메인 소스코드가 등장하지 않아서 키를 어떻게 찾아야 하는지 고민했었습니다. 단서는 랜섬웨어의 메인으로 보이는 scriptedhind 파일이었는데, pyc 디컴파일러를 돌려봐도 제대로 디컴파일이 되지 않았습니다. 하지만 알고 보니 pyc파일은 맞는데 시그니쳐 부분만 날아갔던 것이었고, 그래서 시그니쳐를 복원하고 디컴파일을 돌려봤더니 정상적으로 디컴파일이 되었습니다.

 

[그림 23. 누락되었던 시그니쳐]

 

 이렇게 누락됐던 시그니쳐를 추가한 후 Easy Python Decompiler를 이용하여 pyc파일을 디컴파일했습니다.

 

[그림 24. 디컴파일된 소스코드]

 디컴파일로 추출한 소스코드 내에서 암호화 키를 확인 가능하고, 랜섬웨어가 띄운 창에 입력하면 정상적으로 복호화가 되지만 프로그램 자체가 무척 불안정해서 응답을 하지 않거나 복호화 하는 데에 시간이 상당히 소요됩니다. 복호화를 해주는 툴은 C++로 직접 짜볼 생각이며, 추후 포스팅하여 링크하겠습니다.


 복호화 키 얻는 방법 요약

1. python.org 에서 파이썬 설치. 2, 3 버전 관계 없음.

2. 랜섬웨어 파일을 pyinstxtractor를 이용하여 디컴파일하여 패킹된 파일들을 추출.

3. 내부에 scriptedhind 파일을 hxd등을 이용하여 맨 앞에 Hex값으로 '03 F3 0D 0A 00 00 00 00'을 추가 후 scriptedhind.pyc로 확장자 변경

4. Easy Python Decompiler를 이용하여 방금 만든 scriptedhind.pyc 파일을 디컴파일

5. 디컴파일되어 떨어진 scriptedhind.pyc_dis 파일을 열어서 strongkey 값 확인


 현재 가진 샘플 두 종류의 MD5 Hash와 키 값은 다음과 같습니다.

0d9e127186433171791aaf2820be9575 : !0oPQrSuUaOwc7upKgesZqrUOrwP7XCugYX3xHUKjZrc321308

eab2c08156d5372c34f02b486d70c2f6 : 1qazxsw21@@@

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,





 애플뮤직에서 추천해준 곡인데 처음 딱 듣자마자 좋은 곡이라는걸 느꼈습니다. '우리들은 기적으로 되어 있다(僕らは奇跡でできている)' 라는 드라마의 오프닝 곡이라는 듯 합니다(출처). Shiggy Jr. 은 처음 듣는 그룹인데 음악 스타일이 취향저격이네요. 기타도 좋고 목소리도 좋고... 다른 곡들도 찾아서 들어봐야겠습니다.

블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

[그림 1. 실제 악성코드에서 사용한 GetProcAddress]

 보통 WinAPI를 사용하면 IAT에 사용하는 함수가 무엇인지 그대로 박혀있거나, 최소한 GetProcAddress 등 함수를 이용해 동적으로 API를 로드해서 사용하기 때문에 IAT에 GetModuleHandle이나 GetProcAddress 등이 나와 어디에서 어떤 함수를 로드하는지, 결국 어떤 함수를 사용해서 대강 무슨 행위를 하는지 노출됩니다. 그런데 최근 분석해봤던 악성코드는 이를 회피하여 GetModuleHandle이나 GetProcAddress 함수조차도 IAT에 없는데도 자유자재로 API를 가져다 사용했습니다. 이를 직접 구현해보기 위해 이전에 작성했던 글에서 API 없이 현재 로드된 모듈의 정보를 알아내는 방법에 대해 알아봤습니다. 이번엔 이렇게 가져온 모듈에서 API 주소를 가져와 사용하는 방법에 대해 알아보겠습니다. 그렇게 복잡하고 어려운 내용은 아니므로 먼저 소스코드를 놓고, 하나하나 짚어가도록 하겠습니다.


main.cpp

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
#include <Windows.h>
#include <winternl.h>
#include <stdio.h>
#include <wchar.h>
 
PIMAGE_DOS_HEADER GetBaseAddress(const wchar_t *moduleName)
{
    
    PLDR_DATA_TABLE_ENTRY orgPtr = nullptr;
    PLDR_DATA_TABLE_ENTRY curPtr = nullptr;
    wchar_t *foundDllName = nullptr;
    __asm
    {
        mov eax, fs:[0x18]        // TIB
        mov eax, [eax + 0x30]    // TIB->PEB
        mov eax, [eax + 0x0C]     // PEB->Ldr
        lea ebx, [eax + 0x1C]    // Ldr->InInitializationOrderLinks
        mov orgPtr, ebx
    loadOrderLoop :
        mov edx, [ebx]            // InInitializationOrderLinks->Flink
        mov curPtr, edx
        mov edx, [edx + 0x20]   // LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer
        test edx, edx
        je loadOrderFailed
        mov foundDllName, edx
    }
    if(!_wcsicmp(foundDllName, moduleName))
        return (PIMAGE_DOS_HEADER)curPtr->InMemoryOrderLinks.Flink; // -0x10, as used InInitializationOrderLinks
    __asm{
    loadOrderFailed :
        mov ebx, curPtr
        mov ebx, [ebx]
        mov edx, orgPtr
        cmp ebx, edx
        jne loadOrderLoop
    }
    return nullptr;
}
 
int main()
{
 
    // 1. Get Base Address of ntdll.dll
    PIMAGE_DOS_HEADER ntdllBase = GetBaseAddress(L"ntdll.dll");
    if (ntdllBase == nullptr) {
        printf("[-] Failed to get ntdll.dll base address\n");
        return -1;
    }
    printf("[+] ntdll.dll : 0x%08x\n", (DWORD)ntdllBase);
    
    // 2. Parse Module - IMAGE_NT_HEADERS
    PIMAGE_NT_HEADERS peSignature = (PIMAGE_NT_HEADERS)((PBYTE)ntdllBase + ntdllBase->e_lfanew);
    if (peSignature->Signature != 'EP') {
        printf("[-] Failed to find PE Signature\n");
        return -1;
    }
    printf("[+] PE Signature : 0x%08x\n", (DWORD)peSignature);
 
    // 3. Parse Module - Export Table
    PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)ntdllBase + peSignature->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    DWORD sizeofIED = peSignature->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    printf("[+] Export Table : 0x%08x, Size : 0x%08x\n", (DWORD)pIED, sizeofIED);
    
    // 4. Find LdrGetProcedureAddress Function
    PDWORD funcAddresses      = (PDWORD)((PBYTE)ntdllBase + pIED->AddressOfFunctions);
    PDWORD funcNames          = (PDWORD)((PBYTE)ntdllBase + pIED->AddressOfNames);
    PWORD  funcOrdinals       = (PWORD)((PBYTE)ntdllBase + pIED->AddressOfNameOrdinals);
    void(__stdcall *pLdrGetProcedureAddress)(HMODULE, PANSI_STRING, DWORD, PVOID) = nullptr;
    for (unsigned int i = 0; i < pIED->NumberOfFunctions; i++)
    {
        if (!strcmp("LdrGetProcedureAddress", (char *)((PBYTE)ntdllBase + funcNames[i]))) 
        {
            pLdrGetProcedureAddress = (void(__stdcall *)(HMODULE, PANSI_STRING, DWORD, PVOID))((PBYTE)ntdllBase + funcAddresses[funcOrdinals[i]]);
            break;
        }
    }
    if (pLdrGetProcedureAddress == nullptr)
    {
        printf("[-] Failed to find LdrGetProcedureAddress\n");
        return -1;
    }
    printf("[+] LdrGetProcedureAddress : 0x%08x\n", (DWORD)pLdrGetProcedureAddress);
    
    // 5. Find Functions With LdrGetProcedureAddress
    HMODULE hKernel32 = (HMODULE)GetBaseAddress(L"kernel32.dll");
    if (hKernel32 == nullptr)
    {
        printf("[-] Can't find kernel32.dll\n");
        return -1;
    }
    printf("[+] kernel32.dll : 0x%08x\n", (DWORD)hKernel32);
 
    ANSI_STRING targetFunction;
    RtlInitAnsiString(&targetFunction, "CreateFileA");
    HANDLE(__stdcall *pCreateFileA)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE) = nullptr;
    pLdrGetProcedureAddress(hKernel32, &targetFunction, 0&pCreateFileA);
    printf("[+] CreateFileA : 0x%p\n", pCreateFileA);
 
    RtlInitAnsiString(&targetFunction, "WriteFile");
    BOOL(__stdcall *pWriteFile)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED) = nullptr;
    pLdrGetProcedureAddress(hKernel32, &targetFunction, 0&pWriteFile);
    printf("[+] WriteFile : 0x%p\n", pWriteFile);
 
    RtlInitAnsiString(&targetFunction, "CloseHandle");
    BOOL(__stdcall *pCloseHandle)(HANDLE) = nullptr;
    pLdrGetProcedureAddress(hKernel32, &targetFunction, 0&pCloseHandle);
    printf("[+] CloseHandle : 0x%p\n", pCloseHandle);
 
    // 6. Use Functions
    DWORD Length = 0;
    HANDLE hFile = pCreateFileA("Test.txt", GENERIC_ALL, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    pWriteFile(hFile, "PEB Test String"15&Length, NULL);
    pCloseHandle(hFile);
    return 0;
}


 전체적인 소스코드는 위와 같습니다. 내용을 요약하면 ntdll.dll 모듈의 BaseAddress를 이용하여 PE 헤더를 파싱하고, Export Table을 찾아서 여기서 LdrGetProcedureAddress 함수의 주소를 얻어온 다음 이 API를 이용하여 원하는 함수 주소를 가져와 사용하는 것입니다. 

빌드를 위해서는 소스코드 내에서 RtlInitAnsiString을 사용하기 때문에 [Visual Studio 프로젝트 속성->링커->입력->추가 종속성]에 'ntdll.lib'을 넣어줘야 합니다.


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
PIMAGE_DOS_HEADER GetBaseAddress(const wchar_t *moduleName)
{
    
    PLDR_DATA_TABLE_ENTRY orgPtr = nullptr;
    PLDR_DATA_TABLE_ENTRY curPtr = nullptr;
    wchar_t *foundDllName = nullptr;
    __asm
    {
        mov eax, fs:[0x18]        // TIB
        mov eax, [eax + 0x30]    // TIB->PEB
        mov eax, [eax + 0x0C]     // PEB->Ldr
        lea ebx, [eax + 0x1C]    // Ldr->InInitializationOrderLinks
        mov orgPtr, ebx
    loadOrderLoop :
        mov edx, [ebx]            // InInitializationOrderLinks->Flink
        mov curPtr, edx
        mov edx, [edx + 0x20]   // LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer
        test edx, edx
        je loadOrderFailed
        mov foundDllName, edx
    }
    if(!_wcsicmp(foundDllName, moduleName))
        return (PIMAGE_DOS_HEADER)curPtr->InMemoryOrderLinks.Flink; // -0x10, as used InInitializationOrderLinks
    __asm{
    loadOrderFailed :
        mov ebx, curPtr
        mov ebx, [ebx]
        mov edx, orgPtr
        cmp ebx, edx
        jne loadOrderLoop
    }
    return nullptr;
}


 가장 먼저 볼 부분은 GetBaseAddress라고 이름붙인 이 함수입니다. 이전 글에서 사용한 방식과 완전히 동일한 방법을 사용하여 원하는 모듈의 LDR_DATA_TABLE_ENTRY를 찾은 후 BaseAddress를 가져오는 함수입니다. InLoadOrderLinks나 InMemoryOrderLinks가 아닌 InInitializationOrderLinks를 사용한 이유는 Windows 7 환경에서 실행시켰을 때, InLoadOrderLinks나 InMemoryOrderLinks에서는 ntdll.dll이 순환 연결 리스트 내부에 존재하지 않았기 때문입니다. 여기에서의 내용이 관련이 있을지도 모르겠습니다. 그래서인지 실제 악성코드에서도 InInitializationOrderLinks를 사용했었습니다.


1
2
3
4
5
6
7
    // 1. Get Base Address of ntdll.dll
    PIMAGE_DOS_HEADER ntdllBase = GetBaseAddress(L"ntdll.dll");
    if (ntdllBase == nullptr) {
        printf("[-] Failed to get ntdll.dll base address\n");
        return -1;
    }
    printf("[+] ntdll.dll : 0x%08x\n", (DWORD)ntdllBase);


 위에서 설명한 GetBaseAddress 함수에 L"ntdll.dll"을 인자로 넘겨 ntdll.dll의 BaseAddress를 가져옵니다. BaseAddress는 모듈이 메모리에 매핑된 기준점이 되는 주소이기 때문에 해당 메모리 주소에 접근하면 모듈 바이너리를 통째로 보는것과 다름없습니다. PE 헤더 파싱을 위해 PIMAGE_DOS_HEADER형으로 받아줍니다.


1
2
3
4
5
6
7
    // 2. Parse Module - IMAGE_NT_HEADERS
    PIMAGE_NT_HEADERS peSignature = (PIMAGE_NT_HEADERS)((PBYTE)ntdllBase + ntdllBase->e_lfanew);
    if (peSignature->Signature != 'EP') {
        printf("[-] Failed to find PE Signature\n");
        return -1;
    }
    printf("[+] PE Signature : 0x%08x\n", (DWORD)peSignature);


그렇게 받아온 모듈을 파싱하여 IMAGE_DOS_HEADER에서 IMAGE_NT_HEADER를 찾아내어 마찬가지로 PIMAGE_NT_HEADER형으로 받아줍니다.


1
2
3
4
    // 3. Parse Module - Export Table
    PIMAGE_EXPORT_DIRECTORY pIED = (PIMAGE_EXPORT_DIRECTORY)((PBYTE)ntdllBase + peSignature->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
    DWORD sizeofIED = peSignature->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].Size;
    printf("[+] Export Table : 0x%08x, Size : 0x%08x\n", (DWORD)pIED, sizeofIED);


 DLL 파일이므로 Export Table에 제공하는 API의 이름 및 RVA가 작성되어 있을 것입니다. 이를 얻어오기 위해 우선 Export Table을 가져옵니다. 딱히 쓰지는 않지만 가져오는김에 Export Table의 크기도 구해봤습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    // 4. Find LdrGetProcedureAddress Function
    PDWORD funcAddresses      = (PDWORD)((PBYTE)ntdllBase + pIED->AddressOfFunctions);
    PDWORD funcNames          = (PDWORD)((PBYTE)ntdllBase + pIED->AddressOfNames);
    PWORD  funcOrdinals       = (PWORD)((PBYTE)ntdllBase + pIED->AddressOfNameOrdinals);
    void(__stdcall *pLdrGetProcedureAddress)(HMODULE, PANSI_STRING, DWORD, PVOID) = nullptr;
    for (unsigned int i = 0; i < pIED->NumberOfFunctions; i++)
    {
        if (!strcmp("LdrGetProcedureAddress", (char *)((PBYTE)ntdllBase + funcNames[i]))) 
        {
            pLdrGetProcedureAddress = (void(__stdcall *)(HMODULE, PANSI_STRING, DWORD, PVOID))((PBYTE)ntdllBase + funcAddresses[funcOrdinals[i]]);
            break;
        }
    }
    if (pLdrGetProcedureAddress == nullptr)
    {
        printf("[-] Failed to find LdrGetProcedureAddress\n");
        return -1;
    }
    printf("[+] LdrGetProcedureAddress : 0x%08x\n", (DWORD)pLdrGetProcedureAddress);


 그렇게 파싱한 Export Table 내부에서 LdrGetProcedureAddress 함수를 찾아 만들어둔 함수 포인터에 저장합니다. Export Table 파싱은 여기를 참고하였습니다. 

 함수 포인터에 __stdcall을 붙인 이유는 VS 기본 설정상 함수 호출 규약(Calling Convention)이 __cdecl로 설정되어 있지만 WinAPI의 경우 __stdcall을 사용하기 때문입니다. 이를 지정하지 않을 경우 스택 정리가 엉망이 되어 가져온 함수를 호출하고 난 이후가 정상 작동하지 않습니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
    // 5. Find Functions With LdrGetProcedureAddress
    HMODULE hKernel32 = (HMODULE)GetBaseAddress(L"kernel32.dll");
    if (hKernel32 == nullptr)
    {
        printf("[-] Can't find kernel32.dll\n");
        return -1;
    }
    printf("[+] kernel32.dll : 0x%08x\n", (DWORD)hKernel32);
 
    ANSI_STRING targetFunction;
    RtlInitAnsiString(&targetFunction, "CreateFileA");
    HANDLE(__stdcall *pCreateFileA)(LPCSTR, DWORD, DWORD, LPSECURITY_ATTRIBUTES, DWORD, DWORD, HANDLE) = nullptr;
    pLdrGetProcedureAddress(hKernel32, &targetFunction, 0&pCreateFileA);
    printf("[+] CreateFileA : 0x%p\n", pCreateFileA);
 
    RtlInitAnsiString(&targetFunction, "WriteFile");
    BOOL(__stdcall *pWriteFile)(HANDLE, LPCVOID, DWORD, LPDWORD, LPOVERLAPPED) = nullptr;
    pLdrGetProcedureAddress(hKernel32, &targetFunction, 0&pWriteFile);
    printf("[+] WriteFile : 0x%p\n", pWriteFile);
 
    RtlInitAnsiString(&targetFunction, "CloseHandle");
    BOOL(__stdcall *pCloseHandle)(HANDLE) = nullptr;
    pLdrGetProcedureAddress(hKernel32, &targetFunction, 0&pCloseHandle);
    printf("[+] CloseHandle : 0x%p\n", pCloseHandle);


 이제 위에서 구한 LdrGetProcedureAddress 함수 주소를 이용하여 원하는 함수의 주소를 찾아올 수 있습니다.

 LdrGetProcedureAddress는 첫번째 인자로 모듈 핸들, 두 번째 인자로 ANSI_STRING으로 만든 함수명, 세 번째는 Ordinal이라는 파라미터(이지만 0으로 넘김), 마지막에 결과를 전달받을 함수 포인터의 주소값을 넣으면 됩니다. 여기서 첫 번째 인자인 모듈 핸들의 경우, 사실 놀랍게도 HMODULE 타입은 모듈의 Base Address와 완전히 같은 값이기 때문에(https://stackoverflow.com/questions/9545732/what-is-hmodule) GetBaseAddress 함수의 결과를 HMODULE로 캐스팅하기만 하면 핸들로 사용할 수 있습니다. 이렇게 원하는 함수 주소를 구할 수 있습니다.


1
2
3
4
5
    // 6. Use Functions
    DWORD Length = 0;
    HANDLE hFile = pCreateFileA("Test.txt", GENERIC_ALL, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    pWriteFile(hFile, "PEB Test String"15&Length, NULL);
    pCloseHandle(hFile);


 마지막으로 그렇게 받아온 함수를 사용하기만 하면 됩니다. 저는 CreateFileA / WriteFile / CloseHandle을 가져와서 테스트용 텍스트 파일을 만들고, 쓰고, 정상적으로 닫는 작업까지 진행해 봤습니다.


[그림 2. 프로그램 실행 결과]

 결과는 위와 같이, 정상적으로 텍스트 파일이 만들어졌습니다.


[그림 3. IAT]

 위는 빌드한 파일의 IAT입니다. kernel32.dll 내부에서 임포트하는 함수 중 어딜 봐도 GetProcAddress나 CreateFileA, WriteFile, CloseHandle은 없는 것을 볼 수 있습니다. 이제 함수명과 위 루틴들을 조금만 난독화하면 분석가들이 정적으로 분석하기 무척 까다로워지는 환경을 만들 수 있겠습니다. :)

'Analysis > Technique' 카테고리의 다른 글

TIB, PEB를 이용해 로드된 DLL 정보 가져오기  (0) 2018.10.22
CFG(Control Flow Guard)  (0) 2018.08.29
GS + DEP + SafeSEH + SEHOP 우회 Exploit  (0) 2018.07.25
GS + SafeSEH 우회 SEH Overwrite Exploit  (0) 2018.07.11
SEH Overwrite  (0) 2018.05.29
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,

 최근 악성코드를 분석해 보던 중, IAT에 Import하는 함수가 하나도 없는데 WinAPI를 자유자재로 사용하는 것을 보고 방법이 궁금해져서 공부한 내용을 작성하기로 했습니다. 여기서는 TIB, PEB를 이용해 API를 사용하지 않고 현재 로드된 모듈의 핸들을 가져오는 방법에 대해서 알아보겠습니다. 이후 추가로 글을 작성하여 자신이 원하는 WinAPI의 주소를 가져오는 방법까지 알아보겠습니다.


 API 없이 로드된 정보를 가져오기 위해서는 우선 TIB에 대하여 알아 두어야 합니다.

 TIB(Thread Information Block)는 현재 실행 중인 Thread에 대한 정보를 저장하고 있는 자료 구조이며, TEB(Thread Environment Block)이라고도 합니다. TIB의 경우 SEH Overwrite를 공부하면서 몇번 본 적이 있습니다. (SEH Overwrite : http://5kyc1ad.tistory.com/308)

 이 TIB의 주소는 x86 환경에서 FS 레지스터에 저장되며, 구조는 다음과 같습니다.


[그림 1. Thread Information Block (출처)]


 특이하게도 FS 자체에 TIB의 주소가 담겨 있는데,  FS:[0x18]에 TIB(또는 Thread Environment Block)의 주소가 또 존재하는 것을 볼 수 있습니다. 여기서도 그렇게 할 것이지만, 보통 TIB의 멤버에 접근할 때는 FS:[0x18]을 한번 거쳐 TIB의 주소를 얻고 거기에서 offset을 이용하여 접근합니다. 예를 들어 TIB + 0x30에 위치한 PEB에 접근하고 싶은 경우, 단순히 FS:[0x30]을 하는 것이 아닌 *(FS:[0x18] + 0x30) 이렇게 접근하는 것입니다. 이렇게 하는 이유는 차후 FS가 가리키는 구조체가 TIB가 아닌 다른 구조체로 변경된다고 하더라도 해당 구조체의 0x18 Offset에 TIB의 주소를 넣어주기만 하면 기존의 코드도 정상 동작할 수 있기 때문입니다(간단히 말하면 호환성을 높이기 위해서입니다). 모듈 정보를 얻기 위해서는 PEB가 필요하므로 이렇게 얻은 PEB를 이용하여 계속 진행하겠습니다.

 - PEB 까지 *(FS:[0x18] + 0x30) 로 접근했습니다.


[그림 2. Process Environment Block ()]


 PEB(Process Environment Block)의 구조는 위와 같습니다. 여기서 사용할 멤버는 0x0C offset에 존재하는 Ldr 이라는 포인터 변수입니다. Ldr은 _PEB_LDR_DATA라는 struct를 가리키는 포인터로, 현재 프로세스에서 로드한 모듈들에 대한 정보를 담고 있습니다.


 - Ldr 까지 (*(FS:[0x18] + 0x30) + 0x0C) 로 접근했습니다.


[그림 3. _PEB_LDR_DATA (출처)]


 _PEB_LDR_DATA는 위와 같은 구조이며, LIST_ENTRY 멤버 셋을 갖고 있는 것을 볼 수 있습니다. 이 LIST_ENTRY는 멤버로 Flink와 Blink를 가지는 순환 참조 리스트로, LDR_DATA_TABLE_ENTRY에서는 LIST_ENTRY를 struct의 멤버 변수로 사용하여 현재 사용중인 모듈에 대한 정보를 담은 LDR_DATA_TABLE_ENTRY들을 순환 참조 리스트로 연결하는 역할을 합니다. 

LDR_DATA_TABLE_ENTRY의 구조는 다음과 같습니다. 


[그림 4. LDR_DATA_TABLE_ENTRY (출처)]


 _PEB_LDR_DATA와 마찬가지로 LIST_ENTRY 구조체를 다른 이름으로 세 개 갖고 있는것을 볼 수 있는데, 이는 모듈의 로드된 순서, 메모리에 매핑된 순서, 초기화된 순서대로 각각 다른 순환 참조 리스트에 LDR_DATA_TABLE_ENTRY들을 저장하고 있기 때문입니다. 여기서, InInitializationOrderLinks의 경우 다른 LIST_ENTRY와 달리 첫 번째 LDR_DATA_TABLE_ENTRY가 아닌 두 번째 LDR_DATA_TABLE_ENTRY를 가리킨다고 합니다. (http://limjunyoung.tistory.com/186) 그래서 출력해보면 InInitializationOrderLinks만 출력되는 결과가 다릅니다.


[그림 5. 전체적인 연결 구조]


 그리고 각각의 LIST_ENTRY들은 같은 종류끼리 연결되어 있기 때문에, InLoadOrderLinks의 Flink나 Blink는 다음, 혹은 이전 노드의 InLoadOrderLinks를 가리키며, InMemoryOrderLinks나 InInitializationOrderLinks도 마찬가지가 됩니다. 이는 접근할 LDR_DATA_TABLE_ENTRY의 멤버 변수가 같더라도 어떤 LIST_ENTRY를 사용하느냐에 따라 Offset이 달라진다는 이야기입니다. 이해를 돕기 위해 아래 그림을 첨부하였습니다.


[그림 6. LDR_DATA_TABLE_ENTRY Offset]


 위 그림에서와 같이, InLoadOrderLinks를 사용하여 LDR_DATA_TABLE_ENTRY에 접근했을 때는 평범하게 LDR_DAT_TABLE_ENTRY에서의 멤버 변수 Offset을 사용하면 되지만, InMemoryOrderLinks나 InInitializationOrderLinks를 사용할 경우 각각 0x08, 0x10만큼의 Offset이 추가되기 때문에 그만큼을 뺀 Offset으로 접근해야 같은 멤버 변수로의 접근이 가능합니다. 위 이미지는 BaseDllName.Buffer 에 접근할 때, 어떤 LIST_ENTRY를 기준으로 하느냐에 따라서 Offset이 어떻게 달라지는지를 표현한 것입니다.


 마지막으로, 지금까지 정리한 내용을 이용하여 현재 로드된 모듈의 BaseName을 출력하는 코드를 작성해 보았습니다.


main.cpp

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
#include <stdio.h>
 
int main()
{
    void *orgPtr = nullptr;
    void *curPtr = nullptr;
    // InLoadOrderLinks
    puts("[*] InLoadOrderLinks");
    __asm
    {
        mov eax, fs:[0x18]        // TIB
        mov eax, [eax + 0x30]    // TIB->PEB
        mov eax, [eax + 0x0C]     // PEB->Ldr
        lea ebx, [eax + 0x0C]    // Ldr->InLoadOrderLinks
        mov orgPtr, ebx            
    loadOrderLoop:
        mov edx, [ebx]            // InLoadOrderLinks->Flink
        mov curPtr, edx            
        mov edx, [edx + 0x30]   // LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer
        test edx, edx
        je loadOrderFailed
        push edx
        call _putws                // Print module name
    loadOrderFailed:
        mov ebx, curPtr
        mov ebx, [ebx]
        mov edx, orgPtr
        cmp ebx, edx
        jne loadOrderLoop
    }
 
    // InMemoryOrderLinks
    puts("[*] InMemoryOrderLinks");
    __asm
    {
        mov eax, fs:[0x18]        // TIB
        mov eax, [eax + 0x30]    // TIB->PEB
        mov eax, [eax + 0x0C]     // PEB->Ldr
        lea ebx, [eax + 0x14]    // Ldr->InMemoryOrderLinks
        mov orgPtr, ebx
    memoryOrderLoop :
        mov edx, [ebx]            // InMemoryOrderLinks->Flink
        mov curPtr, edx
        mov edx, [edx + 0x28]   // LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer
        test edx, edx
        je memoryOrderFailed
        push edx
        call _putws                // Print module name
    memoryOrderFailed:
        mov ebx, curPtr
        mov ebx, [ebx]
        mov edx, orgPtr
        cmp ebx, edx
        jne memoryOrderLoop
    }
 
    // InInitializationOrderLinks
    puts("[*] InInitializationOrderLinks");
    __asm
    {
        mov eax, fs:[0x18]        // TIB
        mov eax, [eax + 0x30]    // TIB->PEB
        mov eax, [eax + 0x0C]     // PEB->Ldr
        lea ebx, [eax + 0x1C]    // Ldr->InInitializationOrderLinks
        mov orgPtr, ebx
    InitOrderLoop:
        mov edx, [ebx]            // InInitializationOrderLinks->Flink
        mov curPtr, edx
        mov edx, [edx + 0x20]   // LDR_DATA_TABLE_ENTRY.BaseDllName.Buffer
        test edx, edx
        je InitOrderFailed
        push edx
        call _putws                // Print module name
    InitOrderFailed:
        mov ebx, curPtr
        mov ebx, [ebx]
        mov edx, orgPtr
        cmp ebx, edx
        jne InitOrderLoop
    }
    return 0;
}


 어떤 LIST_ENTRY를 사용하느냐에 따라 사용하는 Offset이 달라지는 모습을 볼 수 있습니다. 

프로그램의 실행 결과는 다음과 같습니다.



 이렇게 WinAPI를 전혀 사용하지 않고도 로드된 모듈에 대한 정보를 알아오는 데에 성공했습니다. 추후엔 이렇게 가져온 모듈 정보를 이용해 다른 API의 주소를 가져와 사용하는 방법에 대해 알아보겠습니다.

'Analysis > Technique' 카테고리의 다른 글

GetProcAddress 없이 API 주소 가져오기  (0) 2018.10.24
CFG(Control Flow Guard)  (0) 2018.08.29
GS + DEP + SafeSEH + SEHOP 우회 Exploit  (0) 2018.07.25
GS + SafeSEH 우회 SEH Overwrite Exploit  (0) 2018.07.11
SEH Overwrite  (0) 2018.05.29
블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,


 사실 무척 간단한 테크닉인데, 지금까지 모르고 계속 모든 struct를 직접 만들었다가 이제야 알게 되어 작성해 본다.


[직접 만드느라고 고생한 struct PEB_LDR_DATA]



 위 이미지는 직접 한땀한땀 만들어준 struct이다. 악성코드를 분석하는 도중 PEB에 접근하여 LDR을 가져오는 루틴이 있었고, 이를 좀더 보기 쉽게 하기 위해서 struct를 만든 것이다. LIST_ENTRY라는 struct도 따로 필요해서 직접 만들어 주었고, 여러모로 귀찮은 작업이 많았다. 그런데 알고 보니 PEB_LDR_DATA와 같은 윈도우에서 사용하는 범용적인 struct들은 이미 IDA에서 전부 만들어뒀다고 한다.



[Type libraries]


 View->Open subviews->Type libraries 혹은 단축키로 Shift+F11을 눌러보자. 'Loaded Type Libraries'라는 탭이 하나 추가되는 것을 볼 수 있다.



[Load New Type Library]


 우클릭->Load type library 또는 단축키 Ins를 누르면 위와 같은 창이 뜨면서 원하는 타입 라이브러리를 추가할 수 있다. 상당히 많은 라이브러리들이 있는데, 여기서 자기가 원하는 타입이나 struct가 선언되어 있는 라이브러리를 선택해서 로드하기만 하면 직접 struct 구조를 생성해 줄 필요 없이 그대로 사용 가능하다. 이제 위의 ntapi를 로드한 후, struct 탭에서 해당 struct를 생성하거나 변수에 타입을 지정해보자.



[자동으로 로드되는 타입들]


 놀랍게도 위와 같이 자동으로 struct가 생성되고 반영된다. 

나는 지금까지 무슨 삽질을 하고 있었던 것인가... 역시 머리가 나쁘면 몸이 고생한다.






블로그 이미지

__미니__

E-mail : skyclad0x7b7@gmail.com 나와 계약해서 슈퍼 하-카가 되어 주지 않을래?

,