<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Aegis]]></title><description><![CDATA[Aegis]]></description><link>https://blog.dkuaegis.org</link><generator>RSS for Node</generator><lastBuildDate>Wed, 06 May 2026 17:20:41 GMT</lastBuildDate><atom:link href="https://blog.dkuaegis.org/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Android 악성 앱, 왜 여전히 뚫릴까? Louvain 그래프 탐지법이 그 해답일지도!]]></title><description><![CDATA[“바이러스는 진화한다. 그럼 탐지법도 진화해야 하지 않을까?”
악성 앱은 더 똑똑해졌습니다. 단순히 특정 API를 호출하는지 감지하는 방식은 더 이상 통하지 않죠. API가 어떻게 엮이는지, 함께 등장할 때 어떤 ‘행위 패턴’을 만드는지까지 보는 시대입니다. 그리고 바로 여기서, “동시 출현 그래프”와 “커뮤니티 탐지”라는 키워드가 등장합니다.

개념 드리프트, 악성 탐지의 맹점
악성 앱 탐지 모델이 시간이 지나면 무력해지는 현상, 알고 계셨나...]]></description><link>https://blog.dkuaegis.org/android-louvain</link><guid isPermaLink="true">https://blog.dkuaegis.org/android-louvain</guid><category><![CDATA[AI]]></category><category><![CDATA[#ai-tools]]></category><category><![CDATA[Android]]></category><category><![CDATA[#cybersecurity]]></category><dc:creator><![CDATA[노경민]]></dc:creator><pubDate>Mon, 28 Jul 2025 06:30:48 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-4occ67cu7j2065s7iqk64quioynho2zlo2vnoulpc4g6re4658io2dkoyngouyleuphcdsp4ttmzttlbtslbwg7zwy7keaioyviuydhoq5jdigj0">“바이러스는 진화한다. 그럼 탐지법도 진화해야 하지 않을까?”</h2>
<p>악성 앱은 더 똑똑해졌습니다. 단순히 특정 API를 호출하는지 감지하는 방식은 더 이상 통하지 않죠. API가 어떻게 엮이는지, 함께 등장할 때 어떤 ‘행위 패턴’을 만드는지까지 보는 시대입니다. 그리고 바로 여기서, <strong>“동시 출현 그래프”</strong>와 <strong>“커뮤니티 탐지”</strong>라는 키워드가 등장합니다.</p>
<hr />
<h2 id="heading-6rcc64wqioutnoumro2uho2kucwg7jwf7isxio2dkoyngoydmcdrp7nsoja">개념 드리프트, 악성 탐지의 맹점</h2>
<p>악성 앱 탐지 모델이 시간이 지나면 무력해지는 현상, 알고 계셨나요? 이걸 Concept Drift, 즉 <strong>“개념 드리프트”</strong> 라고 부릅니다. 예를 들어 2014년에 만든 모델이 2023년도 앱에 무력하다면 그 이유는 무엇일까요? 바로 개념 드리프트 때문입니다. 개념 드리프트가 발생하는 이유는 악성 코드도 점점 진화하며, 시대를 타니까 입니다.</p>
<p>기존에는 이 문제를 재학습으로 해결하는 경우가 많았습니다. 하지만 매번 새 데이터를 수집하고, 다시 학습하는건? 역시 시간과 돈이 많이 든다는 문제가 있습니다.</p>
<hr />
<h2 id="heading-api">API는 혼자 행동하지 않는다: 동시 출현 그래프란?</h2>
<p>저는 그래서 이걸 해결하기 위해 다음과 같은 새로운 접근을 제안했습니다. API 하나만 보는게 아니라, 함께 등장하는 API의 관계를 본다면 어떨까?</p>
<p>이걸 “동시 출현 그래프”로 모델링하면, 다음과 같이 보입니다.</p>
<p><em>“이 앱은 sendSMS와 getLocation을 자주 함께 호출하네. 위험할 수도 있어!”</em></p>
<p>이러한 동시 출현 관계망을 만든 뒤, Louvain 알고리즘을 사용해서 비슷한 API끼리 커뮤니티로 묶습니다.</p>
<hr />
<h2 id="heading-6riw7iigioygjoqwna">기술 소개</h2>
<p>그래서 저는 이 알고리즘을 이용해서 자동으로 악성 앱을 탐지하는 인공지능 모델을 만들었습니다.</p>
<p>이 모델은 다음과 같이 작동합니다.</p>
<ol>
<li><p>API 간 동시 출현을 그래프로 모델링</p>
</li>
<li><p>Louvain 커뮤니티 탐지로 의미 있는 그룹화</p>
</li>
<li><p>Node2Vec 임베딩으로 의미 기반 벡터화</p>
</li>
<li><p>각 커뮤니티별로 Random Forest 분류기를 개별 학습</p>
</li>
<li><p>테스트 단계에서는, 개념 드리프트 발생 시 threshold를 자동 조정</p>
</li>
</ol>
<p>이 결과, 재학습 없이도 6년치의 악성앱을 매년 80% 넘게 탐지할 수 있었습니다.</p>
<p>아래는 제가 개발한 모델의 구조도입니다.</p>
<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752154384561/dc4b56cc-b664-4606-9bce-838b468facb6.png" alt class="image--center mx-auto" /></p>
<hr />
<h2 id="heading-7jmcioydtcdrsknrspxsnbqg7yq567oe7zwg6rmmpw">왜 이 방법이 특별할까?</h2>
<ol>
<li><p>시간에 강하다! : 제 모델은 2014년부터 2017년까지의 데이터로만 학습하고, 2018년부터 현재까지 재학습 없이 탐지율이 높습니다.</p>
</li>
<li><p>설명 가능한 인공지능: 이 앱은 어떤 커뮤니티에서 문제가 생겼을까? 를 추적할 수 있습니다.</p>
</li>
<li><p>False Positive 최소화: 정상 앱에 대한 불필요한 차단이 적습니다.</p>
</li>
<li><p>개념 변화 인지 임계값 보정: 데이터 분포 변화에 자동으로 적응합니다.</p>
</li>
</ol>
<hr />
<h2 id="heading-kirtlzzqs4tripqg7jeg7j2e6rmmpyoq"><strong>한계는 없을까?</strong></h2>
<ul>
<li><p>일부 앱은 커뮤니티에 속하지 못해 탐지가 제외될 수 있습니다.</p>
</li>
<li><p>커뮤니티 크기 불균형에 따른 학습 성능 편차가 존재합니다.</p>
</li>
<li><p>동적 분석을 포함하지 않아 실행 중 변화 탐지는 불가능합니다</p>
</li>
</ul>
<hr />
<h2 id="heading-66ei66y066as7zwy66mw">마무리하며</h2>
<p>이 글에서 소개한 탐지 기법은 “시간에 굴복하지 않는 악성 앱 탐지기” 라고 부를 수 있습니다. 단순히 성능이 좋은게 아니라, “지속적으로 성능이 좋은 모델”이 진짜 의미 있는 모델 아닐까요?</p>
<hr />
<p>저는 보안에 인공지능을 활용해서 실제 세계 문제를 해결하는 것을 좋아합니다.</p>
<p>다음 번에는 다른 신선한 주제로 얘기해보도록 합시다!</p>
<p>읽어주셔서 감사하고, 건강한 토론은 언제나 환영입니다.</p>
]]></content:encoded></item><item><title><![CDATA[[WriteUp] / [CryptoHack] Keyed Permutations]]></title><description><![CDATA[One-to-one correspondence는 수학적으로 전사(surjective) + 단사(injective) 조건을 모두 만족하는 함수, 즉 **전단사 함수(bijective function)**를 말한다.

암호 알고리즘에서는 각 입력이 유일한 출력과 대응하고, 그 반대도 성립해야 복호화가 가능하므로 bijection이 필수 조건이 된다.


bijection이란?

Bijection은 두 집합 사이의 **1:1 대응(one-to-one...]]></description><link>https://blog.dkuaegis.org/writeup-cryptohack-keyed-permutations</link><guid isPermaLink="true">https://blog.dkuaegis.org/writeup-cryptohack-keyed-permutations</guid><dc:creator><![CDATA[cooku222]]></dc:creator><pubDate>Mon, 28 Jul 2025 06:30:42 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752217391179/0178f3f9-40ac-4b65-958d-58328b4dcbbe.png" alt class="image--center mx-auto" /></p>
<ul>
<li><p><strong>One-to-one correspondence</strong>는 수학적으로 <strong>전사(surjective)</strong> + <strong>단사(injective)</strong> 조건을 모두 만족하는 함수, 즉 **전단사 함수(bijective function)**를 말한다.</p>
</li>
<li><p>암호 알고리즘에서는 <strong>각 입력이 유일한 출력과 대응하고</strong>, 그 반대도 성립해야 <strong>복호화가 가능</strong>하므로 <strong>bijection</strong>이 필수 조건이 된다.</p>
</li>
</ul>
<p><strong>bijection이란?</strong></p>
<ul>
<li><strong>Bijection</strong>은 두 집합 사이의 **1:1 대응(one-to-one correspondence)**을 의미하는 함수</li>
</ul>
<p><strong>Flag</strong></p>
<pre><code class="lang-bash">crypto{bijection}
</code></pre>
]]></content:encoded></item><item><title><![CDATA[[angr] angr가 뭐죠?]]></title><description><![CDATA[배경
바이너리 분석 방법에는 크게 2가지가 있다.

수동: gdb 같은 디버거로 어셈 코드를 한줄씩 실행하면서 레지스터와 메모리의 변환를 추적하는 분석

자동: angr와 같은 자동화된 분석 프레임워크를 사용


이 2가지는 상호보완적이다.
이 글에서는 자동 바이너리 분석 프레임워크 angr에 대해서 알아보겠다.
소개
angr는 바이너리 분석을 위한 다재다능한 파이썬 기반 프레임워크이다. 중요한 철학은 바이너리 분석 작업을 프로그래밍 방식으로 ...]]></description><link>https://blog.dkuaegis.org/angr</link><guid isPermaLink="true">https://blog.dkuaegis.org/angr</guid><category><![CDATA[angr]]></category><category><![CDATA[Pwnable]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[random6]]></dc:creator><pubDate>Mon, 28 Jul 2025 06:30:30 GMT</pubDate><content:encoded><![CDATA[<h2 id="heading-67cw6rk9">배경</h2>
<p>바이너리 분석 방법에는 크게 2가지가 있다.</p>
<ol>
<li><p>수동: gdb 같은 디버거로 어셈 코드를 한줄씩 실행하면서 레지스터와 메모리의 변환를 추적하는 분석</p>
</li>
<li><p>자동: angr와 같은 자동화된 분석 프레임워크를 사용</p>
</li>
</ol>
<p>이 2가지는 상호보완적이다.</p>
<p>이 글에서는 자동 바이너리 분석 프레임워크 angr에 대해서 알아보겠다.</p>
<h2 id="heading-7iam6rcc">소개</h2>
<p>angr는 바이너리 분석을 위한 다재다능한 파이썬 기반 프레임워크이다. 중요한 철학은 바이너리 분석 작업을 프로그래밍 방식으로 자동화하는 것이다. API를 통해 복잡한 분석 스크립트를 쉽게 작성할 수 있다.</p>
<h2 id="heading-7juq66as">원리</h2>
<p>아래 컴퍼넌트 들이 서로 상호작용하며 작동한다.</p>
<ol>
<li><p>Loader: 분석할 바이너리를 메모리에 올림, 바이너를 파싱하고 각 세그먼트를 angr가 이해할 수 있는 주소 공간에 매핑한다.</p>
</li>
<li><p>State: 프로그램의 특정 시점 스냅샷이다. angr 분석의 기본 단위이며, 초기 상태(entry_state)에서 시작한다.</p>
</li>
<li><p>Claripy: 솔버다. 입력값처럼 알 수 없는 값을 심볼로 표현하고 프로그램에 가해지는 수학적 논리적 제약 조건들을 추적한다.</p>
</li>
<li><p>SimEngine: State를 입력 받아 코드 한 블록을 실행하고 그 결과로 다음 State들을 만들어낸다. 예를 들어 if문을 만나면 2개의 true, false State를 생성한다.</p>
</li>
<li><p>PathGroup: 탐색할 State를 관리하는 매니저이다.</p>
</li>
</ol>
<p>이런 컴포넌트들이 상호작용하며 바이너리 로드 → 초기 상태 설정 → 심볼릭 실행으로 경로 탐색 → 목표 상태 도달 이라는 분석 과정을 자동화한다.</p>
<h2 id="heading-7iuk7iq1">실습</h2>
<p>아래와 같은 C언어 코드를 컴파일해서 angr로 분석해보자.</p>
<pre><code class="lang-c"><span class="hljs-comment">// gcc -g -o auth_check auth_check.c</span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdio.h&gt;</span></span>
<span class="hljs-meta">#<span class="hljs-meta-keyword">include</span> <span class="hljs-meta-string">&lt;stdlib.h&gt;</span></span>

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">success</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Success!\n"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">failure</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Failure.\n"</span>);
}

<span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">main</span><span class="hljs-params">(<span class="hljs-keyword">int</span> argc, <span class="hljs-keyword">char</span> **argv)</span> </span>{
    <span class="hljs-keyword">if</span> (argc &lt; <span class="hljs-number">2</span>) {
        <span class="hljs-built_in">printf</span>(<span class="hljs-string">"Usage: %s &lt;password&gt;\n"</span>, argv[<span class="hljs-number">0</span>]);
        <span class="hljs-keyword">return</span> <span class="hljs-number">1</span>;
    }

    <span class="hljs-keyword">int</span> key = <span class="hljs-number">0xdeadbeef</span>;
    <span class="hljs-keyword">int</span> input_val = atoi(argv[<span class="hljs-number">1</span>]);

    <span class="hljs-keyword">if</span> ((key ^ input_val) == <span class="hljs-number">0x12345678</span>) {
        success();
    } <span class="hljs-keyword">else</span> {
        failure();
    }

    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
}
</code></pre>
<p>이 프로그램은 0xdeadbeef라는 key 값과 사용자가 입력한 input_val을 XOR해서 결과가 0×12345678과 같은지를 검사한다.</p>
<h3 id="heading-angr">angr를 이용한 자동분석</h3>
<pre><code class="lang-python"><span class="hljs-keyword">import</span> angr
<span class="hljs-keyword">import</span> claripy

<span class="hljs-comment"># 1. 바이너리 로딩</span>
project = angr.Project(<span class="hljs-string">'./auth_check'</span>, auto_load_libs=<span class="hljs-literal">False</span>)

<span class="hljs-comment"># 2. 심볼릭 인자 설정 및 초기 State 생성</span>
<span class="hljs-comment"># argv[1]은 최대 10자리 숫자로 가정 (null 포함 11바이트)</span>
<span class="hljs-comment"># Claripy를 이용해 심볼릭 비트벡터(변수)를 생성한다.</span>
sym_argv1 = claripy.BVS(<span class="hljs-string">'sym_argv1'</span>, <span class="hljs-number">10</span> * <span class="hljs-number">8</span>)
<span class="hljs-comment"># entry_state가 아닌 full_init_state를 사용하면 argc, argv 설정이 더 용이하다.</span>
state = project.factory.full_init_state(args=[<span class="hljs-string">'./auth_check'</span>, sym_argv1])

<span class="hljs-comment"># 3. SimulationManager 생성 및 탐색</span>
simgr = project.factory.simulation_manager(state)

<span class="hljs-comment"># success 함수의 주소를 찾아 'find' 대상으로, failure 함수 주소는 'avoid' 대상으로 설정</span>
find_addr = project.loader.find_symbol(<span class="hljs-string">'success'</span>).rebased_addr
avoid_addr = project.loader.find_symbol(<span class="hljs-string">'failure'</span>).rebased_addr
simgr.explore(find=find_addr, avoid=avoid_addr)

<span class="hljs-comment"># 4. 결과 추출 및 확인</span>
<span class="hljs-keyword">if</span> simgr.found:
    found_state = simgr.found[<span class="hljs-number">0</span>]
    <span class="hljs-comment"># success 경로에 도달하기 위한 구체적인 입력값을 solver를 통해 얻어낸다.</span>
    solution = found_state.solver.eval(sym_argv1, cast_to=bytes)

    print(<span class="hljs-string">"🚀 Success! Angr found the solution."</span>)
    <span class="hljs-comment"># atoi() 함수는 숫자 부분만 인식하므로, null 바이트 이전까지 출력</span>
    print(<span class="hljs-string">f"🔑 Input value: <span class="hljs-subst">{solution.split(<span class="hljs-string">b'\\x00'</span>)[<span class="hljs-number">0</span>].decode()}</span>"</span>)
<span class="hljs-keyword">else</span>:
    print(<span class="hljs-string">"😥 Could not find the solution."</span>)
</code></pre>
<p>위 스크립트를 실행하면 angr가 심볼릭 실행으로 <code>(0xdeadbeef ^ atoi(sym_argv1)) == 0x12345678</code> 라는 제약 조건을 만들고, 솔버가 방정식을 만족하는 값을 찾아준다.</p>
<pre><code class="lang-bash">🚀 Success! Angr found the solution.
🔑 Input value: -862328681
</code></pre>
<h2 id="heading-66gc7jqw66ci67koioq0goygkoyxkoyencdrs7gg7ius67o866atioylpo2wiq">로우레벨 관점에서 본 심볼릭 실행</h2>
<ol>
<li><p>심볼릭 레지스터와 메모리: 레지스터와 스택 변수들은 angr의 State객체 안에서 관리된다. 값이 정해져 있으면 구체적인 값으로 알 수 없는 값이라면 Claripy가 만든 심볼릭 표현으로 저장된다. 그리고 <code>state.reges.eax</code> 같은 코드로 이 값에 접근을 할 수 있다.</p>
</li>
<li><p>경로 분기와 제약 조건: cmp 어셈블리어를 만나면 State가 복제된다. fork한다.</p>
</li>
</ol>
<h2 id="heading-7zwc6roe">한계</h2>
<p>angr는 직접 추적하기 어려운 복잡한 경로를 자동으로 탐색하고 답을 구하는 강력한 도구이다.</p>
<p>하지만 아래와 같은 한계점이 있다.</p>
<ul>
<li>상태 폭발: 프로그램이 복잡해지고 분기가 많아지면 탐색할 State의 수가 기하급수적으로 증가하고 분석에 필요한 시간과 메모리가 매우 커지게 된다.</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[[WriteUp] / [CryptoHack] Resisting Bruteforce]]></title><description><![CDATA[해석
블록 암호가 안전하려면, 공격자가 AES의 출력과 무작위 비트 배열을 구분할 수 없어야 한다. 또한, 키를 무작위로 전부 시도하는(브루트포싱) 것보다 더 효율적인 복호화 방법이 있어서는 안 된다. 그래서 이론적으로라도 브루트포싱보다 적은 연산으로 복호화가 가능하다면, 그 암호는 '깨졌다고(broken)' 간주된다.
128비트 키를 브루트포싱하는 건 얼마나 어려울까? 누군가 계산했는데, 비트코인 전체 채굴 연산력을 동원해도 우주의 나이보다 ...]]></description><link>https://blog.dkuaegis.org/writeup-cryptohack-resisting-bruteforce</link><guid isPermaLink="true">https://blog.dkuaegis.org/writeup-cryptohack-resisting-bruteforce</guid><dc:creator><![CDATA[cooku222]]></dc:creator><pubDate>Mon, 28 Jul 2025 06:30:12 GMT</pubDate><content:encoded><![CDATA[<p><img src="https://cdn.hashnode.com/res/hashnode/image/upload/v1752217909645/f99c5078-f162-4b6b-9705-8762cb64e139.png" alt class="image--center mx-auto" /></p>
<h3 id="heading-7zw07isd">해석</h3>
<p>블록 암호가 안전하려면, 공격자가 AES의 출력과 무작위 비트 배열을 구분할 수 없어야 한다. 또한, 키를 무작위로 전부 시도하는(브루트포싱) 것보다 더 효율적인 복호화 방법이 있어서는 안 된다. 그래서 이론적으로라도 브루트포싱보다 적은 연산으로 복호화가 가능하다면, 그 암호는 '깨졌다고(broken)' 간주된다.</p>
<p>128비트 키를 브루트포싱하는 건 얼마나 어려울까? 누군가 계산했는데, 비트코인 전체 채굴 연산력을 동원해도 우주의 나이보다 100배 더 오래 걸린다고 함. 즉, 현실적으로 AES-128은 브루트포스로 못 뚫음.</p>
<p>브루트포스보다 약간 더 나은 공격이 있긴 한데, 그 차이는 매우 미미함. AES-128의 보안을 128비트에서 126.1비트로 약간 깎는 수준. 8년 넘게 개선도 없고, 실제 위협으로 간주되지 않음.</p>
<p>양자 컴퓨터는 Shor 알고리즘을 통해 RSA 같은 공개키 암호를 깨뜨릴 수 있음. 대칭키 암호(AES)는 Grover 알고리즘을 통해 보안 수준이 절반으로 감소함. 그래서 양자 시대 대비하려면 AES-256을 쓰라는 이유. (256 → √ 연산으로도 128비트 보안 유지됨)</p>
<p>AES에 대한 가장 잘 알려진 단일 키 공격은 무엇인가?</p>
<p><strong>Flag</strong></p>
<pre><code class="lang-bash">crypto{biclique}
</code></pre>
<p><a target="_blank" href="https://en.wikipedia.org/wiki/Biclique_attack">https://en.wikipedia.org/wiki/Biclique_attack</a></p>
<p>→ 레퍼런스를 참고해 이해한 부분까지 적어보자면 AES에 대한 가장 잘 알려진 단일 키 공격으로 전체 라운드 수를 공격한다.</p>
]]></content:encoded></item><item><title><![CDATA[열심히 만든 리버싱 문제를 날먹하는 방법]]></title><description><![CDATA[열심히 만든 리버싱 문제를 날먹으로부터 지키는 방법
이 글을 읽기 전에 위의 드림핵 커뮤니티에 올라온 글을 보면 이해하기 수월하다.
서론
리버싱 문제는 출제자가 의도한 대로 정석적인 방법으로 역연산을 하여 플래그를 구할 수도 있지만, 편법을 사용하여 날먹하는 것이 가능하다.
편법에는 여러가지 방법이 있지만 위 드림핵 커뮤니티 글을 읽고 아이디어를 얻어 리버싱 문제를 해결했기 때문에 그 방법을 소개하고자 한다.
문제 풀이
해결한 문제는 Rev L...]]></description><link>https://blog.dkuaegis.org/7je07ius7z6iiounjoutocdrpqzrsotsi7eg66y47kcc66w8ioucoououe2vmouklcdrsknrspu</link><guid isPermaLink="true">https://blog.dkuaegis.org/7je07ius7z6iiounjoutocdrpqzrsotsi7eg66y47kcc66w8ioucoououe2vmouklcdrsknrspu</guid><dc:creator><![CDATA[skybridge]]></dc:creator><pubDate>Mon, 28 Jul 2025 06:30:08 GMT</pubDate><content:encoded><![CDATA[<p><a target="_blank" href="https://dreamhack.io/forum/community/free-lounge/posts/1436-%EC%97%B4%EC%8B%AC%ED%9E%88-%EB%A7%8C%EB%93%A0-%EB%A6%AC%EB%B2%84%EC%8B%B1-%EB%AC%B8%EC%A0%9C%EB%A5%BC-%EB%82%A0%EB%A8%B9%EC%9C%BC%EB%A1%9C%EB%B6%80%ED%84%B0-%EC%A7%80%ED%82%A4%EB%8A%94-%EB%B0%A9%EB%B2%95/">열심히 만든 리버싱 문제를 날먹으로부터 지키는 방법</a></p>
<p>이 글을 읽기 전에 위의 드림핵 커뮤니티에 올라온 글을 보면 이해하기 수월하다.</p>
<h2 id="heading-7isc66gg">서론</h2>
<p>리버싱 문제는 출제자가 의도한 대로 정석적인 방법으로 역연산을 하여 플래그를 구할 수도 있지만, 편법을 사용하여 날먹하는 것이 가능하다.</p>
<p>편법에는 여러가지 방법이 있지만 위 드림핵 커뮤니티 글을 읽고 아이디어를 얻어 리버싱 문제를 해결했기 때문에 그 방법을 소개하고자 한다.</p>
<h2 id="heading-66y47kccio2sgoydta">문제 풀이</h2>
<p>해결한 문제는 <a target="_blank" href="https://dreamhack.io/wargame/challenges/1422">Rev Level-3 Call more functions</a> 이다.</p>
<p>아래에는 Writeup이 존재하기 때문에 문제를 온전하게 즐기고 싶은 사람은 아쉽지만 뒤로 가기를 눌러주길 바란다.</p>
<pre><code class="lang-c"><span class="hljs-function">__int64 __fastcall <span class="hljs-title">main</span><span class="hljs-params">(__int64 a1, <span class="hljs-keyword">char</span> **a2, <span class="hljs-keyword">char</span> **a3)</span>
</span>{
  <span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span> v3; <span class="hljs-comment">// ebx</span>

  sub_11E9(a1, a2, a3);
  __printf_chk(<span class="hljs-number">1L</span>L, <span class="hljs-string">"Input: "</span>);
  __isoc99_scanf(<span class="hljs-string">"%65s"</span>, s);
  v3 = <span class="hljs-number">1</span>;
  <span class="hljs-keyword">if</span> ( <span class="hljs-built_in">strlen</span>(s) == <span class="hljs-number">64</span> )
  {
    v3 = sub_1311();
    <span class="hljs-keyword">if</span> ( v3 )
    {
      <span class="hljs-built_in">puts</span>(<span class="hljs-string">":("</span>);
      <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;
    }
    <span class="hljs-keyword">else</span>
    {
      __printf_chk(<span class="hljs-number">1L</span>L, <span class="hljs-string">"Correct! The flag is DH{%s}\n"</span>, s);
    }
  }
  <span class="hljs-keyword">return</span> v3;
}
</code></pre>
<p>먼저 Input 값의 길이가 64바이트인지 확인한다. 그 후, <code>sub_1311()</code>을 호출하여 입력된 값이 플래그와 일치하는지 검증 과정을 수행한다.</p>
<pre><code class="lang-c"><span class="hljs-function">__int64 <span class="hljs-title">sub_1311</span><span class="hljs-params">()</span>
</span>{
  sub_1236(<span class="hljs-number">63L</span>L);
  sub_1257();
  sub_1236(<span class="hljs-number">51L</span>L);
  sub_12CB();
  sub_1236(<span class="hljs-number">62L</span>L);
  sub_1257();
  sub_1290();
  sub_1236(<span class="hljs-number">4L</span>L);
  sub_12CB();
  sub_1236(<span class="hljs-number">61L</span>L);
  sub_1257();
  sub_1236(<span class="hljs-number">101L</span>L);
  sub_12CB();
  sub_1236(<span class="hljs-number">60L</span>L);
  sub_1257();
  sub_1290();
  sub_1290();
  sub_1236(<span class="hljs-number">80L</span>L);
  sub_12CB();
  sub_1236(<span class="hljs-number">59L</span>L);
  sub_1257();
  sub_1236(<span class="hljs-number">102L</span>L);
  ...
  <span class="hljs-keyword">return</span> (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span>)dword_5088;
}
</code></pre>
<p><code>sub_1311</code>을 열어보면 많은 양의 함수가 호출되는 것을 볼 수 있다. 여기서 호출되는 모든 함수를 분석하는 것은 매우 복잡한 일이다. 따라서 우리는 마지막 return 값에 주목할 필요가 있다.</p>
<p><code>sub_1311</code>은 <code>dword_5088</code>을 리턴하고 있다. 다시 위로 올라가 main 함수를 살펴보면 <code>v3</code> 이 1이면 검증 과정을 실패한다. 즉, <code>dword_5088</code>이 0이 되는 값을 찾는 것이 우리의 목표이다.</p>
<p>이제 <code>sub_1311</code>에서 호출하는 함수들을 살펴볼 차례이다. 우리는 날먹을 하는 것이 목표이기 때문에 모든 함수를 자세히 살펴볼 필요가 없다. <code>dword_5088</code>에 직접적인 연관을 주는 함수만 찾아본다.</p>
<pre><code class="lang-c"><span class="hljs-function">_BOOL8 <span class="hljs-title">sub_12CB</span><span class="hljs-params">()</span>
</span>{
  _BOOL8 result; <span class="hljs-comment">// rax</span>

  <span class="hljs-keyword">if</span> ( (<span class="hljs-keyword">unsigned</span> <span class="hljs-keyword">int</span>)dword_508C &lt;= <span class="hljs-number">1</span> )
    <span class="hljs-built_in">exit</span>(<span class="hljs-number">1</span>);
  result = byte_50A0[dword_508C - <span class="hljs-number">2</span>] != byte_50A0[dword_508C - <span class="hljs-number">1</span>];
  dword_5088 |= result;
  --dword_508C;
  <span class="hljs-keyword">return</span> result;
}
</code></pre>
<p><code>sub_12CB</code>에서 <code>result</code>를 계산하고 <code>dword_5088</code>에 or 연산을 통해 값을 업데이트 해주고 있다. <code>result</code>를 계산하는 부분이 앞에서 입력 값을 전처리한 다음, 저장된 값과 비교하는 핵심 부분일 것으로 추정된다.</p>
<p>여기서 주목할 것은 or 연산을 하기 때문에 한번 1로 고정되면 그 값이 변하지 않는 점이다.</p>
<p><code>dword_5088 |= result;</code> 에 bp를 걸어주고 결과 값이 1인지 0인지 비교하는 스크립트를 사용하도록 하자. gdb에서 bp에 히트했을 때 실행하는 간단한 스크립트를 만들 수 있다.</p>
<pre><code class="lang-bash">gdb-peda$ b *0x00005555555552f6
Breakpoint 1 at 0x5555555552f6
gdb-peda$ <span class="hljs-built_in">command</span> 1
Type commands <span class="hljs-keyword">for</span> breakpoint(s) 1, one per line.
End with a line saying just <span class="hljs-string">"end"</span>.
&gt;<span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 1
 &gt;info b
 &gt;<span class="hljs-built_in">continue</span>
 &gt;end
&gt;<span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 0
 &gt;<span class="hljs-built_in">continue</span>
 &gt;end
&gt;end
</code></pre>
<p>연산한 결과 값은 eax 레지스터에 저장되기 때문에 eax의 값을 비교하는 것으로 <code>dword_5088</code>의 값이 변화하는 것을 추적 가능하다. 이 문제에서 값의 비교는 마지막 문자부터 일어나기 때문에 payload의 뒤부터 값을 변경해나간다.</p>
<pre><code class="lang-bash">gdb-peda$ ru
Starting program: /home/skybridge/workspace/dreamhack/rev/Level-3/Call_more_functions/main 
[Thread debugging using libthread_db enabled]
Using host libthread_db library <span class="hljs-string">"/lib/x86_64-linux-gnu/libthread_db.so.1"</span>.
Input: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaac
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555552f6 
 breakpoint already hit 1 time
        <span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 1
          info b
          <span class="hljs-built_in">continue</span>
        end
        <span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 0
          <span class="hljs-built_in">continue</span>
        end

Breakpoint 1, 0x00005555555552f6 <span class="hljs-keyword">in</span> ?? ()
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555552f6 
 breakpoint already hit 2 <span class="hljs-built_in">times</span>
        <span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 1
          info b
          <span class="hljs-built_in">continue</span>
        end
        <span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 0
          <span class="hljs-built_in">continue</span>
        end

...
</code></pre>
<p>eax가 1인 경우에만 <code>info b</code>를 실행하기 때문에 첫 번째 문자가 틀렸다면 1번째 히트했을 때부터 64번째 히트한 부분까지 모두 출력된다.</p>
<pre><code class="lang-bash">gdb-peda$ ru
Starting program: /home/skybridge/workspace/dreamhack/rev/Level-3/Call_more_functions/main 
[Thread debugging using libthread_db enabled]
Using host libthread_db library <span class="hljs-string">"/lib/x86_64-linux-gnu/libthread_db.so.1"</span>.
Input: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa3

Breakpoint 1, 0x00005555555552f6 <span class="hljs-keyword">in</span> ?? ()
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555552f6 
 breakpoint already hit 2 <span class="hljs-built_in">times</span>
        <span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 1
          info b
          <span class="hljs-built_in">continue</span>
        end
        <span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 0
          <span class="hljs-built_in">continue</span>
        end

Breakpoint 1, 0x00005555555552f6 <span class="hljs-keyword">in</span> ?? ()
Num     Type           Disp Enb Address            What
1       breakpoint     keep y   0x00005555555552f6 
 breakpoint already hit 3 <span class="hljs-built_in">times</span>
        <span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 1
          info b
          <span class="hljs-built_in">continue</span>
        end
        <span class="hljs-keyword">if</span> <span class="hljs-variable">$eax</span> == 0
          <span class="hljs-built_in">continue</span>
        end
...
</code></pre>
<p>이번에는 브루트포스를 통해 뒤의 1바이트를 맞춘 상태이다. 이번에는 두 번째 bp에 히트한 부분부터 출력되었다. 그 이유는 첫번째 bp에서는 <code>dword_5088</code> 값이 0이었기 때문에 gdb 스크립트에 의해 출력되지 않은 것이다.</p>
<p>이 방식으로 64바이트의 값을 날먹으로 모두 구할 수 있다.</p>
<pre><code class="lang-python"><span class="hljs-keyword">from</span> pwn <span class="hljs-keyword">import</span> *

p = process([<span class="hljs-string">'gdb'</span>, <span class="hljs-string">'./main'</span>])

p.sendline(<span class="hljs-string">'r'</span>)
p.sendline(<span class="hljs-string">'12345'</span>)
p.sendline(<span class="hljs-string">'b *0x00005555555552f6'</span>)

p.sendline(<span class="hljs-string">'command 1'</span>)
p.sendline(<span class="hljs-string">'if $eax == 1'</span>)
p.sendline(<span class="hljs-string">'info b'</span>)
p.sendline(<span class="hljs-string">'continue'</span>)
p.sendline(<span class="hljs-string">'end'</span>)
p.sendline(<span class="hljs-string">'if $eax == 0'</span>)
p.sendline(<span class="hljs-string">'continue'</span>)
p.sendline(<span class="hljs-string">'end'</span>)
p.sendline(<span class="hljs-string">'end'</span>)


flag = <span class="hljs-string">b'a'</span> * <span class="hljs-number">64</span>
flag = bytearray(flag)

letter = <span class="hljs-string">b'abcdef0123456789'</span>

<span class="hljs-keyword">for</span> i <span class="hljs-keyword">in</span> range(len(flag)):
    idx = <span class="hljs-number">63</span> - i
    <span class="hljs-keyword">for</span> c <span class="hljs-keyword">in</span> letter:
        flag[idx] = c <span class="hljs-comment"># 뒤에서부터 한글자씩 바꾸기</span>
        print(flag)
        p.sendline(<span class="hljs-string">'run'</span>)
        p.sendlineafter(<span class="hljs-string">b'Input: '</span>, flag)
        p.recvuntil(<span class="hljs-string">b'breakpoint already hit '</span>)
        attempt = int(p.recvuntil(<span class="hljs-string">b' '</span>)[:<span class="hljs-number">-1</span>])
        <span class="hljs-keyword">if</span> attempt == (i + <span class="hljs-number">1</span>):  <span class="hljs-comment">#아직 안맞는다면</span>
            <span class="hljs-keyword">continue</span>
        <span class="hljs-keyword">else</span>: <span class="hljs-comment">#정답을 찾음</span>
            <span class="hljs-keyword">break</span>

print(flag)

p.interactive()
</code></pre>
<p>물론 이 모든 과정을 pwntools을 사용하여 자동화 가능하다.</p>
<h2 id="heading-6rkw66gg">결론</h2>
<p>이 방법은 일종의 편법이라 할 수 있다. 리버싱 문제 해결의 중간 과정을 완전히 생략해버리기 때문에 분석 능력 향상에 큰 도움이 되지 않는다고 생각한다. 모든 문제 날먹을 권장하는 것이 아닌 여러가지 리버싱 문제 접근 방법 중 한 가지로 생각해 주었으면 한다.</p>
]]></content:encoded></item></channel></rss>