그냥 전에 올렸던 PyV8을 이용한 스크립트 분석기를 계속 개발하다 보니 여러 모로 개선해야 할 점이 많아서 좀 끄적거려본다.

 특정 상황에만 행위를 하거나 True/False를 체크하여 행위를 한다거나 하는 악성코드도 있어서 이런걸 커스터마이징할 수 있도록 해 볼 예정... 또 자바스크립트 내에 주석 등을 통해 Dummy Code를 넣는다거나 하는 경우도 있는데 이런것도 정규식을 이용하던지 해서 좀 제거해봐야 할 듯 하다...

블로그 이미지

__미니__

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

,

[Python] PyV8을 이용한 Javascript 분석


여기에 작성된 글을 보고 이것을 이용하면 난독화된 악성 스크립트를 안전하고 효율적으로 분석이 가능하겠다는 생각이 들어서 비슷한 방식으로 만들어보기로 마음먹었다.


[PyV8은 여기에서 다운받을 수 있다. 마지막 업로드가 2012년인 것이 조금 걸리지만...]

 PyV8은 구글에서 만든 자바스크립트 엔진인 V8의 파이썬 래퍼로, 자바스크립트 코드를 해석하고 실행하여 그 결과를 파이썬에서 받아볼 수 있다. 하지만 PyV8은 자바스크립트 문법을 해석해 줄 뿐 운영체제의 API에 연결되어 있지 않기 때문에, 악성 스크립트에서 자주 사용하는 WScript.Shell 등을 사용하면 정의되지 않았다면서 에러가 발생한다. 그래서 직접 더미 API를 만들어서 넘겨주어야 하는 필요성이 있고, 위에서 언급했던 블로그에서는 각 모듈과 함수들을 직접 정의하여 넘겨주는 방식으로 제작했었다. 그런데 그렇게 만들게 되면 모든 모듈과 함수에 대응하는 클래스 및 메서드를 직접 다 만들어서 전달해야 한다는 불편함이 있었고, 결국 내가 원하는 것은 '어떤 함수가 어떤 인자를 받아서 실행되었는가' 이기 때문에 전부 간단하게 추상화시킬 수 있다고 판단하여 결국 간단하게 추상화 하는 것에 성공하였다. 자세한 내용은 소스코드를 보면서 작성하겠다.


http://5kyc1ad.kr/files/ScriptAnalyzer.zip

(악성코드 분석하려고 만든건데 티스토리에서 악성코드라며 첨부파일을 막아버렸네요; 첨부파일은 제 서버 주소로 대체하겠습니다.

꽤 시간이 흐른 뒤에 발견해버린 터라 실제 본문의 스크립트와는 상당히 달라졌을 수 있습니다.)

ScriptAnalyzer.py

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
import os
 
import PyV8
 
from DummyAPI.Global import Global
 
class ScriptAnalyzer:
    def __init__(self):
        pass
 
    def __del__(self):
        pass
 
    def Analyze(self, targetJSFile):
        if not os.path.exists(targetJSFile):
            print "[-] Target File not found : %s" % (targetJSFile)
            return False
 
        with open(targetJSFile, "rb") as f:
            javaScript = f.read()
 
        ctx = PyV8.JSContext(Global())
        ctx.enter()
        ctx.eval(javaScript)
 
        return True
 
def main():
    scriptAnalyzer = ScriptAnalyzer()
    scriptAnalyzer.Analyze("..\\script4.js-")
 
if __name__ == '__main__':
    main()
cs

 PyV8을 이용하여 Javascript를 실행하는 분석기와 그를 이용하는 main 함수가 존재하는 소스코드 파일이다. 별다른건 없고 이후 나올 DummyAPI.Global 모듈에서 Global 클래스를 가져와 넘기는 것만 확인하면 된다.


Global.py

1
2
3
4
5
6
7
import PyV8
 
from DummyAPI.AbstractType import AbstractType
 
class Global(PyV8.JSClass):
    def __getattr__(self, name):
        return AbstractType(name)
cs

 추상화에 성공하여 무척 간결해져 버린 Global 클래스의 소스코드이다. 원래라면 클래스 멤버 변수로 각 모듈들에 대응하는 클래스의 인스턴스를 가지고 있어야 하지만, 어떤 모듈을 호출할 지 모르므로 __getattr__ 메서드를 정의하는 것으로 한번에 해결해 버렸다. __getattr__ 메서드는 classA.not_exist_value 처럼 클래스 내부에 존재하지 않는 변수, 혹은 메서드에 접근했을 경우 호출되는 함수이다. 현재 Global 클래스 내부에 저 함수 외에는 아무것도 정의되지 않았으므로 Javascript 내에서 어떤 모듈 혹은 Global 함수를 호출하던 간에 AbstractType(name) 을 리턴받게 될 것이다. AbstractType은 직접 작성한 타입으로, 다음 소스코드에서 확인이 가능하다.


AbstractType.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import PyV8
import Utils
 
class AbstractType(PyV8.JSClass):
    def __init__(self, thisName):
        self.thisName = thisName
 
    # Type is Function
    def __call__(self, *args, **kwargs):
        if len(args) > 0 and len(kwargs.keys()) > 0:
            print ("[*] %s(%s, %s)" % (self.thisName, Utils.argsToStr(args), Utils.kwargsToStr(kwargs)))
        elif len(kwargs.keys()) > 0:
            print ("[*] %s(%s)" % (self.thisName, Utils.kwargsToStr(kwargs)))
        else:
            print ("[*] %s(%s)" % (self.thisName, Utils.argsToStr(args)))
 
        if len(args) > 0:
            return AbstractType(args[0])
        else:
            return AbstractType("By_%s" % (self.thisName))
 
    # Type is Module
    def __getattr__(self, name):
        return AbstractType("%s.%s" % (self.thisName, name))
cs

 이번에 추상화에서 얻어낸 가장 큰 산물인 AbstractType 클래스이다. 생성자에서 호출한 이름을 인자로 받으며(Global.py 참고), __call__ 메서드를 정의하여 인스턴스가 함수로써 사용될 경우 *args, **kwargs 등 가변 길이 변수를 받아서 print로 이름과 함께 로깅하도록 하였다. 또한 Global.py에서처럼 __getattr__ 메서드를 정의하여 모듈 내의 함수에 접근하는 것과 같은 행위를 할 경우 현재 이름에 새 이름을 붙여 AbstractType 클래스를 새로 생성해 리턴하는 것으로 함수의 경우 위에서 정의한 __call__ 에 의해 자동으로 로깅이 되도록 하였다.


 이를 이용하면 따로 모듈과 함수를 직접 구현해주지 않고도 호출하는 문자열을 이용해 그때그때 상황에 맞는 AbstractType을 생성하여 로깅하는 것이 가능해지며, 악성 스크립트 분석에 큰 도움이 될 수 있다.


[실제 악성 스크립트 분석 결과]


 실제로 스크립트 분석 결과 구체적인 모듈이나 함수 정의는 전혀 하지 않았음에도 모듈 및 함수, 그 인자까지 전부 로깅되는 것을 볼 수 있다. 한 언어에 대한 이해가 깊어지면 이렇게 효율적이고 추상적인 코드 작성이 가능해진다는 것을 제대로 느낄 수 있는 프로젝트였다. 현재는 print로밖에 로깅을 진행하지 않지만 이후 파일이나 리턴 값으로 받을 수 있게 하면 자동 분석에도 응용이 가능할 것으로 보인다. 좀 더 고도화를 진행해 볼 예정이다.


블로그 이미지

__미니__

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

,


 기사 : https://www.bleepingcomputer.com/news/security/pubg-ransomware-decrypts-your-files-if-you-play-playerunknowns-battlegrounds/


 배틀그라운드를 일정 시간 플레이하면 파일을 복호화해주는 랜섬웨어가 등장해서 분석해 보았습니다. 진지하게 만든 건 아니고, 그냥 장난삼아 만든 것으로 보입니다.




PDF 보고서가 보이지 않는다면 아래의 '분석 보고서 열기' 버튼을 클릭하여 열람하시기 바랍니다.


블로그 이미지

__미니__

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

,

Schecter C-1 FR S Apocalypse 구매


[프리버드에서 판매중이었던 해당 모델, 현재는 품절]

쉑터 아포칼립스... 서스테이너가 달려 있는 상당히 고급진 모델입니다. 분명 몇달 전에 여기에 포스팅한 것과 같이 펜더 머스탱을 사서 기뻐하면서 열심히 치고 다녔는데, 아무 생각 없이 뮬 중고장터에서 돌아다니다가 이 모델을 반값 수준으로 팔고 있는 것을 보고 지름신이 강림하셔서 즉시 문자넣고 약속잡아서 사버렸습니다.


[집에 모셔둔 펜더 머스탱과 투샷]


 이렇게 놓고 보니까 확실히 24프렛 슈퍼스트랫이라 그런지 스케일이 훨씬 기네요. 핑거보드는 에보니 지판입니다.


[바디 부분 확대샷]


 사진에선 찍히지 않았지만 보통은 지판 정면에 그대로 박히는 프렛 마커가 이 기타에서는 왼쪽 측면, 즉 연주시 보이는 부분에 박혀 있습니다. 인레이에는 정삼각형 모양으로 15, 24프렛에 박혀있네요.  이 기타의 가장 큰 특징인 서스테이너는 건전지로 작동하는 액티브 픽업인데, 직접 써보니까 그냥 소리가 끊기지가 않습니다; 계속 쭉 뻗어서 소름이 돋더라구요. 머스탱은 스케일도 짧고 서스테인도 상당히 짧은 편이라 불만이 좀 있었는데 험버커 픽업 + 서스테이너까지 하니까 만족감이 확 차올랐습니다.


 노브는 볼륨 + 볼륨 + 톤으로 이루어져 있습니다. 3단계 픽업 스위치가 위쪽에, 아래에는 서스테이너 On/Off를 설정하는 2단계 토글 스위치와 세 가지 서스테이너의 모드를 설정하는 3단계 토글 스위치가 있습니다.  픽업은 역시 험버커로, Schecter USA Apocalypse-VI 라고 합니다. 잘은 모르겠지만 앰프 연결해놓고 쳐봤을때 험버커라 그런지 엄청 강력한 소리를 내주긴 하더군요.


[헤드 확대샷]


 헤드부분은 쉑터에서 흔히 보이는 모양이고 쉑터 다이아몬드 시리즈라고 박혀있습니다. 멋있네요.

 사실 쉑터를 사고 싶었던 이유는 이 분이 한동안 쉑터 캘리포니아 커스텀을 가지고 기타를 치셨는데 그게 너무 멋있었기 때문입니다. 지금은 다른 기타들도 많이 사용하고 계시지만 이분 영상을 제일 처음 접한게 캘컴 쓰실 적 영상이라 기억에 강하게 남았네요.

 매우 저렴한 가격으로 이런 좋은 기타를 구하게 되어서 너무 좋습니다. 아직 기타의 가치를 손이 따라가지 못하고 있지만 열심히 배워서 멋지게 곡을 연주할 수 있도록 해야겠습니다 :D



블로그 이미지

__미니__

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

,


 2017년 5월에 등장하여 큰 파장을 불러일으켰던 랜섬웨어 WannaCry를 분석해봤습니다.




 PDF 보고서가 보이지 않는다면 아래의 '분석 보고서 열기' 버튼을 클릭하여 열람하시기 바랍니다.



블로그 이미지

__미니__

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

,

Python TimedRotatingFileHandler.doRollOver() Error 32 문제


 Python의 logging 모듈에서 기본적으로 제공하는 TimedRotatingFileHandler를 사용하는 도중, 아래와 같은 에러가 발생했다.


https://stackoverflow.com/questions/29220435/windowserror-using-rotatingfilehandler-and-subprocess-popen


Traceback (most recent call last):

  File "c:\Python27\lib\logging\handlers.py", line 77, in emit

self.doRollover()

  File "c:\Python27\lib\logging\handlers.py", line 142, in doRollover

    os.rename(self.baseFilename, dfn)

WindowsError: [Error 32] The process cannot access the file because it is being used by another process


 doRollOverTimedRotatingFileHandler에 지정한 기간이 지났을 경우 사용하던 파일을 백업하고 새로운 로그 파일을 생성하도록 하는 함수이다. Error 32는 다른 프로세스나 스레드에서 같은 파일의 핸들러를 가지고 있기 때문에 변경이 불가능할 때 발생하는 에러이다. 당시 Multiprocessing 환경에서 사용중이었기 때문에 이것이 문제인가 싶어서 프로세스별로 다른 Logger를 사용하도록 변경해 보았으나 그래도 마찬가지로 발생하였고, 이를 해결하려고 수없이 돌아다녔다.

=== [2019-04-03] 업데이트

한동안 발생하지 않아서 방심하고 있었는데 여전히 버그는 발생했다. Error 32가 뜻하는 바도 그렇고 분명 뭔가 멀티프로세스 환경에서 다른 프로세스가 핸들을 물고 있어서 Rotating이 되지 않는 것이 분명했으므로 이를 확인하기 위해 테스트 코드를 짜 봤다.


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
import os
import time
import logging
import logging.handlers
import multiprocessing
 
def GetParentDirectory(filePath):
    return os.path.sep.join(filePath.split(os.path.sep)[:-1])
 
def Mkdirs(dirPath):
    if not os.path.exists(dirPath):
        os.makedirs(dirPath)
 
def CreateLogger(loggerName):
    # Create Logger
    logger = logging.getLogger(loggerName)
    if len(logger.handlers) > 0:
        return logger
 
    logger.setLevel(logging.DEBUG)
    formatter = logging.Formatter('\n[%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s')
 
    # Create Handlers
    logPath = os.path.join(os.path.realpath(""), "logs", loggerName + ".log")
    Mkdirs(GetParentDirectory(logPath))
    streamHandler = logging.StreamHandler()
    streamHandler.setLevel(logging.DEBUG)
    streamHandler.setFormatter(formatter)
    rotatingHandler = logging.handlers.TimedRotatingFileHandler(logPath, when='M', backupCount=10)
    rotatingHandler.setLevel(logging.DEBUG)
    rotatingHandler.setFormatter(formatter)
 
    # Add handlers to logger
    logger.addHandler(streamHandler)
    logger.addHandler(rotatingHandler)
    return logger
 
def LoggingTest(pid):
    subLogger = CreateLogger("Process_%d" % (pid))
    count = 0
    while True:
        subLogger.info("Log : 0x%08x" % count)
        time.sleep(5)
        count += 1
 
def main():
    PROC_COUNT = 10
    mainLogger = CreateLogger("Main-Process")
 
    mainLogger.info("Start Creating Process")
    procList = []
    for i in range(PROC_COUNT):
        procList.append(multiprocessing.Process(target=LoggingTest, args=(i,)))
        procList[i].start()
        time.sleep(0.5)
    mainLogger.info("End Creating Process")
 
    count = 0
    while True:
        mainLogger.info("Main-Log : 0x%08x" % count)
        time.sleep(5)
        count += 1
 
if __name__ == '__main__':
    main()
cs


 1분마다 Rotating하도록 설정하여 돌려봤는데, 아니나다를까 Main-Process 로거에서 doRollOver 에러가 발생한다. multiprocessing.Process로 생성한 자식 프로세스에서 main의 mainLogger 핸들러를 물고 있는 것이 아닌가 하는데... 


 돌려놓고 Rotating이 발생하기 전에 Process Hacker를 이용해 파일 핸들러를 찍어본 모습이다. Main과 Sub 모두 자신의 핸들러를 가지고 있는데, Sub에서 Main의 핸들러를 물고 있는 것을 볼 수 있다.



 doRollOver Error32가 발생한 이후 찍은 모습이다. Main에서는 Rotating을 위해 핸들러를 닫았지만 Sub에서는 계속 물고 있는 것을 볼 수 있다. 이래서 Error32가 발생한 것으로 파악할 수 있었다. 문제는 이걸 어떻게 해결하냐는 것인데... 파이썬 logging 라이브러리 자체가 멀티 프로세싱 환경을 고려하지 않고 만들어졌기 때문에 발생한 일이라 간단하게 해결하긴 힘들어 보인다. 여기(https://docs.python.org/ko/3/howto/logging-cookbook.html)에서는 소켓에 로그를 작성하고 이를 읽어서 기록하는 스레드를 따로 만들어 두고 돌리는 방식으로 처리가 가능함을 설명하고 있으며 실제로 multilog(https://pypi.org/project/multilog/)라고 하는 모듈이 이미 존재한다. 어느 쪽이든 로깅 모듈이 쓰이던 곳이 한두군데가 아니었기 때문에 고치기는 힘들겠지만 최대한 노력해 봐야겠다 (._.


 Windows 환경 해결법

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
import os
import time
import platform
import logging
import logging.handlers
import multiprocessing
 
def GetParentDirectory(filePath):
    return os.path.sep.join(filePath.split(os.path.sep)[:-1])
 
def Mkdirs(dirPath):
    if not os.path.exists(dirPath):
        os.makedirs(dirPath)
 
def CreateLogger(loggerName):
    # Create Logger
    logger = logging.getLogger(loggerName)
    if len(logger.handlers) > 0:
        print "Already Exists : %s" % (loggerName)
        return logger
    print "Non-Exists : %s" % (loggerName)
    logger.setLevel(logging.DEBUG)
    formatter = logging.Formatter('\n[%(levelname)s|%(name)s|%(filename)s:%(lineno)s] %(asctime)s > %(message)s')
 
    # Create Handlers
    logPath = os.path.join(os.path.realpath(""), "logs", loggerName + ".log")
    Mkdirs(GetParentDirectory(logPath))
    streamHandler = logging.StreamHandler() 
    streamHandler.setLevel(logging.DEBUG)
    streamHandler.setFormatter(formatter)
    rotatingHandler = logging.handlers.TimedRotatingFileHandler(logPath, when='M', backupCount=10)
    rotatingHandler.setLevel(logging.DEBUG)
    rotatingHandler.setFormatter(formatter)
    if platform.system() == 'Windows':
        import msvcrt
        import win32api
        import win32con
        win32api.SetHandleInformation(msvcrt.get_osfhandle(rotatingHandler.stream.fileno()), win32con.HANDLE_FLAG_INHERIT, 0)
 
    # Add handlers to logger
    logger.addHandler(streamHandler)
    logger.addHandler(rotatingHandler)
    
    return logger
 
def LoggingTest(pid):
    subLogger = CreateLogger("Process_%d" % (pid))
    count = 0
    while True:
        subLogger.info("Log : 0x%08x" % count)
        time.sleep(5)
        count += 1
 
def main():
    PROC_COUNT = 10
    mainLogger = CreateLogger("Main-Process")
 
    mainLogger.info("Start Creating Process")
    procList = []
    for i in range(PROC_COUNT):
        procList.append(multiprocessing.Process(target=LoggingTest, args=(i,)))
        procList[i].start()
        time.sleep(0.5)
    mainLogger.info("End Creating Process")
 
    count = 0
    while True:
        mainLogger.info("Main-Log : 0x%08x" % count)
        time.sleep(5)
        count += 1
 
if __name__ == '__main__':
    main()
cs


CreateLogger 함수 내부, TimedRotatingFileHandler를 생성하는 부분 밑에 다음의 코드가 추가되었다.

1
2
3
4
5
    if platform.system() == 'Windows':
        import msvcrt
        import win32api
        import win32con
        win32api.SetHandleInformation(msvcrt.get_osfhandle(rotatingHandler.stream.fileno()), win32con.HANDLE_FLAG_INHERIT, 0)
cs


파이썬 2.7~3.4에서는 multiprocessing으로 프로세스를 생성했을 때, 자식 프로세스가 부모 프로세스의 핸들러들을 상속받아서 같이 물고 있기 때문에 위와 같은 버그가 발생했었는데, 핸들러의 File Handle을 직접 받아와서 SetHandleInformation API를 사용해 상속하지 않도록 설정해주면 자식 프로세스가 생성될때 받아가지 않는다. (참고 : https://stackoverflow.com/questions/948119/preventing-file-handle-inheritance-in-multiprocessing-lib)


블로그 이미지

__미니__

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

,

 몇달 전에 구매한 일펜 머스탱의 셋업을 맡기기 위해서 건대입구역 근처에 위치한 닥터에이에 다녀왔습니다. 뮤직포스에서 리페어 팀장직을 맡고 계시다가 2013년에 마치시고 작업실을 오픈하셨다고 하십니다. (뮤직포스는 잘은 모르지만 무척 고가의 하이엔드 기타를 취급하는 곳이라고만 알고 있습니다.) 네이버에서 블로그 운영도 하고 계시네요.

요기입니다 : https://blog.naver.com/sorktlfmsl2


 대학교 동아리에서 기타 치는 친구가 추천해줘서 가게 되었는데, 인터넷으로 검색해보니 실력이 정말 좋다는 말도 많지만 조금 권위적이고 불친절하다는 말도 가끔 보여서 조금 불안했습니다. 하지만 직접 가서 느껴본 바로는 전혀 그런 것 없이 친절하셨습니다 xD.


[닥터에이 위치]


 2호선 건대입구역 1번출구로 나와서 대략 10분정도 걸어가면 도착할 수 있는 위치에 있습니다. 먼 거리는 아니지만 제가 간 날은 하필이면 비가 와서 기타를 메고 가는 데에 조금 힘들었네요. (들어갈 때 문이 닫혀있고 외출중 팻말이 걸려있어서 아직 오픈하지 않은 줄 알았는데 아니었더군요)

 기타 치는 데에 큰 문제가 있는 것은 아니었지만 줄도 새로 갈고 싶었고 인토네이션이 맞지 않는 문제도 있어서 그 부분을 말씀드렸고, 줄은 엘릭서 010으로 교체했습니다. 사실 줄의 차이도 잘 모르지만(010이 009에 비해서 장력이 좀 더 강하다는 것만 압니다...) 엘릭서가 코팅이 되어 있어 오염에 강한 비싼 줄이라는 것을 어디서 주워들었기 때문에 이왕 하는거 좀 비싸더라도 좋은 걸로 하고 싶었습니다. 전화로 미리 예약 후 방문하면 되고 간단한 점검과 셋업은 1시간 내로 끝납니다. 그동안 나갔다가 와도 되고, 안에서 기다려도 됩니다. 저는 안에서 기다리는 것을 택했고, 처음으로 와본 리페어샵이라 주변을 둘러보는데 신기하기만 했습니다. 


[셋업 받으면서 찍은 기타]


 PRS나 존써 등 인터넷에서 사진과 영상으로만 봤던 고가의 악기들도 있었습니다. 위 사진의 가운데에 있는 PRS는 탑이 너무 예쁘고 취향이라 꼭 갖고 싶었던 녀석이네요.

 블로그에서 보면 테스트용 장비들이기 때문에 기다리면서 마음껏 쳐봐도 된다고는 하셨지만 일렉기타를 시작한지 얼마 되지도 않은 초보이기도 하고 저런 비싼 장비를 건드리는 것은 조금 무서워서 구경만 했습니다. 나중에 여유가 생기고 실력도 늘면 이런 기타로 멋들어지게 칠 수 있지 않을까 하는 기대감도 들었습니다.

 기타 구경하면서 돌아다니다 보니 셋팅이 모두 끝났고, 커다란 앰프에 물려서 테스트해보라고 넘겨주셨는데 사실 테스트라고 해도 아직 일렉기타를 잘 모르는 저에게는 '소리가 잘 나고, 드라이브 잘 먹고, 연습했던 곡 음색만 맞게 나오는지' 보는게 다였기 때문에 적당히 몇번 쳐보고, 드라이브 걸고 몇번 쳐보고 금방 테스트를 끝냈습니다. 앰프 소리는 확실히 무지막지하게 좋았습니다. 집이 원룸이라 앰프를 크게 틀어놓고 칠 수가 없어서 늘 헤드폰을 끼고 치는데, 정말 연습실을 하나 빌려서 쓰고 싶다는 생각까지 들 정도였습니다. 가격은 위에 링크해둔 블로그에 잘 나와 있으니 참고하시면 되겠습니다.


 몇달 치고는 있지만 아직도 일렉기타에 대해서는 잘 모르기 때문에 얼마나 주기를 가지고 셋업을 받는 것이 좋은지 등을 전혀 모르지만 나중에 셋업 받을때도 여기로 갈 것 같습니다. 실력이 좀 늘면 고가의 기타는 어떤 연주감이고 어떤 소리를 내는지도 한번 쳐보면서 느껴보고 싶네요. :D

블로그 이미지

__미니__

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

,


 출시 전부터 쭉 엄청난 인기를 끌고 있는 유명 게임 '배틀그라운드'의 핵으로 위장, 국내 게이머를 타겟팅한 악성코드를 분석했습니다.



[분석 보고서 - PDF]


 PDF 보고서가 보이지 않는다면 아래의 '분석 보고서 열기' 버튼을 클릭하여 열람하시기 바랍니다.



 아래 첨부파일은 악성코드 내부의 리소스를 복호화하는 데에 사용했던 C# 프로젝트 소스코드입니다.

필요하신 분은 다운받아서 참고하여 사용해주시면 되겠습니다.

PUBG_H_Resource_Parser.zip


블로그 이미지

__미니__

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

,



 2018년을 맞이해서 열심히 풀기 시작했던 Codefights.com 의 Arcade - Python 부분의 모든 문제를 클리어하였다.

전 문제가 영어로 나오는데다 가끔 문제 수준이 언어에 대한 이해도가 상당히 높아야 해결 가능한 경우가 있어서 여러 번 막혔지만, 그래도 몇시간 붙잡고 풀다 보니 전부 푸는데 성공할 수 있었다.



 그 중에서도 가장 재밌었던 챕터를 꼽으라면 55~64번째번째 문제가 속한 Drilling the Lists 가 아닐까 싶다. 그림에서도 보다시피 map, filter, reduce 및 lambda를 자유자재로 활용하여 짧고 간결하고 효율적인 코드를 작성하는 방법에 대한 문제들이다. 실제로 여기서 문제를 풀다 보니 회사에서 사용하는 파이썬 코드도 좀 더 간결하고 효율적으로 짤 수 있게 되었고, 활용의 폭이 매우 넓어졌다고 느끼고 있다. 매일 한 문제 이상 푸는 것을 목표로 풀어나가고 있는데, 이 속도대로라면 이번 년도가 끝나기 전 Arcade를 전부 클리어 가능하지 않을까 하고 기대하고 있다.

블로그 이미지

__미니__

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

,

파이썬 공부를 계속하다가 데코레이터(Decorator)를 공부하게 되었고, 그 내용을 정리하고자 포스팅한다.


여기 아주 엄청난 일을 하는 함수가 있다.

1
2
def HelloName(name):
    print "Hello, %s!" % (name)
cs

 무려 인자로 이름을 입력받아 인사를 해주는 무척 대단한 함수이다. 아무 문제 없이 함수를 사용하다 보니, 이제 만났을때 하는 인사 외에도 헤어질 때 하는 인사를 만들고 싶어졌다. 정말 열심히 공을 들여서 다음과 같은 함수를 만들어냈다.


1
2
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
cs

 만났을 때 인사하고, 헤어질 때 인사하고 나니 안부를 묻는 말도 만들고 싶어졌다. 또 열심히 공을 들여서 다음과 같은 함수를 만들어냈다.


1
2
def HowAreYouName(name):
    print "How are you, %s?" % (name)
cs

 뿌듯하게 바라보는 와중, 문득 머릿속에서 이 함수들의 기능만으로는 너무 밋밋하다는 생각이 들어서 출력하기 전에 위아래에 경계선을 출력하도록 만들고 싶어졌다. 그래서 만들어진 결과가 다음과 같다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
def HelloName(name):
    print "============================"
    print "Hello, %s!" % (name)
    print "============================"
 
def GoodbyeName(name):
    print "============================"
    print "Goodbye, %s!" % (name)
    print "============================"
 
def HowAreYouName(name):
    print "============================"
    print "How are you, %s?" % (name)
    print "============================"
cs

 딱 봐도 무엇인가 많이 반복되고 있고, 불편함이 느껴진다. 이 함수들은 모두 다른 함수들이지만, 공통적으로 위아래에 경계선을 출력한다는 행위는 동일하다.

 파이썬에서 함수는 다른 함수의 인자로 넘길 수도 있고, 리턴할 수도 있는 특징을 가지고 있다. 이를 이용해서 일단 데코레이터를 사용하지 않고 원시적인 방법을 사용하여 이 문제를 해결해 보자.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def AddLine(targetFunction, args):
    print "============================"
    targetFunction(args)
    print "============================"
 
def HelloName(name):
    print "Hello, %s!" % (name)
 
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
 
def HowAreYouName(name):
    print "How are you, %s?" % (name)
 
def main():
    AddLine(HelloName, "5kyc1ad")
    AddLine(GoodbyeName, "5kyc1ad")
    AddLine(HowAreYouName, "5kyc1ad")
cs

 함수와 그 인자를 넘겨 함수 시작 전, 함수가 끝난 후에 경계선을 출력하도록 하는 새로운 함수를 만들고, 그것을 이용해 각자의 함수를 실행하도록 변경해 보았다. 원하던 대로 결과가 나오기는 하지만, 무엇인가 불편한 기분이 드는 것은 여전하다. 호출하고 싶은 함수는 HelloName, GoodbyeName, HowAreYouName 인데 실제 호출하는 함수는 AddLine이고, 인자의 전달도 직관적으로 눈에 들어오지 않는다.

직관적으로 HelloName 함수를 실행하는 것이 보이면서 인자의 전달도 깔끔하게 정리할 수 있는 방법이 없을까?

답은 '함수를 새로 만들어서 사용하는 것' 이다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def AddLine(targetFunction):
    def wrapper(args):
        print "============================"
        targetFunction(args)
        print "============================"
    return wrapper
 
def HelloName(name):
    print "Hello, %s!" % (name)
 
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
 
def HowAreYouName(name):
    print "How are you, %s?" % (name)
 
def main():
    newHelloName     = AddLine(HelloName)
    newGoodbyeName   = AddLine(GoodbyeName)
    newHowAreYouName = AddLine(HowAreYouName)
 
    newHelloName("5kyc1ad")
    newGoodbyeName("5kyc1ad")
    newHowAreYouName("5kyc1ad")
cs

 AddLine 함수 내에 wrapper라는 이름으로 다른 함수 하나를 선언하고, 내부에서 인자로 전달받은 함수를 실행하기 전, 실행이 끝난 후에 줄을 추가로 출력하도록 한 후, AddLine 함수가 끝날 때 wrapper 함수 자체를 반환하도록 만들었다. 이제 main 에서 AddLine 함수에 원하는 출력 함수를 넣은 후 리턴된 함수를 받아와서 사용해주면 된다.

 정답에 아주 가까워졌지만 여전히 불편한 점이 몇가지 있다. 내가 선언한 함수는 HelloName인데 새로 newHelloName을 만들어서 사용해주어야 한다는 점, 함수마다 전부 저 작업을 추가로 해주어야 한다는 점 등이다. 선언한 함수를 그냥 사용할 수 없다는 점에서 또 직관성이 떨어지는 코드이기도 하다.

 이 모든 문제점을 해결하기 위해 사용하는 것이 데코레이터(Decorator)이다. 다른 함수들의 시작 전이나 후에 특정한 장식(Decoration)을 붙여 주는 것이다. 여기의 예제에서는 위아래에 붙은 경계선이 '장식' 되시겠다.

사용법은 생각보다 무척이나 간단하다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def AddLine(targetFunction):
    def wrapper(*args, **kwargs):
        print "============================"
        targetFunction(*args, **kwargs)
        print "============================"
    return wrapper
 
@AddLine
def HelloName(name):
    print "Hello, %s!" % (name)
 
@AddLine
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
 
@AddLine
def HowAreYouName(name):
    print "How are you, %s?" % (name)
 
def main():
    HelloName("5kyc1ad")
    GoodbyeName("5kyc1ad")
    HowAreYouName("5kyc1ad")
cs

wrapper 에서 받는 인자가 *args, **kwargs 가 되어서 당황할 수 있지만, 사실 이는 가변 인자를 넘겨주기 위한 것으로 생각보다 간단한 개념이다. 여기에 잘 설명되어 있으므로 참고.

 AddLine 함수는 달라진 점이 전혀 없고, 각 함수 위에 @AddLine 한 줄씩이 추가된 것을 볼 수 있다. @AddLine은 데코레이터 AddLine을 이 함수에 적용시키겠다는 의미이다. 각 함수들은 선언 순간 데코레이터 AddLine이 적용되어 리턴된 wrapper 함수를 자기 자신으로서 갖게 되고, 따라서 사용할 때는 별도의 작업 없이 선언한 함수를 그대로 사용하면 된다.

이것이 가장 기초적인 데코레이터의 사용법이다.


이제 여기서 한 계단만 더 올라가보자.

 데코레이터를 사용하다 보면 데코레이터 자체가 인자를 받는 모습을 볼 수 있다. 예를 들면 Flask를 이용한 REST API 개발을 해본 사람이라면, 다음과 같은 코드가 함수 앞에 붙는 것을 봤을 것이다.


1
2
3
@app.route("/file", methods=["GET"])
def blah_blah():
    pass
cs

 딱 봐서는 blah_blah 라는 함수에 app.route라는 데코레이터를 적용하는 코드로 보이는데, 지금까지 보지 못한 부분이 추가되었다.

바로 app.route에 ("/file", methods=["GET"]) 로 인자를 받는 부분이 추가되었다는 것이다.


 생각해 보면 지금까지 작성한 예시에서 데코레이터가 하는 일을 나타내어 봤을 때 '함수 시작 전과 끝난 후에 특정 줄을 하나씩 출력한다'라고 표현할 수 있다. 그런데, 모든 함수가 다 같은 줄만을 출력하는 것이 아닌 특정 문자열을 출력하고 싶은 것일 수도 있다. 즉, 같은 데코레이터를 이용하여 다른 내용을 출력할 수 있도록 만들고 싶을 수 있는 것이다.

 이렇게 데코레이터에 인자를 넣기 위해서는 데코레이터 함수를 한번 더 감싸주면 된다.


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
def AddLine(targetStr):
    def RealDecorator(targetFunction):
        def wrapper(*args, **kwargs):
            print targetStr
            targetFunction(*args, **kwargs)
            print targetStr
        return wrapper
    return RealDecorator
 
@AddLine("===HelloName===")
def HelloName(name):
    print "Hello, %s!" % (name)
 
@AddLine("===GoodbyeName===")
def GoodbyeName(name):
    print "Goodbye, %s!" % (name)
 
@AddLine("===HowAreYouName===")
def HowAreYouName(name):
    print "How are you, %s?" % (name)
 
def main():
    HelloName("5kyc1ad")
    GoodbyeName("5kyc1ad")
    HowAreYouName("5kyc1ad")
cs

이런 식으로 말이다.

함수 위에 @AddLine으로 명시된 함수들은 선언 순간 AddLine 데코레이터를 적용시키게 되며, AddLine 데코레이터에는 출력할 문자열을 인자로 넘기도록 되어 있다. 위 예시의 실행 결과는 다음과 같다.


각각의 함수에서 AddLine 데코레이터에 인자로 넘긴 문자열이 함수의 시작과 끝에서 정상적으로 출력되는 모습을 확인할 수 있다.


 데코레이터를 잘만 이용하면 Repeat 등의 이름을 가진 데코레이터를 만들어서 한 함수를 여러 번 반복시키는 것도 가능하다.

대표적인 문제가 codefights.com 의 Arcade - Python의 77번째 문제인 'Merging Vines' 이다.

상당히 재미있고 데코레이터를 익히는 데에 적절한 문제이므로 한번씩 풀어보는 것도 좋을 것 같다.

블로그 이미지

__미니__

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

,