<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Cr3xis</title><description>Security &amp; Development Notes</description><link>https://cr3xis.github.io/</link><language>ko</language><item><title>Network Devices</title><link>https://cr3xis.github.io/posts/network-devices/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/network-devices/</guid><description>네트워크 장비의 역할과 실제 웹 통신 흐름을 정리합니다.</description><pubDate>Fri, 17 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Network Device란?&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Network Device&lt;/code&gt;는 네트워크에서 트래픽을 전달하거나 제어하거나 보호하는 장비를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;L2&lt;/code&gt; 장비는 MAC 주소를 기준으로 Frame을 전달하고 &lt;code&gt;L3&lt;/code&gt; 장비는 IP 주소를 기준으로 Packet을 라우팅 한다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;L4&lt;/code&gt; 장비는 Port와 Protocol을 기준으로 트래픽을 제어하고 &lt;code&gt;L7&lt;/code&gt; 장비는 HTTP Request 같은 애플리케이션 데이터를 기준으로 동작한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Layer&lt;/th&gt;
&lt;th&gt;기준 정보&lt;/th&gt;
&lt;th&gt;대표 장비&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L1&lt;/td&gt;
&lt;td&gt;전기 신호&lt;/td&gt;
&lt;td&gt;Hub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2&lt;/td&gt;
&lt;td&gt;MAC Address&lt;/td&gt;
&lt;td&gt;L2 Switch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3&lt;/td&gt;
&lt;td&gt;IP Address&lt;/td&gt;
&lt;td&gt;Router / L3 Switch&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L4&lt;/td&gt;
&lt;td&gt;IP / Port / Protocol&lt;/td&gt;
&lt;td&gt;Firewall, Load Balancer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L7&lt;/td&gt;
&lt;td&gt;HTTP Header, URI, Body&lt;/td&gt;
&lt;td&gt;Proxy / WAF / L7 Load Balancer&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;2. NIC&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;NIC(Network Interface Card)&lt;/code&gt;는 PC나 서버가 네트워크에 연결되기 위한 인터페이스다.&lt;/p&gt;
&lt;p&gt;물리적인 랜카드일 수도 있고 가상 머신이나 VPN에서 생성되는 가상 인터페이스일 수도 있다.&lt;/p&gt;
&lt;p&gt;NIC에는 일반적으로 MAC 주소가 존재하며 OS는 NIC를 통해 Frame을 송수신한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/nic.png&quot; alt=&quot;NIC&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;NIC&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Hub&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Hub&lt;/code&gt;는 L1 장비이며 들어온 전기 신호를 해석하지 않고 연결된 모든 포트로 그대로 전달한다.&lt;/p&gt;
&lt;p&gt;MAC 주소나 IP 주소를 보고 판단하지 않기 때문에 목적지와 상관없이 모든 장비가 같은 신호를 받게 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/hub.png&quot; alt=&quot;hub&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;HUB&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. L2 Switch&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;L2 Switch&lt;/code&gt;는 MAC 주소를 기준으로 Frame을 전달하는 장비이다.&lt;/p&gt;
&lt;p&gt;Switch는 Frame의 출발지 MAC 주소를 학습해서 MAC 주소 테이블을 만들고 목적지 MAC 주소를 기준으로 어느 포트로 보낼지 결정한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/l2table.png&quot; alt=&quot;table&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;MAC Address Table&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/l2switch.png&quot; alt=&quot;switch&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;L2 Switch&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;동작&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Frame을 수신한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;출발지 MAC 주소를 MAC Address Table에 학습한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;목적지 MAC 주소를 조회한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;MAC 주소가 있으면 해당 포트로만 전달한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;MAC 주소가 없으면 Flooding을 수행하고 이후 학습한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;5. L3 Switch&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;L3 Switch&lt;/code&gt;는 기존 &lt;code&gt;L2 Switch&lt;/code&gt;에서 Routing 기능을 추가한 장비이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;L2 Switch&lt;/code&gt;가 MAC 주소를 기준으로 Frame을 전달한다면 &lt;code&gt;L3 Switch&lt;/code&gt;는 각 VLAN의 Gateway(SVI)를 통해 서로 다른 VLAN이나 서브넷 사이를 라우팅 할수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/l3switch.png&quot; alt=&quot;switch&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;L3 Switch&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;동작&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Packet을 수신하고 목적지 IP 주소를 확인한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Routing Table에서 목적지 네트워크를 조회한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;목적지 VLAN을 결정한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;목적지 MAC Address를 확인한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;새로운 Ethernet Frame을 생성하여 전달한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Router&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Router&lt;/code&gt;는 서로 다른 네트워크를 연결하는 L3 장비이다.&lt;/p&gt;
&lt;p&gt;목적지 IP 주소를 기준으로 Routing Table을 조회하여 Packet을 다음 네트워크로 전달한다.&lt;/p&gt;
&lt;p&gt;일반적으로 내부 네트워크의 Default Gateway 역할을 하며 Internet과 같은 외부 네트워크를 연결하는 데 사용된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/router.png&quot; alt=&quot;router&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Router&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;단계&lt;/th&gt;
&lt;th&gt;동작&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Packet을 수신하고 목적지 IP Address를 확인한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Routing Table에서 다음 경로를 조회한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;목적지 MAC Address를 확인한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;새로운 Ethernet Frame을 생성하여 다음 장비로 전달한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;L2 &amp;amp; L3 장비 차이점&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;장비&lt;/th&gt;
&lt;th&gt;주요 특징&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L2 Switch&lt;/td&gt;
&lt;td&gt;MAC Address 기반 Switching&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3 Switch&lt;/td&gt;
&lt;td&gt;LAN 환경에서 Routing 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Router&lt;/td&gt;
&lt;td&gt;WAN을 포함한 네트워크 간 Routing 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Firewall&lt;/h2&gt;
&lt;p&gt;Firewall은 네트워크를 통과하는 트래픽을 허용하거나 차단하는 보안 장비이다.&lt;/p&gt;
&lt;p&gt;사전에 정의된 Rule을 기준으로 여러가지 정보를 검사하여 허용 또는 차단을 결정한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Rule 기준&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source IP&lt;/td&gt;
&lt;td&gt;192.168.0.10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination IP&lt;/td&gt;
&lt;td&gt;10.0.0.20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Port&lt;/td&gt;
&lt;td&gt;80, 443, 22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Protocol&lt;/td&gt;
&lt;td&gt;TCP, UDP, ICMP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Action&lt;/td&gt;
&lt;td&gt;Allow, Deny&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Firewall은 일반적으로 네트워크 경계에서 내부와 외부 네트워크 사이의 접근을 제어하기 위해 사용된다.&lt;/p&gt;
&lt;p&gt;WAF와 달리 Firewall은 HTTP Body나 SQL Injection, XSS와 같은 웹 공격 패턴을 분석하는 장비는 아니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. IDS&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;IDS(Intrusion Detection System)&lt;/code&gt;는 침입 탐지 시스템이다.&lt;/p&gt;
&lt;p&gt;네트워크를 통과하는 트래픽이나 시스템 이벤트를 모니터링하여 의심스러운 행위를 탐지하고 관리자에게 알려준다.&lt;/p&gt;
&lt;p&gt;IDS는 일반적으로 SPAN/TAP을 통해 전달받은 트래픽의 복사본을 분석한다.&lt;/p&gt;
&lt;p&gt;따라서 실제 통신에는 직접 관여하지 않으며 공격을 탐지하더라도 트래픽을 차단하지는 않는다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/ids.png&quot; alt=&quot;ids&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IDS Diagram&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. IPS&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;IPS(Intrusion Prevention System)&lt;/code&gt;는 침입 방지 시스템이다.&lt;/p&gt;
&lt;p&gt;IPS는 IDS처럼 의심스러운 트래픽을 탐지하지만 탐지 이후 공격으로 판단되면 트래픽을 차단할 수 있다.&lt;/p&gt;
&lt;p&gt;IPS는 일반적으로 네트워크 통신 경로 중간에 Inline 형태로 배치되어 모든 트래픽을 검사한다.&lt;/p&gt;
&lt;p&gt;정상적인 트래픽은 그대로 전달하고 악성 트래픽은 Drop, Block, Reset 등의 방식으로 서버에 도달하기 전에 차단한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/ips.png&quot; alt=&quot;ips&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IPS Diagram&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;IDS/IPS 차이점&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;IDS&lt;/th&gt;
&lt;th&gt;IPS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;목적&lt;/td&gt;
&lt;td&gt;공격 탐지&lt;/td&gt;
&lt;td&gt;공격 탐지 및 차단&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;배치&lt;/td&gt;
&lt;td&gt;SPAN/TAP(Mirroring)&lt;/td&gt;
&lt;td&gt;Inline&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;통신 경로&lt;/td&gt;
&lt;td&gt;통과하지 않음&lt;/td&gt;
&lt;td&gt;모든 트래픽이 반드시 통과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동작&lt;/td&gt;
&lt;td&gt;Alert 중심&lt;/td&gt;
&lt;td&gt;Drop, Block, Reset 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Proxy&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Proxy&lt;/code&gt;는 Client와 Server 사이에서 요청과 응답을 대신 전달하는 중계 서버이다.&lt;/p&gt;
&lt;p&gt;Proxy는 요청을 그대로 전달할 수도 있고 헤더를 수정하거나 캐시된 응답을 반환하는 등 다양한 기능을 수행할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/forward.png&quot; alt=&quot;forward&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Forward Proxy&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/reverse.png&quot; alt=&quot;reverse&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Reverse Proxy&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;위치&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Forward Proxy&lt;/td&gt;
&lt;td&gt;Client 앞&lt;/td&gt;
&lt;td&gt;Client를 대신하여 서버에 요청을 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reverse Proxy&lt;/td&gt;
&lt;td&gt;Server 앞&lt;/td&gt;
&lt;td&gt;Server를 대신하여 Client의 요청을 받아 내부 서버로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;Forward Proxy&lt;/code&gt;는 Client를 대신하여 외부 서버와 통신하며 익명성 제공이나 인터넷 접근 제어를 위해 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Reverse Proxy&lt;/code&gt;는 Server를 대신하여 Client의 요청을 처리하며 서버 보호, 부하 분산, Cache, TLS 종료 등의 목적으로 사용된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. Load Balancer&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;Load Balancer&lt;/code&gt;는 들어오는 트래픽을 여러 서버로 분산하는 장비다.&lt;/p&gt;
&lt;p&gt;한 서버에 모든 요청이 몰리지 않도록 분산하고 장애가 발생한 서버를 제외하여 서비스의 가용성과 성능을 향상시킨다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/before.png&quot; alt=&quot;before&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Before&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/after.png&quot; alt=&quot;after&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;After&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;기준 정보&lt;/th&gt;
&lt;th&gt;예시&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L4 Load Balancer&lt;/td&gt;
&lt;td&gt;IP / Port / Protocol&lt;/td&gt;
&lt;td&gt;TCP 443 트래픽 분산&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L7 Load Balancer&lt;/td&gt;
&lt;td&gt;HTTP Header, URI, Cookie&lt;/td&gt;
&lt;td&gt;&lt;code&gt;/api&lt;/code&gt;는 API 서버, &lt;code&gt;/images&lt;/code&gt;는 이미지 서버로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;12. WAF&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;WAF(Web Application Firewall)&lt;/code&gt;는 웹 애플리케이션을 보호하기 위한 L7 보안 장비다.&lt;/p&gt;
&lt;p&gt;HTTP 또는 HTTPS 요청의 내용을 분석하여 웹 공격으로 판단되는 요청을 차단한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Devices/waf.png&quot; alt=&quot;waf&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;WAF&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;검사 대상&lt;/th&gt;
&lt;th&gt;탐지 가능한 공격&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;URI&lt;/td&gt;
&lt;td&gt;Path Traversal / 비정상 URL 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Query String&lt;/td&gt;
&lt;td&gt;SQL Injection / XSS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTP Header&lt;/td&gt;
&lt;td&gt;Header 변조 / 비정상 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Cookie&lt;/td&gt;
&lt;td&gt;Cookie 변조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Request Body&lt;/td&gt;
&lt;td&gt;SQL Injection / XSS / 악성 파일 업로드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;참고 자료&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;https://www.paloaltonetworks.co.kr/cyberpedia/what-is-an-intrusion-detection-system-ids&lt;/li&gt;
&lt;li&gt;https://www.cloudflare.com/ko-kr/learning/cdn/glossary/reverse-proxy/&lt;/li&gt;
&lt;li&gt;https://www.cloudflare.com/ko-kr/learning/performance/what-is-load-balancing/&lt;/li&gt;
&lt;li&gt;https://www.cloudflare.com/ko-kr/learning/ddos/glossary/web-application-firewall-waf/&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>L7 - Application Layer</title><link>https://cr3xis.github.io/posts/application-layer/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/application-layer/</guid><description>OSI 7계층의 역할과 핵심 개념을 정리합니다.</description><pubDate>Thu, 16 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Application Layer란?&lt;/h2&gt;
&lt;p&gt;Application Layer는 OSI 7계층에서 7계층에 해당한다.&lt;/p&gt;
&lt;p&gt;사용자와 가장 가까운 계층이며 애플리케이션이 네트워크 기능을 사용할 수 있도록 통신 규칙을 제공한다.&lt;/p&gt;
&lt;p&gt;L7은 서비스별 데이터 형식과 요청 방식 그리고 응답 방식을 정의한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[주요 역할]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;웹 페이지 요청과 응답&lt;/li&gt;
&lt;li&gt;도메인 이름 해석&lt;/li&gt;
&lt;li&gt;IP 주소 자동 할당&lt;/li&gt;
&lt;li&gt;파일 전송&lt;/li&gt;
&lt;li&gt;원격 접속&lt;/li&gt;
&lt;li&gt;이메일 송수신&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Client - Server 구조&lt;/h2&gt;
&lt;p&gt;Client는 서비스를 요청하는 쪽이다.&lt;/p&gt;
&lt;p&gt;Server는 요청을 처리하고 결과를 응답하는 쪽이다.&lt;/p&gt;
&lt;p&gt;웹에서는 브라우저가 Client 역할을 하고 웹 서버가 Server 역할을 한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/clienttoserver.png&quot; alt=&quot;clienttoserver&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Client - Server&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Client - Server 구조는 L7 프로토콜을 이해할 때 가장 기본이 되는 흐름이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 대표 프로토콜&lt;/h2&gt;
&lt;p&gt;L7 프로토콜은 사용자가 실제로 사용하는 서비스와 직접 연결된다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;th&gt;Common Port&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;td&gt;웹 문서 요청과 응답&lt;/td&gt;
&lt;td&gt;TCP 80&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;td&gt;TLS로 보호되는 웹 통신&lt;/td&gt;
&lt;td&gt;TCP 443&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DNS&lt;/td&gt;
&lt;td&gt;도메인 이름을 IP 주소로 변환&lt;/td&gt;
&lt;td&gt;UDP/TCP 53&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DHCP&lt;/td&gt;
&lt;td&gt;IP 주소와 네트워크 설정 자동 할당&lt;/td&gt;
&lt;td&gt;UDP 67/68&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FTP&lt;/td&gt;
&lt;td&gt;파일 전송&lt;/td&gt;
&lt;td&gt;TCP 20/21&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;td&gt;보안 원격 접속&lt;/td&gt;
&lt;td&gt;TCP 22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SMTP&lt;/td&gt;
&lt;td&gt;메일 전송&lt;/td&gt;
&lt;td&gt;TCP 25/587/465&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POP3&lt;/td&gt;
&lt;td&gt;메일 수신&lt;/td&gt;
&lt;td&gt;TCP 110/995&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IMAP&lt;/td&gt;
&lt;td&gt;메일 동기화&lt;/td&gt;
&lt;td&gt;TCP 143/993&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;4. HTTP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;HTTP(HyperText Transfer Protocol)&lt;/code&gt;는 웹에서 리소스를 요청하고 응답받기 위한 프로토콜이다.&lt;/p&gt;
&lt;p&gt;브라우저는 HTTP Request를 보내고 서버는 HTTP Response를 돌려준다.&lt;/p&gt;
&lt;p&gt;HTTP 자체는 상태를 저장하지 않는다.&lt;br /&gt;
그래서 로그인 상태 같은 정보는 Cookie나 Session 같은 별도 방식으로 유지한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/httprequest.png&quot; alt=&quot;request&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;HTTP Request&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/httpresponse.png&quot; alt=&quot;response&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;HTTP Response&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Method&lt;/strong&gt; : 클라이언트가 서버에 요청하는 방식 (GET, POST, PUT, DELETE ...)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;URI&lt;/strong&gt; : 요청 대상 리소스의 위치&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;HTTP Version&lt;/strong&gt; : 사용 중인 HTTP 프로토콜 버전 (HTTP/1.1, HTTP/2 ...)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Status Code &amp;amp; Message&lt;/strong&gt; : 서버의 요청 처리 결과 (200 OK, 404 Not Found, 500 Internal Server Error ...)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Header&lt;/strong&gt; : 요청 및 응답에 대한 부가 정보&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Body&lt;/strong&gt; : 실제 전송되는 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. HTTPS&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;HTTPS(Hypertext Transfer Protocol Secure)&lt;/code&gt;는 HTTP를 &lt;code&gt;TLS(Transport Layer Security)&lt;/code&gt;로 보호하는 방식이다.&lt;/p&gt;
&lt;p&gt;HTTP의 요청과 응답 구조는 그대로 유지하면서 전송 구간을 암호화하여 데이터를 안전하게 보호한다.&lt;/p&gt;
&lt;p&gt;HTTPS를 사용하면 중간에서 패킷을 캡처하더라도 HTTP Request와 Response 내용을 직접 확인할 수 없으며&lt;br /&gt;
Wireshark에서는 &lt;code&gt;TLS Application Data&lt;/code&gt; 형태로 표시된다.&lt;/p&gt;
&lt;p&gt;또한 서버 인증서를 검증하여 접속한 서버가 신뢰할 수 있는 대상인지 확인한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/https.png&quot; alt=&quot;https&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;HTTPS (Wireshark)&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;14 ~ 18&lt;/strong&gt; : TCP 3-Way Handshake&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;19, 37&lt;/strong&gt; : TLS Handshake (보안 연결 설정)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;39 ~&lt;/strong&gt; : HTTPS 통신 시작 (HTTP Request/Response가 TLS로 암호화되어 &lt;code&gt;Application Data&lt;/code&gt;로 전송)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;6. DNS&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DNS(Domain Name System)&lt;/code&gt;는 사람이 읽기 쉬운 도메인 이름(예: google.com)을&lt;br /&gt;
컴퓨터가 통신에 사용하는 IP 주소(예: 142.250.198.163)로 변환해 주는 시스템이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/dns.png&quot; alt=&quot;dns&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;DNS (Wireshark)&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\Windows\System32&amp;gt;nslookup google.co.kr
Server:  dns.google
Address: 8.8.8.8

Non-authoritative answer:
Name:    google.co.kr
Addresses:  2404:6800:4005:826::2003
          142.250.198.163
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Record&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;도메인 이름을 IPv4 주소에 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;AAAA&lt;/td&gt;
&lt;td&gt;도메인 이름을 IPv6 주소에 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;CNAME&lt;/td&gt;
&lt;td&gt;다른 도메인 이름을 가리키는 별칭&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MX&lt;/td&gt;
&lt;td&gt;메일을 수신하는 메일 서버를 지정(SMTP 전달 대상)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NS&lt;/td&gt;
&lt;td&gt;해당 도메인의 DNS 정보를 관리하는 네임 서버를 지정&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;7. DHCP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;DHCP(Dynamic Host Configuration Protocol)&lt;/code&gt;는 네트워크에 접속한 장비에게 IP 주소와 네트워크 설정을 자동으로 할당하는 프로토콜이다.&lt;/p&gt;
&lt;p&gt;사용자는 직접 IP를 설정하지 않아도 DHCP 서버를 통해 필요한 정보를 자동으로 받아 네트워크를 사용할 수 있다.&lt;/p&gt;
&lt;p&gt;DHCP를 통해 일반적으로 다음 정보를 할당받는다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IP Address / Subnet Mask / Default Gateway / DNS Server&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;Discover → Offer → Request → ACK
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/dhcp.png&quot; alt=&quot;dhcp&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;DHCP (Wireshark)&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;위 과정을 &lt;strong&gt;DORA&lt;/strong&gt;라고 부른다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Discover&lt;/strong&gt; : 클라이언트가 DHCP 서버를 찾으며 IP 주소를 요청한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Offer&lt;/strong&gt; : DHCP 서버가 사용 가능한 IP 주소와 네트워크 설정(IP, Subnet Mask, Gateway, DNS 등)을 제안한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Request&lt;/strong&gt; : 클라이언트가 제안받은 IP 주소를 사용하겠다고 요청한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ACK&lt;/strong&gt; : DHCP 서버가 요청을 승인하고 IP 주소와 네트워크 설정을 최종 할당한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;DHCP가 완료되면 클라이언트는 할당받은 IP 주소와 네트워크 설정을 이용하여 같은 네트워크 및 외부 인터넷과 통신할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. FTP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;FTP(File Transfer Protocol)&lt;/code&gt;는 Client와 Server 사이에서 파일을 업로드하거나 다운로드할 때 사용하는 프로토콜이다.&lt;/p&gt;
&lt;p&gt;FTP는 명령을 전달하는 &lt;strong&gt;Control Connection&lt;/strong&gt;과 실제 파일을 전송하는 &lt;strong&gt;Data Connection&lt;/strong&gt;을 별도로 사용한다.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;https://medium.com/@ananthakrish16ks/ftp-control-and-data-connections-f74ceb9eae95&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/ftp.png&quot; alt=&quot;FTP Control Connection&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;FTP Control Connection&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/ftp2.png&quot; alt=&quot;FTP Data Connection&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;FTP Data Connection&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Connection&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Control&lt;/td&gt;
&lt;td&gt;로그인, 파일 목록 조회(LIST), 다운로드(RETR) 등 명령 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;실제 파일 및 디렉터리 목록 전송&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;기본 FTP는 암호화를 제공하지 않기 떄문에 계정 정보와 파일 내용이 평문으로 전송될 수 있어 현재는 &lt;strong&gt;SFTP&lt;/strong&gt; 또는 &lt;strong&gt;FTPS&lt;/strong&gt;를 사용하는 경우가 많다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. SSH&lt;/h2&gt;
&lt;p&gt;SSH(Secure Shell)는 원격 서버에 안전하게 접속하여 명령을 실행하기 위한 프로토콜이다.&lt;/p&gt;
&lt;p&gt;기본 포트는 TCP 22번을 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/ssh.png&quot; alt=&quot;ssh&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;SSH Remote Login&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;[주요 기능]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;원격 로그인&lt;/li&gt;
&lt;li&gt;원격 명령 실행&lt;/li&gt;
&lt;li&gt;암호화된 터널링&lt;/li&gt;
&lt;li&gt;키 기반 인증&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;SSH는 모든 통신을 암호화하여 평문 원격 접속(Telnet)의 보안 문제를 해결한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10. SMTP / POP3 / IMAP&lt;/h2&gt;
&lt;p&gt;이메일은 하나의 프로토콜만으로 동작하지 않는다.&lt;/p&gt;
&lt;p&gt;메일을 보내는 역할과 메일을 받는 역할이 서로 분리되어 있다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Role&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SMTP&lt;/td&gt;
&lt;td&gt;메일을 보내거나 다른 메일 서버로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;POP3&lt;/td&gt;
&lt;td&gt;서버의 메일을 클라이언트로 내려받음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;IMAP&lt;/td&gt;
&lt;td&gt;서버에 메일을 유지한 채 여러 장치와 동기화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;SMTP는 메일을 발신하거나 메일 서버 간 메일을 전달할 때 사용된다.&lt;/p&gt;
&lt;p&gt;POP3는 메일을 클라이언트로 다운로드하는 방식이며 일반적으로 서버의 메일을 내려받아 사용하는 구조이다.&lt;/p&gt;
&lt;p&gt;IMAP은 메일을 서버에 그대로 유지하므로 PC, 스마트폰 등 여러 장치에서 동일한 메일함을 동기화하여 사용할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. 실제 웹 접속 과정&lt;/h2&gt;
&lt;p&gt;사용자가 브라우저에 URL을 입력하면 여러 프로토콜이 순차적으로 동작하여 웹 페이지를 화면에 표시한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L7/naver.png&quot; alt=&quot;naver&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Web Access Flow (Wireshark)&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Wireshark 캡처에서는 다음과 같은 순서로 웹 접속이 이루어진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;DNS Query / Response&lt;/strong&gt; : 도메인 이름을 IP 주소로 변환한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TCP 3-Way Handshake&lt;/strong&gt; : 서버와 TCP 연결을 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;TLS Handshake&lt;/strong&gt; : 서버 인증서를 검증하고 암호화 통신을 위한 세션 키를 교환한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Application Data&lt;/strong&gt; : HTTP Request와 HTTP Response가 TLS로 암호화되어 전송된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위 사진 흐름을 순서대로 풀어서 설명하면 아래처럼 표현할수 있다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;사용자가 브라우저에 URL을 입력한다.&lt;/li&gt;
&lt;li&gt;DNS를 통해 도메인 이름을 IP 주소로 변환한다.&lt;/li&gt;
&lt;li&gt;서버와 TCP 3-Way Handshake를 수행하여 연결을 생성한다.&lt;/li&gt;
&lt;li&gt;HTTPS라면 TLS Handshake를 수행하여 서버 인증서를 검증하고 암호화 통신을 준비한다.&lt;/li&gt;
&lt;li&gt;브라우저가 HTTP Request를 전송한다.&lt;/li&gt;
&lt;li&gt;서버가 HTTP Response를 반환한다.&lt;/li&gt;
&lt;li&gt;브라우저가 HTML, CSS, JavaScript를 해석하여 화면을 렌더링한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이처럼 하나의 웹 페이지를 열기 위해서는 DNS, TCP, TLS, HTTP 등 여러 프로토콜이 순차적으로 동작한다.&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>L4 - Transport Layer</title><link>https://cr3xis.github.io/posts/transport-layer/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/transport-layer/</guid><description>OSI 4계층의 역할과 핵심 개념을 정리합니다.</description><pubDate>Wed, 15 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Transport Layer&lt;/h2&gt;
&lt;p&gt;Transport Layer는 OSI 7계층에서 4계층에 해당한다.&lt;/p&gt;
&lt;p&gt;L4는 &lt;code&gt;Port 번호&lt;/code&gt;를 기반으로 호스트 내부의 어떤 프로세스와 통신할지 구분한다.&lt;/p&gt;
&lt;p&gt;이 계층에서 데이터는 TCP 기준으로 &lt;code&gt;Segment&lt;/code&gt;, UDP 기준으로 &lt;code&gt;Datagram&lt;/code&gt; 단위로 다뤄진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[주요 역할]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;프로세스 간 통신 구분&lt;/li&gt;
&lt;li&gt;Port 번호 기반 데이터 전달&lt;/li&gt;
&lt;li&gt;TCP를 통한 신뢰성 있는 전송&lt;/li&gt;
&lt;li&gt;UDP를 통한 빠른 비연결형 전송&lt;/li&gt;
&lt;li&gt;흐름 제어와 혼잡 제어&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Port&lt;/h2&gt;
&lt;p&gt;Port는 하나의 호스트 안에서 어떤 프로세스가 데이터를 받을지 구분하기 위한 번호이다.&lt;/p&gt;
&lt;p&gt;IP 주소가 목적지 호스트를 찾기 위한 값이라면 Port 번호는 그 호스트 안의 애플리케이션을 찾기 위한 값이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L4/port.png&quot; alt=&quot;port&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Port&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;출발지인 &lt;code&gt;49754&lt;/code&gt;는 클라이언트가 임의로 생성한 포트이며 운영체제가 자동으로 할당한다.&lt;/p&gt;
&lt;p&gt;목적지인 &lt;code&gt;443&lt;/code&gt;은 서버가 HTTPS 서비스를 제공하는 고정 포트이다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[Port 범위]&lt;/strong&gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Name&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0 ~ 1023&lt;/td&gt;
&lt;td&gt;Well-Known Port&lt;/td&gt;
&lt;td&gt;주요 서비스에서 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1024 ~ 49151&lt;/td&gt;
&lt;td&gt;Registered Port&lt;/td&gt;
&lt;td&gt;특정 애플리케이션에서 등록해 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;49152 ~ 65535&lt;/td&gt;
&lt;td&gt;Dynamic Port&lt;/td&gt;
&lt;td&gt;클라이언트 임시 포트로 주로 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Socket&lt;/h2&gt;
&lt;p&gt;Socket은 운영체제가 제공하는 네트워크 통신의 끝점(Endpoint)이다.&lt;/p&gt;
&lt;p&gt;프로그램은 소켓을 생성한 뒤 IP 주소, Port 번호, Protocol(TCP/UDP) 정보를 이용하여 다른 호스트와 통신한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;TCP 192.168.10.5:50000  →  93.184.216.34:80
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 예시에서 &lt;code&gt;192.168.10.5:50000&lt;/code&gt;은 클라이언트 소켓, &lt;code&gt;93.184.216.34:80&lt;/code&gt;은 서버 소켓을 의미한다.&lt;/p&gt;
&lt;p&gt;같은 IP 주소를 사용하는 호스트라도 Port 번호가 다르면 운영체제는 서로 다른 프로세스로 데이터를 전달할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. TCP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;TCP(Transmission Control Protocol)&lt;/code&gt;는 연결 지향형 전송 프로토콜이다.&lt;/p&gt;
&lt;p&gt;데이터를 보내기 전에 연결을 수립하고, 전송 중에는 순서 보장과 재전송을 통해 신뢰성을 제공한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[특징]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;연결 지향&lt;/li&gt;
&lt;li&gt;신뢰성 있는 데이터 전송&lt;/li&gt;
&lt;li&gt;데이터 순서 보장&lt;/li&gt;
&lt;li&gt;손실 발생 시 재전송&lt;/li&gt;
&lt;li&gt;흐름 제어와 혼잡 제어 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TCP는 속도보다 데이터가 정확히 도착하는 것이 중요한 통신에 적합하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. TCP Header&lt;/h2&gt;
&lt;p&gt;TCP Header에는 신뢰성 있는 전송을 위해 필요한 정보가 포함된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L4/tcpheader.png&quot; alt=&quot;tcpheader&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Tcp Header&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source Port&lt;/td&gt;
&lt;td&gt;송신 프로세스의 Port 번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination Port&lt;/td&gt;
&lt;td&gt;수신 프로세스의 Port 번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Sequence Number&lt;/td&gt;
&lt;td&gt;데이터 순서 확인에 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Acknowledgment Number&lt;/td&gt;
&lt;td&gt;다음에 받아야 할 데이터 번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Header Length&lt;/td&gt;
&lt;td&gt;TCP Header의 길이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Flags&lt;/td&gt;
&lt;td&gt;연결 제어 상태 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Window Size&lt;/td&gt;
&lt;td&gt;수신 측이 받을 수 있는 데이터 크기&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checksum&lt;/td&gt;
&lt;td&gt;오류 검출&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;6. TCP Flags&lt;/h2&gt;
&lt;p&gt;TCP Flags는 TCP 연결 상태와 제어 정보를 나타내는 값이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L4/tcpflags.png&quot; alt=&quot;tcpflags&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Tcp flags&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Flag&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;SYN&lt;/td&gt;
&lt;td&gt;연결 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ACK&lt;/td&gt;
&lt;td&gt;응답 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FIN&lt;/td&gt;
&lt;td&gt;연결 종료 요청&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RST&lt;/td&gt;
&lt;td&gt;연결 강제 종료&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PSH&lt;/td&gt;
&lt;td&gt;데이터를 즉시 상위 계층으로 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;URG&lt;/td&gt;
&lt;td&gt;긴급 데이터 표시&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;TCP 3-Way Handshake와 4-Way Handshake는 이 Flags를 이용해 연결을 수립하고 종료한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 3-Way Handshake&lt;/h2&gt;
&lt;p&gt;3-Way Handshake는 TCP 연결을 수립하는 과정이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L4/3wayf.png&quot; alt=&quot;3way&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;https://wiki.wireshark.org/&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L4/3way.png&quot; alt=&quot;3way&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;3-way&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Client -&amp;gt; Server : SYN
Server -&amp;gt; Client : SYN + ACK
Client -&amp;gt; Server : ACK
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Client가 Server에게 &lt;code&gt;SYN&lt;/code&gt;을 보내 연결을 요청한다.&lt;/li&gt;
&lt;li&gt;Server는 &lt;code&gt;SYN + ACK&lt;/code&gt;로 요청을 수락한다.&lt;/li&gt;
&lt;li&gt;Client가 &lt;code&gt;ACK&lt;/code&gt;를 보내면 연결이 성립된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;이 과정을 통해 양쪽은 서로 통신 가능한 상태인지 확인하고 초기 Sequence Number를 교환한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 4-Way Handshake&lt;/h2&gt;
&lt;p&gt;4-Way Handshake는 TCP 연결을 종료하는 과정이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L4/4wayf.png&quot; alt=&quot;4way&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;https://wiki.wireshark.org/&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L4/4way.png&quot; alt=&quot;4way&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;4-way&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;현재 이미지의 경우 서버가 즉시 종료 가능해 ACK와 FIN을 하나의 패킷으로 합쳐 보냈기에 3개로 보인다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Client -&amp;gt; Server : FIN
Server -&amp;gt; Client : ACK
Server -&amp;gt; Client : FIN
Client -&amp;gt; Server : ACK
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;한쪽에서 &lt;code&gt;FIN&lt;/code&gt;을 보내 연결 종료를 요청한다.&lt;/li&gt;
&lt;li&gt;상대는 &lt;code&gt;ACK&lt;/code&gt;로 종료 요청을 확인한다.&lt;/li&gt;
&lt;li&gt;상대도 보낼 데이터가 끝나면 &lt;code&gt;FIN&lt;/code&gt;을 보낸다.&lt;/li&gt;
&lt;li&gt;마지막으로 &lt;code&gt;ACK&lt;/code&gt;를 보내 연결을 종료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;TCP는 양방향 통신이므로 각 방향의 종료를 따로 확인한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. UDP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;UDP(User Datagram Protocol)&lt;/code&gt;는 비연결형 전송 프로토콜이다.&lt;/p&gt;
&lt;p&gt;TCP와 달리 연결을 수립하지 않고 데이터를 바로 전송한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[특징]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;비연결형&lt;/li&gt;
&lt;li&gt;Header 구조가 단순함&lt;/li&gt;
&lt;li&gt;전송 속도가 빠름&lt;/li&gt;
&lt;li&gt;순서 보장 없음&lt;/li&gt;
&lt;li&gt;손실 발생 시 자체 재전송 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UDP는 실시간성이 중요한 통신에 자주 사용된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10. UDP Header&lt;/h2&gt;
&lt;p&gt;UDP Header는 TCP Header보다 단순하다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L4/udp.png&quot; alt=&quot;udp&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;UDP Header&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Source Port&lt;/td&gt;
&lt;td&gt;송신 프로세스의 Port 번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Destination Port&lt;/td&gt;
&lt;td&gt;수신 프로세스의 Port 번호&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Length&lt;/td&gt;
&lt;td&gt;UDP Header와 Data의 전체 길이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Checksum&lt;/td&gt;
&lt;td&gt;오류 검출&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;UDP는 연결 관리, 순서 제어, 재전송 기능이 없기 때문에 Header가 짧고 처리 오버헤드가 낮다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. TCP vs UDP&lt;/h2&gt;
&lt;p&gt;TCP와 UDP는 모두 L4 프로토콜이지만 목적이 다르다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;TCP&lt;/th&gt;
&lt;th&gt;UDP&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;연결 방식&lt;/td&gt;
&lt;td&gt;연결형&lt;/td&gt;
&lt;td&gt;비연결형&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;신뢰성&lt;/td&gt;
&lt;td&gt;높음&lt;/td&gt;
&lt;td&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;순서 보장&lt;/td&gt;
&lt;td&gt;보장&lt;/td&gt;
&lt;td&gt;보장하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;재전송&lt;/td&gt;
&lt;td&gt;지원&lt;/td&gt;
&lt;td&gt;지원하지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;속도&lt;/td&gt;
&lt;td&gt;느림&lt;/td&gt;
&lt;td&gt;빠름&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;사용 예시&lt;/td&gt;
&lt;td&gt;HTTP, HTTPS, SSH, FTP&lt;/td&gt;
&lt;td&gt;DNS, DHCP, VoIP, Streaming&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;TCP는 정확성이 중요한 통신에 적합하고 UDP는 속도와 실시간성이 중요한 통신에 적합하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;12. 대표 Port 번호&lt;/h2&gt;
&lt;p&gt;자주 사용되는 서비스는 Well-Known Port를 사용한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Port&lt;/th&gt;
&lt;th&gt;Protocol&lt;/th&gt;
&lt;th&gt;Service&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;20, 21&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;FTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;SSH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;Telnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;SMTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;TCP/UDP&lt;/td&gt;
&lt;td&gt;DNS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;67, 68&lt;/td&gt;
&lt;td&gt;UDP&lt;/td&gt;
&lt;td&gt;DHCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;HTTP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;110&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;POP3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;143&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;IMAP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;443&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;HTTPS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3389&lt;/td&gt;
&lt;td&gt;TCP&lt;/td&gt;
&lt;td&gt;RDP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Port 번호는 통신 대상 서비스나 프로세스를 구분하기 위한 값이다.&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>L3 - Network Layer</title><link>https://cr3xis.github.io/posts/network-layer/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/network-layer/</guid><description>OSI 3계층의 역할과 핵심 개념을 정리합니다.</description><pubDate>Tue, 14 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Network Layer&lt;/h2&gt;
&lt;p&gt;Network Layer는 OSI 7계층에서 3계층에 해당한다.&lt;/p&gt;
&lt;p&gt;L3는 &lt;code&gt;IP 주소&lt;/code&gt;를 기반으로 서로 다른 네트워크 간 Packet을 전달한다.&lt;/p&gt;
&lt;p&gt;이 계층에서 데이터는 &lt;code&gt;Packet&lt;/code&gt; 단위로 다뤄진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[주요 역할]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;IP 주소 기반 통신&lt;/li&gt;
&lt;li&gt;서로 다른 네트워크 간 Packet 전달&lt;/li&gt;
&lt;li&gt;목적지 네트워크 판단&lt;/li&gt;
&lt;li&gt;라우팅 경로 결정&lt;/li&gt;
&lt;li&gt;오류 및 진단 메시지 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip
Network Layer는 목적지 IP 주소를 기준으로 Packet이 어느 네트워크로 가야 하는지 결정하는 계층이다.
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. IPv4&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;IPv4(Internet Protocol version 4)&lt;/code&gt;는 가장 널리 사용되는 L3 주소 체계이다.&lt;/p&gt;
&lt;p&gt;IPv4 주소는 32bit로 구성되며 사람이 읽기 쉽도록 8bit 단위의 10진수로 표현한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L3/ipv4.png&quot; alt=&quot;ipv4&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IPv4&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;IPv4 주소는 크게 &lt;code&gt;Network ID&lt;/code&gt;와 &lt;code&gt;Host ID&lt;/code&gt;로 나눌 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Network ID&lt;/strong&gt; : 장비가 속한 네트워크를 식별&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Host ID&lt;/strong&gt; : 해당 네트워크 안의 장비를 식별&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;3. IPv6&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;IPv6(Internet Protocol version 6)&lt;/code&gt;는 IPv4 주소 부족 문제를 해결하기 위해 만들어진 IP 주소 체계이다.&lt;/p&gt;
&lt;p&gt;IPv6 주소는 128bit로 구성되며 16bit씩 8개의 그룹으로 나누어 16진수와 콜론(&lt;code&gt;:&lt;/code&gt;)을 사용해 표현한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L3/ipv6.png&quot; alt=&quot;ipv6&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IPv6&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;IPv4가 약 43억 개의 주소를 제공하는 반면 IPv6는 거의 무한에 가까운 주소 공간을 제공한다.&lt;/p&gt;
&lt;p&gt;또한 NAT 의존도를 줄이고 주소 자동 설정(SLAAC)과 같은 기능을 지원하여 대규모 네트워크 환경에 적합하다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;IPv4&lt;/th&gt;
&lt;th&gt;IPv6&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;주소 길이&lt;/td&gt;
&lt;td&gt;32bit (8bit × 4)&lt;/td&gt;
&lt;td&gt;128bit (16bit × 8)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;표현 방식&lt;/td&gt;
&lt;td&gt;10진수 (&lt;code&gt;0~9&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;16진수 (&lt;code&gt;0~9&lt;/code&gt;, &lt;code&gt;A~F&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;주소 예시&lt;/td&gt;
&lt;td&gt;&lt;code&gt;192.168.10.5&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;2001:db8::1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;전송 방식&lt;/td&gt;
&lt;td&gt;Unicast&amp;lt;br&amp;gt;Multicast&amp;lt;br&amp;gt;Broadcast&lt;/td&gt;
&lt;td&gt;Unicast&amp;lt;br&amp;gt;Multicast&amp;lt;br&amp;gt;Anycast&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote&gt;
&lt;p&gt;IPv6는 Broadcast를 지원하지 않으며 필요한 경우 Multicast와 Anycast를 사용한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Subnet Mask&lt;/h2&gt;
&lt;p&gt;Subnet Mask는 IP 주소에서 Network ID와 Host ID를 구분하기 위해 사용한다.&lt;/p&gt;
&lt;p&gt;예를 들어 아래와 같은 주소가 있다고 가정한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IP Address  : 192.168.10.5
Subnet Mask : 255.255.255.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 경우 &lt;code&gt;192.168.10.0&lt;/code&gt;은 네트워크 주소가 되고 &lt;code&gt;5&lt;/code&gt;는 해당 네트워크 안의 호스트를 의미한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Network Address : 192.168.10.0
Host Address    : 192.168.10.5
Broadcast       : 192.168.10.255
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;5. CIDR&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;CIDR(Classless Inter-Domain Routing)&lt;/code&gt;은 Subnet Mask를 &lt;code&gt;/prefix&lt;/code&gt; 형태로 표현하는 방식이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;192.168.10.0/24
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 표현에서 &lt;code&gt;/24&lt;/code&gt;는 앞의 24bit가 네트워크 영역이라는 뜻이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CIDR&lt;/th&gt;
&lt;th&gt;Subnet Mask&lt;/th&gt;
&lt;th&gt;사용 가능한 Host 수&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;/24&lt;/td&gt;
&lt;td&gt;255.255.255.0&lt;/td&gt;
&lt;td&gt;254&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/25&lt;/td&gt;
&lt;td&gt;255.255.255.128&lt;/td&gt;
&lt;td&gt;126&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/26&lt;/td&gt;
&lt;td&gt;255.255.255.192&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;/30&lt;/td&gt;
&lt;td&gt;255.255.255.252&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;CIDR은 네트워크를 더 작게 나누거나 여러 네트워크를 하나로 묶어 표현할 때 사용한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Classful Addressing&lt;/h2&gt;
&lt;p&gt;Classful Addressing은  IPv4 주소 체계에서 IP 주소를 A, B, C, D, E 클래스로 나누어 사용하는 방식이다.&lt;/p&gt;
&lt;p&gt;각 클래스는 기본 Subnet Mask가 정해져 있으며 네트워크 규모에 따라 구분된다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Class&lt;/th&gt;
&lt;th&gt;Range&lt;/th&gt;
&lt;th&gt;Default Subnet Mask&lt;/th&gt;
&lt;th&gt;Usage&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;1.0.0.0 ~ 126.255.255.255&lt;/td&gt;
&lt;td&gt;255.0.0.0&lt;/td&gt;
&lt;td&gt;대규모 네트워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;128.0.0.0 ~ 191.255.255.255&lt;/td&gt;
&lt;td&gt;255.255.0.0&lt;/td&gt;
&lt;td&gt;중간 규모 네트워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;192.0.0.0 ~ 223.255.255.255&lt;/td&gt;
&lt;td&gt;255.255.255.0&lt;/td&gt;
&lt;td&gt;소규모 네트워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;224.0.0.0 ~ 239.255.255.255&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Multicast&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;240.0.0.0 ~ 255.255.255.255&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;Reserved&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;7. Network Address&lt;/h2&gt;
&lt;p&gt;Network Address는 특정 네트워크 자체를 나타내는 주소이다.&lt;/p&gt;
&lt;p&gt;Host 영역의 bit가 모두 &lt;code&gt;0&lt;/code&gt;인 주소가 Network Address가 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IP Address      : 192.168.10.5/24
Network Address : 192.168.10.0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Network Address는 네트워크를 식별하기 위한 주소이므로 일반 호스트에 직접 할당하지 않는다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. Broadcast Address&lt;/h2&gt;
&lt;p&gt;IPv4에서 Broadcast Address는 같은 네트워크 안의 모든 호스트에게 데이터를 보낼 때 사용하는 주소이다.&lt;/p&gt;
&lt;p&gt;Host 영역의 bit가 모두 &lt;code&gt;1&lt;/code&gt;인 주소가 Broadcast Address가 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;IP Address        : 192.168.10.5/24
Broadcast Address : 192.168.10.255
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Broadcast Address 역시 네트워크 전체를 대상으로 하는 주소이므로 일반 호스트에 직접 할당하지 않는다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. Default Gateway&lt;/h2&gt;
&lt;p&gt;Default Gateway는 내 네트워크 밖으로 나갈 때 Packet을 전달하는 기본 경로이다.&lt;/p&gt;
&lt;p&gt;같은 네트워크 안의 장비끼리는 L2 통신으로 직접 전달할 수 있지만 다른 네트워크로 가려면 라우터를 거쳐야 한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L3/gateway.png&quot; alt=&quot;gateway&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Gateway&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이때 사용하는 라우터의 IP 주소가 Default Gateway이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10. Routing&lt;/h2&gt;
&lt;p&gt;Routing은 목적지 IP 주소를 보고 Packet을 어느 경로로 보낼지 결정하는 과정이다.&lt;/p&gt;
&lt;p&gt;라우터는 목적지 IP가 어느 네트워크에 속하는지 확인한 뒤 다음 장비(Next Hop) 또는 출력 인터페이스를 선택한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L3/routing.png&quot; alt=&quot;routing&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Routing&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Packet의 Destination IP를 확인한다.&lt;/li&gt;
&lt;li&gt;Routing Table에서 가장 적합한 경로를 찾는다.&lt;/li&gt;
&lt;li&gt;Next Hop 또는 출력 인터페이스를 결정한다.&lt;/li&gt;
&lt;li&gt;해당 경로로 Packet을 전달한다.&lt;/li&gt;
&lt;li&gt;다음 라우터가 동일한 과정 반복&lt;/li&gt;
&lt;li&gt;목적지 네트워크에 도착하면 최종 호스트에게 전달&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h2&gt;11. Routing Table&lt;/h2&gt;
&lt;p&gt;Routing Table은 목적지 네트워크별로 Packet을 어디로 보낼지 정리한 표이다.&lt;/p&gt;
&lt;p&gt;라우터나 운영체제는 Routing Table을 보고 Packet의 다음 경로를 결정한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L3/routingtable.png&quot; alt=&quot;routingtable&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Routing table&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Destination&lt;/td&gt;
&lt;td&gt;목적지 네트워크&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Gateway&lt;/td&gt;
&lt;td&gt;다음으로 전달할 장비&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Interface&lt;/td&gt;
&lt;td&gt;Packet을 내보낼 인터페이스&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Metric&lt;/td&gt;
&lt;td&gt;경로의 우선순위&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;12. Static Routing&lt;/h2&gt;
&lt;p&gt;Static Routing은 관리자가 직접 경로를 설정하는 방식이다.&lt;/p&gt;
&lt;p&gt;네트워크 구조가 단순하고 변경이 적은 환경에서는 정적 라우팅을 사용하기 쉽다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ip route 10.10.20.0 255.255.255.0 10.10.10.2
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;구분&lt;/th&gt;
&lt;th&gt;내용&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;장점&lt;/td&gt;
&lt;td&gt;구조가 단순하고 예측하기 쉽다&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;단점&lt;/td&gt;
&lt;td&gt;네트워크 변경 시 관리자가 직접 수정해야 한다&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;13. Dynamic Routing&lt;/h2&gt;
&lt;p&gt;Dynamic Routing은 라우터끼리 라우팅 정보를 교환하여 최적의 경로를 자동으로 학습하는 방식이다.&lt;/p&gt;
&lt;p&gt;네트워크 규모가 커지거나 경로 변경이 자주 발생하는 환경에서는 사용된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[대표적인 Dynamic Routing Protocol]&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Routing
├── Static Routing
└── Dynamic Routing
    ├── IGP (Interior Gateway Protocol) // 동일 AS 내부
    │   ├── RIP
    │   ├── OSPF
    │   ├── IS-IS
    │   └── EIGRP (Cisco)
    │
    └── EGP (Exterior Gateway Protocol) // 서로 다른 AS 간
        └── BGP
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;14. RIP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;RIP(Routing Information Protocol)&lt;/code&gt;은 거리 벡터 기반 라우팅 프로토콜이다.&lt;/p&gt;
&lt;p&gt;RIP은 목적지까지의 &lt;code&gt;Hop Count&lt;/code&gt;를 기준으로 경로를 선택한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[특징]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Bellman-Ford 알고리즘 사용&lt;/li&gt;
&lt;li&gt;Hop Count를 Metric로 사용하며 최대 15 Hop까지 지원&lt;/li&gt;
&lt;li&gt;큰 네트워크보단 소규모 네트워크 환경에 적합&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;15. OSPF&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;OSPF(Open Shortest Path First)&lt;/code&gt;는 링크 상태 기반 라우팅 프로토콜이다.&lt;/p&gt;
&lt;p&gt;OSPF는 네트워크의 링크 상태 정보를 교환하고 이를 기반으로 최단 경로를 계산한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[특징]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Dijkstra 알고리즘(SPF) 사용&lt;/li&gt;
&lt;li&gt;Link State 기반&lt;/li&gt;
&lt;li&gt;Cost(Metric)를 기준으로 최적 경로 계산&lt;/li&gt;
&lt;li&gt;Area 단위로 네트워크를 나눌 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;16. BGP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;BGP(Border Gateway Protocol)&lt;/code&gt;는 &lt;code&gt;AS(Autonomous System)&lt;/code&gt; 간 라우팅에 사용되는 프로토콜이다.&lt;/p&gt;
&lt;p&gt;인터넷은 여러 AS가 연결된 구조이며 BGP는 AS 사이에서 라우팅 정보를 교환하여 최적의 경로를 결정한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[특징]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AS 간 라우팅&lt;/li&gt;
&lt;li&gt;인터넷 백본에서 사용&lt;/li&gt;
&lt;li&gt;경로 속성(Path Attribute)을 기반으로 경로 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;17. ICMP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ICMP(Internet Control Message Protocol)&lt;/code&gt;는 IP 통신 중 발생하는 오류나 진단 정보를 전달하기 위한 프로토콜이다.&lt;/p&gt;
&lt;p&gt;대표적으로 &lt;code&gt;ping&lt;/code&gt; 명령에서 사용하는 Echo Request와 Echo Reply가 ICMP를 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L3/icmp.png&quot; alt=&quot;ICMP&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;ICMP&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[사용 예시]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Echo Request / Echo Reply&lt;/li&gt;
&lt;li&gt;Destination Unreachable&lt;/li&gt;
&lt;li&gt;Time Exceeded&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;18. NAT&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;NAT(Network Address Translation)&lt;/code&gt;는 IP 주소를 변환하는 기술이다.&lt;/p&gt;
&lt;p&gt;일반적으로 내부 사설 IP를 외부 공인 IP로 변환할 때 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;192.168.10.5:50000 -&amp;gt; 203.0.113.10:40001
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NAT를 사용하면 여러 내부 장비가 하나의 공인 IP를 공유하여 외부 네트워크와 통신할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[주요 목적]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사설 IP를 공인 IP로 변환하여 외부 네트워크와 통신할 수 있게 한다.&lt;/li&gt;
&lt;li&gt;하나의 공인 IP를 여러 내부 장비가 공유하여 공인 IP를 절약한다.&lt;/li&gt;
&lt;li&gt;외부에서는 공인 IP만 보이므로 내부 네트워크 구조를 숨길 수 있다.&lt;/li&gt;
&lt;li&gt;IP와 Port를 함께 &lt;code&gt;변환(PAT)&lt;/code&gt;하여 여러 내부 호스트가 동시에 외부와 통신할 수 있도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>L2 - Data Link Layer</title><link>https://cr3xis.github.io/posts/data-link-layer/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/data-link-layer/</guid><description>OSI 2계층의 역할과 핵심 개념을 정리합니다.</description><pubDate>Sat, 11 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Data Link Layer&lt;/h2&gt;
&lt;p&gt;Data Link Layer는 OSI 7계층에서 2계층에 해당한다.&lt;/p&gt;
&lt;p&gt;데이터링크 계층은 같은 네트워크 내에서 &lt;code&gt;MAC 주소&lt;/code&gt;를 이용하여 실제 장비까지 프레임을 전달하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;이 계층에서 데이터는 &lt;code&gt;Frame&lt;/code&gt; 단위로 다뤄진다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[주요 역할]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MAC 주소 기반 통신&lt;/li&gt;
&lt;li&gt;Frame 단위 캡슐화&lt;/li&gt;
&lt;li&gt;같은 LAN 내부에서의 데이터 전달&lt;/li&gt;
&lt;li&gt;오류 검출을 위한 FCS 사용&lt;/li&gt;
&lt;li&gt;스위치를 통한 프레임 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;:::tip
Data Link Layer는 같은 네트워크 구간 안에서 MAC 주소를 기반으로 Frame을 전달하는 계층이다.
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Ethernet&lt;/h2&gt;
&lt;p&gt;Ethernet은 가장 널리 사용되는 Data Link Layer 기술이다.&lt;/p&gt;
&lt;p&gt;일반적인 유선 LAN 환경에서는 대부분 Ethernet을 기반으로 통신이 이루어진다.&lt;/p&gt;
&lt;p&gt;Ethernet에서는 데이터를 &lt;code&gt;Ethernet Frame&lt;/code&gt; 형태로 만들어 전송한다.&lt;br /&gt;
이 Frame 안에는 송/수신자 MAC 주소, 상위 프로토콜 정보 등이 포함된다.&lt;/p&gt;
&lt;p&gt;정리하자면 이더넷은 같은 네트워크 안에서 Frame을 어떤 형식으로 만들고 전달할지 정한 규칙이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Ethernet Frame&lt;/h2&gt;
&lt;p&gt;Ethernet Frame은 L2에서 사용하는 데이터 전달 단위이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[Destination MAC][Source MAC][EtherType][Payload][FCS]
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Destination MAC&lt;/td&gt;
&lt;td&gt;Frame을 받을 장비의 MAC 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Source MAC&lt;/td&gt;
&lt;td&gt;Frame을 보낸 장비의 MAC 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;EtherType&lt;/td&gt;
&lt;td&gt;상위 계층 프로토콜 종류&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Payload&lt;/td&gt;
&lt;td&gt;실제 전달할 데이터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;FCS&lt;/td&gt;
&lt;td&gt;Frame 오류 검출 값&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L2/ethernet.png&quot; alt=&quot;Ethernet II Frame&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Wireshark에서 확인한 Ethernet II Frame&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;위 사진에서는 Ethernet II Frame 안에 아래와 같은 정보가 포함되어 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source MAC      : 00:15:5d:00:06:06
Destination MAC : 00:15:5d:88:22:18
Type            : IPv4 (0x0800)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;Type&lt;/code&gt; 값이 &lt;code&gt;IPv4 (0x0800)&lt;/code&gt; 이므로 Ethernet Frame의 Payload에는 IPv4 Packet이 들어있다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;:::tip
Wireshark에서 NIC가 처리한 이후의 데이터를 캡처하기 때문에 트레일러나 일부 정보는 보이지 않는 경우가 있다.
:::&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. MAC Address&lt;/h2&gt;
&lt;p&gt;MAC Address는 네트워크 인터페이스를 식별하기 위한 L2 주소이다.&lt;/p&gt;
&lt;p&gt;IP 주소가 논리적인 주소라면 MAC 주소는 같은 네트워크 안에서 실제 Frame 전달에 사용되는 주소이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Source MAC      : 00:15:5d:00:06:06
Destination MAC : 00:15:5d:88:22:18
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;스위치는 Frame의 Source MAC 주소를 보고 MAC Address Table을 학습한다.&lt;br /&gt;
이후 Destination MAC 주소를 기준으로 어떤 포트로 Frame을 전달할지 결정한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. ARP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;ARP(Address Resolution Protocol)&lt;/code&gt;는 IP 주소를 MAC 주소로 변환하기 위한 프로토콜이다.&lt;/p&gt;
&lt;p&gt;이전 글에서는 ARP를 L3와 관련된 프로토콜로 분류했지만 실제 역할은 IP 주소를 L2의 MAC 주소로 연결 해주는것이다.&lt;/p&gt;
&lt;p&gt;L3에서는 목적지를 IP 주소로 표현하지만 같은 LAN 안에서 실제로 Frame을 보내기 위해서는 목적지 MAC 주소가 필요하다.&lt;/p&gt;
&lt;p&gt;ARP 동작 흐름&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;송신자가 목적지 IP에 해당하는 MAC 주소를 모른다.&lt;/li&gt;
&lt;li&gt;ARP Request를 브로드캐스트로 전송한다.&lt;/li&gt;
&lt;li&gt;해당 IP를 가진 장비가 ARP Reply로 자신의 MAC 주소를 응답한다.&lt;/li&gt;
&lt;li&gt;송신자는 응답받은 MAC 주소를 Ethernet Frame의 Destination MAC으로 사용한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L2/arp.png&quot; alt=&quot;ARP Request and Reply&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;ARP Request와 Reply 흐름&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;위 캡처에서는 다음과 같은 ARP 통신을 확인할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Who has 172.30.253.4? Tell 172.30.240.1
172.30.253.4 is at 00:15:5d:00:06:06
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;172.30.253.4&lt;/code&gt; IP를 가진 장비의 MAC 주소가 &lt;code&gt;00:15:5d:00:06:06&lt;/code&gt; 임을 응답받은 것이다.&lt;/p&gt;
&lt;p&gt;ARP는 같은 네트워크 안에서 IP 기반 통신을 실제 Ethernet Frame 전달로 연결해주는 역할을 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. VLAN&lt;/h2&gt;
&lt;p&gt;VLAN(Virtual LAN)은 하나의 물리적 네트워크를 여러 개의 논리적 네트워크로 분리하는 기술이다.&lt;/p&gt;
&lt;p&gt;같은 스위치에 연결되어 있더라도 VLAN이 다르면 서로 다른 네트워크에 있는 것처럼 동작한다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L2/vlan.png&quot; alt=&quot;vlan&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;VLAN 구성&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[VLAN 사용 목적]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;브로드캐스트 도메인 분리&lt;/li&gt;
&lt;li&gt;네트워크 관리 단순화&lt;/li&gt;
&lt;li&gt;부서 또는 서비스 단위의 네트워크 분리&lt;/li&gt;
&lt;li&gt;불필요한 트래픽 확산 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;예를 들어 하나의 스위치에 개발팀, 관리팀, 서버망이 함께 연결되어 있어도 VLAN을 나누면 서로 다른 네트워크처럼 분리할 수 있다.&lt;/p&gt;
&lt;p&gt;VLAN이 나뉜 네트워크끼리 통신하려면 L3 장비를 통한 라우팅이 필요하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. STP&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;STP(Spanning Tree Protocol)&lt;/code&gt;는 스위치 네트워크에서 루프를 방지하기 위한 프로토콜이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L2/stpon.png&quot; alt=&quot;stpon&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;STP ON&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/L2/stpoff.png&quot; alt=&quot;stpoff&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;STP OFF&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;스위치를 여러 경로로 연결하면 장애 발생 시 다른 경로를 사용할 수 있지만 동시에 L2 루프가 발생할 수 있다.&lt;/p&gt;
&lt;p&gt;STP는 스위치끼리 BPDU를 교환하여 루프 없는 경로를 계산하고 하나의 포트를 &lt;code&gt;Blocking&lt;/code&gt; 상태로 전환해 루프를 방지한다.&lt;/p&gt;
&lt;p&gt;실제로 확인해보니 STP가 활성화된 상태에서 &lt;strong&gt;FA0/0 포트가 BLK&lt;/strong&gt; 상태가 되었으며 Event List에서 &lt;strong&gt;ICMP &amp;amp; STP&lt;/strong&gt; 패킷이 함께 전송되는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;반대로 STP를 비활성화하면 &lt;code&gt;Blocking&lt;/code&gt; 포트가 없어져 L2 루프가 발생하고 프레임이 네트워크 내부에서 반복 전달되면서 ICMP 통신도 목적지 PC까지 정상적으로 완료되지 않았다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;[STP 사용 목적]&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;스위치 간 루프 방지&lt;/li&gt;
&lt;li&gt;브로드캐스트 폭주 방지&lt;/li&gt;
&lt;li&gt;이중화 경로 유지&lt;/li&gt;
&lt;li&gt;장애 발생 시 대체 경로 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>OSI 7 Layer &amp; TCP/IP 4 Layer</title><link>https://cr3xis.github.io/posts/osi7_tcpip4/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/osi7_tcpip4/</guid><description>OSI 7계층과 TCP/IP 4계층의 구조, 각 계층의 역할 및 대표 프로토콜을 정리합니다.</description><pubDate>Fri, 10 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Osi7/osi7tcpip4.png&quot; alt=&quot;osi&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;OSI 7 Layer &amp;amp; TCP/IP 4 Layer&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;:::tip
OSI 7계층은 이론적 모델이며, TCP/IP 4계층은 실제 인터넷에서 사용되는 실용적인 모델이다.
:::&lt;/p&gt;
&lt;h2&gt;1. OSI 7계층이란 ?&lt;/h2&gt;
&lt;p&gt;OSI(Open Systems Interconnection) 7계층은 네트워크 통신 과정을 7개의 계층으로 나눠 표준화 한 참조 모델이다.&lt;/p&gt;
&lt;p&gt;각 계층은 서로 다른 역할을 수행하며, 하위 계층의 서비스를 이용하여 데이터를 전달한다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;계층&lt;/th&gt;
&lt;th&gt;이름&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;대표 프로토콜&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;L7&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Application&lt;/strong&gt; (응용)&lt;/td&gt;
&lt;td&gt;사용자에게 네트워크 서비스 제공&lt;/td&gt;
&lt;td&gt;HTTP, FTP, SMTP, DHCP, DNS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L6&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Presentation&lt;/strong&gt; (표현)&lt;/td&gt;
&lt;td&gt;데이터 형식 변환, 암호화, 압축&lt;/td&gt;
&lt;td&gt;TLS/SSL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L5&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Session&lt;/strong&gt; (세션)&lt;/td&gt;
&lt;td&gt;세션 생성, 유지, 종료&lt;/td&gt;
&lt;td&gt;NetBIOS, RPC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L4&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt; (전송)&lt;/td&gt;
&lt;td&gt;신뢰성 있는 데이터 전송&lt;/td&gt;
&lt;td&gt;TCP, UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L3&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Network&lt;/strong&gt; (네트워크)&lt;/td&gt;
&lt;td&gt;IP 주소 지정 및 라우팅&lt;/td&gt;
&lt;td&gt;IP, ICMP, ARP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L2&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Data Link&lt;/strong&gt; (데이터 링크)&lt;/td&gt;
&lt;td&gt;MAC 주소 기반 프레임 전송&lt;/td&gt;
&lt;td&gt;Ethernet, PPP, HDLC&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;L1&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Physical&lt;/strong&gt; (물리)&lt;/td&gt;
&lt;td&gt;전기/광 신호 전송&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;2. TCP/IP 4계층이란 ?&lt;/h2&gt;
&lt;p&gt;TCP/IP 모델은 실제 인터넷에서 사용하는 네트워크 모델로 OSI 7계층을 4개의 계층으로 단순화한 구조이다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;계층&lt;/th&gt;
&lt;th&gt;역할&lt;/th&gt;
&lt;th&gt;대표 프로토콜&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Application&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;사용자 서비스 제공&lt;/td&gt;
&lt;td&gt;HTTP, HTTPS, FTP, SMTP, POP3, DNS, DHCP, SNMP, Telnet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;종단 간 데이터 전송&lt;/td&gt;
&lt;td&gt;TCP, UDP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Internet&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;IP 주소 지정 및 라우팅&lt;/td&gt;
&lt;td&gt;IP, ICMP, ARP, RARP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network Interface&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;네트워크 접근&lt;/td&gt;
&lt;td&gt;Ethernet, PPP, HDLC&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
&lt;h2&gt;캡슐화 &amp;amp; 역캡슐화&lt;/h2&gt;
&lt;h3&gt;캡슐화(Encapsulation)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;송신 측에서 상위 계층의 데이터가 하위 계층으로 전달되면서 각 계층의 헤더 또는 트레일러가 추가되는 과정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;역캡슐화(Decapsulation)&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;수신 측에서 하위 계층으로부터 상위 계층으로 전달되면서 각 계층의 헤더 또는 트레일러를 제거하여 원래의 데이터를 복원하는 과정이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;데이터 전송 과정&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;송신 측에서 데이터가 계층별로 캡슐화 된다.&lt;/li&gt;
&lt;li&gt;물리 계층에서 비트 신호로 변환되어 전송 매체를 통해 수신 측으로 전달된다.&lt;/li&gt;
&lt;li&gt;수신 측에서 계층별로 역캡슐화되어 원래 데이터가 복원된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;전송 계층에서는 TCP 또는 UDP 헤더가 추가되며 데이터 링크 계층에서는 헤더와 트레일러가 추가된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Osi7/capsulation.png&quot; alt=&quot;capsul&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;캡슐화 &amp;amp; 역캡슐화 과정&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;:::note[헤더에 포함되는 주요 정보]&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;전송 계층: 송/수신 포트 번호&lt;/li&gt;
&lt;li&gt;네트워크 계층: 송/수신 IP 주소&lt;/li&gt;
&lt;li&gt;데이터 링크 계층: 송/수신 MAC 주소, FCS(트레일러)
:::&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;PDU (Protocol Data Unit)&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;데이터는 계층을 거치면서 헤더가 추가되고 계층마다 다른 이름으로 불린다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;계층&lt;/th&gt;
&lt;th&gt;PDU&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Application&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Transport&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Segment(TCP) / Datagram(UDP)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Network&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Packet&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Data Link&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Frame&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Physical&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Bit&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>Network Basic</title><link>https://cr3xis.github.io/posts/network-basics/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/network-basics/</guid><description>Network, LAN/WAN, Topology, Packet Switching의 기본 개념을 정리합니다.</description><pubDate>Thu, 02 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. Network란?&lt;/h2&gt;
&lt;p&gt;네트워크(Network)는 여러 장치(Device)가 서로 데이터를 주고받기 위해 연결된 구조이다.&lt;/p&gt;
&lt;p&gt;장치 간 데이터를 안정적으로 전달하기 위해 다양한 규칙(Protocol)과 장비를 사용한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. LAN과 WAN&lt;/h2&gt;
&lt;h3&gt;LAN&lt;/h3&gt;
&lt;p&gt;LAN(Local Area Network)은 제한된 공간 안에서 구성되는 네트워크이다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Basic/LAN.png&quot; alt=&quot;LAN&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;LAN&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;집 내부 네트워크&lt;/li&gt;
&lt;li&gt;회사 내부망&lt;/li&gt;
&lt;li&gt;학교 네트워크&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;WAN&lt;/h3&gt;
&lt;p&gt;WAN(Wide Area Network)은 여러 지역에 있는 네트워크를 연결하는 구조이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Basic/WAN.png&quot; alt=&quot;WAN&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;WAN&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;대표적으로 인터넷이 WAN에 해당한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Topology&lt;/h2&gt;
&lt;p&gt;Topology는 네트워크 장치들이 어떤 형태로 연결되어 있는지를 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;주요 형태&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Bus : 하나의 케이블을 여러 장치가 공유한다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Basic/Bus.png&quot; alt=&quot;LAN&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;LAN&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Star : 중앙 장비(Switch)를 중심으로 연결한다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Basic/Star.png&quot; alt=&quot;Star&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Star&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Ring : 원형으로 연결되어 순차적으로 데이터를 전달한다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Basic/Ring.png&quot; alt=&quot;Ring&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Ring&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Mesh : 모든 장치가 서로 연결되어 높은 안정성을 제공한다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Basic/Mesh.png&quot; alt=&quot;Mesh&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Mesh&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Packet Switching&lt;/h2&gt;
&lt;h3&gt;데이터그램 패킷 교환 방식&lt;/h3&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Basic/Datagram.png&quot; alt=&quot;Datagram&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Datagram&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터를 전송하기 전에 논리적 연결이 설정되어 있지 않음 ( 비연결형 )&lt;/li&gt;
&lt;li&gt;송신 측에서 전송한 순서와 수신 측에서 도착한 순서가 다를수 있음&lt;/li&gt;
&lt;li&gt;패킷이 독립적으로 전송됨&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;가상회선 패킷 교환 방식&lt;/h3&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Network/Basic/Circuit.png&quot; alt=&quot;Circuit&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Circuit&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터를 전송하기 전에 논리적 연결이 설정됨 (연결형)&lt;/li&gt;
&lt;li&gt;각 패킷에는 식별 번호가 포함되어 있으며 모든 패킷을 전송하면 가상회선이 해제됨&lt;/li&gt;
&lt;li&gt;패킷들은 전송된 순서대로 도착함&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 캐스팅(Casting) 전송 방식&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;방식&lt;/th&gt;
&lt;th&gt;통신 형태&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Unicast&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 : 1&lt;/td&gt;
&lt;td&gt;단일 송신자 → 단일 수신자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Multicast&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 : N&lt;/td&gt;
&lt;td&gt;단일 송신자 → 특정 그룹의 다수 수신자&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Broadcast&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 : All&lt;/td&gt;
&lt;td&gt;단일 송신자 → 동일 네트워크의 모든 호스트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Anycast&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;1 : 1&lt;/td&gt;
&lt;td&gt;단일 송신자 → 동일 서비스를 제공하는 여러 노드 중 가장 가까운 노드&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>게임핵의 원리 (5) - Silent Aimbot</title><link>https://cr3xis.github.io/posts/game-hacking-silent-aimbot/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/game-hacking-silent-aimbot/</guid><description>CreateMove 입력 흐름과 인라인 후킹을 이용한 Silent Aimbot의 동작 원리를 분석합니다.</description><pubDate>Wed, 01 Jul 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;해당 문서는 게임에서 사용되는 불법 프로그램들의 동작 원리와 구현 구조를 분석하는 기술 문서입니다.&lt;br /&gt;
본 문서는 보안 연구 및 교육 목적으로 작성되었으며 실제 게임에 대한 악용을 권장하지 않습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이전 문서에서는 ViewAngle을 수정하는 Aimbot의 기본 원리를 살펴보았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/game-hacking-aimbot/&quot;&gt;게임핵의 원리 (4) - Aimbot 핵&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이번 문서에서는 화면은 움직이지 않고 발사에 사용되는 각도만 바꾸는 &lt;code&gt;Silent Aimbot&lt;/code&gt;의 동작 원리를 분석한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Silent Aimbot이란 무엇인가?&lt;/h2&gt;
&lt;p&gt;기본적으로 Aimbot은 계산된 목표 각도를 현재 VeiwAngle에 적용하여 사용자의 화면을 실제 타겟 방향으로 이동한다.&lt;/p&gt;
&lt;p&gt;하지만 Silent Aimbot은 화면에 보이는 시야각을 바꾸지 않고 발사나 입력 처리에 사용되는 각도만 타겟 방향으로 바꾸는 것이다.&lt;br /&gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Silent/silent.png&quot; alt=&quot;Silent Aimbot 동작 결과&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Silent Aimbot&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;그러므로 Silent Aimbot에서 중요한 것은 화면에 보이는 &lt;code&gt;dwViewAngles&lt;/code&gt;가 아니다.&lt;br /&gt;
공격 입력이 처리되는 순간 어떤 각도가 사용되는지 찾는 것이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;입력 명령 흐름&lt;/h2&gt;
&lt;p&gt;공격 입력이 처리될 때 어떤 입력 명령이 만들어지고 그 안에 어떤 각도가 들어가는지 봐야 한다.&lt;/p&gt;
&lt;p&gt;Source SDK의 &lt;code&gt;CInput::CreateMove&lt;/code&gt; 흐름에서 확인할 수 있다.&lt;br /&gt;
&lt;code&gt;in_main.cpp&lt;/code&gt; 파일의 목적 주석에는 서버로 보낼 movement command를 만든다고 되어 있다.&lt;br /&gt;
실제 함수 내부에서도 &lt;code&gt;CUserCmd&lt;/code&gt;를 가져온 뒤 입력값을 채운다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CUserCmd *cmd = &amp;amp;m_pCommands[sequence_number % MULTIPLAYER_BACKUP];

cmd-&amp;gt;command_number = sequence_number;
cmd-&amp;gt;tick_count = gpGlobals-&amp;gt;tickcount;
cmd-&amp;gt;buttons = GetButtonBits(1);
VectorCopy(viewangles, cmd-&amp;gt;viewangles);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;CUserCmd&lt;/code&gt;는 한 tick에서 사용자의 입력 상태를 담는 구조체이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;usercmd.h&lt;/code&gt; 기준으로 아래와 같은 정보를 담게 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;viewangles: 사용자가 바라보는 시야각&lt;/li&gt;
&lt;li&gt;buttons: 공격, 점프, 앉기 같은 입력 버튼 비트 플래그&lt;/li&gt;
&lt;li&gt;command_number: 현재 입력 명령의 번호&lt;/li&gt;
&lt;li&gt;tick_count: 해당 입력 명령이 어느 tick 기준으로 생성되었는지&lt;/li&gt;
&lt;li&gt;forwardmove: 앞뒤 이동 값&lt;/li&gt;
&lt;li&gt;sidemove: 좌우 이동 값&lt;/li&gt;
&lt;li&gt;mousedx / mousedy: 마우스 이동량&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;정리하자면 아래와 같이 생각할수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CInput::CreateMove 호출
-&amp;gt; 현재 입력값을 CUserCmd에 저장
-&amp;gt; ClientMode CreateMove 흐름에서 cmd 수정 가능
-&amp;gt; 최종 UserCmd가 네트워크 메시지로 직렬화
-&amp;gt; 서버로 전송
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 흐름 때문에 일반적인 Silent Aim은 &lt;code&gt;CreateMove&lt;/code&gt; 계열 흐름에서 &lt;code&gt;CUserCmd&lt;/code&gt; 또는 Subtick 입력 명령의 각도를 수정하는 방식으로 설명된다.&lt;/p&gt;
&lt;p&gt;이번 구현에서는 &lt;code&gt;CUserCmd&lt;/code&gt;나 Subtick 구조체 전체를 먼저 복원하지 않았다.&lt;br /&gt;
대신 &lt;code&gt;CreateMove&lt;/code&gt; 관련 입력 처리 흐름을 따라가며 실제 Pitch와 Yaw가 기록되는 지점을 찾는 방식으로 접근했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;CreateMove&lt;/h2&gt;
&lt;p&gt;Silent Aimbot은 발사에 사용되는 각도와 관련 있다.&lt;br /&gt;
그러면 단순히 카메라 각도 주소를 볼 것이 아니라 입력 명령이 만들어지고 공격 입력이 처리되는 흐름을 봐야 한다.&lt;/p&gt;
&lt;p&gt;Source SDK의 &lt;code&gt;CInput::CreateMove&lt;/code&gt;는 &lt;code&gt;CUserCmd&lt;/code&gt;를 만들고 &lt;code&gt;cmd-&amp;gt;viewangles&lt;/code&gt;를 채운다.&lt;br /&gt;
이후 &lt;code&gt;g_pClientMode-&amp;gt;CreateMove(..., cmd)&lt;/code&gt;를 호출하고 그 결과에 따라 &lt;code&gt;engine-&amp;gt;SetViewAngles(cmd-&amp;gt;viewangles)&lt;/code&gt;와 prediction view angle을 갱신한다.&lt;/p&gt;
&lt;p&gt;CS2 내부 구조가 Source SDK와 완전히 같다고 단정할수는 없지만&lt;br /&gt;
Source 계열에서 입력 명령과 ViewAngle이 &lt;code&gt;CreateMove&lt;/code&gt; 흐름내에 있다고 생각할수 있다.&lt;/p&gt;
&lt;p&gt;그래서 IDA에서 &lt;code&gt;CreateMove&lt;/code&gt; 문자열을 먼저 검색했다.&lt;br /&gt;
문자열이 있다고 해서 그 위치가 곧바로 함수의 시작점이라는 뜻은 아니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Silent/IDA.png&quot; alt=&quot;CreateMove Strings&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;CreateMove Strings&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;검색 결과 &lt;code&gt;CreateMove&lt;/code&gt; 관련 문자열은 여러 개가 나왔다.&lt;br /&gt;
그중 &lt;code&gt;dev_create_move_report&lt;/code&gt;처럼 리포트 성격에 가까운 문자열도 있었고 &lt;code&gt;invalid player history&lt;/code&gt;처럼 특정 검증 흐름에 가까운 문자열도 있었다.&lt;/p&gt;
&lt;p&gt;내가 우선적으로 본 것은 &lt;code&gt;cmd&lt;/code&gt;, &lt;code&gt;tick&lt;/code&gt;, &lt;code&gt;attack history&lt;/code&gt; 같은 단어가 함께 등장하고 같은 함수 안에서 여러 번 참조되는 흐름이었다.&lt;br /&gt;
이 단어들은 입력 명령과 공격 기록을 다루는 흐름일 가능성이 높기 때문이다.&lt;/p&gt;
&lt;p&gt;따라서 해당 문자열을 참조하는 함수의 XREF를 따라가고 함수 내부에서 실제 각도 값이 사용되는지 동적 분석으로 확인했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;동적 분석&lt;/h2&gt;
&lt;p&gt;후보 함수의 흐름을 따라가며 XMM 레지스터를 확인했다.&lt;br /&gt;
각도 값은 float 형태로 처리되므로 XMM 레지스터에 현재 ViewAngle과 같은 값이 들어오는지 확인했다.&lt;/p&gt;
&lt;p&gt;분석 중 아래와 같은 구간을 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Silent/infunc.png&quot; alt=&quot;Pitch / Yaw&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Pitch / Yaw&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;movss xmm0, [rdi+10]
movss [rcx+18], xmm0

movss xmm1, [rdi+14]
movss [rcx+1C], xmm1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;xmm0 에 저장된 Pitch와 xmm1에 저장된 Yaw가 각각 기록되는것을 확인할수 있다.&lt;br /&gt;
해당 값을 수정하면 화면은 그대로지만 발사 방향이 다른곳으로 바뀌는 것을 확인했고&lt;br /&gt;
해당 위치가 발사에 참조되는 각도 기록 지점이라고 판단했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;후킹&lt;/h2&gt;
&lt;p&gt;이제 필요한 것은 해당 위치에서 원본 Pitch와 Yaw 대신 내가 계산한 Pitch와 Yaw를 기록하는 것이다.&lt;/p&gt;
&lt;p&gt;이번 구현에서는 MASM으로 Stub을 작성했다.&lt;br /&gt;
후킹 지점에는 Stub으로 이동하는 점프만 설치하고 실제 어셈블리 흐름은 ASM 파일에 작성하는 방식이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;후킹 지점
-&amp;gt; MASM Stub으로 이동
-&amp;gt; Silent 온/오프 체크
-&amp;gt; 켜진 상태라면 Pitch / Yaw 덮어쓰기
-&amp;gt; 원본 명령어 실행
-&amp;gt; 원래 코드로 복귀
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;FF 25 00 00 00 00&lt;/code&gt;은 다음 8바이트에 들어있는 주소를 읽어 그 위치로 점프하는 형태이다.&lt;br /&gt;
이 방식으로 64비트 주소를 직접 Stub으로 넘길 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FF 25 00 00 00 00
8 byte address
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;인라인 훅 설치&lt;/h2&gt;
&lt;p&gt;후킹을 설치하는 과정은 단순하다.&lt;br /&gt;
후킹할 코드 영역의 메모리 보호 속성을 변경한 뒤 그 위치에 점프 코드를 덮어쓴다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Silent/inlinehook.png&quot; alt=&quot;JMP&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;JMP&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;VirtualProtect(hookAddress, hookSize, PAGE_EXECUTE_READWRITE, &amp;amp;oldProtect);

patch[0] = 0xFF;
patch[1] = 0x25;
memcpy(patch + 6, &amp;amp;stubAddress, sizeof(stubAddress));
memset(patch + 14, 0x90, hookSize - 14);

memcpy(hookAddress, patch, hookSize);

FlushInstructionCache(GetCurrentProcess(), hookAddress, hookSize);
VirtualProtect(hookAddress, hookSize, oldProtect, &amp;amp;temp);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;코드 영역을 쓰기 가능상태로 변경한뒤 후킹 지점에서 ASM Stub 으로 점프시키고 남는 바이트는 정리하면 된다.&lt;/p&gt;
&lt;p&gt;이렇게 하면 게임 코드가 해당 위치에 도달했을 때 원래 명령어를 실행하기 전에 내가 작성한 Stub으로 먼저 이동한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;MASM Stub&lt;/h2&gt;
&lt;p&gt;MASM을 사용한 이유는 x64 MSVC에서는 C++ 인라인 어셈블리를 사용할 수 없고&lt;br /&gt;
ASM Stub으로 분리하면 실제 어셈블리 흐름을 그대로 보면서 작성할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Silent/asmstub.png&quot; alt=&quot;ASM&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;ASM&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmp byte ptr [g_SilentActive], 0
je originalcode

movss xmm0, dword ptr [g_SilentPitch]
movss xmm1, dword ptr [g_SilentYaw]

movss dword ptr [rcx+18h], xmm0
movss dword ptr [rcx+1Ch], xmm1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;g_SilentActive&lt;/code&gt;가 꺼져 있다면 원본 코드로 바로 이동한다.&lt;br /&gt;
켜져 있다면 &lt;code&gt;g_SilentPitch&lt;/code&gt;와 &lt;code&gt;g_SilentYaw&lt;/code&gt; 값을 이용해 각도를 덮어쓴다.&lt;/p&gt;
&lt;p&gt;그 다음에는 후킹으로 덮어쓴 원본 명령어를 다시 실행해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;originalcode:
    movss xmm0, dword ptr [rdi+18h]
    mov dword ptr [rcx+10h], eax
    movss dword ptr [rcx+20h], xmm0

    mov rcx, qword ptr [g_SilentLeaTarget]
    jmp qword ptr [g_SilentReturn]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;마지막에는 &lt;code&gt;g_SilentReturn&lt;/code&gt;으로 점프해 원래 코드 흐름으로 돌아간다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Silent Angle 적용&lt;/h2&gt;
&lt;p&gt;아래와 같이 해당 기능이 켜져있다면 아래와 같이 각도만 수정해주면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt; if (Options::SilentAimbot)
 {
     g_SilentPitch = targetAngle.x;
     g_SilentYaw = targetAngle.y;
     g_SilentActive = 1;
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사용자의 화면은 움직이지 않지만 공격은 타겟 방향으로 처리되는 형태가 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;결과&lt;/h2&gt;
&lt;p&gt;적을 바라보지 않고 아무곳이나 바라보더라도 적이 죽는 &lt;code&gt;Silent Aim&lt;/code&gt; 이 완성되었다.&lt;br /&gt;
Fov 조건을 함께 적용하면 특정 범위 안의 대상만 선택하도록 할수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe
width=&quot;100%&quot;
height=&quot;450&quot;
src=&quot;https://www.youtube.com/embed/chfWA_pIAbU&quot;
title=&quot;Aimbot(Silent)&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
referrerpolicy=&quot;strict-origin-when-cross-origin&quot;
allowfullscreen&amp;gt;
&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Silent Aimbot은 화면 ViewAngle을 수정하는 것이 아닌 입력 명령이나 발사 처리에서 사용되는 각도를 찾아&lt;br /&gt;
그 지점에서 값을 변경하는 방식이다.&lt;/p&gt;
&lt;p&gt;해당 시리즈를 작성하기 위해 관련 자료를 찾아보고 직접 분석하며 하나씩 동작을 확인하는 과정을 반복했다.&lt;br /&gt;
하지만 시간이 지나면 구조와 오프셋이 변경되어 다시 분석해야하는 경우가 많았고 생각보다 많은 시간과 노력이 필요하다.&lt;/p&gt;
&lt;p&gt;그래서 앞으로도 새로운 내용을 정리할 생각은 있지만 분석 난이도와 유지보수 부담을 고려하면 이 시리즈를 계속 쓰는것은 고민할것 같다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;참고&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ValveSoftware/source-sdk-2013/blob/master/src/game/client/in_main.cpp&quot;&gt;Valve Source SDK 2013 - in_main.cpp&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ValveSoftware/source-sdk-2013/blob/master/src/game/shared/usercmd.h&quot;&gt;Valve Source SDK 2013 - usercmd.h&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;in_main.cpp&lt;/code&gt;에서는 &lt;code&gt;CInput::CreateMove&lt;/code&gt;가 &lt;code&gt;CUserCmd&lt;/code&gt;를 만들고 &lt;code&gt;command_number&lt;/code&gt;, &lt;code&gt;tick_count&lt;/code&gt;, &lt;code&gt;buttons&lt;/code&gt;, &lt;code&gt;viewangles&lt;/code&gt; 같은 입력 값을 채우는 흐름을 확인할 수 있다.&lt;br /&gt;
&lt;code&gt;usercmd.h&lt;/code&gt;에서는 &lt;code&gt;CUserCmd&lt;/code&gt; 구조체가 실제로 입력 명령에 필요한 값을 담는 구조체임을 확인할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>게임핵의 원리 (4) - Aimbot 핵</title><link>https://cr3xis.github.io/posts/game-hacking-aimbot/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/game-hacking-aimbot/</guid><description>각도 계산, FOV, Smooth 처리를 중심으로 Aimbot의 동작 원리를 분석합니다.</description><pubDate>Sat, 20 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;해당 문서는 게임에서 사용되는 불법 프로그램들의 동작 원리와 구현 구조를 분석하는 기술 문서입니다.&lt;br /&gt;
본 문서는 보안 연구 및 교육 목적으로 작성되었으며 실제 게임에 대한 악용을 권장하지 않습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이전 문서에서는 게임 내부 데이터를 읽고 화면 위에 표시하는 ESP의 동작 원리를 살펴보았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/game-hacking-esp/&quot;&gt;게임핵의 원리 (3) - ESP 핵&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이번 문서에서는 ESP에서 사용했던 좌표와 각도 개념을 이어서 Aimbot의 기본 원리를 분석한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Aimbot이란 무엇인가?&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Aimbot/aimbot.png&quot; alt=&quot;Aimbot&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Aimbot&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Aimbot은 사용자가 직접 조준점을 움직이지 않아도 특정 대상을 자동으로 조준하게 만드는 기능이다.&lt;/p&gt;
&lt;p&gt;FPS 게임에서 총알은 일반적으로 사용자가 바라보는 방향을 기준으로 발사된다.&lt;br /&gt;
그러므로 에임봇의 핵심은 내가 바라봐야 하는 방향을 계산하고 그 방향으로 시점을 이동시키는 것이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Aimbot의 구현 방식&lt;/h2&gt;
&lt;h3&gt;1. 각도를 계산하여 회전하는 방식&lt;/h3&gt;
&lt;p&gt;내 위치와 적 위치를 이용해 적을 바라보기 위한 각도를 계산한다.&lt;br /&gt;
그 다음 게임 메모리에 저장된 ViewAngle을 수정하여 카메라를 회전시키는 방식이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;내 위치 + 적 위치
-&amp;gt; 목표 Pitch와 Yaw 계산
-&amp;gt; ViewAngle 수정
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이번 문서에서 중점적으로 다룰 방식이다.&lt;/p&gt;
&lt;h3&gt;2. 화면 좌표를 계산하여 마우스를 이동하는 방식&lt;/h3&gt;
&lt;p&gt;적의 3D 월드 좌표를 2D 화면 좌표로 변환한 뒤 화면 중앙에서 해당 위치까지 마우스를 이동시키는 방식이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;적 월드 좌표
-&amp;gt; WorldToScreen
-&amp;gt; 화면 중앙 기준 이동량 계산
-&amp;gt; SendInput 같은 입력 API로 마우스 이동
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방식은 ViewAngle 메모리를 직접 수정하지 않아도 된다.&lt;/p&gt;
&lt;h3&gt;3. AI를 이용한 방식&lt;/h3&gt;
&lt;p&gt;게임 메모리를 읽지 않고 화면 이미지를 분석하여 적을 찾는 방식도 존재한다.&lt;br /&gt;
이는 모델 인식이나 색상 인식에 가까운 방식이며 이 글에서는 다루지 않는다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;필요한 정보&lt;/h2&gt;
&lt;p&gt;에임봇은 플레이어의 시점에서 목표 위치까지 방향을 계산하여 카메라가 바라보는 방향을 변경하는 방식으로 동작한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Camera Position (Eye Position) : 현재 시점의 위치&lt;/li&gt;
&lt;li&gt;Target Position : 조준할 대상의 위치&lt;/li&gt;
&lt;li&gt;View Angle (Pitch, Yaw) : 계산된 각도를 적용할 플레이어의 시점&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;위 정보만으로 가장 기본적인 에임봇의 형태를 완성 시킬수 있다.&lt;br /&gt;
하지만 원하는 대상만 조준하기 위해서는 아래와 같은 정보들도 사용하는 경우가 많다.&lt;/p&gt;
&lt;p&gt;팀, 체력, 벽, 타격위치, 거리 등등이 있을걸로 생각된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Euler Angle&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Aimbot/eulerangle.png&quot; alt=&quot;Euler Angle&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Euler Angle&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;게임에서 바라보는 방향은 보통 각도로 표현된다.&lt;br /&gt;
이때 자주 보이는 개념이 Euler Angle이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Pitch -&amp;gt; 위를 볼지 아래를 볼지
Yaw   -&amp;gt; 왼쪽을 볼지 오른쪽을 볼지
Roll  -&amp;gt; 화면을 기울일지
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;FPS 게임에서 조준에 주로 필요한 값은 Pitch와 Yaw이다.&lt;br /&gt;
Roll은 화면 기울기와 관련된 값이라 일반적인 조준 계산에서는 거의 사용하지 않는다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Degree와 Radian&lt;/h2&gt;
&lt;p&gt;사람이 각도를 생각할 때는 보통 90도나 180도처럼 Degree 단위를 사용한다.&lt;br /&gt;
하지만 C++의 &lt;code&gt;atan2&lt;/code&gt; 같은 삼각 함수는 Radian 단위로 값을 반환한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Degree: 한 바퀴를 360도로 표현 ( 30° , 60°, 90° )&lt;/li&gt;
&lt;li&gt;Radian: 원의 반지름과 호의 길이를 기준으로 표현 ( 1.2 , 3.4, 5.5 )&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래서 &lt;code&gt;atan2&lt;/code&gt;를 통해 얻은 값을 게임에서 사용하는 각도 값으로 쓰려면 Degree로 변환해야 한다.&lt;/p&gt;
&lt;p&gt;계산 공식은 아래와 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;degree = radian * 180.0f / PI;
radian = degree * PI / 180.0f;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Yaw / Pitch 계산&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Aimbot/3d.png&quot; alt=&quot;3D&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;3D&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;필요한 데이터와 기본 수학 개념을 알았다면 이제 실제 각도를 계산할 수 있다.&lt;br /&gt;
계산의 시작은 내 눈 위치를 기준점으로 두는 것이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vector3 delta;
delta.x = target.x - eye.x;
delta.y = target.y - eye.y;
delta.z = target.z - eye.z;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;delta&lt;/code&gt;는 내 위치에서 타겟까지의 방향 벡터이다.&lt;br /&gt;
월드 전체 기준 좌표를 내 위치 기준 좌표처럼 바꿔서 보는 과정이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;1. Yaw (좌/우)&lt;/h3&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Aimbot/yaw.png&quot; alt=&quot;YAW&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;YAW&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Yaw는 좌우 회전 각도이다.&lt;br /&gt;
위에서 내려다본 2D 평면을 기준으로 내 위치가 &lt;code&gt;0, 0, 0&lt;/code&gt;에 있다고 가정한다.&lt;/p&gt;
&lt;p&gt;위에서 바라본 시점에서는 높이 값인 &lt;code&gt;z&lt;/code&gt;를 생각하지 않는다.&lt;br /&gt;
바닥 평면에서 타겟이 어느 방향에 있는지만 계산하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;float yaw = atan2(delta.y, delta.x) * 180.0f / PI;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;atan2(delta.y, delta.x)&lt;/code&gt;를 이용해 타겟이 어느 방향에 있는지 각도를 구한다.&lt;br /&gt;
반환값은 Radian이므로 &lt;code&gt;180 / PI&lt;/code&gt;를 곱해 Degree로 변환한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;2. Pitch (상/하)&lt;/h3&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/Aimbot/pitch.png&quot; alt=&quot;PITCH&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;PITCH&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Pitch는 위 아래 회전 각도이다.&lt;br /&gt;
Pitch를 구하려면 먼저 내 위치에서 타겟까지의 수평 거리가 필요하다.&lt;/p&gt;
&lt;p&gt;수평 거리는 &lt;code&gt;x&lt;/code&gt;와 &lt;code&gt;y&lt;/code&gt;만 사용해 구한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;float hyp = sqrt(delta.x * delta.x + delta.y * delta.y);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;hyp&lt;/code&gt;는 위에서 바라본 평면에서 내 위치와 타겟 사이의 거리이다.&lt;br /&gt;
이 값과 높이 차이인 &lt;code&gt;delta.z&lt;/code&gt;를 이용하면 위 아래 각도를 구할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;float pitch = -atan2(delta.z, hyp) * 180.0f / PI;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;앞의 &lt;code&gt;-&lt;/code&gt; 부호는 게임에서 사용하는 Pitch 방향에 맞추기 위한 값이다.&lt;br /&gt;
게임마다 위를 볼 때 값이 증가하는지 감소하는지가 다를 수 있으므로 실제 값은 디버깅을 통해 확인해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;CalcAngle&lt;/h3&gt;
&lt;p&gt;위 내용을 코드로 정리하면 아래처럼 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// src = ViewPos, dst = TargetPos
Vector2 CalcAngle(const Vector3&amp;amp; src, const Vector3&amp;amp; dst)
{
    const Vector3 delta
    {
        dst.x - src.x,
        dst.y - src.y,
        dst.z - src.z
    };

    const float hyp = std::sqrt(delta.x * delta.x + delta.y * delta.y);

    return
    {
        -std::atan2(delta.z, hyp) * 180.0f / PI, std::atan2(delta.y, delta.x) * 180.0f / PI
        // { pitch, yaw }
    };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이러한 형태의 계산식을 통해 어디를 바라봐야할지 각도를 구할수 있게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;ViewAngle 적용&lt;/h2&gt;
&lt;p&gt;목표 각도를 구했다면 현재 ViewAngle을 해당 값으로 바꾸면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vector2 targetAngle = CalcAngle(localEyePos, targetBonePos);
*(Vector2*)(Client + Offsets::dwViewAngles) = targetAngle;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 방식은 가장 단순하며 타겟이 정해지는 순간 시점이 바로 적을 향해 이동한다.&lt;br /&gt;
아래 영상을 통해 확인을 할수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;iframe
width=&quot;100%&quot;
height=&quot;450&quot;
src=&quot;https://www.youtube.com/embed/Hgv_lMx5X0U&quot;
title=&quot;Aimbot(Standard)&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
referrerpolicy=&quot;strict-origin-when-cross-origin&quot;
allowfullscreen&amp;gt;
&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Advanced&lt;/h2&gt;
&lt;p&gt;지금까지 내 위치와 타겟의 위치를 이용해 목표 각도를 계산한 뒤 ViewAngle에 적용하는 가장 기본적인 Aimbot 방식을 알아보았다.&lt;/p&gt;
&lt;p&gt;하지만 이 방식만으로는 대상에 대한 구분없이 항상 조준하게 되므로 여러 문제가 발생하게 된다.&lt;br /&gt;
같은 팀이나 이미 사망한 플레이어를 조준한다던지, 화면 밖이나 벽 너머의 적까지 대상으로 선택할수 있게 된다.&lt;/p&gt;
&lt;p&gt;따라서 실제 구현에서는 조준 대상을 선택하기 전에 다양한 조건을 통해 유효한 타겟만 선별하는 과정이 필요하다.&lt;/p&gt;
&lt;p&gt;일반적으로 체력, 팀, 거리, Fov, 벽 충돌 여부, 타격 부위 등의 조건을 이용해 대상을 필터링 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;FOV&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Fov(Field of View)&lt;/code&gt;는 에임봇이 반응할 범위를 제한하기 위해 사용한다.&lt;br /&gt;
현재 시점에서 가까이 있는 적이 아닌 멀리있는 적을 조준할 필요는 없기 때문이다.&lt;/p&gt;
&lt;p&gt;구하는 방식은 현재 내 시야각과 타겟을 바라보기 위해 계산한 각도의 차이를 이용해 구하면 된다.&lt;/p&gt;
&lt;p&gt;이후 Fov 기능이 켜져 있다면 설정한 범위 밖의 대상은 제외하는 조건을 걸면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vector2 delta = GetAngleDelta(MyViewAngle, targetAngle);
float angleDistance = sqrt(delta.x * delta.x + delta.y * delta.y);

if (angleDistance &amp;gt; maxAngleRange) continue;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;iframe
width=&quot;100%&quot;
height=&quot;450&quot;
src=&quot;https://www.youtube.com/embed/KMzn3_fLH_8&quot;
title=&quot;Aimbot(Fov)&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
referrerpolicy=&quot;strict-origin-when-cross-origin&quot;
allowfullscreen&amp;gt;
&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Smooth&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Smooth&lt;/code&gt; 는 계산된 각도로 한 번에 이동하지 않고 조금씩 이동시키기 위해 사용한다.&lt;br /&gt;
목표 각도로 바로 적용하면 시점이 순간적으로 타겟을 향하기 때문에 자연스러움을 추가 한거라고 생각하면 편하다.&lt;/p&gt;
&lt;p&gt;방법은 현재 내 시야각과 최종적으로 선택된 타겟 각도의 차이를 구한 뒤 그 값을 &lt;code&gt;smooth&lt;/code&gt; 값으로 나누어 적용하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vector2 delta = GetAngleDelta(MyViewAngle, targetAngle);

FinalAngle.x = MyViewAngle.x + delta.x / smooth;
FinalAngle.y = MyViewAngle.y + delta.y / smooth;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;iframe
width=&quot;100%&quot;
height=&quot;450&quot;
src=&quot;https://www.youtube.com/embed/RhOxRNrwhUI&quot;
title=&quot;Smooth&quot;
frameborder=&quot;0&quot;
allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot;
referrerpolicy=&quot;strict-origin-when-cross-origin&quot;
allowfullscreen&amp;gt;
&amp;lt;/iframe&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Aimbot의 핵심은 상대를 바라보기 위한 목표 각도를 계산해 View Angle에 적용하는 것이다.&lt;/p&gt;
&lt;p&gt;실제 구현에서는 계산된 각도 뿐만 아니라 Fov, Smooth 등의 조건을 함께 이용해보았다.&lt;/p&gt;
&lt;p&gt;다음 글에서는 화면은 움직이지 않고 발사 각도만 변경하는 &lt;code&gt;Silent Aimbot&lt;/code&gt;의 동작원리에 대해서 알아보도록 하겠다.&lt;/p&gt;
</content:encoded></item><item><title>게임핵의 원리 (3) - ESP 핵</title><link>https://cr3xis.github.io/posts/game-hacking-esp/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/game-hacking-esp/</guid><description>View Matrix와 WorldToScreen 변환을 이용한 ESP 표시 원리를 분석합니다.</description><pubDate>Wed, 10 Jun 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;해당 문서는 게임에서 사용되는 불법 프로그램들의 동작 원리와 구현 구조를 분석하는 기술 문서입니다.&lt;br /&gt;
본 문서는 보안 연구 및 교육 목적으로 작성되었으며 실제 게임에 대한 악용을 권장하지 않습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이전 문서에서는 Direct3D9의 렌더링 함수 후킹을 이용하여 Z-Buffer를 조작하는 WallHack의 동작 원리를 살펴보았다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/game-hacking-direct3d-wallhack/&quot;&gt;게임핵의 원리 (2) - Direct3D 기반 WallHack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이번 문서에서는 WallHack과 함께 자주 언급되는 ESP의 동작 원리를 분석한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;ESP란 무엇인가?&lt;/h2&gt;
&lt;p&gt;ESP는 단순히 벽 뒤의 모델을 보이게 만드는 방식과는 다르게 게임 내부의 데이터를 읽고 해당 정보를 화면 위에 별도로 표시하는 방식이다.&lt;/p&gt;
&lt;p&gt;ESP는 Extra Sensory Perception의 약자로 게임 내부의 정보를 사용자 화면에 추가로 표시하는 기능을 의미한다.&lt;br /&gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ESPall.jpg&quot; alt=&quot;ESP&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;ESP&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;예를 들어 게임 내부에는 다음과 같은 데이터가 존재한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;플레이어 위치&lt;/li&gt;
&lt;li&gt;체력&lt;/li&gt;
&lt;li&gt;팀 정보&lt;/li&gt;
&lt;li&gt;이름&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ESP는 이러한 데이터를 읽어와서 화면 위에 시각적으로 표시한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;게임 내부 데이터
    -&amp;gt; 플레이어의 3D 월드 좌표 획득
    -&amp;gt; 2D 화면 좌표로 변환
    -&amp;gt; 오버레이 또는 렌더링 후킹을 통해 화면에 표시
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;핵심은 3D 좌표를 2D 좌표로 변환하는 과정이다.&lt;br /&gt;
게임 내부의 위치 정보는 &lt;code&gt;{ x, y, z }&lt;/code&gt; 형태의 월드 좌표이지만&lt;br /&gt;
화면에 선이나 문자를 그리기 위해서는 &lt;code&gt;{ x, y }&lt;/code&gt; 형태의 픽셀 좌표가 필요하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Z-Buffer WallHack과 ESP의 차이&lt;/h2&gt;
&lt;p&gt;Z 버퍼를 조작하여 벽 뒤의 오브젝트가 보이게 하는 방식이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Z-Buffer WallHack
-&amp;gt; 렌더링 상태를 변경한다.
-&amp;gt; 깊이 검사를 조작하여 벽 뒤의 모델을 그대로 렌더링한다.
-&amp;gt; 게임 렌더링 파이프라인에 직접 개입한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ESP는 모델 자체를 벽 너머로 보이게 만드는 것이 아니라 게임 내부 데이터를 읽어 별도의 정보를 화면 위에 그린다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ESP
-&amp;gt; 엔티티 정보를 읽는다.
-&amp;gt; 월드 좌표를 화면 좌표로 변환한다.
-&amp;gt; 이름, 체력, 박스, 뼈대 등을 따로 그린다.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;즉 ESP는 렌더링 상태를 직접 변조하는 방식이라기보다 게임 내부 데이터를 기반으로 부가적인 UI를 그리는 방식에 가깝다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;ESP에 필요한 정보&lt;/h2&gt;
&lt;p&gt;ESP에서 가장 먼저 필요한 정보는 엔티티 리스트이다.&lt;/p&gt;
&lt;p&gt;FPS 게임에서 ESP를 구현하려면 결국 상대 플레이어의 좌표가 필요하다.&lt;br /&gt;
상대 플레이어의 좌표, 체력, 이름, 뼈대 정보는 일반적으로 엔티티 객체 또는 그와 연결된 객체에서 얻을 수 있다.&lt;/p&gt;
&lt;p&gt;일반적인 게임에서는 엔티티 리스트가 단순한 배열처럼 구성되어 아래와 같은 경우로 되어있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EntityList[0]
EntityList[1]
EntityList[2]
...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그러나 분석한 게임은 단순히 &lt;code&gt;EntityList[i]&lt;/code&gt;로 접근하는 구조가 아니었다.&lt;br /&gt;
엔티티 리스트가 청크 단위로 관리되고 있었고 Controller와 Pawn객체가 분리되어 있었다.&lt;/p&gt;
&lt;p&gt;분석한 결과 아래와 같은 형식으로 되어 있었다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/Controller.png&quot; alt=&quot;Controller&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Controller&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/Pawn.png&quot; alt=&quot;Pawn&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Pawn&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;이를 코드로 표현하면 아래와 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;uint32_t pawnHandle = 0;
uintptr_t pawnListEntry = 0;
uintptr_t C_CSPlayerPawn = 0;

ReadMemory(Client + Offsets::dwEntityList, dwEntityList);
ReadMemory(dwEntityList + (8 * ((i &amp;amp; 0x7FFF) &amp;gt;&amp;gt; 9)) + 0x10, controllerListEntry);
ReadMemory(controllerListEntry + 0x70 * (i &amp;amp; 0x1FF), CCSPlayerController);
ReadMemory(CCSPlayerController + Offsets::m_hPlayerPawn, pawnHandle);

// entity list table + pawn handle chunk index -&amp;gt; pawn이 들어있는 list entry(chunk)
ReadMemory(dwEntityList + (8 * ((pawnHandle &amp;amp; 0x7FFF) &amp;gt;&amp;gt; 9)) + 0x10, pawnListEntry);
// pawn list entry + pawn handle slot index -&amp;gt; C_CSPlayerPawn
ReadMemory(pawnListEntry + 0x70 * (pawnHandle &amp;amp; 0x1FF), C_CSPlayerPawn);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Pawn 핸들은 단순 주소가 아니라 엔티티 리스트 내부에서 실제 객체를 찾기 위한 값으로 사용된다.&lt;br /&gt;
분석한 게임은 엔티티를 하나의 배열로 관리하지 않고 메모리 성능을 위해 여러 개의 청크로 분할하여 저장하기에&lt;br /&gt;
핸들에는 어느 청크에 위치하는지와 청크 내부의 몇 번째 슬롯인지를 함께 저장하며 이를 이용해 실제 객체를 찾는다.&lt;/p&gt;
&lt;p&gt;예를 들어 분석한 구조에서는 다음과 같은 형태의 계산이 사용되었다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chunk = (handle &amp;amp; 0x7FFF) &amp;gt;&amp;gt; 9;
slot  = handle &amp;amp; 0x1FF;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;0x1FF&lt;/code&gt; 는 하위 9비트만 추출하여 청크 내부의 슬롯 번호를 구한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;0x7FFF&lt;/code&gt; 는 Handle의 유효한 하위 15비트만 사용한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;&amp;gt;&amp;gt; 9&lt;/code&gt; 는 하위 9비트를 제외하여 청크 번호를 얻는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;계산된 Chunk Index를 이용해 dwEntityList에서 해당 Pawn이 저장된 Pawn List Entry를 찾고 이후 Slot Index를&lt;br /&gt;
이용해 해당 Entry 내부의 C_CSPlayerPawn을 얻는다.&lt;/p&gt;
&lt;p&gt;이를 통해 Pawn에서 위치, 체력, 팀 등의 정보를 읽어 ESP를 구현한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;카메라와 좌표 변환이 필요한 이유&lt;/h2&gt;
&lt;p&gt;게임 내부의 좌표는 3D 월드 좌표를 이용한다.&lt;br /&gt;
예를 들어 플레이어의 머리 위치는 아래와 같은 값으로 표현된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vector3 worldPos;
worldPos.x = 좌우 위치
worldPos.y = 전후 위치
worldPos.z = 높이
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;하지만 내 화면에 그리기 위해서는 2D 픽셀 좌표가 필요하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Vector2 screenPos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 변환 과정을 일반적으로 &lt;code&gt;WorldToScreen&lt;/code&gt;이라고 부른다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;3D 월드 좌표 { x, y, z } -&amp;gt; 2D 화면 좌표 { x, y }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;좌표 변환을 이해하기 위해서는 먼저 카메라에 대한 개념이 필요하다.&lt;br /&gt;
현실에서 사람이 눈을 통해 물체를 보는 것처럼 게임 내부에서도 물체를 화면에 보여주기 위한 기준점이 필요하다.&lt;br /&gt;
이 기준점이 바로 카메라이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ViewFrustum.png&quot; alt=&quot;View Frustum&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;View Frustum&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;위 그림에서 카메라 위치를 눈이라고 생각하면 된다.&lt;br /&gt;
게임 월드에 존재하는 물체들은 실제 위치 그대로 화면에 표시되는 것이 아니라 카메라를 기준으로 어디에 보이는지 계산된 뒤 화면에 출력된다.&lt;/p&gt;
&lt;p&gt;그러므로 특정 플레이어의 월드 좌표를 알고 있더라도 그 좌표만으로는 화면의 어느 픽셀에 표시해야 하는지 알 수 없다.&lt;br /&gt;
카메라 위치, 카메라가 바라보는 방향, 시야각, 화면 크기 등을 반영하여 3D 좌표를 2D 좌표로 변환해야 한다.&lt;/p&gt;
&lt;p&gt;그림에서 카메라 앞쪽으로 펼쳐진 공간을 View Frustum이라고 한다.&lt;br /&gt;
이는 카메라가 볼 수 있는 공간을 의미하며 이 범위 안에 있는 오브젝트만 화면에 보이게 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Near Plane&lt;/code&gt;: 카메라와 가장 가까운 절단 평면&lt;/li&gt;
&lt;li&gt;&lt;code&gt;Far Plane&lt;/code&gt;: 카메라가 렌더링하는 최대 거리&lt;/li&gt;
&lt;li&gt;&lt;code&gt;View Frustum&lt;/code&gt;: 카메라가 실제로 볼 수 있는 3차원 공간&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;정리하면 ESP는 엔티티에서 얻은 월드 좌표를 카메라 기준으로 변환하고 최종적으로 화면 픽셀 좌표로 바꾸는 과정이 필요하다.&lt;/p&gt;
&lt;p&gt;이 좌표 변환 방법은 크게 두 가지 방식으로 이야기 할수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1. Direct3D 함수를 이용한 좌표 변환
   -&amp;gt; 게임의 D3D Device를 얻어 Viewport와 Matrix를 가져온다.
   -&amp;gt; D3DXVec3Project() 같은 함수를 이용해 좌표를 변환한다.

2. 행렬 계산을 이용한 좌표 변환
   -&amp;gt; 게임 메모리에서 ViewMatrix를 직접 찾는다.
   -&amp;gt; 월드 좌표와 행렬을 직접 곱해 화면 좌표를 계산한다.
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Direct3D 함수를 이용한 좌표 변환&lt;/h2&gt;
&lt;p&gt;(Internal 방식)&lt;br /&gt;
D3D9 기반은 Direct3D에서 제공하는 &lt;code&gt;D3DXVec3Project()&lt;/code&gt; 함수를 이용해 3D 좌표를 2D 화면 좌표로 변환할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;D3DXVECTOR3* D3DXVec3Project(
    D3DXVECTOR3* pOut,
    const D3DXVECTOR3* pV,
    const D3DVIEWPORT9* pViewport,
    const D3DXMATRIX* pProjection,
    const D3DXMATRIX* pView,
    const D3DXMATRIX* pWorld
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;각 인자의 의미는 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pV&lt;/code&gt;: 변환할 3D 좌표&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pOut&lt;/code&gt;: 변환 결과가 저장될 화면 좌표&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pViewport&lt;/code&gt;: 화면의 위치와 크기 정보&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pProjection&lt;/code&gt;: 3D 공간을 화면에 투영하기 위한 Projection Matrix&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pView&lt;/code&gt;: 월드 좌표를 카메라 기준 좌표로 바꾸기 위한 View Matrix&lt;/li&gt;
&lt;li&gt;&lt;code&gt;pWorld&lt;/code&gt;: 오브젝트의 로컬 좌표를 월드 좌표로 바꾸기 위한 World Matrix&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;여기서 중요한 것은 &lt;code&gt;View Matrix&lt;/code&gt;, &lt;code&gt;Projection Matrix&lt;/code&gt;, &lt;code&gt;World Matrix&lt;/code&gt;의 역할이다.&lt;/p&gt;
&lt;h3&gt;World Matrix&lt;/h3&gt;
&lt;p&gt;World Matrix는 오브젝트의 로컬 좌표를 월드 좌표로 변환하는 행렬이다.&lt;/p&gt;
&lt;p&gt;예를 들어 캐릭터 모델 내부에서 머리의 위치가 &lt;code&gt;(0, 70, 0)&lt;/code&gt;이라면 이는 모델을 기준으로 한 로컬 좌표이다.&lt;br /&gt;
이후 캐릭터가 게임 월드의 &lt;code&gt;(500, 200, 0)&lt;/code&gt; 위치에 배치되면 World Matrix를 통해 머리의 위치도 실제 월드 좌표로 변환된다.&lt;/p&gt;
&lt;p&gt;하지만 대부분의 게임에서 ESP는 메모리에서 플레이어 위치나 Bone 위치를 직접 읽어온다.&lt;br /&gt;
이러한 값들은 이미 게임 엔진에서 World Matrix 연산이 완료된 월드 좌표이기 때문에 별도로 World Matrix를 다시 적용할 필요가 없다.&lt;/p&gt;
&lt;p&gt;정리하자면 일반적인 ESP에서는 World Matrix를 직접 사용하는 경우는 별로없고 이미 계산된 월드 좌표를 View Matrix에 전달하여 화면 좌표로 변환하는 과정에서 주로 사용된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;D3DXMatrixIdentity(&amp;amp;worldMatrix);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;View Matrix&lt;/h3&gt;
&lt;p&gt;View Matrix는 월드 좌표를 카메라 기준 좌표로 변환하는 행렬이다.&lt;/p&gt;
&lt;p&gt;월드 좌표는 게임 맵 전체를 기준으로 한 위치이다.&lt;br /&gt;
하지만 화면에 물체를 그리려면 카메라 기준으로 해당 물체가 앞에 있는지, 왼쪽에 있는지, 오른쪽에 있는지 판단해야 한다.&lt;/p&gt;
&lt;p&gt;즉 View Matrix는 다음 변환을 담당한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;월드 기준 좌표 -&amp;gt; 카메라 기준 좌표
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Projection Matrix&lt;/h3&gt;
&lt;p&gt;Projection Matrix는 카메라 기준 3D 좌표를 2D 화면으로 투영하기 위한 행렬이다.&lt;/p&gt;
&lt;p&gt;3D 공간의 물체를 2D 화면에 표시할 때는 원근감이 반영되어야 한다.&lt;br /&gt;
가까운 물체는 크게 보이고 먼 물체는 작게 보인다.&lt;br /&gt;
이러한 원근 투영을 담당하는 것이 Projection Matrix이다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;D3DXVec3Project()&lt;/code&gt;는 내부적으로 다음 흐름을 처리한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Local 좌표
-&amp;gt; World Matrix
-&amp;gt; View Matrix
-&amp;gt; Projection Matrix
-&amp;gt; Viewport 변환
-&amp;gt; Screen 좌표
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 함수에 필요한 Viewport와 Matrix는 &lt;code&gt;pDevice&lt;/code&gt;에서 얻을 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;D3DVIEWPORT9 viewport;
D3DXMATRIX viewMatrix;
D3DXMATRIX projectionMatrix;
D3DXMATRIX worldMatrix;

pDevice-&amp;gt;GetViewport(&amp;amp;viewport);
pDevice-&amp;gt;GetTransform(D3DTS_VIEW, &amp;amp;viewMatrix);
pDevice-&amp;gt;GetTransform(D3DTS_PROJECTION, &amp;amp;projectionMatrix);
D3DXMatrixIdentity(&amp;amp;worldMatrix);

D3DXVec3Project(
    &amp;amp;screenPos,
    &amp;amp;worldPos,
    &amp;amp;viewport,
    &amp;amp;projectionMatrix,
    &amp;amp;viewMatrix,
    &amp;amp;worldMatrix
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;GetViewport()&lt;/code&gt;는 현재 렌더링 대상의 화면 크기와 위치를 가져온다.&lt;br /&gt;
&lt;code&gt;GetTransform()&lt;/code&gt;은 D3D9의 고정 함수 파이프라인에서 사용되는 View / Projection / World 행렬을 가져온다.&lt;/p&gt;
&lt;p&gt;이 방식은 게임에서 사용되는 &lt;code&gt;pDevice&lt;/code&gt;에 접근해야하기 때문에 일반적으로 &lt;code&gt;EndScene()&lt;/code&gt;이나 &lt;code&gt;DrawIndexedPrimitive()&lt;/code&gt; 같은 렌더링 함수를 후킹하여 &lt;code&gt;pDevice&lt;/code&gt;를 얻어야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;EndScene / DIP Hook
-&amp;gt; 게임이 사용하는 pDevice 획득
-&amp;gt; GetViewport / GetTransform 호출 -&amp;gt; View, Projection 얻기
-&amp;gt; D3DXVec3Project로 3D 좌표를 2D 좌표로 변환
-&amp;gt; ID3DXFont, ID3DXLine 또는 Direct3D 렌더링 API를 이용해 화면에 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;D3D11 기반 게임에서는 D3D9의 &lt;code&gt;EndScene()&lt;/code&gt; 대신 &lt;code&gt;IDXGISwapChain::Present()&lt;/code&gt;를 후킹하는 방식이 자주 사용된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;행렬 계산을 이용한 좌표 변환&lt;/h2&gt;
&lt;p&gt;외부 오버레이 방식에서는 게임의 &lt;code&gt;pDevice&lt;/code&gt;를 직접 사용하지 않는다.&lt;br /&gt;
따라서 &lt;code&gt;pDevice-&amp;gt;GetViewport()&lt;/code&gt;나 &lt;code&gt;pDevice-&amp;gt;GetTransform()&lt;/code&gt;을 호출할 수 없다.&lt;/p&gt;
&lt;p&gt;게임 메모리를 분석하여 ViewMatrix를 찾아야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Matrix viewMatrix;
ReadMemory(client + dwViewMatrix, viewMatrix);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ViewMatrix를 찾는 방법은 게임마다 다르다.&lt;br /&gt;
D3D 기반 게임에서는 &lt;code&gt;D3DXMatrixLookAtLH()&lt;/code&gt;처럼 View Matrix를 생성하는 함수가 호출되는 지점을 후킹하여&lt;br /&gt;
생성되는 값을 추적할 수도 있고 메모리 스캔을 통해 게임 엔진이 저장해둔 ViewMatrix를 직접 찾을 수도 있다.&lt;/p&gt;
&lt;p&gt;게임 메모리에 저장된 ViewMatrix를 찾아 읽고 이를 이용해 직접 &lt;code&gt;WorldToScreen()&lt;/code&gt; 함수를 작성하였다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bool WorldToScreen( const Matrix&amp;amp; view_matrix, const Vector3&amp;amp; world, int screen_width, int screen_height, Vector2&amp;amp; screen)
{
    const float* m = view_matrix.m;

    const float w = m[12] * world.x + m[13] * world.y + m[14] * world.z + m[15];
    if (w &amp;lt; 0.01f)  return false;

    float x = m[0] * world.x + m[1] * world.y + m[2] * world.z + m[3];
    float y = m[4] * world.x + m[5] * world.y + m[6] * world.z + m[7];

    const float inv_w = 1.0f / w;
    x *= inv_w;
    y *= inv_w;

    screen.x = (screen_width * 0.5f) * (1.0f + x);
    screen.y = (screen_height * 0.5f) * (1.0f - y);

    return true;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;, &lt;code&gt;z&lt;/code&gt;는 월드 좌표의 위치 값이다.&lt;/p&gt;
&lt;p&gt;그런데 좌표 변환 과정에서 &lt;code&gt;w&lt;/code&gt;라는 값이 보이는데 해당 값은 동차 좌표계에서 사용하는 값이다.&lt;br /&gt;
일반적으로 3D 좌표는 &lt;code&gt;{ x, y, z }&lt;/code&gt;로 표현하지만 그래픽스에서는 행렬 변환과 원근 투영을 편하게 처리하기 위해 &lt;code&gt;{ x, y, z, w }&lt;/code&gt; 형태를 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;일반 3D 좌표
{ x, y, z }

동차 좌표
{ x, y, z, w }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;동차 좌표계를 사용하는 이유는 이동, 회전, 크기 변환, 투영 같은 여러 변환을 하나의 4x4 행렬 곱셈으로 처리할 수 있기 때문이다.&lt;/p&gt;
&lt;p&gt;좌표 변환 후에는 &lt;code&gt;x&lt;/code&gt;, &lt;code&gt;y&lt;/code&gt;를 &lt;code&gt;w&lt;/code&gt;로 나누는 과정이 필요하다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;x *= 1.0f / w;
y *= 1.0f / w;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/parallel.png&quot; alt=&quot;parallel(평행)&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;parallel(평행)&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/perspective.png&quot; alt=&quot;perspective(원근)&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;perspective(원근)&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;이 과정을 Perspective Divide라고 한다.&lt;br /&gt;
쉽게 말해, 3D 공간의 좌표를 화면에 보이는 비율로 바꾸는 과정이다.&lt;/p&gt;
&lt;p&gt;가까운 물체는 크게 보이고 먼 물체는 작게 보이는 원근감도 이 과정과 관련이 있다.&lt;br /&gt;
카메라에서 멀리 있는 좌표일수록 &lt;code&gt;w&lt;/code&gt;의 영향이 커지고 &lt;code&gt;x / w&lt;/code&gt;, &lt;code&gt;y / w&lt;/code&gt;를 거치면서 화면에서 더 중심에 가까운 작은 변화로 투영된다.&lt;/p&gt;
&lt;p&gt;이후 정규화된 좌표를 실제 화면 픽셀 좌표로 변환한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;screen.x = (screen_width * 0.5f) * (1.0f + x);
screen.y = (screen_height * 0.5f) * (1.0f - y);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 &lt;code&gt;screen_width&lt;/code&gt;와 &lt;code&gt;screen_height&lt;/code&gt;는 오버레이 창의 크기이다.&lt;br /&gt;
오버레이 창은 게임 클라이언트 영역과 같은 위치와 크기로 맞춰두기 때문에 변환된 좌표에 그대로 선이나 문자를 그릴 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;외부 오버레이에 그리기&lt;/h2&gt;
&lt;p&gt;이번 구현에서는 Internal 후킹을 사용하지 않고 외부 오버레이 방식을 선택하였다.&lt;br /&gt;
외부 오버레이 방식에서는 게임 창 위에 투명한 윈도우를 하나 더 생성한다.&lt;br /&gt;
그리고 해당 윈도우에 D3D9 Device를 생성하여 선과 문자를 그린다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;게임 창 위치 획득
-&amp;gt; 투명 오버레이 창 생성
-&amp;gt; D3D9 Device 생성
-&amp;gt; 매 프레임 위치 동기화
-&amp;gt; ESP Render
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;투명 오버레이를 만들때는 아래와 같다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
Overlay::ctx.hwnd = CreateWindowEx(
    WS_EX_TOPMOST | WS_EX_LAYERED | WS_EX_TRANSPARENT | WS_EX_TOOLWINDOW,
    ...
);

SetLayeredWindowAttributes(Overlay::ctx.hwnd, RGB(0, 0, 0), 0, LWA_COLORKEY);

&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;WS_EX_TOPMOST&lt;/code&gt;: 오버레이 창을 항상 게임 창 위에 표시한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WS_EX_LAYERED&lt;/code&gt;: 투명도를 적용할 수 있는 Layered Window를 생성한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WS_EX_TRANSPARENT&lt;/code&gt;: 마우스 입력을 오버레이가 받지 않고 아래의 게임 창으로 전달한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;WS_EX_TOOLWINDOW&lt;/code&gt;: 작업 표시줄과 Alt + Tab 목록에 오버레이 창이 표시되지 않도록 한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;SetLayeredWindowAttributes&lt;/code&gt; : RGB(0, 0, 0)을 투명 색상으로 지정하겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;선을 그릴 때는 &lt;code&gt;ID3DXLine&lt;/code&gt;을 이용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void DrawLine(float x1, float y1, float x2, float y2, D3DCOLOR color)
{
    D3DXVECTOR2 points[2]
    {
        { x1, y1 },
        { x2, y2 }
    };

    pLine-&amp;gt;SetWidth(1.0f);
    pLine-&amp;gt;Draw(points, 2, color);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;문자는 &lt;code&gt;ID3DXFont&lt;/code&gt;를 이용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void DrawString(float x, float y, LPCWSTR text, D3DCOLOR color)
{
    RECT rect;
    SetRect(&amp;amp;rect, x, y, x, y);
    pFont-&amp;gt;DrawTextW(nullptr, text, -1, &amp;amp;rect, DT_CENTER | DT_NOCLIP, color);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;Advanced&lt;/h2&gt;
&lt;p&gt;엔티티의 월드 좌표를 화면 좌표로 변환이 가능하다면 이를 이용해 여러 형태의 ESP를 만들 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Name / Health ESP&lt;/h3&gt;
&lt;p&gt;Name ESP는 플레이어 이름을 읽어 머리 위에 출력하면 된다.&lt;br /&gt;
Health ESP는 플레이어 체력 값을 읽어 발 아래나 박스 하단 기준 위치에 출력하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ESPname.jpg&quot; alt=&quot;Name&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Name&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ESPhealth.jpg&quot; alt=&quot;Health&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Health&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Bone Logger / Skeleton&lt;/h3&gt;
&lt;p&gt;Bone Logger는 Bone Array를 순회하면서 각 Bone 번호를 화면에 출력해 필요한 뼈 번호를 찾을수 있다.&lt;br /&gt;
이를 통해 머리, 목, 가슴, 팔, 다리 등에 해당하는 Bone Index를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;Skeleton은 이렇게 찾은 Bone 좌표들을 화면 좌표로 변환한 뒤 서로 선으로 연결하는 방식이다.&lt;br /&gt;
머리에서 목, 목에서 가슴, 어깨에서 팔꿈치, 팔꿈치에서 손목처럼 주요 관절을 연결하면 캐릭터의 뼈대가 표시된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ESPboneLogger.jpg&quot; alt=&quot;Bone Logger&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Bone Logger&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ESPskeleton.jpg&quot; alt=&quot;Skeleton&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Skeleton&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;2D Box&lt;/h3&gt;
&lt;p&gt;2D Box는 플레이어의 머리 좌표와 발 좌표를 기준으로 사각형을 그리면 된다.&lt;br /&gt;
머리와 발의 월드 좌표를 각각 화면 좌표로 변환한 뒤 두 좌표의 높이 차이를 이용해 박스의 세로 길이를 구한다.&lt;/p&gt;
&lt;p&gt;이후 세로 길이에 일정 비율을 곱해 가로 폭을 정하고 계산된 네 점을 선으로 연결하면 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ESPbox.jpg&quot; alt=&quot;2D Box&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;2D Box&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Snap Line&lt;/h3&gt;
&lt;p&gt;Snap Line은 화면의 기준점에서 대상까지 선을 그리는 기능이다.&lt;br /&gt;
보통 화면 하단 중앙을 시작점으로 잡고 플레어어의 발이나 몸 중앙까지 선을 연결한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ESPsnapline.jpg&quot; alt=&quot;Snap Line&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Snap Line&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/ESP/ESPall.jpg&quot; alt=&quot;All&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;All&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Internal 방식은 게임 내부의 렌더링 흐름을 후킹해 &lt;code&gt;pDevice&lt;/code&gt;, &lt;code&gt;Viewport&lt;/code&gt;, &lt;code&gt;Matrix&lt;/code&gt;를 활용하는 방식이다.&lt;br /&gt;
External 방식은 게임 외부에서 메모리를 읽고 별도의 투명 오버레이 위에 직접 그리는 방식이다.&lt;/p&gt;
&lt;p&gt;본 문서에서는 분석 대상 게임이 D3D11 기반이지만 이전 D3D9 문서와 연결되는 흐름을 만들기 위해 D3D9 기반 외부 오버레이를 사용하였다.&lt;br /&gt;
좌표 변환은 게임 메모리에서 읽은 ViewMatrix를 이용해 직접 수행하고 변환된 좌표를 통해 여러가지 ESP 를 만들어 보았다.&lt;/p&gt;
&lt;p&gt;다음 글에서는 ESP에서 사용한 좌표와 각도 개념을 통해 Aimbot의 기본 원리를 알아보겠습니다.&lt;/p&gt;
</content:encoded></item><item><title>게임핵의 원리 (2) - Direct3D 기반 WallHack</title><link>https://cr3xis.github.io/posts/game-hacking-direct3d-wallhack/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/game-hacking-direct3d-wallhack/</guid><description>Direct3D9 후킹과 렌더링 상태 변경을 이용한 WallHack 동작 원리를 분석합니다.</description><pubDate>Mon, 25 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;해당 문서는 게임에서 사용되는 불법 프로그램들의 동작 원리와 구현 구조를 분석하는 기술 문서입니다.&lt;br /&gt;
본 문서는 보안 연구 및 교육 목적으로 작성되었으며, 실제 게임에 대한 악용을 권장하지 않습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;이번 문서에서는 Direct3D 9 기반 게임에서 사용되는 월핵의 구현 방식을 분석합니다.&lt;/p&gt;
&lt;p&gt;WallHack의 기본 개념과 깊이 버퍼(Z-Buffer)의 동작 원리에 대한 설명은 이전 문서에서 자세히 다루었으므로&lt;br /&gt;
해당 개념이 익숙하지 않다면 먼저 아래 문서를 읽고 오는 것을 권장합니다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/game-hacking-opengl-wallhack/&quot;&gt;게임핵의 원리 (1) - OpenGL 기반 WallHack&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;D3D9 Hook&lt;/h2&gt;
&lt;p&gt;D3D9 함수를 후킹하기 위해서는 내부적으로 어떤식으로 동작을 하는지를 파악해야 한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create a function to initialize Direct3D and create the Direct3D Device&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;// this function initializes and prepares Direct3D for use
void initD3D(HWND hWnd)
{
    d3d = Direct3DCreate9(D3D_SDK_VERSION);    // create the Direct3D interface

    D3DPRESENT_PARAMETERS d3dpp;    // create a struct to hold various device information

    ZeroMemory(&amp;amp;d3dpp, sizeof(d3dpp));    // clear out the struct for use
    d3dpp.Windowed = TRUE;    // program windowed, not fullscreen
    d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;    // discard old frames
    d3dpp.hDeviceWindow = hWnd;    // set the window to be used by Direct3D

    // create a device class using this information and information from the d3dpp stuct
    d3d-&amp;gt;CreateDevice(D3DADAPTER_DEFAULT,
                      D3DDEVTYPE_HAL,
                      hWnd,
                      D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                      &amp;amp;d3dpp,
                      &amp;amp;d3ddev);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 Direct3D9 환경에서 렌더링에 사용되는 &lt;code&gt;IDirect3DDevice9&lt;/code&gt; 객체를 생성하는 예제이다.&lt;br /&gt;
Direct3D9의 렌더링 함수들은 Export 함수 형태로 제공되지 않고 COM 인터페이스 기반으로 구현되어 있다.&lt;/p&gt;
&lt;p&gt;따라서 &lt;code&gt;GetProcAddress()&lt;/code&gt;를 이용하여 함수 주소를 획득할 수 없어&lt;br /&gt;
&lt;code&gt;CreateDevice()&lt;/code&gt;를 통해 생성되는 &lt;code&gt;IDirect3DDevice9&lt;/code&gt; 객체를 기준으로 접근해야 한다.&lt;/p&gt;
&lt;p&gt;이때 &lt;code&gt;IDirect3DDevice9&lt;/code&gt; 객체가 참조하는 VTable을 통해 렌더링 함수들의 주소를 확인할 수 있다.&lt;br /&gt;
이번 예제에서는 렌더링 과정에서 호출되는 &lt;code&gt;DrawIndexedPrimitive()&lt;/code&gt; 함수를 대상으로 분석을 진행한다.&lt;/p&gt;
&lt;p&gt;해당 함수는 3D 모델을 화면에 출력할 때 사용되는 대표적인 렌더링 함수 중 하나로 많은 D3D9 기반 월핵이 이 함수를 후킹하여 구현된다.&lt;/p&gt;
&lt;p&gt;정리하면 전체 과정은 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;IDirect3DDevice9&lt;/code&gt;의 VTable을 찾는다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DrawIndexedPrimitive()&lt;/code&gt; 함수 주소를 획득한다.&lt;/li&gt;
&lt;li&gt;Inline Hook을 설치한다.&lt;/li&gt;
&lt;li&gt;렌더링 과정에 개입하여 벽 뒤의 오브젝트를 화면에 표시한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;h3&gt;Internal &amp;amp; External&lt;/h3&gt;
&lt;p&gt;해당 예제는 외부 프로세스를 이용하는 &lt;code&gt;External&lt;/code&gt; 방식이 아닌 &lt;code&gt;DLL Injection&lt;/code&gt;을 통해 게임 프로세스 내부에서 동작하는 &lt;code&gt;Internal&lt;/code&gt; 방식으로 구현한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/Dll.png&quot; alt=&quot;Dll Injection&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Dll Injection&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;VTable 주소 찾기 (Pattern)&lt;/h3&gt;
&lt;p&gt;앞서 설명한것처럼 Direct3D9의 렌더링 함수는 Export 형태로 제공되지 않는다.&lt;/p&gt;
&lt;p&gt;그래서 &lt;code&gt;DrawIndexedPrimitive()&lt;/code&gt;와 같은 렌더링 함수의 주소를 얻기 위해서&lt;br /&gt;
먼저 &lt;code&gt;IDirect3DDevice9&lt;/code&gt; 객체가 참조하는 VTable의 위치를 찾아야 한다.&lt;/p&gt;
&lt;p&gt;이번 예제에서는 &lt;code&gt;CreateDevice()&lt;/code&gt; 함수를 기준으로 VTable 초기화 과정을 추적하였다.&lt;/p&gt;
&lt;h4&gt;STEP 1. CreateDevice 함수 진입&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;CreateDevice()&lt;/code&gt; 함수의 6번째 인자는 생성된 &lt;code&gt;IDirect3DDevice9&lt;/code&gt; 객체를 반환받기 위한 출력 인자로 사용된다.&lt;br /&gt;
해당 시점에서도 출력 인자는 아직 초기화 되지 않은 상태이며 실제 객체 생성 및 초기화는 내부 함수에서 수행된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/createdevice.png&quot; alt=&quot;CreateDevice&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;CreateDevice&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h4&gt;STEP 2. 내부 객체 초기화 함수 추적&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;CreateDevice()&lt;/code&gt; 내부를 분석하면 Device 객체를 생성하고 초기화 하는 하위 함수를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/subfunc.png&quot; alt=&quot;Sub Function&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Sub Function&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h4&gt;STEP 3. VTable 초기화 코드 확인&lt;/h4&gt;
&lt;p&gt;내부 함수를 추적하다 보면 아래와 같이 VTable 주소를 객체에 기록하는 코드를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/vftable.png&quot; alt=&quot;VTable access&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;VTable access&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;6CEEB022 | C706 3826E96C            | mov dword ptr ds:[esi],&amp;lt;d3d9.sub_6CE92638&amp;gt;        |
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 코드는 &lt;code&gt;IDirect3DDevice9&lt;/code&gt; 객체가 생성되는 과정에서 VTable 주소를 설정하는 부분이고 이를 기반으로 Byte Pattern을 작성한다.&lt;/p&gt;
&lt;p&gt;처음에는 아래와 같은 형태의 패턴을 얻을수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C7 06 24 1D 00 10 89 86 50 32 00 00 89 86 &lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;D3D9 버전이나 빌드 환경에 따라 주소나 일부 구조체 Offset 값이 달라질 수 있으므로&lt;br /&gt;
가변적인 와일드 카드 처리로 치환하면 최종적으로 아래와 같은 패턴이 남게 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;C7 06 ?? ?? ?? ?? 89 86 ?? ?? ?? ?? 89 86&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;DWORD FindPattern( DWORD dwAddress, DWORD dwSize, const std::vector&amp;lt;int&amp;gt;&amp;amp; pattern )
{
    BYTE* pData = (BYTE*)dwAddress;
    for (DWORD i = 0; i &amp;lt; dwSize; i++)
    {
        bool found = true;
        for (size_t j = 0; j &amp;lt; pattern.size(); j++)
        {
            if (pattern[j] != -1 &amp;amp;&amp;amp; pData[i + j] != pattern[j])
            {
                found = false;
                break;
            }
        }

        if (found) return (DWORD)(dwAddress + i);
    }
    return 0;
}

DWORD addr = FindPattern( (DWORD)hD3D, 0x128000, { 0xC7,0x06,-1,-1,-1,-1,0x89,0x86,-1,-1,-1,-1,0x89,0x86 } );

DWORD vtableAddress = *(DWORD*)(addr+2);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드를 통해 패턴이 발견되면 mov [esi], &amp;lt;vtable&amp;gt; 명령어의 주소가 반환된다.&lt;/p&gt;
&lt;p&gt;이를 통해 VTable 주소를 얻어올수 있게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;DrawIndexedPrimitive  함수 주소 구하기&lt;/h3&gt;
&lt;p&gt;VTable 주소를 구했으니 VTable index를 이용하여 &lt;code&gt;DrawIndexedPrimitive&lt;/code&gt; 함수 주소를 얻을수 있다.&lt;br /&gt;
컴파일된 d3d9.dll 바이너리는 index 가 고정되어 있기 때문에 그 부분을 참고하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#define DRAWINDEXEDPRIMITIVE    82
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;공식은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;vtable + 82 * 4(32bit) = DIP Address&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/DIP.png&quot; alt=&quot;DIP&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;DIP&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;메모리에서 확인 &lt;code&gt;DrawIndexedPrimitive&lt;/code&gt; 주소와 코드로 계산한 결과가 일치한것을 확인할수 있다.&lt;/p&gt;
&lt;h3&gt;Inline Hook&lt;/h3&gt;
&lt;p&gt;Inline Hook에 대한 간략한 설명을 하자면 함수 시작 부분을 JMP로 덮어써서 원본 함수가 호출되더라도 먼저 우리가 작성한 함수로 진입하게 만든다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/trampoline.png&quot; alt=&quot;Detours&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Detours&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;간단하게 원본 함수의 일부 명령어는 JMP 패치 과정에서 덮어쓰여지게 된다.&lt;/p&gt;
&lt;p&gt;Trampoline은 해당 명령어들을 별도의 메모리 영역에 복사한 뒤 원본 함수의 다음위치로 복귀하여 원래 실행 흐름이 계속 이어질 수 있도록 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void* DetourFunction(BYTE* src, const BYTE* dst, const int len) {

    BYTE* jmp = (BYTE*)malloc(len + 5);
    DWORD dwBack;
    VirtualProtect(src, len, PAGE_EXECUTE_READWRITE, &amp;amp;dwBack);
    memcpy(jmp, src, len);

    BYTE* jmpTarget = jmp + len;
    *jmpTarget = 0xE9;
    *(DWORD*)(jmpTarget + 1) = (DWORD)(src + len) - (DWORD)jmpTarget - 5; 

    *(BYTE*)src = 0xE9;  
    *(DWORD*)(src + 1) = (DWORD)dst - (DWORD)src - 5;

    for (int i = 5; i &amp;lt; len; i++) src[i] = 0x90;

    VirtualProtect(src, len, dwBack, &amp;amp;dwBack);
    VirtualProtect(jmp, len + 5, PAGE_EXECUTE_READWRITE, &amp;amp;dwBack);

    return jmp;

HRESULT __stdcall hkDrawIndexedPrimitive(
LPDIRECT3DDEVICE9 pDevice,
D3DPRIMITIVETYPE pType,
INT BaseVertexIndex,
UINT MinVertexIndex,
UINT NumVertices,
UINT startIndex,
UINT primCount
){
    printf(&quot;hooked!\n&quot;);
    return oDrawIndexedPrimitive(pDevice, pType, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
}


void hookmain(){
    DWORD vtableAddress = *(DWORD*)(addr+2);
    BYTE* pDIP = (BYTE*)((DWORD*)vtableAddress)[82];
    oDrawIndexedPrimitive = (tDrawIndexedPrimitive)DetourFunction(pDIP, (BYTE*)hkDrawIndexedPrimitive, 5);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 코드는 DIP에 Inline Hook을 설치하는 과정이다.&lt;br /&gt;
먼저 &lt;code&gt;DetourFunction&lt;/code&gt;은 DIP의 프롤로그를 JMP hkDIP 로 변경하여 함수가 호출 될 경우 가장 먼저 Hook 함수가 실행되도록 한다.&lt;/p&gt;
&lt;p&gt;이 과정에서 덮어쓰여진 원본 명령어는 Trampoline 영역에 복사되고 이후 원본 함수의 나머지가 코드가 계속 실행 될 수 있도록 Origin + 5 위치로 복귀하는 JMP 코드가 추가된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;DetourFunction&lt;/code&gt;이 반환한 Trampoline 주소는 oDIP에 저장이 된다.&lt;br /&gt;
따라서 Hook 함수 내부에서 oDIP를 호출하면 유실된 프롤로그를 복구한 뒤 원본 DIP가 정상적으로 수행된다.&lt;/p&gt;
&lt;p&gt;여기서 주의해야할 점은 후킹 함수의 원형과 호출 규약이 원본 함수와 동일해야 한다는것이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/hookbefore.png&quot; alt=&quot;Before&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Before&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/hookafter.png&quot; alt=&quot;After&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;After&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;Depth Buffer Code&lt;/h3&gt;
&lt;p&gt;이제 &lt;code&gt;DrawIndexedPrimitive&lt;/code&gt; 후킹에 성공했으므로 렌더링 상태를 직접 변경할 수 있다.&lt;/p&gt;
&lt;p&gt;이전 &lt;code&gt;OpenGL&lt;/code&gt; 에서도 이야기 했지만 원리는 동일하다.&lt;br /&gt;
깊이 버퍼를 일시적으로 비활성화한 상태에서 오브젝트를 다시 그리게 하는것이다.&lt;/p&gt;
&lt;p&gt;동작 순서는 다음과 같다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;깊이 버퍼를 비활성화 한다.&lt;/li&gt;
&lt;li&gt;원본 DIP 를 호출한다.&lt;/li&gt;
&lt;li&gt;깊이 버퍼를 다시 활성화 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;깊이 버퍼가 비활성화되면 오브젝트가 벽 뒤에 있는지 검사하지 않고 원래 가려져 있던 물체도 화면에 그대로 렌더링된다.&lt;/p&gt;
&lt;p&gt;그리고 렌더링 상태는 전역적으로 적용되기 때문에 원본 함수를 호출한 뒤에는 반드시 깊이 버퍼를 다시 활성화 해야한다.&lt;br /&gt;
그렇지 하지 않으면 이후에 그려지는 다른 물체들까지 모두 영향을 받게 되기 때문이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;HRESULT SetRenderState(
  [in] D3DRENDERSTATETYPE State,
  [in] DWORD              Value
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;D3D9에서는 &lt;code&gt;SetRenderState()&lt;/code&gt; 함수를 통해 다양한 렌더링 상태를 변경할 수 있다.&lt;br /&gt;
그중 &lt;code&gt;D3DRS_ZENABLE&lt;/code&gt;은 깊이 버퍼 사용 여부를 제어하는 옵션이다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;D3DZB_TRUE&lt;/code&gt; : 깊이 버퍼 활성화&lt;/li&gt;
&lt;li&gt;&lt;code&gt;D3DZB_FALSE&lt;/code&gt; : 깊이 버퍼 비활성화&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;HRESULT __stdcall hkDrawIndexedPrimitive(LPDIRECT3DDEVICE9 pDevice, D3DPRIMITIVETYPE pType, INT BaseVertexIndex, UINT MinVertexIndex, UINT NumVertices, UINT startIndex, UINT primCount)
{
 pDevice-&amp;gt;SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE); 
 // 호출 전
 oDrawIndexedPrimitive(pDevice, pType, BaseVertexIndex, MinVertexIndex, NumVertices, startIndex, primCount);
 // 호출 후
 pDevice-&amp;gt;SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/ZBTRUE.png&quot; alt=&quot;Before&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Before&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/ZBFALSE.png&quot; alt=&quot;After&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;After&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;결과적으로 깊이 검사 없이 렌더링이 수행되니 벽이나 장애물 뒤에 있는 오브젝트도 화면에 표시되는것을 확인할수 있다.&lt;/p&gt;
&lt;h3&gt;Advanced Techniques&lt;/h3&gt;
&lt;p&gt;지금까지 구현한 방식은 깊이 버퍼만 비활성화한 상태이므로 DIP를 사용하는 모든 오브젝트가 벽을 통과해 보이게 된다.&lt;/p&gt;
&lt;p&gt;이 경우 플레이어 뿐만 아니라 다른 여러 오브젝트들까지 함께 보이게 되는데 흔히 이러한 형태를 &lt;strong&gt;Glass Wall&lt;/strong&gt; 이라고 부른다.&lt;/p&gt;
&lt;p&gt;그래서 우리는 원하는 오브젝트만 표시하려면 렌더링되는 모델을 구분할 수 있는 기준이 필요하다.&lt;br /&gt;
D3D9에서는 &lt;code&gt;Stride&lt;/code&gt;, &lt;code&gt;NumVertices&lt;/code&gt;, &lt;code&gt;PrimitiveCount&lt;/code&gt; 값을 이용하여 특정 모델을 식별할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;Stride&lt;/code&gt;: 정점 하나의 크기&lt;/li&gt;
&lt;li&gt;&lt;code&gt;NumVertices&lt;/code&gt;: 사용되는 정점 개수&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PrimitiveCount&lt;/code&gt;: 렌더링되는 Primitive 개수&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;특히 &lt;code&gt;Stride&lt;/code&gt;는 모델마다 다른 값을 가지는 경우가 많아 가장 많이 식별되는 기준 중 하나이다.&lt;br /&gt;
이를 찾기 위해 일반적으로 &lt;code&gt;Stride Logger&lt;/code&gt;를 구현하여 현재 렌더링 되는 모델들을 수집한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    if (pDevice-&amp;gt;GetStreamSource(0, &amp;amp;Stream_Data, &amp;amp;Offset, &amp;amp;Stride) == D3D_OK)
    {
        bool Found = false;

        for (auto&amp;amp; Model : Logger)
        {
            if (Model.Stride == Stride &amp;amp;&amp;amp; Model.NumVertices == NumVertices &amp;amp;&amp;amp; Model.PrimitiveCount == primCount) { Found = true; break; }
        }
        if (!Found) { 
            Logger.push_back({ Stride, NumVertices, primCount }); 
            printf(&quot;Stride: %u NV: %u PC: %u\n&quot;, Stride, NumVertices, primCount);
        }
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/Logger.png&quot; alt=&quot;Stride Logger&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Stride Logger&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Stride Logger를 통해 플레이어 모델의 Stride 값을 확인했다면 더 이상 모든 오브젝트를 렌더링할 필요는 없다.&lt;br /&gt;
이제 해당 값을 조건으로 사용하여 플레이어 모델만 별도로 처리하면 된다.&lt;/p&gt;
&lt;p&gt;Stride 조건에 일치하는 모델에 대해서만 별도의 색상을 적용하면 &lt;strong&gt;Chams&lt;/strong&gt;를 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/Chams.png&quot; alt=&quot;Chams&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Chams&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;또한 &lt;code&gt;D3DRS_FILLMODE&lt;/code&gt; 상태를 변경하면 모델을 폴리곤 형태로 렌더링하는 Wireframe 효과를 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/wf1.png&quot; alt=&quot;Wireframe&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Wireframe&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/DirectX/wf2.png&quot; alt=&quot;Wireframe&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Wireframe&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;마무리&lt;/h3&gt;
&lt;p&gt;이번 글에서는 Direct3D9의 &lt;code&gt;DrawIndexedPrimitive&lt;/code&gt; 후킹을 통해 WallHack의 기본 원리를 구현해 보았다.&lt;/p&gt;
&lt;p&gt;깊이 버퍼 조작, 모델 식별, 렌더링 상태 변경을 조합하면 Chams와 Wireframe 같은 다양한 응용 기법도 구현할 수 있다.&lt;/p&gt;
&lt;p&gt;다음 글에서는 기존 월핵보다 더 많이 사용되고 있는 ESP 핵에 대해 분석해 보도록 하겠습니다.&lt;/p&gt;
</content:encoded></item><item><title>게임핵의 원리 (1) - OpenGL 기반 WallHack</title><link>https://cr3xis.github.io/posts/game-hacking-opengl-wallhack/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/game-hacking-opengl-wallhack/</guid><description>OpenGL의 깊이 버퍼와 렌더링 함수를 이용한 WallHack 동작 원리를 분석합니다.</description><pubDate>Sun, 10 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;해당 문서는 게임에서 사용되는 불법 프로그램들의 동작 원리와 구현 구조를 분석하는 기술 문서입니다.&lt;br /&gt;
본 문서는 보안 연구 및 교육 목적으로 작성되었으며, 실제 게임에 대한 악용을 권장하지 않습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;월핵이란 ?&lt;/h2&gt;
&lt;p&gt;월핵은 벽이나 장애물 뒤에 있는 플레이어를 화면에 표시하여 비정상적인 플레이를 하는것을 의미합니다.&lt;br /&gt;
해당 핵을 사용하는 악성 플레이어는 벽 뒤에 존재하는 적의 위치를 파악할수 있게 됩니다.&lt;/p&gt;
&lt;p&gt;월핵을 구현하는 방식은 게임마다 그래픽 라이브러리가 다르기 때문에 조금씩 달라지게 됩니다.&lt;/p&gt;
&lt;h2&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/wallhack1.png&quot; alt=&quot;wallhack&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;wallhack&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/h2&gt;
&lt;h2&gt;Z Buffer (Depth Buffer)&lt;/h2&gt;
&lt;p&gt;월핵을 구현하기 위해서는 먼저 &lt;code&gt;Z Buffer&lt;/code&gt;의 개념을 알아야 합니다.&lt;br /&gt;
게임은 화면에 수많은 오브젝트를 그리는데, 이 과정에서 어떤 물체가 앞에 있고 어떤 물체가 뒤에 있는지를&lt;br /&gt;
판단해야 하는데 이를 위해 &lt;code&gt;Z Buffer&lt;/code&gt;를 사용합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Z Buffer&lt;/code&gt;는 화면의 각 픽셀에 대해 깊이 값을 저장하는 버퍼로 렌더링 과정에서&lt;br /&gt;
현재 픽셀과 새롭게 그려질 픽셀의 깊이를 비교하여 더 가까운 물체만 화면에 출력합니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/zbuffer.png&quot; alt=&quot;Z-buffer&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Z-buffer&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;위 그림을 보면 렌더링 결과를 저장하는 Color Buffer와 별도로 깊이 정보를 저장하는 Depth Buffer가 존재하는 것을 알 수 있습니다.&lt;br /&gt;
오브젝트가 그려질 때마다 해당 픽셀의 깊이 값이 함께 저장되며, 이후 다른 오브젝트가 같은 위치에 그려질 경우 저장된 깊이 값과 비교하여 어떤 픽셀이 화면에 표시될지 결정합니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;분석 환경 및 설명&lt;/h2&gt;
&lt;p&gt;결과적으로 물체가 가려지는 이유가 &lt;code&gt;Z Buffer&lt;/code&gt; 때문인 것을 이제 이해했기에 이런식으로 생각할수 있습니다.&lt;br /&gt;
특정 오브젝트를 렌더링할 때 깊이 비교를 수행하지 않는다면 렌더링 엔진은 해당 오브젝트가 벽 뒤에 있는지&lt;br /&gt;
여부를 판단할수 없게 되며 결과적으로 화면의 다른 오브젝트보다 항상 앞에 그려지게 되는겁니다.&lt;/p&gt;
&lt;p&gt;이것이 바로 &lt;code&gt;Z Buffer&lt;/code&gt;를 이용한 가장 기본적인 월핵의 원리 입니다.&lt;/p&gt;
&lt;p&gt;대부분의 문서의 경우 &lt;code&gt;Assault Cube&lt;/code&gt; 를 기준으로 분석하는 글들이 상당히 많았는데,&lt;br /&gt;
필자의 경우 &lt;code&gt;Counter-Strike 1.6&lt;/code&gt; 를 기준으로 분석하여 글을 작성하도록 하였습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;STEP - 1 렌더링 흐름 분석&lt;/h3&gt;
&lt;p&gt;해당 게임의 경우 &lt;code&gt;OpenGL&lt;/code&gt; 이라는 라이브러리를 사용하고 있었습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;OpenGL&lt;/code&gt;에서 깊이 버퍼 처리를 위해 &lt;code&gt;glDepthFunc()&lt;/code&gt; 라는 함수를 호출하게 되는데,&lt;br /&gt;
이 함수는 각 오브젝트의 깊이값을 비교해 화면에 그릴지 말지 결정하게 됩니다.&lt;/p&gt;
&lt;p&gt;glDepthFunc() 의 핵심적인 인자는 아래와 같습니다.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;값&lt;/th&gt;
&lt;th&gt;의미&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GL_LESS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;더 가까운 물체만 화면에 표시합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GL_LEQUAL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;더 가깝거나 동일한 깊이의 물체를 표시합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GL_ALWAYS&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;깊이 버퍼를 무시하고 항상 표시합니다.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;핵심 인자를 알았으니 직접 패치 해보도록 하겠습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;STEP - 2 glDpethFunc() 인자 패치&lt;/h3&gt;
&lt;p&gt;glDepthFunc() 함수를 호출하는 주소를 역으로 추적했고,&lt;br /&gt;
기존에 사용하던 GL_LEQUAL을 GL_ALWAYS 로 패치해보았습니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/memory1.png&quot; alt=&quot;Memory&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Memory&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/beforedepth.png&quot; alt=&quot;GL_LEQUAL&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;GL_LEQUAL&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/afterdepth.png&quot; alt=&quot;GL_ALWAYS&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;GL_ALWAYS&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;해당 방식으로 패치를 하게되면, 모든사물이 뒤섞이게 되어버립니다.&lt;br /&gt;
그렇다면 원하는 오브젝트만 선택적으로 보이게 할수는 없을까 라는 생각이 들게 됩니다.&lt;/p&gt;
&lt;p&gt;일반적으로 &lt;code&gt;glDrawElements()&lt;/code&gt;나 &lt;code&gt;glDrawArrays()&lt;/code&gt;와 같은 Draw Call 함수를 후킹하여 구현합니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;glDrawElements()&lt;/code&gt;의 경우 렌더링되는 인덱스 개수를 나타내는 &lt;code&gt;count&lt;/code&gt; 인자를 제공하기 때문에&lt;br /&gt;
이를 이용하여 특정 오브젝트만 필터링 하는 방식이 널리 사용이 되었지만&lt;br /&gt;
분석 대상 게임에서는 &lt;code&gt;glDrawElements()&lt;/code&gt; 및 &lt;code&gt;glDrawArrays()&lt;/code&gt; 호출이 되지 않았습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;STEP - 3 렌더링 함수 추적&lt;/h3&gt;
&lt;p&gt;해당 게임은 Draw Call 기반 렌더링이 아닌 구형 OpenGL의 Immediate Mode 방식을 사용하고 있었습니다.&lt;/p&gt;
&lt;p&gt;일반적으로는 &lt;code&gt;glDrawElements()&lt;/code&gt; 와 같은 함수를 기준으로 렌더링 대상을 분석하지만&lt;br /&gt;
해당 게임에서는 해당 함수의 호출을 확인할 수 없었습니다.&lt;/p&gt;
&lt;p&gt;따라서 실제 정점이 전달되는 위치를 추적하는 방향으로 분석을 진행하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;glVertex3f()&lt;/code&gt; 와 &lt;code&gt;glVertex3fv()&lt;/code&gt; 는 정점 좌표를 OpenGL 파이프라인에 전달하는 함수이며&lt;br /&gt;
호출 지점을 역추적한 결과 다음과 같은 렌더링 흐름을 확인할 수 있었습니다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FF 15 3820BC1F        - call dword ptr [hw.dll+A92038] ; { -&amp;gt;OPENGL32.glBegin }
FF 15 9820BC1F        - call dword ptr [hw.dll+A92098] ; { -&amp;gt;OPENGL32.glTexCoord2f }
FF 15 9017BC1F        - call dword ptr [hw.dll+A91790] ; { -&amp;gt;OPENGL32.glColor4f }
FF 15 301FBC1F        - call dword ptr [hw.qwglDeleteContext+3C] ; { -&amp;gt;OPENGL32.glVertex3f }
FF 15 D417BC1F        - call dword ptr [hw.dll+A917D4] ; { -&amp;gt;OPENGL32.glEnd }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이는 &lt;code&gt;glBegin()&lt;/code&gt; 과 &lt;code&gt;glEnd()&lt;/code&gt; 사이에서 정점 정보를 직접 전달하는 전형적인 Immediate Mode 렌더링 구조입니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/glBegin1.png&quot; alt=&quot;glBegin() 호출부&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;glBegin() 호출부&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;동일한 렌더링 루틴 내부에서 &lt;code&gt;GL_TRIANGLE_STRIP(0x05)&lt;/code&gt; 과 &lt;code&gt;GL_TRIANGLE_FAN(0x06)&lt;/code&gt; 이 함께 사용되는 것을&lt;br /&gt;
확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;플레이어 모델 렌더링 과정에서 여러 Primitive 조합으로 구성되어 렌더링되고 있음을&lt;br /&gt;
확인할 수 있었고 각 Primitive Type 인자를 &lt;code&gt;GL_LINE_STRIP(0x02)&lt;/code&gt; 으로 패치하여 확인한 결과&lt;br /&gt;
모두 플레이어 모델 렌더링에 관여하고 있음을 확인할 수 있었습니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/beforebegin.png&quot; alt=&quot;Before&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Before&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/afterbegin.png&quot; alt=&quot;After&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;After&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;이를 통해 플레이어 모델이 렌더링 되는 경로를 식별하였으므로 해당 렌더링 함수를 후킹하여 깊이 테스트를 비활성화 해보겠습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;STEP - 4 캐릭터 렌더링 함수 후킹&lt;/h3&gt;
&lt;p&gt;OpenGL의 깊이 테스트는 전역 상태로 관리되기 때문에, 단순히 &lt;code&gt;GL_DEPTH_TEST&lt;/code&gt;를 비활성화 할 경우&lt;br /&gt;
다른 렌더링 물체에도 영향을 주게 되기 때문에 그 부분을 주의해야 합니다.&lt;/p&gt;
&lt;p&gt;따라서 플레이어 모델이 렌더링 되는 시점에만 깊이 테스트를 비활성화하고&lt;br /&gt;
렌더링이 종료된 이후에는 원래 상태로 복원하는 방식으로 구현했습니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/glBegin1.png&quot; alt=&quot;Before&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Before&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/memory2.png&quot; alt=&quot;After&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;After&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;아래와 같이 플레이어 모델이 렌더링 되는 구간에만 깊이 테스트를 비활성화하여&lt;br /&gt;
벽이나 장애물 뒤에 위치하더라도 정상적으로 표시되는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;lt;div style=&quot;display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:12px; align-items:start;&quot;&amp;gt;
&amp;lt;figure style=&quot;margin:0; text-align:center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/hookok.png&quot; alt=&quot;hook&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;hook&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;이로써 핵심은 특정 렌더링 구간에서 OpenGL 상태를 조작하는 것이라는 걸 알수 있게되고&lt;br /&gt;
동일한 기법을 응용하게 되면 단순한 월핵 뿐만이 아니라 다양한 시각적 효과를 구현할 수 있게 됩니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;STEP - 5 응용하기&lt;/h3&gt;
&lt;p&gt;지금까지 구현한 방식은 단순히 월핵을 구현한 예제에 불과합니다.&lt;br /&gt;
실제로는 렌더링 파이프라인의 상태를 제어할 수 있기 때문에 다양한 형태로 응용이 가능합니다.&lt;/p&gt;
&lt;p&gt;대표적으로는 벽의 투명도를 조절하는 &lt;code&gt;Glass Wall&lt;/code&gt;, 모델의 색상을 변경하는 &lt;code&gt;Chams(형광 월핵)&lt;/code&gt;,&lt;br /&gt;
모델의 구조를 확인할 수 있는 &lt;code&gt;WireFrame&lt;/code&gt; 등이 있으며 모두 동일한 원리를 기반으로 구현됩니다.&lt;/p&gt;
&lt;h4&gt;Glass Wall&lt;/h4&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/GlassWall.png&quot; alt=&quot;GlassWall&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;GlassWall&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;간략하게 설명하자면 알파 블렌딩을 이용하여 오브젝트를 반투명하게 렌더링하는 방식입니다.&lt;br /&gt;
&lt;code&gt;glBlendFunc()&lt;/code&gt;와 &lt;code&gt;GL_BLEND&lt;/code&gt;를 통해 블렌딩을 활성화하고 Alpha 값을 조절하여 투명도를 적용할 수 있습니다.&lt;/p&gt;
&lt;h4&gt;Chams&lt;/h4&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/chams.png&quot; alt=&quot;Chams&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Chams&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;참스는 다양한 방식으로 구현할 수 있지만 예제에서는 &lt;code&gt;glColor4f()&lt;/code&gt;의 색상 값을 패치하여 구현하였습니다.&lt;/p&gt;
&lt;h4&gt;WireFrame&lt;/h4&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/wireframe.png&quot; alt=&quot;wireframe&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;wireframe&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;glPolygonMode()&lt;/code&gt; 는 폴리곤의 렌더링 방식을 결정하는 함수입니다.&lt;br /&gt;
기본적으로 &lt;code&gt;GL_FILL&lt;/code&gt; 상태로 설정되어 있어 폴리곤 내부까지 채워 렌더링하지만&lt;br /&gt;
&lt;code&gt;GL_LINE&lt;/code&gt;으로 변경할 경우 폴리곤의 외곽선만 렌더링 됩니다.&lt;/p&gt;
&lt;h4&gt;Translucent&lt;/h4&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/GameHacking/OpenGL/trans.png&quot; alt=&quot;Translucent&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Translucent&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;동일한 원리를 응용하면 모델의 투명도를 조절하는 반투명 월핵 역시 구현할 수 있습니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이번 글에서는 OpenGL 기반 게임을 대상으로 깊이 버퍼의 동작 원리를 살펴보고 렌더링 함수를 분석하여&lt;br /&gt;
월핵을 구현하는 과정을 정리했습니다.&lt;/p&gt;
&lt;p&gt;결국 핵심은 특정 오브젝트가 렌더링 되는 시점을 식별하고 해당 구간에서 렌더링 상태를 제어하는 것입니다.&lt;/p&gt;
&lt;p&gt;다음 글에서는 Direct3D9 환경에서 동일한 개념이 어떻게 구현되는지 작성해보겠습니다.&lt;/p&gt;
</content:encoded></item><item><title>도메인 연결과 HTTPS 구성 흐름 정리</title><link>https://cr3xis.github.io/posts/server-domain-https/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/server-domain-https/</guid><description>도메인 연결부터 Cloudflare, Nginx, HTTPS 인증서 구성까지의 흐름을 정리합니다.</description><pubDate>Fri, 01 May 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;서버를 외부에 공개하는 것은 단순히 포트를 여는 작업과는 다르다.&lt;br /&gt;
도메인을 연결하고 HTTPS를 적용하고 요청이 어떤 경로를 통해 전달되는지 이해해야 전체 구조가 보인다.&lt;/p&gt;
&lt;p&gt;현재 구성 흐름은 다음과 같다.&lt;/p&gt;
&lt;p&gt;사용자 -&amp;gt; Cloudflare -&amp;gt; Nginx 오리진 서버&lt;/p&gt;
&lt;p&gt;핵심은 사용자가 오리진 서버에 직접 접근하지 않는 구조를 만드는 것이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 도메인 구매 및 Cloudflare 등록&lt;/h2&gt;
&lt;p&gt;나는 Cloudflare에서 도메인을 구매했기 때문에 별도로 네임서버를 변경하는 과정은 없었다.&lt;/p&gt;
&lt;p&gt;도메인을 Cloudflare에서 관리하게 되면 모든 DNS 요청은 Cloudflare를 통해 처리된다.&lt;br /&gt;
이 시점부터 외부 요청은 Cloudflare를 거쳐 오리진 서버로 전달되는 구조가 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. DNS A 레코드 추가 및 Proxy 활성화&lt;/h2&gt;
&lt;p&gt;Cloudflare DNS 설정에서 A 레코드를 추가했다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/03/01.png&quot; alt=&quot;DNS 설정&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;DNS 설정&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Type : A&lt;/li&gt;
&lt;li&gt;Name : example.com&lt;/li&gt;
&lt;li&gt;IPv4 address : Public IP&lt;/li&gt;
&lt;li&gt;Proxy status :  Proxied&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Proxy를 활성화하면 DNS 조회 시 오리진 서버 IP가 아니라 Cloudflare IP가 반환된다.&lt;/p&gt;
&lt;p&gt;즉 사용자 -&amp;gt; Cloudflare -&amp;gt; 오리진 서버 흐름이 만들어진다.&lt;/p&gt;
&lt;p&gt;정상적으로 추가가 되면 아래와 같은 형태로 레코드가 등록된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/03/02.png&quot; alt=&quot;DNS 레코드 등록 상태&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;DNS 레코드 등록 상태&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 단계에서 직접 서버를 외부에 노출하는 구조는 벗어난 상태다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 오리진 서버에서 Let&apos;s Encrypt 인증서 발급&lt;/h2&gt;
&lt;p&gt;Cloudflare에서 HTTPS를 사용하더라도 오리진 서버 자체도 HTTPS를 사용해야 한다.&lt;/p&gt;
&lt;p&gt;먼저 VM에 nginx를 설치하고 실행한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install nginx
sudo systemctl start nginx
sudo systemctl enable nginx
sudo systemctl status nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;nginx가 정상적으로 실행되고 80 포트에서 응답 중인 상태여야 인증서 발급이 정상적으로 진행된다.&lt;/p&gt;
&lt;p&gt;이후 certbot을 설치한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install certbot python3-certbot-nginx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;설치가 완료되면 인증서를 발급한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo certbot --nginx -d example.com -d www.example.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Cloudflare 프록시가 활성화된 상태에서는 인증 과정에서 문제가 발생할 수 있다.&lt;br /&gt;
이 경우 프록시를 잠시 비활성화한 뒤 인증서를 발급하고 다시 활성화하는 방식으로 처리할 수 있다.&lt;/p&gt;
&lt;p&gt;정상적으로 발급이 완료되면 &lt;code&gt;/etc/letsencrypt/live&lt;/code&gt; 경로에 인증서가 생성된다.&lt;/p&gt;
&lt;p&gt;Certbot은 Let&apos;s Encrypt CA를 통해 인증서를 발급하고 nginx 설정에 자동으로 반영한다.&lt;/p&gt;
&lt;p&gt;자동 갱신 동작을 확인하고 싶다면 다음 명령어를 사용한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo certbot renew --dry-run
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 과정을 통해 오리진 서버도 공식 CA 인증서를 사용하는 상태가 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Cloudflare SSL/TLS&lt;/h2&gt;
&lt;p&gt;Cloudflare 대시보드에서 SSL/TLS를 Full Strict로 설정한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/03/03.png&quot; alt=&quot;Cloudflare SSL/TLS&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Cloudflare SSL/TLS&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Full Strict 모드는 다음과 같은 의미를 가진다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;사용자와 Cloudflare 구간은 HTTPS 암호화&lt;/li&gt;
&lt;li&gt;Cloudflare와 오리진 구간도 HTTPS 암호화&lt;/li&gt;
&lt;li&gt;오리진 인증서가 신뢰 가능한 CA 인증서인지 검증&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉 Cloudflare가 오리진 서버의 인증서를 실제로 검증하는 구조다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;현재 구성의 의미&lt;/h2&gt;
&lt;p&gt;외부 사용자는 HTTPS로 암호화된 연결을 통해 서비스에 접근한다.&lt;br /&gt;
Cloudflare Proxy 구조가 적용되어 있으며 오리진 서버는 공식 CA 인증서를 사용한다.&lt;br /&gt;
Cloudflare와 nginx 구간 역시 암호화와 검증이 이루어진다.&lt;/p&gt;
&lt;p&gt;단순히 HTTPS를 적용한 것이 아니라 검증 가능한 TLS 구조를 갖춘 상태다.&lt;/p&gt;
&lt;p&gt;다만 오리진 IP 직접 접근 제한이나 추가 방화벽 설정은 별도의 하드닝 단계로 남아 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;Cloudflare Proxy와 Full Strict 모드 그리고 Let&apos;s Encrypt 인증서를 통해&lt;br /&gt;
외부 사용자는 암호화된 HTTPS 환경에서 서비스에 접근하고&lt;br /&gt;
오리진 서버 역시 신뢰 가능한 인증서를 기반으로 검증되는 구조를 갖추게 된다.&lt;/p&gt;
</content:encoded></item><item><title>서버 방화벽 설정하기</title><link>https://cr3xis.github.io/posts/server-firewall/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/server-firewall/</guid><description>Oracle Cloud와 Ubuntu 환경에서 외부 접속을 위한 방화벽 설정을 정리합니다.</description><pubDate>Mon, 20 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;서버를 생성하고 나서 잊지 않기 위해 이 글을 작성한다.&lt;br /&gt;
분명히 웹 서버도 실행했고 포트도 열었다고 생각했는데 외부에서는 접속이 되지 않는 상황이다.&lt;/p&gt;
&lt;p&gt;이때 대부분 서버 설정을 의심하게 되지만 실제로는 방화벽에서 막히는 경우가 많다.&lt;br /&gt;
대부분 클라우드는 네트워크 단에서 한 번 접근을 제어하고 그 안에 있는 VM 역시 자체 방화벽을 가지고 있다.&lt;/p&gt;
&lt;p&gt;결국 HTTP(80), HTTPS(443) 통신을 정상적으로 사용하려면 클라우드 네트워크와 VM 내부 두 영역을 모두 설정해야 한다.&lt;/p&gt;
&lt;p&gt;오라클 클라우드 네트워크 설정을 먼저 열어주고 그 다음 VM 내부에서 실제 포트를 허용해주면 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;오라클 클라우드 네트워크 방화벽 설정&lt;/h2&gt;
&lt;p&gt;외부 트래픽은 먼저 VCN을 통과한다.&lt;br /&gt;
따라서 인스턴스에 도달하기 전에 VCN에서 해당 포트를 허용해줘야 한다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/02/1.png&quot; alt=&quot;Virtual Cloud Network 화면&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Virtual Cloud Network 화면&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Networking 메뉴에서 Virtual Cloud Networks로 이동한 뒤 현재 인스턴스가 연결된 VCN을 선택한다.&lt;/p&gt;
&lt;p&gt;VCN 내부로 들어가면 보안 관련 항목이 보인다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/02/2.png&quot; alt=&quot;Security 메뉴&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Security 메뉴&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Security 항목으로 이동하면 Security Lists가 있다.&lt;br /&gt;
이 리스트가 실제 인바운드와 아웃바운드 트래픽을 제어하는 역할을 한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/02/3.png&quot; alt=&quot;Security Lists&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Security Lists&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;현재 인스턴스가 연결된 Security List를 선택한 뒤 Security Rules 탭으로 들어간다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/02/4.png&quot; alt=&quot;Ingress Rules 설정 화면&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Ingress Rules 설정 화면&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Add Ingress Rules를 선택하고 다음과 같이 설정해준다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Source: 0.0.0.0/0&lt;/li&gt;
&lt;li&gt;IP Protocol: TCP&lt;/li&gt;
&lt;li&gt;Destination Port Range: 80&lt;/li&gt;
&lt;li&gt;동일한 방식으로 443 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 설정은 외부에서 들어오는 HTTP, HTTPS 요청을 VCN 단계에서 허용하는 과정이다.&lt;/p&gt;
&lt;p&gt;여기까지 마치면 네트워크 레벨에서는 해당 포트가 열려 있다.&lt;br /&gt;
이제 VM 내부로 들어가 실제 리눅스 방화벽을 설정해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;VM(Ubuntu) 내부 방화벽 설정&lt;/h2&gt;
&lt;p&gt;오라클 네트워크에서 허용했더라도 VM 내부에서 막고 있다면 여전히 접속은 되지 않는다.&lt;/p&gt;
&lt;p&gt;Ubuntu에서는 보통 iptables가 기본 방화벽 역할을 한다.&lt;br /&gt;
현재 INPUT 체인 상태를 먼저 확인한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo iptables -L INPUT --line-numbers -n -v
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;L : 현재 규칙 출력&lt;/li&gt;
&lt;li&gt;n : IP주소와 포트를 숫자로 출력&lt;/li&gt;
&lt;li&gt;v : 패킷과 바이트 처리 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;현재 규칙과 기본 정책을 확인하는 과정이다.&lt;br /&gt;
보안을 위해 허용되지 않은 포트는 기본 정책을 DROP으로 두는 경우가 많다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo iptables -P INPUT DROP
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;P : Policy 설정&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 설정은 명시적으로 허용한 포트만 통과시키겠다는 의미다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo iptables -A INPUT -p tcp --dport 80 -j ACCEPT
sudo iptables -A INPUT -p tcp --dport 443 -j ACCEPT
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이제 필요한 포트를 추가해주면 된다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A : 체인에 규칙 추가&lt;/li&gt;
&lt;li&gt;p : 어떤 프로토콜인지&lt;/li&gt;
&lt;li&gt;dport : 어떤 포트&lt;/li&gt;
&lt;li&gt;j ACCEPT : 조건이 맞다면 허용&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;sudo iptables -L INPUT --line-numbers -n -v
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;그리고 실제 서비스가 리슨 중인지 확인한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ss -tln
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 80이나 443이 LISTEN 상태로 보이지 않는다면 웹 서버가 실행 중인지도 함께 점검해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;잘못된 규칙 삭제&lt;/h2&gt;
&lt;p&gt;규칙을 잘못 추가했다면 번호를 확인한 뒤 삭제할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo iptables -D INPUT &amp;lt;번호&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;재부팅 이후 유지&lt;/h2&gt;
&lt;p&gt;iptables 설정은 재부팅 시 사라지기 때문에 현재 상태를 저장해두어야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo netfilter-persistent save
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 명령을 실행하면 현재 규칙이 &lt;code&gt;/etc/iptables/rules.v4&lt;/code&gt; 파일에 저장된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;전체 흐름&lt;/h2&gt;
&lt;p&gt;외부 사용자가 접속할 때의 흐름은 다음과 같다.&lt;/p&gt;
&lt;p&gt;클라이언트 → 오라클 VCN Security Rule → VM iptables → 웹 서버 → 애플리케이션&lt;/p&gt;
&lt;p&gt;이 중 하나라도 막혀 있으면 접속은 실패한다.&lt;br /&gt;
그래서 방화벽 설정은 항상 네트워크와 서버 내부를 함께 확인해야 한다.&lt;/p&gt;
</content:encoded></item><item><title>Oracle Cloud 인스턴스 만들기</title><link>https://cr3xis.github.io/posts/server-oracle-cloud-instance/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/server-oracle-cloud-instance/</guid><description>Oracle Cloud에서 Ubuntu 인스턴스와 고정 공인 IP를 구성하는 과정을 정리합니다.</description><pubDate>Fri, 10 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;h2&gt;1. 인스턴스 생성&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/1.png&quot; alt=&quot;Create a VM instance&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Create a VM instance&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Oracle Cloud에서 인스턴스를 생성한다.&lt;/p&gt;
&lt;p&gt;인스턴스는 결국 하나의 가상 머신이고 이 안에 우리가 사용할 Ubuntu 서버가 올라간다고 생각하면 이해가 쉽다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Basic 설정&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/2.png&quot; alt=&quot;Basic information&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Basic information&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;생성할 인스턴스의 이름을 정해주면 되고 나머지는 기본값으로 설정한다.&lt;/p&gt;
&lt;p&gt;Change Image를 통해 원하는 OS를 선택하면 되는데 나는 Ubuntu 22.04 로 선택했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Networking 설정&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/3.png&quot; alt=&quot;Networking&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Networking&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;여기서는 VNIC과 Subnet를 설정하는 부분인데 인스턴스가 어떤 네트워크 안에 속할지를 결정하는 설정이고&lt;br /&gt;
하나의 VCN 안에 인스턴스가 배치된다고 이해하면 충분하다.&lt;/p&gt;
&lt;p&gt;SSH 관련 설정도 이 단계에서 함께 설정한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/4.png&quot; alt=&quot;SSH&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;SSH&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;public / private 키를 다운로드 받고 잘 보관해야 한다.&lt;br /&gt;
나는 사용자 폴더 내 .ssh 에 저장했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 인스턴스 생성 완료&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/5.png&quot; alt=&quot;Instance&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Instance&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;생성이 완료되면 인스턴스가 Running 상태로 올라오며 서버 자체는 준비가 된거다.&lt;/p&gt;
&lt;p&gt;처음엔 별도로 설정하지 않아도 공인 IP가 인스턴스 자체에 붙어있는 줄 알았는데 그게 아니라&lt;br /&gt;
별도로 설정을 해줘야 되는 구조인듯 하다.&lt;/p&gt;
&lt;p&gt;그래서 생성이 끝났다고 바로 외부에서 접속이 가능한 것은 아니다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Reserved Public IP 연결&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/6.png&quot; alt=&quot;Reserved Public IPs&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Reserved Public IPs&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;공인 IP가 자동으로 할당되지 않았다면 Networking 메뉴에서 Reserved Public IPs로 이동한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/7.png&quot; alt=&quot;Reserved Public IPs&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Reserved Public IPs&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;여기서 Reserved Public IP를 생성 한 뒤 인스턴스의 VNIC으로 지정해 연결하면 된다.&lt;/p&gt;
&lt;p&gt;프리티어 기준으로는 최대 2개까지 발급이 가능하다.&lt;br /&gt;
이 방식의 가장 큰 장점은 인스턴스를 중지했다가 다시 시작해도 IP가 유지된다는 점이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 인스턴스에 공인 IP 연결&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/8.png&quot; alt=&quot;Attached VNICs&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Attached VNICs&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 공인 IP를 실제 VNIC에 연결해준다.&lt;br /&gt;
Compute에서 해당 인스턴스로 들어간 뒤 Networking - Attached VNICs를 선택한다.&lt;br /&gt;
해당 네트워크를 누르게 되면 아래와 같이 뜬다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/9.png&quot; alt=&quot;IP administration&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IP administration&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;IP administration 탭으로 들어가면 현재 Private IP 정보가 표시된다.&lt;br /&gt;
연결하고자 하는 네트워크 우측의 점 3개 옵션에 - Edit 을 클릭한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/10.png&quot; alt=&quot;Edit Private IP Address&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Edit Private IP Address&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;No public IP를 Reserved public IP로 변경한다.&lt;br /&gt;
Reserved IP Address 항목에서 이전에 생성해둔 공인 IP를 선택한 뒤 Update를 누르면 연결이 완료된다.&lt;br /&gt;
이제 외부와 통신 가능한 상태가 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 정상적으로 연결되었는지 확인&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/server/01/11.png&quot; alt=&quot;인스턴스 연결 상태 확인&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;인스턴스 연결 상태 확인&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Instances 화면에서 해당 인스턴스를 확인해보면 Public IP가 표시된다.&lt;br /&gt;
정상적으로 IP가 붙어 있다면 이제 SSH를 통해 외부에서 접속을 시도할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
</content:encoded></item><item><title>YARA가 무엇인지 실습해보기</title><link>https://cr3xis.github.io/posts/malware-yara-basics/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/malware-yara-basics/</guid><description>YARA 규칙의 구조와 파일 검사 방식을 설치부터 Python 연동까지 실습합니다.</description><pubDate>Wed, 01 Apr 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;이전에 동적 메모리 스캐너를 직접 구현하면서 바이트 패턴을 기준으로 탐지 결과를 출력해본 경험이 있었다.&lt;/p&gt;
&lt;p&gt;그 과정에서 자연스럽게 들었던 의문은 이것이었다.&lt;/p&gt;
&lt;p&gt;이런식으로 비교를 하게 된다면 너무 비효율적이고 만드는거 또한 너무 번거로운데 좋은 방법이 없나?&lt;br /&gt;
실제 악성코드 진단 환경에서는 이런 패턴들을 어떤 식으로 관리하고 어떤 기준으로 탐지 판단을 내릴까?&lt;/p&gt;
&lt;p&gt;이 질문에 대한 답을 찾는 과정에서 YARA라는 도구를 제대로 이해해볼 필요가 있다고 느꼈다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;YARA는 무엇을 하기 위한 도구인가&lt;/h2&gt;
&lt;p&gt;YARA는 악성코드에서 자주 등장하는 문자열, 바이트, 코드 패턴 등을 rule이라는 형태로 정의하고&lt;br /&gt;
파일이나 메모리 안에 해당 패턴이 존재하는지를 검사하는 도구다.&lt;/p&gt;
&lt;p&gt;중요한 점은 YARA가 악성 여부를 판단해주는 도구가 아니라는 것이다.&lt;/p&gt;
&lt;p&gt;YARA는 어디까지나 이 파일에서 이런 특징들이 발견되었다라는 탐지 결과를 제공하는 도구에 가깝다.&lt;/p&gt;
&lt;p&gt;이 구조는 내가 직접 만들었던 메모리 스캐너와도 매우 닮아 있었다.&lt;/p&gt;
&lt;p&gt;패턴을 찾는 역할과 그 결과를 어떻게 해석할지는 명확히 분리되어 있다는 점에서 말이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;YARA 룰을 이해해야 하는 이유&lt;/h2&gt;
&lt;p&gt;YARA를 제대로 사용하려면 단순히 명령어를 치는 것보다 룰을 직접 작성할 수 있어야 한다고 느꼈다.&lt;/p&gt;
&lt;p&gt;룰의 구조를 이해하지 못하면 탐지 결과가 왜 나왔는지 설명할 수 없고 오탐인지 아닌지도 판단하기 어렵다.&lt;/p&gt;
&lt;p&gt;그래서 가장 먼저 YARA 룰의 기본 구조부터 살펴봤다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://yara.readthedocs.io/en/latest/&quot;&gt;YARA Docs&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;
rule silent_banker : banker
{
    meta:
        description = &quot;This is just an example&quot;
        threat_level = 3
        in_the_wild = true

    strings:
        $a = {6A 40 68 00 30 00 00 6A 14 8D 91}
        $b = {8D 4D B0 2B C1 83 C0 27 99 6A 4E 59 F7 F9}
        $c = &quot;UVODFRYSIHLNWPEJXQZAKCBGMT&quot;

    condition:
        $a or $b or $c
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 룰을 기준으로 구조를 나눠 간단하게 정리하였다.&lt;/p&gt;
&lt;h3&gt;rule &amp;amp; tags&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;silent_banker&lt;/code&gt;는 룰의 이름이다.&lt;br /&gt;
탐지 결과에서 어떤 룰에 의해 매칭되었는지를 식별하는 기준이 된다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;banker&lt;/code&gt;는 태그로 탐지 결과를 분류하거나 나중에 처리 단계에서 조건을 나눌 때 사용된다.&lt;/p&gt;
&lt;h3&gt;meta&lt;/h3&gt;
&lt;p&gt;meta 영역은 탐지에는 직접적인 영향을 주지 않는다.&lt;br /&gt;
대신 이 룰이 무엇을 위한 룰인지 어떤 악성코드 계열인지 같은 정보를 남기기 위한 공간이다.&lt;/p&gt;
&lt;h3&gt;strings&lt;/h3&gt;
&lt;p&gt;실제로 탐지할 패턴을 정의하는 핵심 영역이다.&lt;br /&gt;
문자열, 헥스 바이트, 정규표현식, 와일드카드 등 다양한 방식으로 작성할 수 있다.&lt;/p&gt;
&lt;h3&gt;condition&lt;/h3&gt;
&lt;p&gt;strings에서 정의한 패턴들을 어떤 조건에서 탐지로 볼 것인지 결정하는 영역이다.&lt;br /&gt;
이 부분이 오탐과 정탐을 가르는 가장 중요한 지점이 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;로컬 환경에서 YARA 설치 &amp;amp; 실행&lt;/h2&gt;
&lt;p&gt;이제 개념을 어느 정도 이해했으니 로컬 환경에서 직접 YARA를 실행해보기로 했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sudo apt-get update
sudo apt-get install yara
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Malware/01/1.png&quot; alt=&quot;YARA 설치 과정&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;YARA 설치 과정&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;설치 후에는 버전을 확인해 명령어가 정상적으로 동작하는지 확인했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yara --version
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Malware/01/2.png&quot; alt=&quot;YARA 버전 확인&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;YARA 버전 확인&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 정상적으로 설치가 되었으니 커맨드를 통해 어떤식으로 동작하는지 알아보도록 하겠다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;간단한 테스트&lt;/h2&gt;
&lt;p&gt;복잡한 룰을 바로 쓰기보다는 YARA가 파일을 어떻게 검사하는지 감을 잡고 싶었다.&lt;br /&gt;
그래서 문자열 하나만 검사하는 가장 단순한 예제를 먼저 만들어봤다.&lt;/p&gt;
&lt;h3&gt;test.yar&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;rule Test_Hello
{
    strings:
        $a = &quot;Yara_test&quot;
    condition:
        $a
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;sample.txt&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;xxxx Yara_test aaaa
aaaa Test_yara xxxx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위와 같이 간단한 샘플을 만든 뒤 터미널을 통해 테스트를 했다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Malware/01/3.png&quot; alt=&quot;테스트 결과&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;테스트 결과&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;룰을 기준으로 파일을 읽고 strings를 검사한 뒤 condition을 만족하면 결과를 출력한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Yara-rules 오픈 소스를 사용해보자&lt;/h2&gt;
&lt;p&gt;단순 문자열 테스트를 마치고 나니 이런 생각이 들었다.&lt;br /&gt;
이런 단순한 방법을 통해 찾는 방법도 있지만 오픈소스를 통해 좀 더 알아보고 싶었다.&lt;/p&gt;
&lt;p&gt;그래서 실제 분석 환경과 최대한 비슷하게 테스트 해보기 위해 오픈소스 YARA 룰을 직접 사용해보기로 했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/Yara-Rules/rules.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Malware/01/4.png&quot; alt=&quot;rules download&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;rules download&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;rules 디렉토리를 살펴보면 수많은 룰들이 카테고리별로 정리되어 있고 index.yar을 기준으로 관리되고 있다.&lt;br /&gt;
이 구조를 보며 패턴을 코드에 하드코딩하던 방식과 달리 룰 파일로 관리하는 이유를 체감할 수 있었다.&lt;/p&gt;
&lt;p&gt;내가 제대로 모르는거 일수도 있지만 해당 오픈소스는 초기에 고질병 같은 버그도 있었고&lt;br /&gt;
감지되는 규칙이 어디서 난건지 찾기 위해 namespace를 출력시켜도 default로 나오는 경우가 있어 조금 불편했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;실제 테스트 구성하기&lt;/h2&gt;
&lt;p&gt;이제 룰만 있는 상태가 아니라 실제로 검사할 대상이 필요했다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://bazaar.abuse.ch/&quot;&gt;MalwareBazaar&lt;/a&gt;을 통해 악성코드 샘플과 함께 정상 파일도 같이 준비했다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Malware/01/5.png&quot; alt=&quot;검사 대상 파일 구성&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;검사 대상 파일 구성&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Python을 통한 실제 테스트&lt;/h2&gt;
&lt;p&gt;커맨드라인으로 단일 파일을 검사하는 것까지는 충분히 확인했다.&lt;br /&gt;
하지만 여러 파일을 반복적으로 검사하고 결과를 코드에서 다루려면 한계가 있었다.&lt;/p&gt;
&lt;p&gt;그래서 YARA에서 제공하는 Python 패키지를 사용해 간단한 테스트 코드를 작성해보기로 했다.&lt;/p&gt;
&lt;p&gt;아래의 명령어를 통해 패키지를 먼저 설치하면 된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pip install yara-python
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Source Code&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import yara
import os

rules = yara.compile(filepath=&quot;./rules/index.yar&quot;) # 룰 컴파일
target_dir = &quot;./my_files&quot; # 검사 대상 

## 디렉터리 내 모든 파일 순회 
for filename in os.listdir(target_dir):
    filepath = os.path.join(target_dir, filename) # 경로 생성

    if not os.path.isfile(filepath):
        continue

    matches = rules.match(filepath) # Yara 룰로 검사

    if matches:
        print(&quot;[+]&quot;, filepath)
        for m in matches:
            print(&quot;   -&quot;, m.rule) 
    else:
        print(&quot;[CLEAN]&quot;, filepath)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 코드를 이제 실행시켜보자&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;python3 yara_test.py
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Malware/01/6.png&quot; alt=&quot;YARA  결과&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;YARA  결과&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;결과를 보면서 가장 먼저 든 생각은 내가 생각한거랑은 좀 다르다는 것이었다.&lt;/p&gt;
&lt;p&gt;나는 특정 악성코드의 패턴을 룰로 등록하면 해당 악성코드를 찾아주는 단순한 도구라고 생각했다.&lt;/p&gt;
&lt;p&gt;하지만 탐지된 결과를 보면 단순한 문자열 시그니처만 있는게 아니라&lt;br /&gt;
PE 정보, SEH, Anti-Debug 등 일반적인 API도 포함되어 있다는것이다.&lt;/p&gt;
&lt;p&gt;어떤식으로 커스텀을 하냐에 따라 다르겠지만 VirusTotal의 오픈 소스는 사용되는 함수들 중&lt;br /&gt;
악성코드에서 주로 사용하는 함수들을 여러가지 조합하여 결과를 토해내는 방식이 제일 좋지 않나 싶긴하다.&lt;br /&gt;
이 방식으로 바이러스를 탐지하는 것을 만드는건 너무 많은 시간과 개념을 요구하기에 조금 어려운거 같긴하다.&lt;/p&gt;
&lt;p&gt;개인적으로 나라면 악성코드 샘플을 제공해주는 사이트를 통해 정상/악성 샘플들을 충분히 모으고&lt;br /&gt;
&lt;a href=&quot;https://github.com/Neo23x0/yarGen/&quot;&gt;yarGen&lt;/a&gt;과 같은 도구를 활용해 yar룰을 생성하고 그것을 토대로 확실한 검증을 하겠금 처리하는게 가장 효율적이지 않나 싶다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;이 글을 통해 목표로 했던 것은 YARA를 깊게 파고드는 것이 아니라 왜 이런 도구가 필요하고&lt;br /&gt;
어떤 흐름으로 사용하게 되는지를 이해하는 것이었다.&lt;/p&gt;
&lt;p&gt;기본적인 룰 작성부터 시작해 로컬 테스트, 공개 룰 활용, Python 연동까지 경험해보니&lt;br /&gt;
YARA가 어떤 위치에 있는 도구인지 조금은 감이 잡혔다.&lt;/p&gt;
</content:encoded></item><item><title>Memory Module Scanner 리뷰</title><link>https://cr3xis.github.io/posts/review-memory-module-scanner/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/review-memory-module-scanner/</guid><description>실행 중인 프로세스의 모듈과 메모리 패턴을 확인하는 Memory Module Scanner를 리뷰합니다.</description><pubDate>Fri, 20 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;PE 구조를 파일 기준으로만 보다가 실제 실행 중인 메모리에서는 어떤식으로 구성이 되어있는지 궁금해 만들게 되었다.&lt;/p&gt;
&lt;p&gt;자세한 소스코드와 내용은 &lt;a href=&quot;https://github.com/Cryptonite7777/Memory-Module-Scanner&quot;&gt;Github&lt;/a&gt; 을 참고하면 된다.&lt;/p&gt;
&lt;p&gt;특정 프로세스에 붙어서 모듈을 기준으로 메모리를 파악하고 바이트 패턴이 실제로 어디에 존재하는지&lt;br /&gt;
확인할 수 있는 아주 단순한 스캐너를 만들기로 했다.&lt;br /&gt;
잘 생각해보면 YARA룰의 시초가 되는 프로그램이지 않을까 라는 생각도 했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;초기&lt;/h2&gt;
&lt;p&gt;가장 먼저 확인하고 싶었던 건 복잡한 구조가 아니었다.&lt;br /&gt;
내가 만든 프로그램이 실제 프로세스에 붙고 실행 파일이 메모리에 올라간 상태를 제대로 보고 있는지가 중요했다.&lt;br /&gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Review/MemoryScanner/executable.png&quot; alt=&quot;실행 화면&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;실행 화면&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;일단 정상적으로 PID를 출력시키고 내가 입력시킨 PID를 기준으로 정보를 잘 읽어오는지 파악하는게 중요했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;모듈 기준으로 검사를 한 이유&lt;/h2&gt;
&lt;p&gt;프로세스를 정한 후 처음으로 생각한 것은 범위를 지정하는 것이었다.&lt;br /&gt;
프로세스의 가상 메모리 주소를 전부 검사한다는것은 공간이 너무 크고 비효율적이라 판단해서&lt;br /&gt;
특정 범위를 지정해줘야 한다고 판단했고 그래서 프로세스에 로드된 모듈을 기준으로 검사를 하는걸로 했다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Review/MemoryScanner/program_region.png&quot; alt=&quot;프로그램 메모리 영역&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;프로그램 메모리 영역&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Review/MemoryScanner/my_region.png&quot; alt=&quot;모듈 정보&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;모듈 정보&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Module32 함수를 사용해 프로세스에 로드된 모듈 정보를 수집하고 필요한 정보만 구조체로 정리해 벡터 형태로 관리했다.&lt;br /&gt;
그리고 memory Region을 보여주는 툴과 비교해 정상적으로 출력이 되었는지 비교했었다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;효율적으로 처리하기 위해&lt;/h2&gt;
&lt;p&gt;모듈의 Base 주소부터 Size만큼 범위를 기준으로 VirtualQueryEx를 호출해 메모리 영역 단위로 순회하며&lt;br /&gt;
MEM_COMMIT 상태 + 접근 가능한 페이지만 처리하겠금 했다.&lt;/p&gt;
&lt;p&gt;패턴 검사 방식 또한 구현 과정에서 몇번이나 수정을 했는지 모르겠다..&lt;br /&gt;
초기에는 main 함수에서 모듈 하나에 패턴 하나 이런식으로 구성해 전달하며 검사하는 구조였는데&lt;br /&gt;
나중에 패턴 수가 늘어날수록 비효율적이며 패턴을 굳이 묶어서 같이 전달할 필요가 없다는 문제 였다.&lt;/p&gt;
&lt;p&gt;이를 해결하기 위해 패턴 관련 로직을 main함수에서 분리시키고 모든 패턴을 PatternDB에 모아두고&lt;br /&gt;
이름:패턴 형태의 map 구조로 관리하도록 변경했고 검사하는 함수에서 받아와 바로 사용하겠금 처리했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;결과&lt;/h2&gt;
&lt;p&gt;구조를 정리한 뒤, 테스트 실행 파일에 심어둔 패턴들이 정상적으로 탐지되는 걸 확인할 수 있었다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Review/MemoryScanner/result.png&quot; alt=&quot;패턴 탐지 결과 출력&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;패턴 탐지 결과 출력&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이 프로젝트를 하면서 느꼈던 점은 일단 가벼운 마음으로 만들면서 이해해보자 였는데&lt;br /&gt;
실제로는 구현 과정에서 반복적으로 발생하는 오류와 수정 과정을 거치면서&lt;br /&gt;
이 코드 하나를 제대로 이해하기 위해 꽤 오랜 시간을 썼던거 같다.&lt;/p&gt;
&lt;p&gt;단순한 기능처럼 보였던 메모리 스캐너도 실제로 구현해보니 메모리 범위 설정, Region 단위 접근,&lt;br /&gt;
읽기 가능한 영역 처리 등 고려해야할 요소가 너무 많았다.&lt;/p&gt;
&lt;p&gt;하지만 그 과정 자체가 나한테는 좋은 학습 경험이 되었던거 같고 또 무엇인가를 만들면서&lt;br /&gt;
나 자신이 성장해간다는것을 느끼게 되는것 같다.&lt;/p&gt;
&lt;p&gt;학습 목적으로 한 매우 단순한 수준의 프로젝트이지만 메모리를 직접&lt;br /&gt;
다뤄보고 구조적으로 이해해본 경험은 확실히 남았다고 생각한다.&lt;/p&gt;
</content:encoded></item><item><title>CreateProcessW 이해하기</title><link>https://cr3xis.github.io/posts/win32-create-process/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/win32-create-process/</guid><description>CreateProcessW의 인자와 프로세스 생성 흐름을 예제 코드와 함께 정리합니다.</description><pubDate>Sun, 15 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;WinAPI를 공부하다 보면 어느 순간부터 문서를 읽는 시간보다 이해하려고 머리를 굴리는 시간이 더 길어질 때가 있다.&lt;br /&gt;
CreateProcessW를 처음 봤을 때가 그런 느낌이었다.
인자는 많고 설명은 길고 흐름은 한 번에 잡히지 않고 뭔가 전체 구조가 제대로 정리돼 있어야 이해가 될 것 같은 함수였다.&lt;/p&gt;
&lt;p&gt;그래서 이번에는 이 함수를 조금 더 편안하게 바라볼 수 있는 흐름으로 정리해 보려고 한다.&lt;br /&gt;
내가 공부하면서 느낀 생각이나 감정이 자연스럽게 스며 있는 방식으로 기록해 본다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;CreateProcessW는 어떤 구조인가&lt;/h2&gt;
&lt;p&gt;CreateProcessW는 말 그대로 새로운 프로세스를 만들어 달라고 운영체제에게 요청하는 함수이다.&lt;br /&gt;
설명만 보면 복잡해 보이지만 하나씩 뜯어보면 결국 프로세스를 생성하는 데 필요한 정보들을&lt;br /&gt;
운영체제에 넘겨주는 역할을 한다는 느낌에 가까웠다.&lt;/p&gt;
&lt;p&gt;MSDN :&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/processthreadsapi/nf-processthreadsapi-createprocessw&quot;&gt;CreateProcessW&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;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
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;처음에는 이 구조체를 보고 막막했지만 하나씩 풀어 이해하고 나니 인자 하나하나가 훨씬 자연스럽게 보였다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;lpApplicationName &amp;amp; lpCommandLine&lt;/h2&gt;
&lt;p&gt;lpApplicationName과 lpCommandLine은 처음 보면 비슷해 보여서 헷갈리기 쉽다.&lt;br /&gt;
실제로는 둘의 개념이 꽤 다르다.&lt;/p&gt;
&lt;p&gt;lpApplicationName은 실행 파일 경로를 운영체제에 정확하게 전달하는 방식이다.&lt;br /&gt;
lpCommandLine은 실행 파일 이름과 인자를 하나의 문자열로 묶어서 넘기는 방식이다.&lt;/p&gt;
&lt;p&gt;Windows는 lpCommandLine을 통째로 받아 내부에서 실행 파일 부분을 다시 파싱한다.&lt;br /&gt;
그래서 둘을 동시에 사용하는 경우도 있지만 문서를 읽다 보면 가능한 한 명확하게 하나만 사용하는 편이&lt;br /&gt;
코드의 흐름을 더 깨끗하게 유지할 수 있다는 생각이 들었다.&lt;/p&gt;
&lt;h2&gt;dwCreationFlags&lt;/h2&gt;
&lt;p&gt;새로 만들어진 프로세스가 어떤 상태로 시작될지 정하는 옵션들이 dwCreationFlags에 들어간다.&lt;br /&gt;
처음에는 해당 필드에 어떤 플래그가 들어가는지를 몰라 찾고자 하는데에 어려움이 있었다.&lt;br /&gt;
하지만 그부분은 내가 문서를 집중해서 읽지 못해 생겼던 문제였었다.&lt;/p&gt;
&lt;p&gt;MSDN :&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/procthread/process-creation-flags&quot;&gt;dwCreationFlags&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;문서를 보면 많은 옵션이 있지만 그 흐름은 단순했다.&lt;br /&gt;
새 프로세스를 어떤 모습으로 시작시키고 싶은지 운영체제에게 알려주는 용도라 생각하면 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;STARTUPINFO 구조체&lt;/h2&gt;
&lt;p&gt;새 프로세스가 어떤 초기 상태로 실행될지 담는 구조체가 STARTUPINFO였다.&lt;br /&gt;
공부하면서 가장 먼저 눈에 들어왔던 부분은 cb라는 필드였다.&lt;/p&gt;
&lt;p&gt;이 cb는 구조체의 크기를 운영체제에 알려주는 역할을 한다.&lt;br /&gt;
처음에는 굳이 왜 이런 행동을 할까 생각해서 알아 봤더니 WinAPI 구조체들은 버전이 바뀌면서&lt;br /&gt;
필드가 추가되는 경우가 있어서 cb 같은 값을 전달하지 않으면 운영체제가 어디까지 읽어야 할지 애매해질 수 있었다.&lt;/p&gt;
&lt;p&gt;이런 구조 덕분에 WinAPI는 오래된 구조체들도 계속 호환을 유지할 수 있었던 것 같다.&lt;br /&gt;
그리고 cb나 dwSize 같은 역할을 하는 애들이 이름이 왜 다른지 찾아보니 Winapi 초기 개발자들이&lt;br /&gt;
팀을 나눠 부서별로 일을 했는데 팀 마다 이름을 작명하는 규칙이 달라 이런것들이 지금까지 이어져 오는것 같다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;PROCESS_INFORMATION 구조체&lt;/h2&gt;
&lt;p&gt;CreateProcessW가 성공하면 이 구조체에 여러 값들을 채워 준다.&lt;br /&gt;
프로세스 ID, 스레드 ID, 프로세스 핸들, 스레드 핸들 같은 값들이 들어간다.&lt;/p&gt;
&lt;p&gt;결국 프로세스를 직접 제어할 때 필요한 정보가 모두 이 구조체 하나에 들어 있다고 보면 된다.&lt;br /&gt;
이 구조를 처음 봤을 때는 뭔가 복잡해 보였지만 실제로는 필요한 정보를 한 곳에 모아둔 결과물이라는 느낌이었다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Example&lt;/h2&gt;
&lt;p&gt;MSDN :&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/procthread/creating-processes&quot;&gt;Example Code&lt;/a&gt;의
기본 예제를 먼저 보고 난 뒤 아래와 같은 변형된 코드를 작성해 보았다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;windows.h&amp;gt;
#include &amp;lt;stdio.h&amp;gt;

int main()
{
    STARTUPINFO si = { 0 };
    PROCESS_INFORMATION pi = { 0 };
    wchar_t title[] = L&quot;CreateProcessW - cmd.exe&quot;;
    si.cb = sizeof(si);
    si.lpTitle = title;
    
    

    if (CreateProcessW(
        L&quot;C:\\Windows\\System32\\cmd.exe&quot;,
        NULL,
        NULL,
        NULL,
        FALSE,
        CREATE_SUSPENDED |CREATE_NEW_CONSOLE,
        NULL, 
        NULL,
        &amp;amp;si, &amp;amp;pi))
    {
       printf(&quot;프로세스 생성 성공 :  PID = %d\n&quot;, pi.dwProcessId);
    }
    int count = 0;
    while (count &amp;lt; 5) {
        printf(&quot;count : %d\n&quot;, count);
        Sleep(1000);
        count++;
    }

    printf(&quot;ResumeThread ! \n&quot;);
    if (count == 5) { ResumeThread(pi.hThread); }
 

    CloseHandle(pi.hProcess);
    CloseHandle(pi.hThread);
    return 0;
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Win32/03/Result.png&quot; alt=&quot;Result&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Result&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;각 필드 분석을 통해 간단한 예제를 만들었는데 코드를 보면 프로세스를 만들고 일시 정지 상태로 시작했다가&lt;br /&gt;
resume을 통해 실행시키는걸 한눈에 이해할수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;대부분의 API는 처음 보면 부담스럽고 어렵게 느껴진다.&lt;br /&gt;
하지만 구조가 왜 이렇게 되어 있는지 하나씩 이해하고 나니 전체 흐름이 자연스럽게 이어지는 느낌이 들었다.&lt;/p&gt;
&lt;p&gt;WinAPI는 공부할 때마다 새로운 벽이 생기는 것 같지만 이런 글 하나를 정리하고 나면 또 어느 순간 이해가 더 깊어져 있는 걸 느낀다.&lt;br /&gt;
이해하는 과정들을 글로 작성하는 시간이 길거나 가끔은 혼란스럽고 버거운 순간도 있지만 그래도 이렇게 하나씩 쌓여 가는 과정이 꽤 즐겁기도 하다.&lt;br /&gt;
시간이 지나면 문서만 봐도 흐름이 그려지는 날이 올 거라 생각하면서 앞으로도 계속 이런 정리를 이어가려고 한다.&lt;/p&gt;
</content:encoded></item><item><title>문자 인코딩과 WinAPI A/W 구조 이해하기</title><link>https://cr3xis.github.io/posts/win32-character-encoding/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/win32-character-encoding/</guid><description>Windows 문자 인코딩과 WinAPI의 ANSI, Unicode, A/W 함수 구조를 정리합니다.</description><pubDate>Tue, 10 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;문자열과 관련된 구조를 다시 살펴보면, 처음에는 단순히 문자 크기만 다르다고 생각했던 부분들이&lt;br /&gt;
실제로는 운영체제의 흐름과 API 구조 전체에 얽혀 있다는 걸 자연스럽게 느끼게 된다.&lt;br /&gt;
Win32를 공부하며 계속 부딪히는 것도 결국 이 흐름이었던것 같다.&lt;br /&gt;
아래는 내가 공부했던 내용들을 정리하여 작성한 글이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;ASCII에서 ANSI, 그리고 유니코드 흐름&lt;/h2&gt;
&lt;p&gt;처음 프로그램에서 문자를 다루기 시작하면 자연스럽게 ASCII가 떠오른다.&lt;br /&gt;
영어 알파벳을 표현하는 데에는 충분했지만, 전 세계 언어를 담기에는 구조적으로 부족했다.&lt;br /&gt;
이 틀을 조금 더 확장하려고 각 국가마다 ANSI 코드 페이지를 만들기 시작했는데 그 순간부터 같은 숫자 값이 환경에 따라 전혀 다른 문자로 보이는 일이 생겼다.&lt;/p&gt;
&lt;p&gt;Windows 역시 오래도록 ANSI 기반으로 동작했기 때문에 char 하나로 문자를 표현하고 코드 페이지에 맞춰 해석하는 구조가 기본이었다.&lt;br /&gt;
그 과정에서 MBCS가 등장했고, 한 글자가 1바이트가 되기도 하고 2바이트가 되기도 하는 유연하지만 계산하기에는 까다로운 구조를 만들어냈다.&lt;/p&gt;
&lt;p&gt;이런 흐름을 지나며 결국 언어마다 서로 다른 규칙을 유지하는 방식이 유지될 수 없다는 결론에 닿았고,
전 세계 문자를 하나로 통합하려는 과정속 만들어진게 바로 유니코드이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Windows는 왜 UTF-16을 기반으로 했을까&lt;/h2&gt;
&lt;p&gt;Windows 내부를 보면 문자열이 대부분 2바이트 단위로 다뤄진다.&lt;br /&gt;
처음엔 이 부분이 의문이었는데, 당시 환경을 떠올리면 이유가 자연스럽게 보인다.&lt;/p&gt;
&lt;p&gt;전 세계 언어를 모두 담아야 하는 상황에서 문자 크기가 계속 변하는 구조(UTF-8)보다는 일정한 폭을 가진 UTF-16이&lt;br /&gt;
운영체제 내부 동작과 API 구조에 잘 맞았기 때문이다.&lt;/p&gt;
&lt;p&gt;UTF-16은 대부분의 일반 문자들이 2바이트 안에서 표현되기 때문에&lt;br /&gt;
문자 하나를 이동하거나 길이를 계산하는 과정이 단순했고, WinAPI를 바꿔야 할 필요도 크게 없었다.&lt;br /&gt;
이런 이유들 때문에 Windows 내부 구조는 지금도 UTF-16 기반으로 동작하고 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;A/W의 의미&lt;/h2&gt;
&lt;p&gt;문자 인코딩을 이해하고 나면 WinAPI가 ANSI와 WIDE 두 가지 버전을 제공하는 이유가 자연스럽게 이어진다.&lt;br /&gt;
Windows가 유니코드로 넘어가는 과정에서 기존 ANSI 기반 프로그램들과의 호환성까지&lt;br /&gt;
모두 끊지 않으려다 보니 A 버전과 W 버전을 함께 유지하게 된 것이다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Win32/02/msgbox.png&quot; alt=&quot;MessageBox 호출 예제&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;MessageBox 호출 예제&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;MessageBox만 봐도 구조가 바로 보인다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MessageBoxA → ANSI 기반&lt;/li&gt;
&lt;li&gt;MessageBoxW → 유니코드 기반&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;컴파일러의 문자 집합 설정에 따라 MessageBox라는 이름은 결국 둘 중 하나로 결정된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Win32/02/char_set.png&quot; alt=&quot;Character Set&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Character Set&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;멀티바이트 환경이라고 해도 전처리기 설정이 유니코드로 묶여 있다면 TCHAR 계열은 계속 wchar_t 기반으로 동작한다.&lt;br /&gt;
문자 집합을 바꿔도 원하는 구조가 바로 반영되지 않았던 이유가 여기에 있었다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;char, wchar_t, TCHAR&lt;/h2&gt;
&lt;p&gt;문자를 담는 자료형을 이해하면 A/W 구조가 훨씬 단순하게 보인다.&lt;br /&gt;
char는 1바이트 정수 값을 그대로 문자로 사용하고, wchar_t는 2바이트 또는 그 이상 크기로 유니코드 문자를 표현한다.&lt;br /&gt;
Windows에서는 wchar_t가 UTF-16과 맞물려 2바이트 구조를 사용한다.&lt;/p&gt;
&lt;p&gt;TCHAR는 이 둘을 환경에 따라 자동으로 선택하는 타입이다.&lt;br /&gt;
UNICODE 환경이면 wchar_t로, 멀티바이트 환경이면 char로 매핑된다.&lt;br /&gt;
하나의 코드로 두 환경을 모두 커버할 수 있게 하려는 방식인듯하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;LP&lt;/h2&gt;
&lt;p&gt;WinAPI 문서를 읽다 보면 LPSTR, LPWSTR 같은 자료형이 계속 등장한다.&lt;br /&gt;
한동안 이 표현이 낯설었는데, 자세히 알아보니 옛날 16비트 윈도우에서 사용 되었던거같다.&lt;br /&gt;
Near Pointer와 Long Pointer 라는 개념이 있었지만 지금 기준에서 보면 그냥 포인터라는 의미에 가깝다.&lt;/p&gt;
&lt;p&gt;여기에 C가 붙으면 const가 붙은 읽기 전용 문자열이 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;ANSI / Unicode / TCHAR&lt;/h2&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Win32/02/types.png&quot; alt=&quot;ANSI/Unicode/TCHAR&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;ANSI/Unicode/TCHAR&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ANSI&lt;br /&gt;
LPSTR : char*&lt;br /&gt;
LPCSTR : const char*&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Unicode&lt;br /&gt;
LPWSTR : wchar_t*&lt;br /&gt;
LPCWSTR : const wchar_t*&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;TCHAR&lt;br /&gt;
LPTSTR : char* 또는 wchar_t*&lt;br /&gt;
LPCTSTR : const char* 또는 const wchar_t*&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;프로젝트를 구성하거나 API를 공부하다보면 문자열 인코딩은 생각보다 많은 영향을 준다.&lt;br /&gt;
단순한 글자로만 보이던 문자열들이 운영체제 내부 구조와 API의 흐름까지 이어진다는 점이 보이기 시작하면&lt;br /&gt;
Win32를 다루는 방식이 훨씬 안정적으로 느껴진다.&lt;br /&gt;
이번에 글을 작성하며 느낀거지만 상대적으로 잊기 쉬워 보이는 내용이더라도 규칙만 생각하면&lt;br /&gt;
결코 쉽게 잊지 않을수 있다고 생각한다.&lt;/p&gt;
</content:encoded></item><item><title>Win32 프로그램의 기본 흐름과 자료형 정리</title><link>https://cr3xis.github.io/posts/win32-program-structure/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/win32-program-structure/</guid><description>WinMain, 메시지 루프, WndProc를 중심으로 Win32 프로그램의 기본 흐름을 정리합니다.</description><pubDate>Thu, 05 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Win32 API를 처음 공부했을 때 가장 크게 다가왔던 부분은 콘솔 프로그램처럼 한 번에 쭉 실행되는 구조가 아니라는 점이었다.&lt;br /&gt;
프로그램은 창을 띄우고 난 뒤에도 계속 살아 있어야 하고, 사용자가 어떤 행동을 할지 모르기 때문에 그 흐름을 운영체제가 전달해주는 방식으로 유지한다.&lt;/p&gt;
&lt;p&gt;그래서 WinMain, 메시지 루프, WndProc 같은 요소들이 등장한다.&lt;br /&gt;
처음에는 형태가 낯설었지만, 전체 흐름을 자연스럽게 따라가면서 이해하려고 하니 그제서야 하나의 구조처럼 보이기 시작했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;프로그램 전체 흐름&lt;/h2&gt;
&lt;p&gt;Win32 프로그램은 이벤트 기반으로 움직인다.&lt;br /&gt;
창이 만들어지면 프로그램은 멈춰 있는 것이 아니라 운영체제가 보내주는 메시지를 계속 기다리는 상태가 된다.&lt;br /&gt;
사용자가 창을 클릭하거나 움직이거나 키보드를 누르면 운영체제가 그 내용을 메시지로 전달하고, 프로그램은 그 메시지를 받아서 필요한 동작을 수행한다.&lt;/p&gt;
&lt;p&gt;이 흐름을 한 번에 바라보면 다음과 같은 느낌이다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Win32/01/WinMain_s.png&quot; alt=&quot;Win32 프로그램의 전체 흐름&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Win32 프로그램의 전체 흐름&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;프로그램이 시작되면 WinMain이 호출되고, 여기서 창을 만들기 위한 준비를 하고, 메시지 루프가 실행된다.&lt;br /&gt;
그 상태로 프로그램은 계속 살아 있으며, 발생하는 모든 메시지는 결국 WndProc으로 전달된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;WinMain&lt;/h2&gt;
&lt;p&gt;WinMain은 콘솔 프로그램의 main처럼 시작점이 되지만, 하는 일은 조금 더 많다.&lt;br /&gt;
창을 만들기 위한 기본 정보들을 준비하고, 실제 창을 생성한 뒤 메시지 루프를 돌리는 역할까지 포함되어 있다.&lt;/p&gt;
&lt;p&gt;함수 앞에 붙은 APIENTRY는 이 함수가 Windows가 지정한 호출 규약(stdcall)을 따른다는 의미다.&lt;br /&gt;
호출 규약이라는 말이 어렵게 느껴질 수 있는데, 쉽게 말하면 함수가 호출될 때 인자를 어떤 방식으로 스택에 쌓고 누가 정리하는지를 정한 약속이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Win32/01/WinMain.png&quot; alt=&quot;WinMain 진입점&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;WinMain 진입점&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;WinMain의 첫 번째 인자인 HINSTANCE는 실행 중인 프로그램 자체를 식별하기 위한 값이다.&lt;br /&gt;
운영체제 입장에서 보면 이 프로그램이 어떤 파일에서 로드되었고 어디에 존재하는지를 나타내는 정보가 필요한데, 그것을 구분하기 위한 번호표 같은 개념이다.&lt;/p&gt;
&lt;p&gt;그리고 다음으로 hPrevInstance는 옛날 16비트 Windows 시절에 사용되던 인자이고 현재는 항상 비어 있는 값으로 전달된다.&lt;br /&gt;
lpCmdLine은 프로그램 실행 시 전달된 문자열 인자를 그대로 담고 있으며, nCmdShow는 처음 창이 어떤 상태로 표시될지를 나타낸다.&lt;/p&gt;
&lt;p&gt;이건 부가적인 이야기 일수도 있지만 &apos;&lt;em&gt;in&lt;/em&gt;&apos;, &apos;&lt;em&gt;in_opt&lt;/em&gt;&apos; 같은 애들은 &lt;code&gt;SAL&lt;/code&gt; 이라고 하는 주석이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;메시지 루프&lt;/h2&gt;
&lt;p&gt;프로그램은 WinMain에서 창을 만들고 나면, 바로 메시지 루프에 들어간다. 여기서 운영체제가 보내주는 메시지를 계속 받으면서 프로그램이 반응하게 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Win32/01/MSG.png&quot; alt=&quot;메시지 루프&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;메시지 루프&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 루프는 프로그램이 살아 있는 동안 계속 실행되며, 운영체제가 보내는 모든 이벤트가 이 흐름을 통해 프로그램에 전달된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;WndProc&lt;/h2&gt;
&lt;p&gt;WndProc은 Win32 프로그램의 중심에 있는 함수라고 해도 좋다.&lt;br /&gt;
창이 이동하거나 크기가 바뀌는 상황, 키보드를 눌렀을 때, 마우스를 움직일 때, 창을 닫으려고 할 때&lt;br /&gt;
거의 모든 흐름이 메시지 형태로 전달되고 이 함수에서 처리된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Win32/01/WndProc.png&quot; alt=&quot;WndProc 메시지 처리 함수&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;WndProc 메시지 처리 함수&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;운영체제는 발생한 상황을 메시지라는 형태로 만들어 보내고, 프로그램은 그 메시지를 WndProc에서 받아 해석한다.&lt;br /&gt;
어떤 메시지가 어떤 상황을 의미하는지 알고 나면 WndProc 내부에서 어떤 처리가 이루어지는지 훨씬 자연스럽게 이해된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Win32에서 자주 등장하는 자료형&lt;/h2&gt;
&lt;p&gt;WinAPI는 C 기반으로 제작된 오래된 구조다 보니 처음 보면 낯선 자료형들이 많다. 하지만 흐름 속에서 의미를 잡아가다 보면 그 존재 이유가 자연스럽게 보인다.&lt;/p&gt;
&lt;h2&gt;HANDLE&lt;/h2&gt;
&lt;p&gt;운영체제가 관리하는 객체들은 대부분 HANDLE이라는 공통된 형태를 사용한다.&lt;br /&gt;
파일, 프로세스, 스레드처럼 운영체제가 직접 들고 있어야 하는 자원들은 직접 주소를 노출하지 않고 이 핸들 값을 통해 접근하도록 되어 있다.
직접 주소를 알려주지 않고 핸들 값을 통해 간접적으로 접근하는 이유 중 가장 큰 이유는 보안성과 안정성 때문이다.&lt;br /&gt;
그리고 사실상 핸들을 주고 관리는 OS가 하기에 좀 더 편하다는 생각이 든다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;HWND&lt;/h2&gt;
&lt;p&gt;HWND는 Window를 식별하기 위한 핸들이다.&lt;br /&gt;
이미 만들어진 창을 조작하거나 그 창으로 메시지를 보낼 때 항상 HWND가 필요하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;WPARAM과 LPARAM&lt;/h2&gt;
&lt;p&gt;WndProc에서 메시지를 처리할 때 추가 정보가 필요한 경우 WPARAM과 LPARAM에 데이터가 들어 있다.&lt;br /&gt;
윈도우 메시지는 상황에 따라 필요한 정보의 양이 다른데, 이 두 변수를 통해 메시지마다 필요한 정보를 유연하게 전달할 수 있다.&lt;/p&gt;
&lt;p&gt;WPARAM은 비교적 간단한 값을 전달하는 데 쓰이고, LPARAM은 좌표나 구조체 포인터처럼 더 많은 데이터가 필요할 때 사용된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;LRESULT&lt;/h2&gt;
&lt;p&gt;WndProc이 메시지를 처리한 뒤 운영체제에게 결과를 알려주는 값이 LRESULT다.&lt;br /&gt;
특정 메시지를 처리했다는 의미를 담거나 기본 동작을 사용하겠다는 판단을 내려줄 때 사용된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;CALLBACK과 WINAPI&lt;/h2&gt;
&lt;p&gt;WinAPI 함수들에는 CALLBACK 또는 WINAPI 같은 키워드가 붙어 있다.&lt;br /&gt;
이 키워드는 함수가 어떤 호출 규약을 따라야 하는지를 정해준다.&lt;br /&gt;
운영체제가 호출하는 함수와 사용자가 직접 호출하는 함수 사이에 혼동이 생기지 않도록 하는 장치라고 보면 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Win32 프로그램은 WinMain으로 시작해 메시지 루프를 통해 계속 이벤트를 받아들이고, 처리는 결국 WndProc에서 이루어진다.&lt;br /&gt;
처음 보면 복잡해 보이던 흐름도 전체 구조 안에서 보면 각각의 역할이 자연스럽게 이어진다.
Win32 API가 사용하는 자료형들도 이런 흐름 안에서 보면 훨씬 이해하기 쉬워지고,&lt;br /&gt;
실제로 코드를 작성하거나 디버깅할 때도 훨씬 편하게 다가온다.
한번에 이해하기는 어렵지만 문서화를 시켜 구체화를 하다보면 잊었던 내용도 다시 생각이 나기에 남겨두는 글이다.&lt;/p&gt;
</content:encoded></item><item><title>RVA to RAW 이해하기</title><link>https://cr3xis.github.io/posts/pe-rva-to-raw/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/pe-rva-to-raw/</guid><description>PE 파일에서 RVA를 RAW 파일 오프셋으로 변환하는 과정을 실습합니다.</description><pubDate>Sun, 01 Mar 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;대부분에 글에서는 실습보다는 이론적으로만 많이 다루고 있길래&lt;br /&gt;
이번 글에서는 Notepad.exe를 기준으로 RVA → RAW 변환 흐름을 하나씩 따라가며&lt;br /&gt;
파일 내부에서 주소가 어떻게 매핑되는지 정리해 보려고 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;RVA to RAW의 정의&lt;/h2&gt;
&lt;p&gt;RVA to RAW는 간단히 말하면 메모리 기준 상대 주소(RVA)를 파일 기준 주소(RAW)로 변환하는 과정이다.&lt;/p&gt;
&lt;p&gt;메모리에서 보는 주소 체계(RVA와 VA)는 실행 환경을 기준으로 잡혀 있지만&lt;br /&gt;
파일에서 보는 주소(RAW)는 디스크에 저장된 구조를 기준으로 한다.&lt;br /&gt;
그래서 이 둘을 연결하려면 PE의 섹션 구조를 반드시 이해하고 있어야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;필수적으로 알아야 하는 용어들&lt;/h2&gt;
&lt;p&gt;RVA와 RAW를 구별하기 위해 최소한 아래 다섯 가지 개념은 알고 있어야 했다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;VA (Virtual Address)&lt;/code&gt;: 메모리 상태에서의 실제 주소&lt;br /&gt;
VA = ImageBase + RVA&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RVA (Relative Virtual Address)&lt;/code&gt;: ImageBase를 0으로 가정한 상대 주소&lt;br /&gt;
PE OptionalHeader의 주소 정보는 RVA 기준으로 되어 있다.&lt;/p&gt;
&lt;p&gt;&lt;code&gt;RAW (File Offset)&lt;/code&gt;: 파일에서의 오프셋&lt;/p&gt;
&lt;p&gt;&lt;code&gt;ImageBase&lt;/code&gt;: 프로그램이 메모리에 로드될 기본 위치 ( 환경에 따라 주소가 달라진다. )&lt;br /&gt;
파일 기준 ImageBase : &lt;strong&gt;0x00400000&lt;/strong&gt;&lt;br /&gt;
메모리에서 실제 로드된 ImageBase : &lt;strong&gt;0x00950000&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;EntryPoint&lt;/code&gt;: 프로그램이 실행을 시작하는 지점 ( RVA로 저장됨 )&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;섹션 헤더 속 필요한 멤버들&lt;/h2&gt;
&lt;p&gt;RVA → RAW를 계산하려면 IMAGE_SECTION_HEADER에서 아래의 필드만 이해해도 문제없다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct _IMAGE_SECTION_HEADER {
    DWORD VirtualAddress;
    DWORD VirtualSize;
    DWORD PointerToRawData;
} IMAGE_SECTION_HEADER;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;VirtualAddress&lt;/code&gt;는 메모리에서의 상대위치인 RVA를 의미하고&lt;br /&gt;
&lt;code&gt;PointerToRawData&lt;/code&gt;는 파일에서의 해당 섹션이 어디에 배치되는지를 알려주는 값이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;File Offset(RAW)을 구하는 방법&lt;/h2&gt;
&lt;h2&gt;1. 해당 RVA가 어느 섹션에 속하는지 판단&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;Section.VirtualAddress(RVA) &amp;lt; RVA &amp;lt; Section.VirtualAddress(RVA) + Section.VirtualSize
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;2. RAW 계산 공식&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;RAW = (RVA - Section.VirtualAddress(RVA)) + Section.PointerToRawData
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 순서만 지킨다면 RAW를 구하는것에 큰 어려움이 없다.&lt;br /&gt;
하지만 우리가 일반적으로 알고 있던 VA는 절대주소라고 이해하고 있을텐데&lt;br /&gt;
NT_HEADER 구조체의 VirtualAddress는 RVA인데 이름 떄문에 VA를 의미하는줄 알고 한참 해맸었다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;실습&lt;/h2&gt;
&lt;p&gt;대부분 자료를 찾아보면 이론적으로만 설명하고 임의로 부여를 해서 하기에 잘 이해가 되지 않았다.&lt;br /&gt;
나는 Notepad.exe를 기준으로 직접 EntryPoint의 RAW를 찾아보도록 하였다.&lt;br /&gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/RVATORAW/info.png&quot; alt=&quot;PE View&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;PE View&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AddressOfEntryPoint : &lt;strong&gt;0x21860 (RVA)&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;.text
&lt;ul&gt;
&lt;li&gt;VirtualAddress(RVA) : &lt;strong&gt;0x1000&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;VirtualSize : &lt;strong&gt;0x223B8&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;PointerToRawData : &lt;strong&gt;0x400&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;PE view를 통해 나는 이러한 정보들을 찾을수 있었고 이를 찾는 공식에 대입을 해보도록 하였다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;공식 적용하기&lt;/h2&gt;
&lt;h2&gt;1. 어느 섹션인지 판단하기&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;1000 &amp;lt; 21860 &amp;lt; 1000 + 223B8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 조건을 만족하므로 EntryPoint는 &lt;strong&gt;.text 섹션&lt;/strong&gt;에 속한다는것을 알수있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. RAW 계산 공식&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;RAW = (21860- 1000) + 400 
RAW = 20C60
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;해당 계산을 통해 EntryPoint의 RAW는 &lt;strong&gt;0x20C60&lt;/strong&gt;이라는 것을 알수있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;덤프에서 직접 확인해보기&lt;/h2&gt;
&lt;p&gt;이제 계산된 File Offset을 직접 확인해보았다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/RVATORAW/dump.png&quot; alt=&quot;Dump&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Dump&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;파일 시점의 EntryPoint가 시작하는 위치의 덤프와 메모리 시점의 EntryPoint가 시작하는 위치의 덤프가 동일하다는것을 알수있게 된다.&lt;/p&gt;
&lt;p&gt;또 한가지 알수 있는게 있는데 파일에서의 보는 EntryPoint위치와 디버거를 통해 보이는 EntryPoint주소가&lt;br /&gt;
서로 다른 이유는 결국 ImageBase 때문이다.&lt;/p&gt;
&lt;p&gt;PE파일은 디스크에 있을 때와 메모리에 로드되었을때 ImageBase가 달라질 수 있는데&lt;br /&gt;
이 차이 때문에 같은 RVA라도 VA로 계산했을때 서로 다른 절대주소가 나오게 된다.&lt;/p&gt;
&lt;p&gt;여기서 하나 해볼수 있는게 VA = ImageBase + RVA 라고 했었다.&lt;br /&gt;
디버거에서의 ImageBase는 0x00950000이고 EntryPoint의 RVA는 0x21860 이다.&lt;br /&gt;
즉 두 값을 더하게 되면 0X00971860이 VA이며 실제 사진에서의 주소와 같은 값을 나타낸다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;RVA to RAW에서는 필드 이름때문에 너무 헛고생을 많이한것 같다.&lt;br /&gt;
결국 중요한건 저 공식만 이해하고 있다면 큰 문제는 없는거 같다.&lt;/p&gt;
&lt;p&gt;나는 구조체의 VirtualAddress를 VA라고 착각해서 시간을 많이 낭비했는데&lt;br /&gt;
이걸 정확히 RVA로 이해하고 나니 전체 구조가 깔끔하게 정리됐다.&lt;/p&gt;
</content:encoded></item><item><title>PE 전체구조 이해하기</title><link>https://cr3xis.github.io/posts/pe-file-structure/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/pe-file-structure/</guid><description>PE 파일의 DOS Header, NT Header, Section Header 구조를 정리합니다.</description><pubDate>Wed, 25 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;PE 파일 구조를 처음 봤을때 느낌은 생각보다 너무 많아보여 이해하기가 쉽지 않았다.&lt;br /&gt;
PEview로 파일을 열어 보면 위쪽에 뭔가 헤더들이 보이고 그 아래로 섹션 목록이 쭉 이어지는데&lt;br /&gt;
도대체 어디부터 어디까지가 무엇을 의미하는지 한 번에 잡히지는 않았다.&lt;/p&gt;
&lt;p&gt;그래서 이번에는 내가 PEview와 HxD를 보면서 이해한 흐름을 기준으로 정리해 보려고한다.&lt;br /&gt;
먼저 전체 레이아웃부터 눈에 익혀 두는 편이 훨씬 이해가 편했다.&lt;br /&gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/01_layout.png&quot; alt=&quot;PE layout&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;PE layout&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 사진만 봐도 위에서부터 DOS 헤더가 나오고 그 뒤에 NT 헤더와 Section 헤더가 붙고&lt;br /&gt;
마지막에 각 섹션의 실제 데이터가 파일 뒤쪽을 채우고 있다는 흐름이 어느 정도 보인다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;DOS Header &amp;amp; DOS Stub&lt;/h2&gt;
&lt;p&gt;PE 파일의 맨 앞에는 DOS 헤더가 들어 있다.&lt;br /&gt;
이 부분은 원래 MS DOS 실행 파일 형식에서 이어져 내려온 헤더라서 지금 기준으로 보면 역사적인 이유가 조금 더 큰 영역이기도 하다.&lt;/p&gt;
&lt;p&gt;구조체만 봐도 IMAGE_DOS_HEADER 구조체가 꽤 길게 정의되어 있다.&lt;/p&gt;
&lt;p&gt;실제로 분석하면서 계속 보게 되는 필드는 많지 않았다.&lt;br /&gt;
나는 e_magic과 e_lfanew 두 가지만 제대로 이해해도 DOS 헤더 쪽은 크게 어렵지 않다고 느꼈다.&lt;/p&gt;
&lt;p&gt;전체 구조체를 다 작성할까 하다가 그냥 중요한 필드들만 빼서 작성하기로 했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct _IMAGE_DOS_HEADER {
    WORD e_magic;
    LONG e_lfanew;
} IMAGE_DOS_HEADER;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PEview로 보면 파일 맨 앞에서 e_magic 값이 바로 눈에 들어온다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/02_dos_header_peview.png&quot; alt=&quot;PEview-IMAGE_DOS_HEADER&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;PEview-IMAGE_DOS_HEADER&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;e_magic에는 MZ라는 값이 들어 있다.&lt;br /&gt;
이 값이 도스 시절 실행 파일 시그니처였고 지금도 그대로 유지되고 있다.&lt;br /&gt;
운영체제는 이 값을 보고 일단 이 파일이 실행 파일 형식을 따르는지부터 확인하게 된다.&lt;/p&gt;
&lt;p&gt;e_lfanew는 NT 헤더가 파일 어디에 있는지 알려 주는 오프셋 역할을 한다.&lt;br /&gt;
HxD로 같은 파일을 열어 보면 이 두 값이 실제로 어떤 위치에 놓여 있는지 더 직접적으로 보인다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/02_dos_header_hxd.png&quot; alt=&quot;HxD-IMAGE_DOS_HEADER&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;HxD-IMAGE_DOS_HEADER&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;맨 앞쪽에 4D 5A 바이트 패턴이 보이는데 이것은 리틀엔디언 방식으로 표기되어 있고 MZ에 해당하는 부분이다.&lt;br /&gt;
조금 아래쪽에 보게되면 있는 값이 e_lfanew 값이다.&lt;br /&gt;
이 값이 가리키는 위치로 가보면 NT 헤더의 시작 지점이라는 것을 알게된다.&lt;/p&gt;
&lt;p&gt;DOS 헤더 바로 뒤에는 DOS Stub이 따라온다.&lt;br /&gt;
이 영역에는 This program cannot be run in DOS mode 같은 문구가 들어 있다 .&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/03_dos_stub_peview.png&quot; alt=&quot;MS-DOS Stub Program&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;MS-DOS Stub Program&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;예전에는 도스 환경에서 이 파일을 실행했을 때 이런 메시지를 보여 주는 역할을 했지만 지금 기준에서는 영향력이 없는것 같아 보인다.&lt;br /&gt;
또한 저 문구가 끝난 후 Rich Header가 존재하는데 정확한 정보는 없지만 알려진 바로는&lt;br /&gt;
프로그램을 빌드한 컴파일러에 대한 정보, 실행파일이 생성된 환경 등이 포함되어 있는것 같다.&lt;br /&gt;
악성코드를 분석할때 참고용으로 활용이 되는것 같지만 PE 구조를 처음 공부할 때는 DOS Stub 내부 구조까지 깊게 파기보다는
DOS 헤더 뒤쪽에 이런 영역이 하나 있고 그 뒤부터 NT 헤더가 나온다는 정도만 잡고 넘어가는 편이 편했다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;IMAGE_NT_HEADERS&lt;/h2&gt;
&lt;p&gt;DOS 헤더의 e_lfanew가 가리키는 위치에는 NT 헤더가 있다.&lt;br /&gt;
PE 구조를 이해할 때 가장 핵심이 되는 부분이라 여기서부터는 조금 더 집중하게 되었다.&lt;/p&gt;
&lt;p&gt;MSDN :&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/winnt/ns-winnt-image_nt_headers32&quot;&gt;IMAGE_NT_HEADERS&lt;/a&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct _IMAGE_NT_HEADERS {
    DWORD Signature;
    IMAGE_FILE_HEADER FileHeader;
    IMAGE_OPTIONAL_HEADER32 OptionalHeader;
} IMAGE_NT_HEADERS32;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;NT 헤더는 3가지로 분류된다.&lt;br /&gt;
Signature가 있고 IMAGE_FILE_HEADER와 IMAGE_OPTIONAL_HEADER 이다.&lt;/p&gt;
&lt;p&gt;HxD로 전체 NT 헤더 영역을 보면 이 세 부분이 연속된 하나의 블록으로 붙어 있는 모습이 보인다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/04_nt_header_hxd.png&quot; alt=&quot;NT_HEADER DUMP&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;NT_HEADER DUMP&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 각각이 어떤 역할을 하는지만 가볍게 짚고 넘어가 보면 구조가 훨씬 자연스럽게 이어진다.&lt;/p&gt;
&lt;h2&gt;NT Signature&lt;/h2&gt;
&lt;p&gt;NT 시그니처는 PE 파일임을 다시 한 번 확인하는 역할을 한다.&lt;br /&gt;
텍스트로 쓰면 PE와 널 문자 둘이 붙어 있는 형태다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/04_nt_signature_peview.png&quot; alt=&quot;NT_Signature&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;NT_Signature&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;DOS 헤더에서 MZ로 한 번 검사를 했고 여기서 다시 PE 시그니처로 형식을 한 번 더 확인하는 느낌으로 보면 이해가 쉽다.&lt;/p&gt;
&lt;h2&gt;FileHeader&lt;/h2&gt;
&lt;p&gt;IMAGE_FILE_HEADER에는 아키텍처 정보와 섹션 개수 그리고 이 파일의 특성을 나타내는 값들이 들어 있다.&lt;/p&gt;
&lt;p&gt;MSDN :&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/winnt/ns-winnt-image_file_header&quot;&gt;IMAGE_FILE_HEADER&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;구조체 전체는 길지만 실제로 계속 보게 된 필드는 세 개였다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct _IMAGE_FILE_HEADER {
    WORD Machine;
    WORD NumberOfSections;
    WORD Characteristics;
} IMAGE_FILE_HEADER;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PEview에서 이 부분만 따로 보면 어떤 아키텍처용 실행 파일인지와 섹션 개수 그리고 파일 특성이 바로 눈에 들어온다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/04_nt_fileheader_peview.png&quot; alt=&quot;NT_File_Header&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;NT_File_Header&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Machine은 이 실행 파일이 어떤 환경을 대상으로 빌드되었는지를 나타낸다.&lt;/p&gt;
&lt;p&gt;NumberOfSections는 이 파일 안에 섹션이 몇 개 있는지를 알려 준다.&lt;/p&gt;
&lt;p&gt;Characteristics에는 이 실행 파일이 어떤 성격을 가진 이미지인지 비트 플래그로 표현되어 있다.&lt;/p&gt;
&lt;h2&gt;OptionalHeader&lt;/h2&gt;
&lt;p&gt;이름은 OptionalHeader이지만 실제로 분석하면서 가장 많이 신경 쓰게 된 부분이 바로 이 영역이었다.&lt;br /&gt;
프로그램이 메모리에 로드될 때 필요한 정보들이 거의 다 OptionalHeader에 들어 있기 때문에&lt;br /&gt;
결국 이 구조를 어느 정도 편하게 읽을 수 있어야 전체 흐름이 잡힌다는 느낌이 들었다.&lt;/p&gt;
&lt;p&gt;MSDN :&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/winnt/ns-winnt-image_optional_header32&quot;&gt;IMAGE_OPTIONAL_HEADER32&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;OptionalHeader의 전체 구조체는 너무 많아서 나는 처음부터 전부 외우려고 하기보다는 실제로 자주 보는 필드들 위주로 정리했다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct _IMAGE_OPTIONAL_HEADER32 {
    WORD  Magic;
    DWORD AddressOfEntryPoint;
    DWORD BaseOfCode;
    DWORD BaseOfData;
    DWORD ImageBase;
    DWORD SectionAlignment;
    DWORD FileAlignment;
    WORD  Subsystem;
    DWORD NumberOfRvaAndSizes;
    IMAGE_DATA_DIRECTORY DataDirectory[16];
} IMAGE_OPTIONAL_HEADER32;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PEview에서 OptionalHeader 부분을 보게 되면 얼마나 많은 멤버들이 있는지 알수 있다.&lt;br /&gt;
하지만 모든 것들을 바로 외우는것은 불가능하다 생각했기에 중요한 부분만 파악해보도록 하였다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/04_nt_optionalheader_peview.png&quot; alt=&quot;NT_Optional_Header&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;NT_Optional_Header&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Magic 값은 이 실행 파일이 32비트 파일인지 64비트 파일인지 구분할수 있게 해준다.&lt;/p&gt;
&lt;p&gt;AddressOfEntryPoint는 코드가 실제로 실행을 시작하는 위치를 나타내는 값이다.&lt;br /&gt;
값 자체는 RVA로 표현되지만 이번 글에서는 RVA 계산을 깊게 다루지는 않고&lt;br /&gt;
단순히 실행 진입점을 가리키는 논리적 위치 정도로만 보았다.&lt;/p&gt;
&lt;p&gt;ImageBase는 이 실행 파일이 메모리에 기본적으로 로드될 시작 주소를 의미한다.&lt;br /&gt;
하지만 실제 실행 시에는 ASLR 이라는 기능 때문에 위치가 바뀔 수도 있지만&lt;br /&gt;
정적분석할 때 기준이 되는 값이라 생각하는 필드였다.&lt;/p&gt;
&lt;p&gt;SectionAlignment와 FileAlignment는 메모리와 파일에서 섹션이 어떤 간격으로 정렬되는지 알려 준다.&lt;br /&gt;
이 값들은 나중에 RVA와 파일 오프셋 사이를 오갈 때 핵심이 되는 값인데&lt;br /&gt;
그 부분은 다른 글에서 RVA와 RAW 변환을 정리할 계획이다.&lt;/p&gt;
&lt;p&gt;Subsystem은 이 실행 파일이 어떤 환경에서 돌아가는지를 나타낸다.&lt;br /&gt;
콘솔 프로그램인지 윈도우 GUI 프로그램인지 같은 정보가 들어 있다.&lt;/p&gt;
&lt;p&gt;마지막으로 NumberOfRvaAndSizes와 DataDirectory 배열이 있다.&lt;br /&gt;
여기에는 Export Import Resource TLS 같은 중요한 디렉터리 정보들이 차례대로 들어 있다.&lt;br /&gt;
이 부분에 대해선 아직까지도 완벽하게 이해를 하지 못해 나중에 따로 공부할 예정이다.&lt;/p&gt;
&lt;p&gt;OptionalHeader는 처음에는 필드가 너무 많아서 부담스럽게 느껴졌는데 실제로 분석할 때 계속 보게 되는 값은 생각보다 적었다.&lt;br /&gt;
그래서 중요한 필드 몇 개를 먼저 익히고 나머지는 필요할 때마다 MSDN을 참고하는 방식이 내 기준에서는 가장 현실적인 공부 순서였다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;IMAGE_SECTION_HEADER&lt;/h2&gt;
&lt;p&gt;NT 헤더가 끝나면 그 뒤에는 Section 헤더들이 연달아 붙어 있다.&lt;br /&gt;
개수는 앞에서 본 NumberOfSections 값과 정확히 일치한다.&lt;/p&gt;
&lt;p&gt;Section 헤더는 각 섹션이 어떤 이름을 가지고 있는지 파일과 메모리에서 어디에 위치하는지&lt;br /&gt;
그리고 어떤 속성을 가지는지에 대한 정보를 담고 있다.&lt;/p&gt;
&lt;p&gt;MSDN :&lt;a href=&quot;https://learn.microsoft.com/ko-kr/windows/win32/api/winnt/ns-winnt-image_section_header&quot;&gt;IMAGE_SECTION_HEADER&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;구조체 전체를 보면 필드가 여러개 있지만 실제로 계속 보게 된 값은 아래 몇 가지였다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;typedef struct _IMAGE_SECTION_HEADER {
    BYTE  Name[IMAGE_SIZEOF_SHORT_NAME];
    union {
        DWORD PhysicalAddress;
        DWORD VirtualSize;
    } Misc;
    DWORD VirtualAddress;
    DWORD SizeOfRawData;
    DWORD PointerToRawData;
    DWORD Characteristics;
} IMAGE_SECTION_HEADER;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여러개의 섹션이 있지만 .text 섹션을 기준으로 확인해보았다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/PE/05_section_header.png&quot; alt=&quot;Section Header&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Section Header&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Name에는 섹션 이름이 들어 있다.&lt;br /&gt;
.text .rdata .data 같은 이름들이 여기에 들어가며 대략 어떤 용도의 섹션인지 감이 온다.&lt;/p&gt;
&lt;p&gt;VirtualSize는 이 섹션이 메모리에서 실제로 차지하는 크기를 나타낸다.&lt;/p&gt;
&lt;p&gt;VirtualAddress는 이 섹션이 ImageBase 기준으로 어느 위치에 매핑되는지를 나타내는 값이다.&lt;br /&gt;
이 값 역시 RVA로 표현되지만 자세한 계산은 RVA 글에서 다루기로 하고&lt;br /&gt;
지금은 섹션이 메모리에서 시작하는 지점을 논리적으로 나타내는 값 정도로 생각하면 될것같다.&lt;/p&gt;
&lt;p&gt;SizeOfRawData는 이 섹션이 파일에서 차지하는 실제 크기다.&lt;br /&gt;
PointerToRawData는 이 섹션 데이터가 파일시점에서 어디에서 시작되는지를 알려 준다.&lt;/p&gt;
&lt;p&gt;나중에 RVA와 파일 오프셋을 서로 변환할 때는&lt;br /&gt;
VirtualAddress와 PointerToRawData 그리고 정렬 정보들을 함께 사용하게 되는데&lt;br /&gt;
지금 단계에서는 Section 헤더가 파일 위치와 메모리 위치를 연결해 주는거라고 생각하면 된다.&lt;/p&gt;
&lt;p&gt;Characteristics에는 이 섹션이 어떤 속성을 가지는지 비트 플래그로 표현되어 있다.&lt;br /&gt;
실행 권한이 있는지 쓰기 가능한지 같은 정보들이 이 값 하나에 묶여 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Section body&lt;/h2&gt;
&lt;p&gt;지금까지는 헤더들만 계속 봤는데 실제 코드와 데이터는 Section 바디에 들어 있다.&lt;/p&gt;
&lt;p&gt;각 섹션의 몸통 부분은 Section 헤더에서 본 PointerToRawData와 SizeOfRawData를 기준으로 파일 뒤쪽에 배치되어 있다.&lt;/p&gt;
&lt;p&gt;여기서는 대표적인 세 가지 섹션만 가볍게 정리했다.&lt;/p&gt;
&lt;p&gt;.text 섹션에는 실행 가능한 코드가 들어 있다.
프로그램의 함수와 명령들이 이 영역에 몰려 있고 보통 실행 권한과 읽기 권한이 있고 쓰기 권한은 없다.&lt;/p&gt;
&lt;p&gt;.rdata 섹션에는 읽기 전용 데이터가 들어 있는 경우가 많았다.&lt;br /&gt;
상수 문자열이나 상수 테이블 그리고 바뀌면 안 되는 데이터들이 여기로 들어온다.&lt;/p&gt;
&lt;p&gt;.data 섹션에는 초기값이 설정된 전역 변수와 정적 변수가 배치된다.&lt;br /&gt;
프로그램이 시작될 때 이미 값이 들어 있는 상태로 메모리에 올라가고 실행 도중 값이 바뀔 수 있는 데이터들이 이 영역에 모여 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이 내용을 이해하고 작성하는것만 1주 이상 시간이 넘게 걸리는것 같다.&lt;br /&gt;
아직까지도 완벽하게 이해했다기 보다는 하나의 Big Picture를 그리기 위한 시도인것 같다.&lt;/p&gt;
&lt;p&gt;PEview로 전체 레이아웃을 보고 DOS 헤더에서 MZ와 e_lfanew를 확인하고&lt;br /&gt;
NT 헤더에서 Signature, FileHeader, OptionalHeader를 하나씩 뜯어 보고&lt;br /&gt;
Section 헤더에서 이름과 메모리 위치, 파일 위치 그리고 속성을 같이 보면서 이해한것 같다.&lt;/p&gt;
&lt;p&gt;이제는 PE 파일이 나름 잘 설계된 구조라는 느낌이 조금씩 들기 시작했다.&lt;/p&gt;
&lt;p&gt;이번 글에서는 의도적으로 RVA와 RAW 변환 같은 계산은 다루지 않았다.&lt;br /&gt;
그것 조차 포함하려니 너무 많은 내용이라 혼동도 오는것 같고 나눠 이야기하는게 더 좋아보여서이다.&lt;/p&gt;
&lt;p&gt;OptionalHeader의 ImageBase나 AddressOfEntryPoint,&lt;br /&gt;
Section 헤더의 VirtualAddress 같은 값이 전부 RVA 중심으로 되어 있어서&lt;br /&gt;
이 부분까지 이해하려면 너무 많은 이야기를 해야 하기 때문이다.&lt;/p&gt;
&lt;p&gt;이 구조가 머릿속에 어느 정도 자리 잡고 나면&lt;br /&gt;
PE 파일을 열었을 때 눈에 들어오는 정보들이 훨씬 친숙하게 느껴질 거라고 생각한다.&lt;/p&gt;
</content:encoded></item><item><title>Win32 그리고 User Mode-Kernel Mode 이해하기</title><link>https://cr3xis.github.io/posts/windows-user-kernel-mode/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/windows-user-kernel-mode/</guid><description>WinAPI 호출이 User Mode에서 Kernel Mode로 이어지는 전체 흐름을 정리합니다.</description><pubDate>Fri, 20 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Windows 내부 구조를 공부하면서 가장 크게 와닿았던 점은 우리가 평소에 호출하는 WinAPI 함수들이&lt;br /&gt;
단순한 기능 호출이 아니라 User Mode에서 Kernel Mode까지 이어지는 흐름 속에 있다는 사실이었다.&lt;/p&gt;
&lt;p&gt;이 구조를 이해하면 WinAPI가 왜 이렇게 설계되었는지, DLL과 Syscall이 어떤 역할을 하는지,&lt;br /&gt;
그리고 User Mode 프로그램이 Kernel을 직접 건드릴 수 없는 이유가 자연스럽게 연결된다.&lt;/p&gt;
&lt;p&gt;아래는 내가 이해한 내용들을 전반적으로 설명한 글이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;전체 흐름을 먼저 떠올려보기&lt;/h2&gt;
&lt;p&gt;우리가 호출하는 WinAPI는 User Mode에서 시작해 DLL, NTDLL, Syscall을 거쳐 Kernel로 내려간다.&lt;/p&gt;
&lt;p&gt;하나의 큰 그림으로 보면 이런 구조다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/03/1.png&quot; alt=&quot;Big Picture&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Big Picture&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Win32와 WinAPI의 역할&lt;/h2&gt;
&lt;p&gt;개발자가 자주 사용하는 &lt;code&gt;MessageBox&lt;/code&gt;, &lt;code&gt;CreateFile&lt;/code&gt;, &lt;code&gt;ReadFile&lt;/code&gt; 같은 함수들은 Win32 API라고 부른다.&lt;br /&gt;
이 함수들은 보기엔 단순한 함수 호출이지만, OS 깊은 영역과 통신하기 위한 첫 번째 관문이 된다.&lt;/p&gt;
&lt;p&gt;WinAPI의 목적은 운영체제의 복잡한 내부 구조를 드러내지 않고 개발자가 이해할 수 있는 방식으로 기능을 제공하는 것이다.&lt;/p&gt;
&lt;p&gt;예를 들어 파일을 읽기 위해 NTFS 구조를 직접 다룰 필요도 없고, 커널 내부 자료구조를 건드릴 필요도 없다.&lt;br /&gt;
WinAPI는 이런 복잡함을 완전히 숨겨준다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;user32.dll과 kernel32.dll의 역할 차이&lt;/h2&gt;
&lt;p&gt;WinAPI는 대부분 DLL 안에 구현되어 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/03/2.png&quot; alt=&quot;user32 &amp;amp; kernel32&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;user32 &amp;amp; kernel32&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h3&gt;user32.dll&lt;/h3&gt;
&lt;p&gt;사용자 인터페이스 관련 기능을 제공한다.&lt;br /&gt;
창, 버튼, 키 입력 처리 등 눈에 보이는 UI 기능들이 여기에 있다.&lt;/p&gt;
&lt;h3&gt;kernel32.dll&lt;/h3&gt;
&lt;p&gt;시스템 자원과 관련된 기능을 제공한다.&lt;br /&gt;
파일 시스템, 프로세스, 스레드, 메모리 관리 등 운영체제의 핵심 기능과 연결되는 API들이 들어 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;DLL에서 NTDLL로 이어지는 구조&lt;/h2&gt;
&lt;p&gt;WinAPI는 실질적인 작업을 직접 수행하지 않는다.&lt;br /&gt;
특히 시스템 자원에 접근하는 기능은 Kernel Mode에서만 가능하기 때문에 WinAPI는 내부적으로 &lt;strong&gt;NTDLL.dll&lt;/strong&gt;의 함수를 호출한다.&lt;/p&gt;
&lt;p&gt;NTDLL은 User Mode에서 Kernel Mode로 넘어가는 마지막 단계다.&lt;/p&gt;
&lt;p&gt;예를 들면 CreateFile → kernel32.dll → NTDLL → NtCreateFile → Syscall 이런 흐름으로 이어진다.&lt;/p&gt;
&lt;p&gt;NTDLL에는 &lt;code&gt;NtReadFile&lt;/code&gt;, &lt;code&gt;NtCreateFile&lt;/code&gt; 같은 Kernel과 직접 연결되는 함수들이 존재한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;시스템콜&lt;/h2&gt;
&lt;p&gt;User Mode는 Kernel 내부에 직접 접근할 수 없다.&lt;br /&gt;
이 제한은 단순한 규칙이 아니라 OS의 안정성과 보안을 유지하기 위한 구조적 장치다.&lt;/p&gt;
&lt;p&gt;그래서 User Mode가 Kernel에 요청을 보내는 유일한 경로가 바로 시스템콜이다.&lt;/p&gt;
&lt;p&gt;시스템콜은 User Mode에서 온 요청을 안전하게 Kernel로 전달하고, Kernel은 해당 요청을 수행한 뒤 다시 User Mode로 결과를 돌려준다.&lt;/p&gt;
&lt;p&gt;이 흐름 덕분에 운영체제는 잘못된 코드가 Kernel 영역을 건드려 전체 시스템을 망가뜨리는 상황을 방지할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;User Mode와 Kernel Mode의 차이&lt;/h2&gt;
&lt;p&gt;Windows는 실행 계층을 두 가지로 나누어 운영한다.&lt;/p&gt;
&lt;h3&gt;User Mode&lt;/h3&gt;
&lt;p&gt;애플리케이션이 실행되는 공간이다.&lt;br /&gt;
직접 메모리를 제어하거나 CPU의 모든 명령을 사용할 수 없도록 제한되어 있다.&lt;br /&gt;
오류가 발생해도 해당 프로세스만 종료되고 시스템 전체는 영향을 받지 않는다.&lt;/p&gt;
&lt;h3&gt;Kernel Mode&lt;/h3&gt;
&lt;p&gt;운영체제가 동작하는 공간이다.&lt;br /&gt;
메모리, 프로세스, 스케줄러, 드라이버 등 모든 자원에 접근할 수 있다.&lt;br /&gt;
여기서 발생한 오류는 시스템 전체에 영향을 미칠 수 있다.&lt;/p&gt;
&lt;p&gt;이 구분은 Windows가 안정성을 유지하는 핵심 원리다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;Ring3 &amp;amp; Ring0&lt;/h2&gt;
&lt;p&gt;Windows는 두 단계(Ring3, Ring0)만 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/03/Ring.png&quot; alt=&quot;CPU 보호 링 구조&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;CPU 보호 링 구조&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ring3는 User Mode&lt;/li&gt;
&lt;li&gt;Ring0은 Kernel Mode&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;EXE, DLL 등 일반 프로그램은 Ring3에서 실행되며 드라이버와 커널 코드는 Ring0에서 실행된다.&lt;/p&gt;
&lt;p&gt;이 구조 덕분에 User Mode 프로그램이 실수로 Kernel의 중요한 메모리를 망가뜨리는 상황을 막을 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;정리&lt;/h2&gt;
&lt;p&gt;Win32 → WinAPI → DLL → NTDLL → Syscall → Kernel로 이어지는 흐름은 Windows가 안정적으로 동작하기 위해 만든 계층 구조다.&lt;/p&gt;
&lt;p&gt;조금 풀어서 이야기하자면 Winapi는 커널 기능을 직접 호출하는 것이 아니라, Kernel32/user32가
ntdll에게 특정API에대한 요청을 하면 NT 함수들을 호출하고 syscall이 커널에게 요청을 전달하는 구조를 하고 있다는 말이다.&lt;/p&gt;
&lt;p&gt;처음에는 복잡하게 보일 수 있지만 한 흐름으로 바라보면&lt;br /&gt;
왜 User Mode에서 Kernel을 직접 건드릴 수 없는지, 왜 DLL이 필요한지, 왜 Syscall이 중요한지 자연스럽게 이해된다.&lt;/p&gt;
&lt;p&gt;이 구조를 이해하면 리버싱, 프로세스 분석, 메모리 구조 분석 같은 작업도 훨씬 선명하게 보이기 시작한다.&lt;/p&gt;
</content:encoded></item><item><title>프로그램이 메모리에서 배치되는 방식 이해하기</title><link>https://cr3xis.github.io/posts/cpp-memory-layout/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cpp-memory-layout/</guid><description>프로그램의 Code, Data, Heap, Stack 영역과 메모리 배치 구조를 정리합니다.</description><pubDate>Sun, 15 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;소스 코드가 실행 파일로 만들어지는 과정까지 이해하고 나면, 자연스럽게 다음 질문이 생긴다.&lt;br /&gt;
&lt;strong&gt;“그럼 이 실행 파일은 메모리에서 어떤 구조로 배치되어 실행될까?”&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Windows 기준으로 보면 프로그램은 실행될 때 운영체제가 정해 둔 형태에 따라 여러 영역으로 나뉘어 올라가게 된다.&lt;br /&gt;
그 구조를 자연스럽게 따라가며 정리하고, 각 영역이 실제 코드에서 어떤 의미로 연결되는지도 함께 설명한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/02/Area.png&quot; alt=&quot;메모리 배치 구조&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;메모리 배치 구조&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;1. Code(Text) 영역&lt;/h2&gt;
&lt;p&gt;프로그램의 핵심 로직, 즉 CPU가 실제로 실행할 기계어 코드가 저장되는 곳이다.&lt;br /&gt;
함수의 본문, 분기, 반복문 같은 모든 흐름이 이 영역에 들어간다.&lt;/p&gt;
&lt;p&gt;아래 예제에서 &lt;code&gt;func()&lt;/code&gt;와 &lt;code&gt;main()&lt;/code&gt;의 본문은 모두 Text 영역에 저장된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;

void func() {
    printf(&quot;Hello from func\n&quot;);
}

int main() {
    func();
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이 코드를 컴파일하면 &lt;code&gt;func&lt;/code&gt;와 &lt;code&gt;main&lt;/code&gt;의 기계어 코드가 Text 영역으로 배치되고,&lt;br /&gt;
CPU는 이 Code 영역의 내용을 순차적으로 읽으며 프로그램을 실행한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Data/BSS 영역&lt;/h2&gt;
&lt;p&gt;전역 변수와 정적 변수는 프로그램이 시작될 때 메모리에 배치되고, 프로그램이 종료될 때까지 유지된다.&lt;br /&gt;
초기값이 있으면 Data, 초기값이 없으면 BSS에 들어간다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int global_init = 10;   // Data 영역
int global_uninit;      // BSS 영역

void func() {
    static int counter = 0; // Data 영역
    counter++;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;global_init&lt;/code&gt; → 초기값이 있으므로 Data&lt;/li&gt;
&lt;li&gt;&lt;code&gt;global_uninit&lt;/code&gt; → 초기값이 없으므로 BSS&lt;/li&gt;
&lt;li&gt;&lt;code&gt;static counter&lt;/code&gt; → 정적 변수이므로 Data&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 변수들은 함수가 끝나도 사라지지 않는다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Heap 영역&lt;/h2&gt;
&lt;p&gt;Heap은 프로그램 실행 중 필요한 만큼 메모리를 할당하고 자유롭게 사용할 수 있는 공간이다.&lt;br /&gt;
필요할 때 확장되며, 개발자가 직접 관리해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#include &amp;lt;stdlib.h&amp;gt;

void func() {
    int* p = (int*)malloc(sizeof(int)); // Heap 할당
    *p = 42;

    printf(&quot;%d\n&quot;, *p);

    free(p); // 직접 해제
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;여기서 포인터 &lt;code&gt;p&lt;/code&gt;는 스택에 저장되지만, &lt;code&gt;malloc&lt;/code&gt;으로 생성된 실제 값은 Heap 영역에 존재한다.&lt;br /&gt;
해제하지 않으면 계속 남아 메모리 누수가 발생한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Stack 영역&lt;/h2&gt;
&lt;p&gt;Stack은 함수 호출 시 자동으로 생성되는 임시 공간이다.&lt;br /&gt;
지역 변수, 매개변수, 복귀 주소 등이 이 영역에 저장된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;void func(int x) {
    int temp = x + 1; // Stack 영역
    printf(&quot;%d\n&quot;, temp);
}

int main() {
    func(5); // 매개변수 또한 Stack에 저장
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;x&lt;/code&gt; → 매개변수 → Stack&lt;/li&gt;
&lt;li&gt;&lt;code&gt;temp&lt;/code&gt; → 지역 변수 → Stack&lt;/li&gt;
&lt;li&gt;함수가 종료되면 둘 다 함께 정리된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;이 구조를 이해하고 나면 포인터와 참조, 변수 생명 주기 같은 개념이 단순 문법처럼 보이지 않고,&lt;br /&gt;
메모리 위에서 실제로 어떻게 동작하는지 자연스럽게 연결된다.&lt;/p&gt;
</content:encoded></item><item><title>C++ 컴파일 과정 이해하기</title><link>https://cr3xis.github.io/posts/cpp-compilation-process/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cpp-compilation-process/</guid><description>C와 C++ 소스 코드가 전처리, 컴파일, 어셈블, 링크를 거치는 과정을 정리합니다.</description><pubDate>Tue, 10 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;C/C++ 코드를 작성하면 곧바로 실행 파일이 생성되는 것처럼 보이지만,&lt;br /&gt;
실제로는 여러 단계의 변환 과정을 지나면서 점점 기계가 이해할 수 있는 형태로 바뀐다.&lt;br /&gt;
아래는 그 흐름을 자연스럽게 따라가며 정리한 내용이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CS/01/C_MEM1.png&quot; alt=&quot;컴파일 전체 흐름&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;컴파일 전체 흐름&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;h2&gt;1. 전처리 단계&lt;/h2&gt;
&lt;p&gt;가장 먼저 전처리기가 소스 코드를 읽어 들인다.&lt;br /&gt;
이 단계에서는 &lt;code&gt;#include&lt;/code&gt;, &lt;code&gt;#define&lt;/code&gt; 같은 전처리 지시문이 모두 처리되며,&lt;br /&gt;
컴파일러가 바로 해석할 수 있는 형태로 코드를 재구성한다.&lt;/p&gt;
&lt;p&gt;이 과정을 거치며 생성되는 파일이 &lt;strong&gt;&lt;code&gt;.i&lt;/code&gt; 파일&lt;/strong&gt;이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 컴파일 단계&lt;/h2&gt;
&lt;p&gt;전처리를 마친 코드는 컴파일러로 전달된다.&lt;br /&gt;
컴파일러는 프로그램의 구조를 분석하며,&lt;br /&gt;
C/C++ 코드를 CPU 명령에 가까운 형태로 바꾸어 &lt;strong&gt;어셈블리 코드(.s)&lt;/strong&gt; 를 만들어낸다.&lt;/p&gt;
&lt;p&gt;문법 오류가 잡히는 단계도 바로 이 과정이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 어셈블 단계&lt;/h2&gt;
&lt;p&gt;어셈블러는 &lt;code&gt;.s&lt;/code&gt; 파일을 읽고,&lt;br /&gt;
CPU가 직접 실행할 수 있는 기계어로 변환하여 &lt;strong&gt;오브젝트 파일(.obj)&lt;/strong&gt; 을 만든다.&lt;/p&gt;
&lt;p&gt;각 함수, 전역 변수, 문자열 등이 목적에 맞게 정리되는 과정이기도 하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 링크 단계&lt;/h2&gt;
&lt;p&gt;마지막으로 링커가 여러 개의 &lt;code&gt;.obj&lt;/code&gt; 파일과 필요한 라이브러리(&lt;code&gt;.lib&lt;/code&gt;)들을 하나로 묶는다.&lt;br /&gt;
이 과정을 통해 Windows에서 실행할 수 있는 &lt;strong&gt;&lt;code&gt;.exe&lt;/code&gt; 파일&lt;/strong&gt;이 만들어진다.&lt;/p&gt;
&lt;p&gt;이 단계에서 비로소 실제로 실행 가능한 &lt;strong&gt;바이너리&lt;/strong&gt;가 완성된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;전처리 → 컴파일 → 어셈블 → 링크 과정은 C/C++ 코드가 실제 실행 가능한 형태로 만들어지는 가장 기본적인 흐름이다.&lt;br /&gt;
이 구조를 이해하면 빌드 오류나 컴파일 과정에서 문제가 생길 때 어느 단계에서 문제가 발생한 것인지 훨씬 쉽게 파악할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Games: Step 3</title><link>https://cr3xis.github.io/posts/cheat-engine-game-03/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-game-03/</guid><description>Cheat Engine Games Step 3에서 생존 상태와 충돌 처리 코드를 분석합니다.</description><pubDate>Thu, 05 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Games Step 3는 튜토리얼에서 배웠던 내용을 실제 게임 상황에 가져와&lt;br /&gt;
&lt;strong&gt;충돌 판정과 생존 여부를 직접 건드려 보는 단계&lt;/strong&gt;다.&lt;/p&gt;
&lt;p&gt;이 스테이지의 목표는 단순하다.&lt;br /&gt;
우리는 게임이 내부적으로 사용하는 &lt;strong&gt;생존 상태 플래그와 충돌 처리 코드&lt;/strong&gt;를 건드려 조건을 우회해서 클리어해 본다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 게임 진행 방식 살펴보기&lt;/h2&gt;
&lt;p&gt;게임을 실행하면 캐릭터와 여러 발판이 보인다.&lt;br /&gt;
모든 빨간 발판을 밟아 초록 발판으로 변경 시킨뒤 문을 통과하면 게임이 통과되는 구조이지만&lt;br /&gt;
모든 발판을 초록 발판으로 변경한다 한들 장애물들이 문을 가로막아 사실상 우회없이는 클리어가 불가능한 단계이다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games3/1.png&quot; alt=&quot;게임 실행 시 화면&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;게임 실행 시 화면&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;발판에 대한 정보와 생존상태에 대한 정보는 게임 상에서 보여지는 정보가 없으므로&lt;br /&gt;
처음부터 Unknown Initial Value를 사용하는 편이 자연스럽다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Unknown Initial Value로 생존 상태 스캔&lt;/h2&gt;
&lt;p&gt;생존 여부를 직접 보여주는 값이 없기 때문에 Scan Type을 &lt;strong&gt;Unknown Initial Value&lt;/strong&gt;로 두고 첫 스캔을 진행한다.&lt;/p&gt;
&lt;p&gt;그 이후에는 캐릭터를 일부러 여러 번 죽였다가 다시 시작하면서&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;살아 있는 동안에는 &lt;strong&gt;Unchanged Value&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;죽고 난 뒤에는 &lt;strong&gt;Changed Value&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;이 두 가지를 번갈아 사용해 필터링한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games3/2.png&quot; alt=&quot;Unknown Initial Value로 생존 값 검색&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Unknown Initial Value로 생존 값 검색&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 과정을 수차례 반복하면 처음엔 수만 개에 달하던 후보가 &lt;strong&gt;약 700개 정도&lt;/strong&gt;까지 줄어든다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 후보 주소를 생존 플래그 관점에서 다시 좁히기&lt;/h2&gt;
&lt;p&gt;이제 남은 700개 중에서 “살았을 때 1, 죽었을 때 0처럼 보이는 플래그 형태의 값”만 골라봐야 한다.&lt;/p&gt;
&lt;p&gt;보통 이런 값은 &lt;code&gt;bool&lt;/code&gt;이나 작은 &lt;code&gt;int&lt;/code&gt;로 저장되기 때문에 0 또는 1 근처의 값들에 먼저 확인하는 편이 좋다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games3/3.png&quot; alt=&quot;필터링 후 테이블에 내린 주소&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;필터링 후 테이블에 내린 주소&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;실제로 살펴보면 대략 이런 패턴이 보인다.&lt;/p&gt;
&lt;p&gt;017로 시작하는 주소들은 &lt;code&gt;igxelpgicd32.dll&lt;/code&gt; 같은 그래픽 모듈에서 나온 값이라 생존 플래그와는 거리가 멀어 보인다.&lt;br /&gt;
018 대역에 있는 값들 중 일부는 캐릭터가 죽을 때 1 → 0, 다시 시작할때 0 → 1로 바뀌는 형태를 보인다.&lt;/p&gt;
&lt;p&gt;이런 식으로 상태 변화를 직접 확인해 보면서 후보를 몇 개로 압축할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 생존 플래그를 직접 고정해서 테스트해 보기&lt;/h2&gt;
&lt;p&gt;후보 중에서 두 개의 주소가 캐릭터가 죽을 때마다 함께 0으로 떨어지고,&lt;br /&gt;
다시 살아날 때 1로 돌아오는 모습을 보인다.&lt;/p&gt;
&lt;p&gt;이 둘을 테이블로 내린 뒤, 둘 다 0으로 고정한 상태에서 발판을 밟아 본다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games3/4.png&quot; alt=&quot;두 값 다 0 고정 후 이펙트 발생 장면&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;두 값 다 0 고정 후 이펙트 발생 장면&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;캐릭터가 분명히 위험한 발판을 밟았는데도 사망 처리 대신 특이한 이펙트만 발생하고 그대로 살아남는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;이걸로 이 두 주소가 실제 충돌 처리와 밀접하게 연결된 값이라는 점을 확인할 수 있다.&lt;br /&gt;
단순히 값만 고정하는 방식으로도 클리어는 가능하지만, 이번 단계에서는 한 발 더 나아가 코드 자체를 건드려 본다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. Find out what writes로 생존 플래그를 갱신하는 코드 찾기&lt;/h2&gt;
&lt;p&gt;이제 두 주소 중 하나를 기준으로 &lt;strong&gt;Find out what writes to this address&lt;/strong&gt;를 실행한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games3/5.png&quot; alt=&quot;Find out what writes to this address 결과&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Find out what writes to this address 결과&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;캐릭터가 위험한 발판과 충돌하거나, 게임이 “죽었다”고 판단하는 순간&lt;br /&gt;
특정 명령어가 이 주소에 값을 쓰는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;이 명령어가 실제로 생존 여부를 바꾸는 핵심 코드다.&lt;br /&gt;
여기서 Show Disassembler로 넘어가 어셈블리 흐름을 자세히 본다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 메모리 뷰에서 조건 분기 확인&lt;/h2&gt;
&lt;p&gt;Disassembler 화면에서 보면 해당 주소에 값을 쓰는 부분 바로 앞에 조건 분기 명령어가 붙어 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games3/6.png&quot; alt=&quot;Show Disassembler로 확인한 메모리 뷰&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Show Disassembler로 확인한 메모리 뷰&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;구체적인 레지스터 이름이나 정확한 주소는 상황마다 다를 수 있지만, 흐름 자체는 대략 이런 형태다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cmp  [생존_관련_값], 0
jne  short 죽음_처리_분기
...
; 이 구간에서 충돌, 이펙트, 사망 플래그 설정 등의 처리가 이루어진다
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;비교 후 &lt;strong&gt;값이 0이 아니면(jne)&lt;/strong&gt; 죽음 처리 루틴으로 분기하는 조건이다.&lt;br /&gt;
여기서 분기를 뒤집으면, 원래 죽어야 할 상황에서도 충돌 루틴이 건너뛰어져  캐릭터가 계속 살아남는 효과를 만들 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 조건문 뒤집기 — jne를 je로 변경&lt;/h2&gt;
&lt;p&gt;이제 Auto Assembler나 CE의 간단한 패치를 이용해 &lt;code&gt;jne&lt;/code&gt;를 &lt;code&gt;je&lt;/code&gt;로 바꿔 준다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games3/7.png&quot; alt=&quot;조건문 변경 (jne → je)&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;조건문 변경 (jne → je)&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 한 줄을 바꾸면 흐름이 이렇게 바뀐다.&lt;/p&gt;
&lt;p&gt;원래는 비교 결과가 다를 때(충돌 조건 만족 시)  죽음 처리 코드가 실행됐다.&lt;br /&gt;
이제는 비교 결과가 같을 때만 죽음 처리 코드로 들어가기에 분기가 일어나지 않으므로 캐릭터가 죽지 않게 된다.&lt;/p&gt;
&lt;p&gt;앞에서 생존 플래그를 0으로 고정했던 것과 비슷한 효과지만, 이번에는 값을 직접 만지는 것이 아니라&lt;br /&gt;
조건문 자체를 손대서 게임 로직을 바꾼 것이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 실제 게임 진행과 Step 3 완료&lt;/h2&gt;
&lt;p&gt;조건을 바꾼 상태로 다시 게임을 진행해 보면 위험한 장애물을 밟아도 캐릭터가 계속 살아남는다.&lt;br /&gt;
장애물에 닿게되면 기존에는 사망처리가 되어야 하지만 정상적으로 통과하여 클리어할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games3/8.png&quot; alt=&quot;Step 3 완료 화면&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Step 3 완료 화면&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Games Step3는 단순한 변경을 통한 클리어가 아닌 플래그, 조건 분기, 충돌 처리 로직을 생각하게 만든 문제인거 같다.&lt;br /&gt;
Unknown Initial Value로 상태 값을 찾고, 플래그를 직접 고정해 보며 의미를 파악한 뒤,&lt;br /&gt;
해당 값을 갱신하는 코드와 분기 조건까지 조작해 보는 흐름이다.&lt;/p&gt;
&lt;p&gt;이런 과정을 한번 경험해 두면 앞으로 다른 게임을 분석할 때도 유용하게 참고정도는 할수 있을것으로 생각이 된다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Games: Step 2</title><link>https://cr3xis.github.io/posts/cheat-engine-game-02/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-game-02/</guid><description>Cheat Engine Games Step 2에서 플레이어와 적 구조체를 구분하는 과정을 정리합니다.</description><pubDate>Tue, 03 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Games Step 2는 단순히 플레이어의 체력을 고정시키는 수준을 넘어서,&lt;br /&gt;
&lt;strong&gt;플레이어와 적의 구조체를 구분하고, 오직 적에게만 데미지가 들어가도록 코드를 조작하는 과정&lt;/strong&gt;을 다루고 있다.&lt;br /&gt;
튜토리얼 파트에서 배웠던 &quot;값 스캔 → 포인터 추적 → 코드 인젝션&quot; 흐름을 실제 게임에 그대로 적용해보는 단계라고 보면 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 게임 실행과 환경 파악&lt;/h2&gt;
&lt;p&gt;게임을 실행하면 플레이어와 두 개의 타깃(Target 1, Target 2)이 등장한다.&lt;br /&gt;
각 개체는 체력을 가지고 있고, 총알 또는 충돌에 의해 체력이 깎이거나 파괴된다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/1.png&quot; alt=&quot;게임 실행 시 화면&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;게임 실행 시 화면&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 단계에서 우리가 목표로 하는 것은 다음과 같다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;플레이어와 적의 체력 주소를 모두 찾아낸다.&lt;/li&gt;
&lt;li&gt;체력을 감소시키는 공통 코드를 분석한다.&lt;/li&gt;
&lt;li&gt;구조체 내부에서 &quot;누가 맞았는지&quot; 구분할 수 있는 필드를 찾는다.&lt;/li&gt;
&lt;li&gt;플레이어는 데미지를 받지 않고, 적만 데미지를 받도록 코드 인젝션을 작성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 플레이어 체력 주소 찾기&lt;/h2&gt;
&lt;p&gt;먼저 화면에 표시된 플레이어 체력을 기준으로 Exact Value 검색을 진행한다.&lt;br /&gt;
체력이 변할 때마다 값을 갱신하면서 Next Scan을 반복하면, 플레이어 체력 주소 하나만 남게 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/2.png&quot; alt=&quot;플레이어 체력 검색 후 테이블로 내린 결과&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;플레이어 체력 검색 후 테이블로 내린 결과&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 이 주소에 실제로 어떤 코드가 접근하는지 추적해볼 차례다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Find out what writes로 체력 감소 코드 확인&lt;/h2&gt;
&lt;p&gt;플레이어 체력 주소를 오른쪽 클릭하고 &lt;strong&gt;Find out what writes to this address&lt;/strong&gt;를 실행한다.&lt;br /&gt;
그 상태에서 플레이어가 데미지를 받도록 일부러 맞아주면, 해당 주소에 값을 쓰는 명령어가 캡처된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/3.png&quot; alt=&quot;플레이어 체력 주소에 대한 Find out what writes 결과&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;플레이어 체력 주소에 대한 Find out what writes 결과&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이때 어셈블리를 보면 이런 형태의 명령어를 확인할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;sub [eax+50], edx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;위 어셈을 정리하자면 이런식으로 추측이 가능하다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;eax+50&lt;/code&gt; 위치에는 체력 값이 저장되어 있다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;edx&lt;/code&gt;에는 이번에 감소될 데미지 값이 들어 있다.&lt;/li&gt;
&lt;li&gt;이 명령이 실행될 때마다 현재 체력에서 데미지만큼 빼는 동작이 이루어진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;즉, 체력 자체는 &lt;code&gt;[eax+50]&lt;/code&gt;에 있고, 데미지는 &lt;code&gt;edx&lt;/code&gt;를 통해 전달되는 구조라고 이해할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. sub [eax+50], edx가 접근하는 모든 주소 확인&lt;/h2&gt;
&lt;p&gt;이번에는 이 명령어가 &lt;strong&gt;어떤 객체들의 체력에 영향을 주는지&lt;/strong&gt;를 확인해야 한다.&lt;br /&gt;
이를 위해 해당 명령어에 대해 &lt;strong&gt;Find out what addresses this instruction accesses&lt;/strong&gt; 기능을 실행한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/6.png&quot; alt=&quot;Find out what addresses this instruction accesses 실행 직전&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Find out what addresses this instruction accesses 실행 직전&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 상태에서 플레이어와 Target 1, Target 2가 서로 공격을 주고받도록 하면서 관찰하면&lt;br /&gt;
&lt;code&gt;sub [eax+50], edx&lt;/code&gt; 명령이 여러 개의 주소에 차례로 접근한다는 것을 확인할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 플레이어와 적의 체력 주소 구분&lt;/h2&gt;
&lt;p&gt;Find out what addresses 결과 창에는 세 개의 주요 주소가 나타난다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/7.png&quot; alt=&quot;플레이어와 적의 체력 주소 확인&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;플레이어와 적의 체력 주소 확인&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;다음과 같은 주소들이 등장한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;01AEE910&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;01AECD80&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;01AEEC20&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;플레이어와 두 개의 적이 각각 하나의 체력 주소를 가지는 형태라고 볼 수 있다.&lt;br /&gt;
공격 상황을 바꿔가며 어떤 주소가 언제 줄어드는지 확인하면 각 주소가 어떤 객체에 해당하는지 감을 잡을 수 있다.&lt;/p&gt;
&lt;p&gt;여기까지 정리하면 다음과 같이 이해할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;sub [eax+50], edx&lt;/code&gt;는 단일 객체 전용 코드가 아니라, &lt;code&gt;eax&lt;/code&gt;가 가리키는 구조체의 체력을 줄이는 것을 알수있게 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;eax&lt;/code&gt; 값이 바뀌는 것에 따라 플레이어가 맞을 수도 있고, Target 1이나 Target 2가 맞을 수도 있다.&lt;/li&gt;
&lt;li&gt;결국 &quot;누가 맞았는지&quot;는 &lt;code&gt;eax&lt;/code&gt;가 가리키는 구조체 내부의 필드를 보고 구분해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 구조체 분석 — Dissect Data/Structures&lt;/h2&gt;
&lt;p&gt;이제 &lt;code&gt;eax&lt;/code&gt;가 가리키는 구조체 내부에서 &lt;strong&gt;팀/타입을 구분하는 필드&lt;/strong&gt;를 찾아야 한다.&lt;br /&gt;
체력 감소 시점에 &lt;code&gt;eax&lt;/code&gt; 레지스터에 들어 있는 주소를 기준으로 &lt;strong&gt;Dissect Data/Structures&lt;/strong&gt; 기능을 실행한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/8.png&quot; alt=&quot;Dissect Data/Structures 실행 직전&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Dissect Data/Structures 실행 직전&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;구조체를 열어보면 다음과 같은 형태의 데이터가 나열되어 있는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/9.png&quot; alt=&quot;Dissect Data/Structures 결과&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Dissect Data/Structures 결과&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;각 필드를 하나씩 바꿔보거나, 플레이어와 적의 구조체를 비교해보면 특정 offset에서 차이가 난다는 것을 알 수 있다.&lt;br /&gt;
예를 들어 다음과 같이 정리할 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;+0x50&lt;/code&gt;: 체력 값 ( 앞에서 본 &lt;code&gt;[eax+50]&lt;/code&gt; )&lt;/li&gt;
&lt;li&gt;&lt;code&gt;+0x5C&lt;/code&gt;: 팀 값 ( 플레이어는 0, 적은 1 )&lt;/li&gt;
&lt;li&gt;그 외 일부 offset: 팀 정보 또는 역할 구분 값으로 사용되는 필드&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;플레이어와 적의 구조체를 나란히 비교해보면&lt;br /&gt;
0x5C 주소에서 플레이어와 적이 서로 다른 값을 가지고 있는 패턴을 확인할 수 있다.&lt;br /&gt;
이 필드를 이용해 플레이어와 적을 구분할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 코드 인젝션 스크립트 작성&lt;/h2&gt;
&lt;p&gt;이제 &lt;code&gt;sub [eax+50], edx&lt;/code&gt; 명령이 실행될 때 &lt;code&gt;eax&lt;/code&gt;가 가리키는 구조체의 팀 값에 따라 데미지를 줄지 말지 결정하면 된다.&lt;/p&gt;
&lt;p&gt;이를 위해 CE에서 &lt;strong&gt;Code Injection&lt;/strong&gt; 템플릿을 생성하고, 원래 명령어를 후킹한 뒤&lt;br /&gt;
조건을 추가하는 방식으로 스크립트를 작성한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/10.png&quot; alt=&quot;코드 인젝션 스크립트 작성&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;코드 인젝션 스크립트 작성&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;기본적인 흐름은 다음과 같이 잡을 수 있다.
여기서 중요한 부분은 다음 두 가지다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;체력 감소는 항상 &lt;code&gt;sub [eax+50], edx&lt;/code&gt;로 이루어진다.&lt;/li&gt;
&lt;li&gt;누가 맞았는지는 구조체 내 팀/역할 필드를 통해 구분한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;팀 offset의 실제 값은 게임마다 다르지만, Dissect Data/Structures 결과를 보며 직접 비교하면 쉽게 찾을 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 스크립트 적용 및 동작 확인&lt;/h2&gt;
&lt;p&gt;스크립트를 적용한 뒤 게임으로 돌아가서 다음과 같이 테스트해본다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games2/11.png&quot; alt=&quot;Step 2 완료&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Step 2 완료&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;정상적으로 동작한다면 다음과 같은 결과를 확인할 수 있다.&lt;br /&gt;
플레이어 체력은 공격을 받아도 감소되지 않고 그대로 유지가 되며&lt;br /&gt;
적의 체력만 줄어들고, 두 타겟 모두 제거할수 있게 되어 Next 조건이 만족하게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Games Step 2는 &lt;strong&gt;공용 데미지 처리 루틴을 후킹하고, 구조체 기반으로 대상을 선별하여  데미지를 선택적으로 적용하는 과정&lt;/strong&gt;을 알수 있게된다.&lt;/p&gt;
&lt;p&gt;이 과정을 통해 자연스럽게 공용 처리 루틴이 어떻게 동작하는지,&lt;br /&gt;
구조체 기반 게임 설계에서 팀/역할/체력과 같은 필드가 어떻게 연결되는지,&lt;br /&gt;
조건 분기를 이용해 원하는 대상에게만 주는 코드인젝션 기법을 익힐수 있게 되었다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Games: Step 1</title><link>https://cr3xis.github.io/posts/cheat-engine-game-01/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-game-01/</guid><description>Cheat Engine Games Step 1에서 숨겨진 값을 검색하고 수정하는 과정을 정리합니다.</description><pubDate>Sun, 01 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Games Step 1은 기존 튜토리얼보다 훨씬 실제 게임에 가까운 환경에서&lt;br /&gt;
값을 탐색하고 원하는 방식으로 조작하는 경험을 제공한다.&lt;br /&gt;
특히 이 단계는 값이 단순하지 않게 숨겨져 있기 때문에,&lt;br /&gt;
기존에 배운 &lt;strong&gt;정확한 값 검색&lt;/strong&gt;, &lt;strong&gt;값 변화 기반 검색&lt;/strong&gt;, &lt;strong&gt;Unknown Initial Value&lt;/strong&gt; 개념을&lt;br /&gt;
실전적으로 어떻게 활용해야 하는지를 자연스럽게 익히게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 게임 시작과 구조 파악&lt;/h2&gt;
&lt;p&gt;게임을 실행하면 단순한 형태의 슈팅 화면이 나타난다.&lt;br /&gt;
플레이어는 총을 발사할 수 있고, 발사할 때마다 총알 수가 줄어들게 되어 있다.&lt;br /&gt;
우리가 해야 할 일은 &lt;strong&gt;총알 값이 저장된 주소를 찾아 수정하는 것&lt;/strong&gt;이다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games1/1.png&quot; alt=&quot;게임 실행 시 화면&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;게임 실행 시 화면&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;처음 총알 값은 5로 표시된다.&lt;br /&gt;
보통 튜토리얼 단계에서는 이런 단순한 값은 바로 Exact Value Scan으로 찾을 수 있지만,&lt;br /&gt;
Games 단계는 다르다.&lt;br /&gt;
겉으로 보이는 값과 메모리에 저장된 값이 일치하지 않을 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 일반적인 Exact Value Scan 시도&lt;/h2&gt;
&lt;p&gt;처음에는 5를 입력해 검색해 보고, 총을 한 발 쏜 뒤 4로 바뀐 값을 기준으로 Next Scan을 시도한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games1/2.png&quot; alt=&quot;총알 값 검색 (5 → 4) 후 Found: 0&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;총알 값 검색 (5 → 4) 후 Found: 0&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;단 한 개의 주소도 검색되지 않는다.&lt;br /&gt;
값이 보이는 그대로 저장되어 있지 않기 때문에 단순 스캔 방식으로는 접근할 수 없다는 의미다.&lt;br /&gt;
이런 상황에서 떠올려야 할 방법이 바로 &lt;strong&gt;Unknown Initial Value&lt;/strong&gt;다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. Unknown Initial Value로 총알 값 찾기&lt;/h2&gt;
&lt;p&gt;정확한 값이 무엇인지 모르기 때문에&lt;br /&gt;
Scan Type을 Unknown Initial Value로 설정한 뒤 첫 스캔을 진행한다.&lt;br /&gt;
이 상태에서 총을 발사하여 값이 감소하는 순간을 기준으로 &lt;strong&gt;Decreased Value&lt;/strong&gt; 스캔을 반복한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games1/3.png&quot; alt=&quot;Unknown Initial Value 스캔으로 찾은 총알 값 주소&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Unknown Initial Value 스캔으로 찾은 총알 값 주소&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;몇 번 반복하면 총을 발사할 때마다 값이 함께 변하는 주소 하나만 남게 된다.&lt;br /&gt;
이 주소가 실제 총알 값을 관리하는 메모리 영역이다.&lt;/p&gt;
&lt;p&gt;여기까지 도달하면 총알 값을 원하는 값으로 직접 수정하거나 유지할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 총알 값 Freeze로 고정시키기&lt;/h2&gt;
&lt;p&gt;찾아낸 주소를 테이블에 추가한 뒤 값을 수정하고 &lt;strong&gt;Freeze(고정)&lt;/strong&gt; 기능을 활성화하면&lt;br /&gt;
총을 계속 쏴도 값이 줄어들지 않게 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Games1/4.png&quot; alt=&quot;총알 값 Freeze 후&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;총알 값 Freeze 후&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 플레이어는 무한한 탄약을 가진 상태로 게임을 진행할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Games Step 1은 단순한 값 스캔으로 해결되지 않는 상황에서 어떤 접근 방식을 사용해야 하는지 알려주는 단계다.&lt;br /&gt;
겉으로 보이는 숫자가 항상 실제 메모리에 저장된 값과 일치하지 않기 때문에 Unknown Initial Value와&lt;br /&gt;
값 변화 기반 스캔은 게임 메모리 분석에서 매우 중요한 기초 기술이다.&lt;/p&gt;
&lt;p&gt;이 단계를 이해하면 이후 Games 시리즈에서 등장하는 더 복잡한 값 구조나 포인터 기반 변수들도 훨씬 자연스럽게 분석할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Tutorial: Step 9</title><link>https://cr3xis.github.io/posts/cheat-engine-tutorial-09/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-tutorial-09/</guid><description>Cheat Engine Tutorial Step 9의 구조체 구분과 코드 인젝션 과정을 정리합니다.</description><pubDate>Sat, 24 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Step 9에서는 지금까지의 모든 내용을 종합하여&lt;br /&gt;
&lt;strong&gt;나와 팀·적의 체력을 구분한 뒤, 오직 적의 체력만 감소하도록 코드 인젝션을 적용하는 과정&lt;/strong&gt;을 다룬다.&lt;br /&gt;
이전 단계들보다 확실히 실전적인 구조이며, 게임 구조를 이해하는 능력과 코드 조작 능력을 동시에 요구한다.&lt;/p&gt;
&lt;p&gt;튜토리얼의 목표는 다음과 같다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;모든 플레이어의 체력을 찾는다.&lt;/li&gt;
&lt;li&gt;체력 감소 코드를 분석해 어떤 플레이어에 적용되는지 파악한다.&lt;/li&gt;
&lt;li&gt;팀 정보(아군/적군)를 확인한다.&lt;/li&gt;
&lt;li&gt;적군에게만 체력 감소가 적용되도록 코드 인젝션을 작성한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 모든 플레이어 체력 찾기&lt;/h2&gt;
&lt;p&gt;우선 튜토리얼 창에 있는 모든 플레이어 목록을 확인한 후 각 플레이어의 체력을 CE에서 검색해준다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step9/1.png&quot; alt=&quot;각 플레이어들의 체력 주소&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;각 플레이어들의 체력 주소&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;한 번 검색으로 여러 개의 Health 값이 나오게 되며, 이를 하나하나 테이블에 내리면서 누구의 체력인지 매칭한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 특정 플레이어의 체력에 접근하는 코드 분석&lt;/h2&gt;
&lt;p&gt;체력 주소 중 하나를 선택하여 &lt;strong&gt;Find out what writes to this address&lt;/strong&gt;를 수행하면&lt;br /&gt;
해당 플레이어의 체력을 조작하는 코드를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step9/2.png&quot; alt=&quot;&quot;Find out what writes to this address&quot;로 Dave의 체력 접근 코드 확인&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Find out what writes to this address&quot;로 Dave의 체력 접근 코드 확인&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 코드는 Hit me 버튼을 눌렀을 때 Dave의 체력을 감소시키는 역할을 수행한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 명령어 구조 분석 — 어떤 필드가 어떤 정보를 담고 있는가&lt;/h2&gt;
&lt;p&gt;이제 해당 명령어의 주소를 기준으로 &lt;strong&gt;Show Disassembler&lt;/strong&gt;를 열어 레지스터와 구조체 필드를 분석한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step9/3.png&quot; alt=&quot;Show Disassembler로 명령어 분석&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Show Disassembler로 명령어 분석&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Health 감소 로직은 보통 &lt;code&gt;mov [reg+offset], value&lt;/code&gt; 형태로 구성된다.&lt;br /&gt;
여기서 reg가 가리키는 구조체 내부에 플레이어의 정보(팀, 체력 등)가 저장되어 있다.&lt;br /&gt;
따라서 이 구조체를 분석해야 정확한 조건 분기가 가능해진다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 플레이어 구조체 분석 — 팀 정보 찾기&lt;/h2&gt;
&lt;p&gt;구조체 내부 필드들을 확인하기 위해 &lt;strong&gt;Dissect Data/Structures&lt;/strong&gt; 기능을 사용한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step9/4.png&quot; alt=&quot;Dissect Data/Structures 실행 직전&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Dissect Data/Structures 실행 직전&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 기능을 통해 reg가 가리키는 구조체의 필드들이 정리되어 나타난다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step9/5.png&quot; alt=&quot;Dissect Data/Structures로 팀 정보 확인&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Dissect Data/Structures로 팀 정보 확인&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;여기에서 플레이어의 팀 정보를 포함한 필드(offset)가 확인되며,&lt;br /&gt;
이를 기준으로 &quot;아군인지, 적군인지&quot;를 구분할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 코드 인젝션 템플릿 생성&lt;/h2&gt;
&lt;p&gt;체력 감소 명령어를 선택한 뒤 &lt;strong&gt;Code Injection&lt;/strong&gt; 기능을 통해 인젝션용 템플릿을 생성한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step9/6.png&quot; alt=&quot;코드 인젝션 템플릿 추가&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;코드 인젝션 템플릿 추가&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 템플릿 안에 조건문을 작성하여 적군에게만 체력이 감소하도록 조작할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 코드 인젝션 스크립트 작성&lt;/h2&gt;
&lt;p&gt;생성된 스크립트에는 아래 조건을 반영한다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;플레이어 구조체의 팀 정보 필드를 확인한다.&lt;/li&gt;
&lt;li&gt;팀값이 &quot;적군&quot;을 의미하는 값일 때만 원래의 감소 코드를 실행한다.&lt;/li&gt;
&lt;li&gt;나머지(아군, 나 자신)는 체력이 감소하지 않도록 우회한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step9/7.png&quot; alt=&quot;코드 인젝션 스크립트 작성&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;코드 인젝션 스크립트 작성&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 부분이 Step 9의 핵심이며, 정확한 오프셋과 원본 코드를 이해해야만 제대로 동작한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 스크립트 적용 및 테스트&lt;/h2&gt;
&lt;p&gt;작성한 스크립트를 적용한 뒤 Hit me 버튼을 눌러 테스트해 보면&lt;br /&gt;
적군의 체력만 줄어들고 아군과 나의 체력은 그대로 유지된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step9/8.png&quot; alt=&quot;스크립트 적용 후 테스트 및 Next 버튼 활성화&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;스크립트 적용 후 테스트 및 Next 버튼 활성화&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;정상적으로 동작하면 Next 버튼이 활성화된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Step 9는 CE 튜토리얼 중 가장 실제 게임 구조에 가까운 내용을 다룬다.&lt;br /&gt;
플레이어 구조체를 분석하고, 그 안의 팀/체력/상태 등 다양한 정보를 이해하며&lt;br /&gt;
그에 따라 조건 분기를 만들어내는 과정은 게임 메모리 해킹에서 매우 자주 사용되는 기법이다.&lt;/p&gt;
&lt;p&gt;이 단계를 정확히 이해하면 다중 포인터, 구조체 기반 로직, 조건부 코드 인젝션 등&lt;br /&gt;
중급 난이도 이상의 작업도 훨씬 자연스럽게 다룰 수 있게 된다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Tutorial: Step 8</title><link>https://cr3xis.github.io/posts/cheat-engine-tutorial-08/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-tutorial-08/</guid><description>Cheat Engine Tutorial Step 8의 다중 포인터 추적 과정을 정리합니다.</description><pubDate>Thu, 22 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Step 8에서는 지금까지 배웠던 포인터 개념을 한 번 더 확장하여&lt;br /&gt;
&lt;strong&gt;여러 단계로 연결된 다중 포인터 구조를 직접 추적하는 과정&lt;/strong&gt;을 다루게 된다.&lt;br /&gt;
이 단계의 목표는 최종 값을 가리키는 베이스 포인터(Base Pointer)를 찾아 CE 테이블에 올바르게 구성하는 것이다.&lt;/p&gt;
&lt;p&gt;포인터 체인이 여러 단계로 이어져 있기 때문에&lt;br /&gt;
각 단계의 포인터를 하나씩 직접 추적해 가며 최종 주소를 찾아내는 과정이 핵심이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 초기 값 검색 및 테이블로 내리기&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 보이는 값(예: 3883)을 기준으로 Exact Value 스캔을 진행하여 값을 찾는다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/1.png&quot; alt=&quot;초기 값 3883 검색 후 테이블로 내리기&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;초기 값 3883 검색 후 테이블로 내리기&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;값이 정확히 확인되면 테이블로 내리고 이제 이 값에 접근하는 포인터를 추적하는 단계로 넘어간다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 1단계 포인터 확인&lt;/h2&gt;
&lt;p&gt;테이블에서 해당 값을 오른쪽 클릭해 &lt;strong&gt;Find out what accesses this address&lt;/strong&gt;를 선택하면&lt;br /&gt;
이 값에 접근하는 첫 번째 포인터를 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/2.png&quot; alt=&quot;&quot;Find out what accesses this address&quot;로 1단계 포인터 확인&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Find out what accesses this address&quot;로 1단계 포인터 확인&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;해당 명령어에서 사용되는 레지스터를 통해 실제 1단계 포인터 주소를 추적할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 1단계 포인터 주소 검색 후 테이블로 내리기&lt;/h2&gt;
&lt;p&gt;포인터가 가리키는 주소를 메모리에서 찾아 Hex 옵션을 사용하여 그대로 검색한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/3.png&quot; alt=&quot;1단계 포인터 주소 검색 후 테이블로 내리기&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;1단계 포인터 주소 검색 후 테이블로 내리기&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;찾은 포인터 주소 역시 테이블로 내린 뒤 다음 단계 포인터를 추적한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 2단계 포인터 추적&lt;/h2&gt;
&lt;p&gt;1단계 포인터에서 다시 &lt;strong&gt;Find out what accesses this address&lt;/strong&gt;를 수행한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/4.png&quot; alt=&quot;2단계 포인터 추적&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;2단계 포인터 추적&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 두 번째 포인터가 어떤 주소를 가리키는지 확인할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 2단계 포인터 주소 검색 및 테이블 등록&lt;/h2&gt;
&lt;p&gt;추적된 주소를 다시 Hex 검색하여 2단계 포인터 주소를 찾고 테이블에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/5.png&quot; alt=&quot;2단계 포인터 주소 검색 후 테이블로 내리기&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;2단계 포인터 주소 검색 후 테이블로 내리기&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 3단계 포인터 추적&lt;/h2&gt;
&lt;p&gt;2단계 포인터에 대해 다시 Find out what accesses this address 기능을 사용하여 3단계 포인터를 확인한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/6.png&quot; alt=&quot;3단계 포인터 추적&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;3단계 포인터 추적&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 3단계 포인터 주소 검색 및 테이블 추가&lt;/h2&gt;
&lt;p&gt;Hex 검색을 통해 3단계 포인터 주소를 찾고 테이블에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/7.png&quot; alt=&quot;3단계 포인터 주소 검색 후 테이블로 내리기&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;3단계 포인터 주소 검색 후 테이블로 내리기&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 4단계 포인터 추적&lt;/h2&gt;
&lt;p&gt;3단계 포인터에서 다시 포인터 접근 정보를 확인하여 4단계 포인터를 찾는다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/8.png&quot; alt=&quot;4단계 포인터 추적&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;4단계 포인터 추적&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;여기까지 추적했다면 거의 베이스 포인터에 도달한 상태다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;9. 베이스 포인터 주소 검색 및 테이블 추가&lt;/h2&gt;
&lt;p&gt;Hex 검색을 통해 베이스 포인터 주소를 찾고 테이블에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/9.png&quot; alt=&quot;베이스 주소 검색 후 테이블로 내리기&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;베이스 주소 검색 후 테이블로 내리기&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 전체 포인터 체인 구성이 가능하다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;10. 다중 포인터 경로 구성&lt;/h2&gt;
&lt;p&gt;지금까지 찾은 포인터들을 순서대로 연결하여 CE 테이블에 포인터 경로를 구성한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/10.png&quot; alt=&quot;다중 포인터 경로 구성&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;다중 포인터 경로 구성&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;각 단계의 오프셋을 정확히 입력하면  어떤 상황에서도 항상 올바른 값에 접근할 수 있는 완전한 포인터 체인이 완성된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;11. 값 변경 및 고정&lt;/h2&gt;
&lt;p&gt;마지막으로 값에 접근하여 원하는 값(예: 5000)으로 변경하고 Active 체크로 고정한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step8/11.png&quot; alt=&quot;값을 5000으로 변경하고 고정&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;값을 5000으로 변경하고 고정&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 튜토리얼에서 Change pointer 버튼을 눌러도 포인터 체인이 자동으로 값을 추적하므로 값은 유지되고&lt;br /&gt;
Next 버튼이 활성화된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Step 8은 여러 단계로 이어진 포인터 체인을 직접 따라가며&lt;br /&gt;
실제 프로그램이 어떤 구조로 값을 관리하는지 이해할 수 있는 중요한 단계다.&lt;br /&gt;
이 과정을 익히면 복잡한 게임 구조에서도 베이스 포인터와 오프셋 조합을 빠르게 파악할 수 있게 된다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Tutorial: Step 7</title><link>https://cr3xis.github.io/posts/cheat-engine-tutorial-07/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-tutorial-07/</guid><description>Cheat Engine Tutorial Step 7의 코드 인젝션과 Auto Assembler 사용법을 정리합니다.</description><pubDate>Tue, 20 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Step 7에서는 **코드 인젝션(Code Injection)**을 이용해 기존 프로그램의 동작을 직접 수정하는 과정을 다루게 된다.&lt;br /&gt;
이전 단계들은 특정 값을 찾고 수정하는 방식이었지만, 이번 단계는 &lt;strong&gt;게임 로직 자체를 원하는 방향으로 바꾼다&lt;/strong&gt;는 점에서&lt;br /&gt;
이전과 확실히 차별화된다.&lt;/p&gt;
&lt;p&gt;튜토리얼에서 요구하는 것은  &lt;strong&gt;체력이 감소하는 대신 증가하도록 코드를 수정하는 것&lt;/strong&gt;이며,&lt;br /&gt;
이를 위해 CE의 &lt;strong&gt;Auto Assembler&lt;/strong&gt; 기능을 사용하게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 초기 값 검색&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 보이는 값은 처음에 100이며, 이를 기준으로 CE에서 Exact Value 스캔을 진행한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step7/1.png&quot; alt=&quot;초기 값 100 검색&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;초기 값 100 검색&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 값이 변경된 뒤 다시 스캔하기&lt;/h2&gt;
&lt;p&gt;Hit me 버튼을 누르면 값이 99가 된다.&lt;br /&gt;
CE에서도 값을 99로 바꿔 Next Scan을 수행한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step7/2.png&quot; alt=&quot;&quot;Hit me&quot; 클릭 후 값 99로 검색&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Hit me&quot; 클릭 후 값 99로 검색&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 과정을 통해 Health 값을 수정하는 메모리 주소를 특정하게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 해당 주소에 값을 쓰는 코드 확인하기&lt;/h2&gt;
&lt;p&gt;찾은 주소를 테이블에 추가한 뒤 &lt;strong&gt;Find out what writes to this address&lt;/strong&gt; 메뉴를 통해&lt;br /&gt;
Health 값을 감소시키는 명령어를 추적한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step7/3.png&quot; alt=&quot;&quot;Find out what writes to this address&quot;로 명령어 확인&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Find out what writes to this address&quot;로 명령어 확인&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 명령어가 실행되는 순간에 Health가 감소하므로&lt;br /&gt;
이 코드를 수정하면 체력을 증가시키는 로직으로 바꿀 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 메모리 뷰에서 해당 코드 위치 확인하기&lt;/h2&gt;
&lt;p&gt;Show Disassembler를 통해 해당 명령의 메모리 위치를 확인한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step7/4.png&quot; alt=&quot;Show Disassembler로 메모리 뷰 확인&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Show Disassembler로 메모리 뷰 확인&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 위치가 코드 인젝션을 적용할 지점이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 코드 인젝션 템플릿 열기&lt;/h2&gt;
&lt;p&gt;해당 명령어를 선택한 상태에서 &lt;strong&gt;&quot;Replace with code that does nothing&quot;&lt;/strong&gt; 또는 &lt;strong&gt;&quot;Code Injection&quot;&lt;/strong&gt; 기능을 선택한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step7/5.png&quot; alt=&quot;Code Injection 템플릿 선택 직전&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Code Injection 템플릿 선택 직전&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 기능은 CE가 자동으로 인젝션용 코드 템플릿을 생성해준다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. Auto Assembler 템플릿 생성&lt;/h2&gt;
&lt;p&gt;템플릿이 생성되면 아래와 같은 기본 구조가 만들어진다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step7/6.png&quot; alt=&quot;Code Injection 기본 템플릿&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Code Injection 기본 템플릿&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 기존 코드를 내가 원하는대로 커스터마이징이 가능해진다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 체력 감소 코드를 증가 코드로 수정하기&lt;/h2&gt;
&lt;p&gt;원래 코드는 체력을 감소시키는 명령어였다.&lt;br /&gt;
해당 줄을 주석 처리하고 그 자리에 증가 코드를 작성한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step7/7.png&quot; alt=&quot;원래 코드 주석 처리 후 증가 코드 추가&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;원래 코드 주석 처리 후 증가 코드 추가&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이렇게 하면 Hit me 버튼을 눌러도 Health가 줄지 않고 오히려 계속 증가하게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;8. 코드가 정상적으로 적용되었는지 확인하기&lt;/h2&gt;
&lt;p&gt;코드를 적용한 뒤 다시 메모리 뷰를 보면 인젝션된 코드가 정상적으로 삽입된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step7/8.png&quot; alt=&quot;코드 주입 후 변경된 메모리 뷰&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;코드 주입 후 변경된 메모리 뷰&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;튜토리얼 창에서 Hit me 버튼을 눌러보면 이제 Health 값이 감소하지 않고 증가한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Step 7은 이전 단계들과 달리 &lt;strong&gt;프로그램의 동작을 직접 조작하는 단계&lt;/strong&gt;다.&lt;br /&gt;
Code Injection을 통해 실제 게임 로직을 원하는 방향으로 바꾸는 과정은 실전에서 매우 자주 활용되는 기법이기도 하다.&lt;/p&gt;
&lt;p&gt;이 단계만 정확히 이해해도 CE의 Auto Assembler 기능을 활용하는 데 큰 어려움이 없어지며&lt;br /&gt;
이후의 더 복잡한 메모리 조작 과정에도 자연스럽게 응용할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Tutorial: Step 6</title><link>https://cr3xis.github.io/posts/cheat-engine-tutorial-06/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-tutorial-06/</guid><description>Cheat Engine Tutorial Step 6의 포인터 추적과 테이블 등록 과정을 정리합니다.</description><pubDate>Sun, 18 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Step 6에서는 단순히 값만 수정하는 것이 아니라,&lt;br /&gt;
&lt;strong&gt;값을 가리키는 포인터 구조를 추적해서 올바른 방식으로 수정하는 과정&lt;/strong&gt;을 다루게 된다.&lt;br /&gt;
튜토리얼에서는 값의 주소가 고정되어 있지 않기 때문에, 값 자체만 수정하면 금방 다시 바뀌어버린다.&lt;br /&gt;
따라서 이번 단계에서는 &lt;strong&gt;포인터를 직접 찾아 CE 테이블에 등록하는 것&lt;/strong&gt;이 목표다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 초기 값 검색&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 보이는 값은 처음에 100으로 시작한다.&lt;br /&gt;
따라서 CE에서도 100을 기준으로 첫 스캔을 진행한다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step6/1.png&quot; alt=&quot;초기 값 100 검색&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;초기 값 100 검색&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;검색 결과가 여러 개 나오지만, 다음 단계에서 값이 변하는 순간을 이용하여 후보를 좁혀나가게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Change value 눌러 값 변경 후 다시 검색하기&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 &lt;strong&gt;Change value&lt;/strong&gt; 버튼을 누르면 값이 920으로 변경된다.&lt;br /&gt;
CE에서도 검색 값을 920으로 바꾸고 &lt;em&gt;Next Scan&lt;/em&gt;을 수행한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step6/2.png&quot; alt=&quot;&quot;Change value&quot; 클릭 후 값 920으로 검색&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Change value&quot; 클릭 후 값 920으로 검색&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;여러 번 반복하면 결국 한 개의 주소만 남게 된다. 해당 주소를 테이블에 추가하여 다음 과정을 이어간다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 해당 주소에 어떤 코드가 접근하는지 확인하기&lt;/h2&gt;
&lt;p&gt;남은 주소를 테이블에 추가한 뒤 오른쪽버튼을 클릭하여 &lt;strong&gt;Find out what writes to this address&lt;/strong&gt;를 선택한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step6/2-1.png&quot; alt=&quot;&quot;Find out what writes to this address&quot; 선택&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Find out what writes to this address&quot; 선택&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 상태에서는 아직 아무 코드도 보이지 않는다.&lt;br /&gt;
값이 변경되는 기록이 남기에 다시 Change value 버튼을 눌러야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Change value 실행 시 나타나는 명령어 확인하기&lt;/h2&gt;
&lt;p&gt;튜토리얼에서 Change value를 다시 눌러보면 해당 주소에 값을 쓰는 명령어가 추적 창에 나타난다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step6/3.png&quot; alt=&quot;&quot;Change value&quot; 클릭 후 나타난 코드&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Change value&quot; 클릭 후 나타난 코드&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;명령어 형태를 보면 &lt;code&gt;[edx]&lt;/code&gt;에 값을 쓰고 있다는 것을 알 수 있다.&lt;br /&gt;
즉, &lt;code&gt;edx&lt;/code&gt;가 이 값의 실제 주소를 가리키고 있다는 의미다.&lt;br /&gt;
이제 &lt;strong&gt;edx가 어떤 주소를 가리키는지&lt;/strong&gt;를 따라가는 것이 포인터 분석의 핵심이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 포인터 주소 검색하기&lt;/h2&gt;
&lt;p&gt;Advanced Options에 표시된 edx 값(실제 주소)을 복사한 뒤 CE에서 Hex 옵션을 체크하고 이 주소를 그대로 검색한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step6/4.png&quot; alt=&quot;포인터 주소 검색 및 테이블 추가&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;포인터 주소 검색 및 테이블 추가&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;검색되는 값들 중 exe 모듈 영역에 해당하는 녹색 주소가 실제 포인터일 가능성이 높다.&lt;br /&gt;
해당 주소를 테이블에 추가하여 구조를 살펴본다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;6. 포인터 등록 및 오프셋 입력&lt;/h2&gt;
&lt;p&gt;테이블에 추가된 포인터 항목을 보면 해당 주소가 아까 찾았던 값의 위치를 가리키고 있음을 확인할 수 있다.&lt;br /&gt;
이제 이를 CE 포인터로 등록하기 위해 &lt;strong&gt;Add Address Manually → Pointer 체크&lt;/strong&gt; 후 아래와 같이 입력한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step6/5.png&quot; alt=&quot;포인터 주소와 오프셋 추가&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;포인터 주소와 오프셋 추가&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;명령어에서 &lt;code&gt;[edx]&lt;/code&gt; 형태였기 때문에 오프셋은 0이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;7. 포인터 값 수정 및 고정&lt;/h2&gt;
&lt;p&gt;포인터를 등록한 뒤 Value를 5000으로 수정하고 Active 체크를 하게되면 값이 고정된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step6/6.png&quot; alt=&quot;포인터 값 5000으로 설정 및 고정&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;포인터 값 5000으로 설정 및 고정&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 Change pointer 버튼을 눌러도 값이 변경되지 않는다.&lt;br /&gt;
포인터 기반으로 주소를 추적했기 때문에 튜토리얼에서 값이 이동해도 올바르게 따라가게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Step 6는 단순 주소 수정에서 벗어나  &lt;strong&gt;값이 어떻게 가리켜지고 이동하는지&lt;/strong&gt;,&lt;br /&gt;
그리고 &lt;strong&gt;포인터를 통해 안정적인 접근이 왜 필요한지&lt;/strong&gt;를 알려주는 단계다.&lt;br /&gt;
이 과정을 정확히 이해하면 이후 다단계 포인터 구조나 동적 메모리 구조도 훨씬 쉽게 분석할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Tutorial: Step 5</title><link>https://cr3xis.github.io/posts/cheat-engine-tutorial-05/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-tutorial-05/</guid><description>Cheat Engine Tutorial Step 5의 코드 추적과 NOP 패치 과정을 정리합니다.</description><pubDate>Fri, 16 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Step 5에서는 &lt;strong&gt;값이 실행할 때마다 달라지는 주소를 직접 수정하는 대신&lt;/strong&gt;,&lt;br /&gt;
그 주소에 값을 쓰는 &lt;strong&gt;코드 자체를 차단하는 방식&lt;/strong&gt;으로 문제를 해결하게 된다.&lt;br /&gt;
주소가 매번 달라지기 때문에 단순히 값을 검색해서 바꾸는 방식으로는 해결할 수 없다.&lt;br /&gt;
따라서 이 단계에서는 코드가 어떤 주소에 값을 쓰는 순간을 포착하고, 그 코드를 NOP 처리하는 과정이 핵심이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 초기 값 검색&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 보이는 값은 100으로 시작한다.&lt;br /&gt;
CE에서 100을 입력하여 첫 스캔을 진행한다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step5/1.png&quot; alt=&quot;초기 값 100 검색&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;초기 값 100 검색&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;검색 결과는 여러 개가 나오지만, 이후 값이 바뀔 때 후보가 빠르게 좁혀진다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 값 변경 후 다시 검색하기&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 &lt;strong&gt;Change value&lt;/strong&gt; 버튼을 클릭하면 값이 155로 변한다.&lt;br /&gt;
CE에서도 검색 값을 155로 바꾸고 &lt;em&gt;Next Scan&lt;/em&gt;을 실행해 실제 값이 있는 주소만 남긴다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step5/2.png&quot; alt=&quot;&quot;Change value&quot; 클릭 후 값 155로 검색&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Change value&quot; 클릭 후 값 155로 검색&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;여러 번 반복하게 되면 최종적으로 하나의 주소만 남게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 해당 주소에 어떤 코드가 접근하는지 확인하기&lt;/h2&gt;
&lt;p&gt;남은 주소를 테이블에 추가한 뒤&lt;br /&gt;
오른쪽 클릭하여 &lt;strong&gt;Find out what writes to this address&lt;/strong&gt;를 선택한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step5/3.png&quot; alt=&quot;&quot;Find out what writes to this address&quot; 선택&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Find out what writes to this address&quot; 선택&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 시점에서는 아직 아무 코드도 나타나지 않는다.&lt;br /&gt;
이제 값을 변경하여 해당 주소에 쓰기를 수행하는 순간을 포착해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Change value를 눌러 코드 실행을 포착하기&lt;/h2&gt;
&lt;p&gt;튜토리얼에서 다시 &lt;strong&gt;Change value&lt;/strong&gt; 버튼을 누르면 CE의 추적 창에&lt;br /&gt;
해당 주소에 값을 쓰는 코드가 기록된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step5/4.png&quot; alt=&quot;&quot;Change value&quot; 클릭 후 나타난 코드&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Change value&quot; 클릭 후 나타난 코드&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 명령어가 바로 값이 변경되는 원인이며, 이 코드를 차단하면 값이 더 이상 바뀌지 않게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;5. 코드를 NOP으로 교체하기&lt;/h2&gt;
&lt;p&gt;이제 나타난 명령어를 선택하고 &lt;strong&gt;Replace&lt;/strong&gt;를 눌러 NOP으로 교체한다.&lt;br /&gt;
NOP은 아무 동작도 하지 않는 명령어이기 때문에 코드가 실행되더라도&lt;br /&gt;
실제 주소에 값이 쓰이지 않게 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step5/5.png&quot; alt=&quot;코드를 NOP으로 교체&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;코드를 NOP으로 교체&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이제 Change value 버튼을 다시 눌러 보면 값이 더 이상 바뀌지 않고 그대로 유지되는 것을 확인할 수 있다.&lt;br /&gt;
이 상태가 되면 Step 5의 조건이 충족되며 Next 버튼이 활성화된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Step 5는 단순한 값 변경이 아니라, 어떤 코드가 값을 수정하는지 추적하고 그 동작을 차단하는&lt;br /&gt;
실전적인 CE 활용 단계다.&lt;br /&gt;
게임 해킹이나 메모리 분석에서도 자주 사용하는 기법이기 때문에&lt;br /&gt;
이 과정을 정확히 이해해두면 이후 단계를 훨씬 수월하게 진행할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Tutorial: Step 4</title><link>https://cr3xis.github.io/posts/cheat-engine-tutorial-04/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-tutorial-04/</guid><description>Cheat Engine Tutorial Step 4의 Float와 Double 값 검색 과정을 정리합니다.</description><pubDate>Wed, 14 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Step 4에서는 &lt;strong&gt;자료형(Float / Double)&lt;/strong&gt; 에 따라 값을 스캔하는 방법을 직접 경험하게 된다.&lt;br /&gt;
체력(Health)은 Float, 탄약(Ammo)은 Double 형태로 저장되어 있기 때문에&lt;br /&gt;
이전 단계들처럼 단순히 4 Bytes로만 검색하면 제대로 된 주소가 나오지 않는다.&lt;br /&gt;
자료형을 올바르게 지정하는 것이 이 단계의 핵심이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 체력 값 검색 — Float 형태&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 체력 값을 확인한 뒤, CE에서 Value Type을 Float로 설정한다.&lt;br /&gt;
아래 화면은 Float 타입으로 Health 값을 검색한 모습이다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step4/1.png&quot; alt=&quot;체력주소 찾기&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;체력주소 찾기&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;검색 결과가 적절히 좁혀졌다면 Hit me를 눌러 값이 함께 변하는지 확인하며 실제 주소를 특정한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 탄약 값 검색 — Double 형태&lt;/h2&gt;
&lt;p&gt;탄약 값은 Double 형태로 저장되어 있다.&lt;br /&gt;
따라서 Value Type을 Double로 설정하고 같은 방식으로 검색한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step4/2.png&quot; alt=&quot;탄약주소 찾기&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;탄약주소 찾기&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;체력 때와 마찬가지로 Fire 버튼을 눌러 값이 변경되는지 확인하면서 올바른 주소가 맞는지 확인하면 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 찾은 값을 5000으로 변경하기&lt;/h2&gt;
&lt;p&gt;Health와 Ammo 두 값 모두 찾았다면 CE 테이블에 추가한 뒤&lt;br /&gt;
Value를 더블 클릭하여 5000으로 수정한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step4/3.png&quot; alt=&quot;각 주소의 벨류를 5000으로 변경&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;각 주소의 벨류를 5000으로 변경&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;두 값이 모두 5000 이상으로 설정되면 Step 4의 조건이 충족되어 Next 버튼이 활성화된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Step 4는 값의 자료형을 올바르게 이해하는 것이 가장 중요한 단계다.&lt;br /&gt;
Float, Double 같은 자료형이 실제 메모리에서 어떻게 저장되고&lt;br /&gt;
왜 정확한 검색이 필요한지를 직접 경험할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Tutorial: Step 3</title><link>https://cr3xis.github.io/posts/cheat-engine-tutorial-03/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-tutorial-03/</guid><description>Cheat Engine Tutorial Step 3의 Unknown Initial Value 검색 방법을 정리합니다.</description><pubDate>Mon, 12 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Step 3에서는 &lt;strong&gt;초기 값을 모르는 상태에서 값이 어떻게 변하는지를 기반으로 실제 주소를 찾아내는 과정&lt;/strong&gt;을 경험하게 된다.&lt;br /&gt;
이전 단계와는 달리 Health 값이 0~500 사이의 임의의 값으로 설정되어 있기 때문에,&lt;br /&gt;
정확한 값을 입력해서 스캔할 수 없다는 점이 이 단계의 핵심이라고 볼 수 있다.&lt;/p&gt;
&lt;p&gt;따라서 이번 단계에서 가장 중요한 개념은&lt;br /&gt;
&lt;strong&gt;Unknown initial value&lt;/strong&gt;와 &lt;strong&gt;값의 변화에 따른 필터링&lt;/strong&gt;이다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 초기 스캔 — Unknown initial value 사용&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에 표시된 Health 값은 숫자로 정확히 주어지지 않는다.&lt;br /&gt;
따라서 CE에서도 처음에는 어떤 값인지 알 수 없다.&lt;br /&gt;
이럴 때 사용하는 옵션이 바로 &lt;em&gt;Unknown initial value&lt;/em&gt;이다.&lt;/p&gt;
&lt;p&gt;아래 화면은 Unknown initial value로 전체 메모리를 첫 스캔한 모습이다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step3/1.png&quot; alt=&quot;Unknown initial value로 초기 스캔&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Unknown initial value로 초기 스캔&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;검색 결과는 매우 많은 주소가 나오는데,&lt;br /&gt;
지금은 그중 어떤 것이 Health인지 알 수 없으므로&lt;br /&gt;
값의 변화를 통해 후보를 줄여야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 값이 감소한 뒤 다시 스캔하기&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 Hit me를 누르면 Health가 감소한다.&lt;br /&gt;
이때 CE에서 Scan Type을 &lt;em&gt;Decreased value&lt;/em&gt;로 변경하고 Next Scan을 수행하면&lt;br /&gt;
Health처럼 실제로 값이 감소한 주소만 남게 된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step3/2.png&quot; alt=&quot;Hit me 클릭 후 Decreased value 스캔&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;Hit me 클릭 후 Decreased value 스캔&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 과정을 여러 번 반복하게 되면 Health 값처럼 일정하게 줄어드는 주소들만 남기 때문에 최종적으로 몇 개의 후보만 남게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 남은 주소 확인 후 테이블로 내리기&lt;/h2&gt;
&lt;p&gt;필터링된 주소 목록에서 값이 일정 범위(0~500)에 있는 주소를 선택하여 CE 테이블에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step3/3.png&quot; alt=&quot;필터링된 주소에서 체력 값 선택&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;필터링된 주소에서 체력 값 선택&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;주소를 테이블에 추가한 뒤 튜토리얼 창에서 Hit me를 눌러 값이 함께 변하는지 확인하면 실제로 Health를 나타내는 값인지 쉽게 구별할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. Health 값을 직접 수정하기&lt;/h2&gt;
&lt;p&gt;Health 주소가 맞다는 것을 확인했다면 이제 값을 직접 변경해볼 차례다.&lt;br /&gt;
테이블에서 해당 Value를 더블 클릭하여 5000으로 수정하면 튜토리얼 창에서도 값이 즉시 반영된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step3/4.png&quot; alt=&quot;체력 값을 5000으로 변경&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;체력 값을 5000으로 변경&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Health 값이 충분히 큰 값으로 설정되면&lt;br /&gt;
Step 3의 조건을 충족하여 Next 버튼이 활성화된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Step 3는 눈에 보이는 값만을 기준으로 직접 검색하던 방식에서 벗어나,&lt;br /&gt;
&lt;strong&gt;값이 어떻게 변화하는지를 이용해 메모리 주소를 특정하는 방법&lt;/strong&gt;을 익히는 단계다.&lt;br /&gt;
Unknown initial value, Decreased value 같은 옵션이 어떤 상황에서 쓰이는지를 이해하면 앞으로 더 복잡한 구조의 프로그램을 분석할 때 큰 도움이 된다.&lt;/p&gt;
</content:encoded></item><item><title>[Cheat Engine] Tutorial: Step 2</title><link>https://cr3xis.github.io/posts/cheat-engine-tutorial-02/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/cheat-engine-tutorial-02/</guid><description>Cheat Engine Tutorial Step 2의 Exact Value Scan과 값 수정 과정을 정리합니다.</description><pubDate>Sat, 10 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Step 2는 Cheat Engine의 가장 기본적인 기능인 **(Exact Value Scan)**을 활용하여&lt;br /&gt;
게임 내부 값을 직접 수정해보는 단계다.&lt;br /&gt;
초보자라도 따라가기 쉬운 구조이지만, 실제로는 메모리 구조를 이해하는 데 중요한 기반이 된다.&lt;/p&gt;
&lt;p&gt;이번 단계에서는 **Health 값(초기 100)**을 찾아 &lt;strong&gt;1000으로 변경&lt;/strong&gt;하면 다음 단계로 넘어갈 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 초기 값 스캔&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 Health가 &lt;strong&gt;100&lt;/strong&gt;으로 표시되어 있다.&lt;br /&gt;
따라서 CE에서 가장 먼저 해야 할 일은 이 값을 메모리에서 그대로 찾아오는 것이다.&lt;/p&gt;
&lt;p&gt;아래 화면은 CE에서 Value를 100으로 입력하고 First Scan을 수행한 모습이다.
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step2/2.png&quot; alt=&quot;값 100으로 초기 스캔&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;값 100으로 초기 스캔&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;스캔 결과로 많은 주소가 검색된다.&lt;br /&gt;
이는 “100이라는 숫자”가 메모리 곳곳에서 사용되기 때문이고,&lt;br /&gt;
우리가 원하는 Health 값만 특정하기 위해선 값이 변하는 순간을 활용해야 한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. 값이 변한 뒤 다시 스캔하기&lt;/h2&gt;
&lt;p&gt;튜토리얼 창에서 Hit me 버튼을 클릭하면 Health가 감소한다.&lt;br /&gt;
예를 들어 95가 되었다면 CE에서도 검색 값을 95로 바꾸고 Next Scan을 수행한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step2/3.png&quot; alt=&quot;&quot;Hit me&quot; 클릭 후 다음 스캔&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;&quot;Hit me&quot; 클릭 후 다음 스캔&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;이 과정을 반복하면 입력 값이 변한 주소만 남게 되므로 최종적으로 한두 개의 주소만 남게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 주소를 테이블로 추가하기&lt;/h2&gt;
&lt;p&gt;남은 주소가 실제 Health 주소인지 확인하기 위해 CE 테이블에 추가한다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step2/4.png&quot; alt=&quot;주소를 주소 목록에 추가&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;주소를 주소 목록에 추가&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;테이블에 추가된 값을 확인하면서 Hit me를 눌러 값이 연동되는지 살펴보면 실제 Health 주소인지 바로 알 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 값을 직접 수정해보기&lt;/h2&gt;
&lt;p&gt;테이블에 추가한 값은 이제 자유롭게 수정할 수 있다.&lt;br /&gt;
Value를 더블 클릭하여 1000으로 바꾸면 튜토리얼 창에도 바로 반영된다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/CheatEngine/Step2/5.png&quot; alt=&quot;값을 1000으로 변경&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;값을 1000으로 변경&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;Health 값이 1000으로 설정되면 Step 2의 조건이 충족되어 Next 버튼이 활성화된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Step 2는 단순한 값 변경 작업처럼 보이지만&lt;br /&gt;
메모리 값이 어떤 방식으로 검색되고 좁혀지는지 직접 경험할 수 있는 기본 단계다.&lt;br /&gt;
이 흐름을 이해하면 이후 단계에서 포인터 분석이나 코드 추적을 할 때 훨씬 수월하게 접근할 수 있다.&lt;/p&gt;
</content:encoded></item><item><title>[Dreamhack] Rev-basic-2 Writeup</title><link>https://cr3xis.github.io/posts/rev-basic-2/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/rev-basic-2/</guid><description>Dreamhack Rev-basic-2의 배열 기반 문자열 검증 구조를 분석합니다.</description><pubDate>Mon, 05 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Rev-basic-2 문제는 입력된 문자열을 배열 형태로 저장된 값들과 비교하는 구조를 가지고 있다.&lt;br /&gt;
Rev-basic-1에서 한 글자씩 비교하는 방식이었다면, 이번 문제는 배열 인덱싱과 반복 구조를 조금 더 복잡하게 다루고 있어서 한 단계 심화된 형태라고 볼 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;문제 파일&lt;/h2&gt;
&lt;p&gt;&amp;lt;ul class=&quot;file-list&quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;i class=&quot;fa-solid fa-file&quot;&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;strong&amp;gt;File Download:&amp;lt;/strong&amp;gt;
&amp;lt;a href=&quot;https://dreamhack.io/wargame/challenges/16&quot; target=&quot;_blank&quot;&amp;gt;Rev-basic-2&amp;lt;/a&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 프로그램 실행 화면&lt;/h2&gt;
&lt;p&gt;프로그램을 실행하면 Rev-basic 시리즈와 같은 형태로 입력을 받는 화면이 먼저 나타난다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\Users\cryptonite7777&amp;gt;chall2.exe
input :
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;입력된 값을 내부 배열과 비교하여 조건을 모두 만족시키면 Correct를 출력한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Main 함수 흐름&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;x64 Main&lt;/em&gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Rev-basic/Rev-basic-2/Main_asm.png&quot; alt=&quot;IDA에서 확인한 main 함수&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IDA에서 확인한 main 함수&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;메인 함수에서 입력된 문자열을 버퍼에 저장한 뒤, sub 함수로 넘겨 검증하는 구조는 동일하다.&lt;br /&gt;
rev-basic 시리즈의 공통된 흐름으로 볼 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 비교 로직 분석&lt;/h2&gt;
&lt;p&gt;아래 이미지는 sub 함수 내부의 핵심 비교 과정이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Rev-basic/Rev-basic-2/Sub_asm.png&quot; alt=&quot;배열 값을 비교하는 검증 함수&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;배열 값을 비교하는 검증 함수&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;em&gt;x64 Sub&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;코드를 자세히 보면 배열 기반 반복 구조라는 것을 확인할 수 있다.&lt;br /&gt;
루프 변수를 증가시키며 배열의 특정 인덱스로 접근하고,&lt;br /&gt;
입력된 문자열의 각 문자를 가져와 배열에 저장된 값과 비교하는 방식으로 동작한다.&lt;/p&gt;
&lt;p&gt;입력 문자열은 1바이트씩 가져오며,&lt;br /&gt;
배열은 4바이트 단위로 정렬되어 있기 때문에 인덱스 계산 시 &lt;code&gt;i * 4&lt;/code&gt; 형태로 접근하는 흐름을 보게 된다.&lt;br /&gt;
이 계산 방식만 이해하면 전체 비교 구조는 어렵지 않게 따라갈 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 비교 배열 분석&lt;/h2&gt;
&lt;p&gt;배열에 저장된 값을 따라가다 보면 최종적으로 어떤 문자열이 필요한지 확인할 수 있다.&lt;br /&gt;
문자 단위로 비교되지만 배열 구조를 그대로 따라가면 다음과 같은 플래그 형태가 구성된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Comp4re_the_arr4y
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;FLAG&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;DH{Comp4re_the_arr4y}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Rev-basic-2 문제는 배열 기반 문자열 비교 흐름을 이해하는 데 도움이 되는 문제다.&lt;br /&gt;
루프 구조, 인덱스 계산, 배열 접근 방식 등을 자연스럽게 익힐 수 있으며&lt;br /&gt;
이후 더 복잡한 문자열 변환 문제를 분석할 때 기반이 되는 개념들이다.&lt;br /&gt;
Rev-basic-0과 1을 기반으로 차근차근 접근하면 무리 없이 해결할 수 있는 문제다.&lt;/p&gt;
</content:encoded></item><item><title>[Dreamhack] Rev-basic-1 Writeup</title><link>https://cr3xis.github.io/posts/rev-basic-1/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/rev-basic-1/</guid><description>Dreamhack Rev-basic-1의 문자 단위 비교 로직과 정답 도출 과정을 정리합니다.</description><pubDate>Sat, 03 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Rev-basic-1 문제는 문자열을 한 글자씩 순차적으로 검사하는 구조로 되어 있다.&lt;br /&gt;
프로그램은 입력된 문자열을 특정 규칙에 따라 하나씩 비교하며, 모든 비교가 조건을 만족할 때만 Correct를 출력한다.&lt;br /&gt;
조금 더 세밀하게 코드를 따라가야 하기 때문에, Rev-basic-0보다 한 단계 더 분석적인 사고가 필요한 문제라고 볼 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;문제 파일&lt;/h2&gt;
&lt;p&gt;&amp;lt;ul class=&quot;file-list&quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;i class=&quot;fa-solid fa-file&quot;&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;strong&amp;gt;File Download:&amp;lt;/strong&amp;gt;
&amp;lt;a href=&quot;https://dreamhack.io/wargame/challenges/15&quot; target=&quot;_blank&quot;&amp;gt;Rev-basic-1&amp;lt;/a&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 프로그램 실행 화면&lt;/h2&gt;
&lt;p&gt;프로그램을 실행하면 Rev-basic-0과 동일하게 문자열 입력을 요구하는 단순한 형태로 시작된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\Users\cryptonite7777&amp;gt;chall1.exe
input :
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;입력한 문자열이 조건을 통과하면 Correct, 아니면 Wrong을 출력하는 구조다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Main 함수 흐름&lt;/h2&gt;
&lt;p&gt;&lt;em&gt;x64 Main&lt;/em&gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Rev-basic/Rev-basic-1/Main_asm.png&quot; alt=&quot;IDA에서 확인한 main 함수&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IDA에서 확인한 main 함수&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;입력값을 받아 특정 조건에 따라 검증하는 구조라는 점에서는 Rev-basic-0과 동일하다.&lt;br /&gt;
입력된 문자열은 버퍼에 저장된 뒤, 내부의 sub 함수를 호출하여 비교 과정을 진행하게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. 비교 로직 분석&lt;/h2&gt;
&lt;p&gt;아래 이미지는 x64dbg로 살펴본 sub 함수 내부의 핵심 부분이다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Rev-basic/Rev-basic-1/sub_function.png&quot; alt=&quot;문자 비교를 수행하는 검증 함수&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;문자 비교를 수행하는 검증 함수&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;em&gt;x64 Sub&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;어셈블리를 따라가다 보면 반복문 형태로 문자열의 각 문자를 하나씩 가져와 비교하는 흐름이라는 것을 알 수 있다.&lt;br /&gt;
rax 또는 eax 레지스터가 루프 인덱스처럼 사용되고, 입력 문자열 주소(rcx)에 인덱스를 더한 뒤 해당 문자를 가져와 비교한다.&lt;/p&gt;
&lt;p&gt;입력된 문자를 한 글자씩 읽어 하드코딩된 값과 비교하는 방식으로 동작하며,&lt;br /&gt;
중간에 하나라도 조건이 맞지 않으면 Wrong을 출력하고 종료된다.&lt;br /&gt;
모든 문자가 조건에 맞아야만 Correct가 출력되는 구조다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 비교 대상 문자열 확인&lt;/h2&gt;
&lt;p&gt;함수 내부에서 비교되는 값들을 확인해보면 최종적으로 요구되는 문자열을 유추할 수 있다.&lt;br /&gt;
비교는 문자 단위로 이루어지지만, 전체 흐름을 따라가면 아래와 같은 플래그 형태로 구성되어 있음을 알 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Compar3_the_ch4ract3r
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;입력 문자열이 위 플래그와 정확히 일치해야 프로그램은 Correct를 출력한다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;FLAG&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;DH{Compar3_the_ch4ract3r}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Rev-basic-1 문제는 문자열을 순차적으로 비교하는 기본적인 루프 구조를 이해하는 데 도움이 되는 문제다.&lt;br /&gt;
어셈블리에서 루프가 어떻게 구성되고, 인덱스 증가와 비교가 어떤 방식으로 이루어지는지를 직접 확인할 수 있다.&lt;br /&gt;
이 과정을 익혀두면 이후 더 복잡한 문자열 처리 로직을 분석할 때도 큰 도움이 될 것이다.&lt;/p&gt;
</content:encoded></item><item><title>[Dreamhack] Rev-basic-0 Writeup</title><link>https://cr3xis.github.io/posts/rev-basic-0/</link><guid isPermaLink="true">https://cr3xis.github.io/posts/rev-basic-0/</guid><description>Dreamhack Rev-basic-0의 문자열 검증 흐름을 IDA와 디버거로 분석합니다.</description><pubDate>Thu, 01 Jan 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Rev-basic-0 문제는 입력된 문자열이 특정 조건과 일치하는지를 검사하는 형태로 구성되어 있다.&lt;br /&gt;
겉으로 보기엔 간단한 프로그램이지만, 내부의 흐름을 따라가다 보면 문자열 비교가 어떤 방식으로 이루어지는지&lt;br /&gt;
자연스럽게 파악할 수 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;문제 파일&lt;/h2&gt;
&lt;p&gt;&amp;lt;ul class=&quot;file-list&quot;&amp;gt;
&amp;lt;li&amp;gt;
&amp;lt;i class=&quot;fa-solid fa-file&quot;&amp;gt;&amp;lt;/i&amp;gt; &amp;lt;strong&amp;gt;File Download:&amp;lt;/strong&amp;gt;
&amp;lt;a href=&quot;https://dreamhack.io/wargame/challenges/14&quot; target=&quot;_blank&quot;&amp;gt;Rev-basic-0&amp;lt;/a&amp;gt;
&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;1. 프로그램 실행 화면&lt;/h2&gt;
&lt;p&gt;프로그램을 실행하면 아래와 같이 입력을 기다리는 단순한 형태로 시작된다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;C:\Users\cryptonite7777&amp;gt;chall0.exe
input :
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;이런 형태의 프로그램은 보통 입력된 값을 특정 기준과 비교한 뒤 결과를 출력하는 구조를 가지고 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;2. Main 흐름 살펴보기&lt;/h2&gt;
&lt;p&gt;아래 이미지는 분석 과정에서 확인한 메인 함수의 흐름이다.&lt;/p&gt;
&lt;p&gt;&lt;em&gt;x64 Main&lt;/em&gt;
&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Rev-basic/Rev-basic-0/Main_asm.png&quot; alt=&quot;IDA에서 확인한 main 함수&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IDA에서 확인한 main 함수&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;&lt;/p&gt;
&lt;p&gt;디컴파일된 코드를 보면 전체적인 동작 방식이 단순하다는 것을 알 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;int __fastcall main(int argc, const char **argv, const char **envp)
{
  char v4[256];

  memset(v4, 0, sizeof(v4));
  sub_1400013E0(&quot;Input : &quot;, argv, envp);
  sub_140001440(&quot;%256s&quot;, v4);
  if ((unsigned int)sub_140001000(v4))
    puts(&quot;Correct&quot;);
  else
    puts(&quot;Wrong&quot;);
  return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;입력은 &lt;code&gt;v4&lt;/code&gt;에 저장되고, 이후 &lt;code&gt;sub_140001000&lt;/code&gt; 함수를 통해 문자열 검증 과정을 거치게 된다.&lt;br /&gt;
반환값이 참이면 Correct, 거짓이면 Wrong을 출력하는 구조다.&lt;br /&gt;
즉, 실제 비교 로직은 모두 &lt;code&gt;sub_140001000&lt;/code&gt; 내부에 있다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;3. sub_140001000 함수 분석&lt;/h2&gt;
&lt;p&gt;아래 이미지는 해당 함수 내부의 핵심 부분을 보여준다.&lt;/p&gt;
&lt;p&gt;&amp;lt;figure style=&quot;text-align: center;&quot;&amp;gt;
&amp;lt;img src=&quot;/assets/img/Rev-basic/Rev-basic-0/Sub_asm.png&quot; alt=&quot;IDA에서 확인한 검증 함수&quot; style=&quot;display: block; margin: 0 auto;&quot;&amp;gt;
&amp;lt;figcaption&amp;gt;IDA에서 확인한 검증 함수&amp;lt;/figcaption&amp;gt;
&amp;lt;/figure&amp;gt;
&lt;em&gt;x64 Sub&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;어셈블리를 따라가다 보면 입력 문자열과 특정 하드코딩된 문자열을 직접 비교하는 구조라는 것을 확인할 수 있다.&lt;br /&gt;
RCX에는 내가 입력한 문자열이 들어가고, RDX에는 비교할 하드코딩된 문자열이 들어간다.&lt;br /&gt;
이후 strcmp 함수를 호출하여 서로 값이 같으면 Correct, 다르면 Wrong을 출력하는 방식으로 동작한다.&lt;/p&gt;
&lt;p&gt;아래 어셈블리 부분이 비교 로직의 핵심이다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;lea rdx,qword ptr ds:[7FF749EB2220]  ; 하드코딩된 문자열 주소
mov rcx,qword ptr ss:[rsp+40]        ; 입력 문자열 주소
call &amp;lt;JMP.&amp;amp;strcmp&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;strcmp 결과가 같은 경우에만 프로그램이 Correct를 출력하게 된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;4. 비교 문자열 확인&lt;/h2&gt;
&lt;p&gt;rdx가 가리키는 주소를 확인해보면 프로그램이 원하는 문자열을 알 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Compar3_the_str1ng
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사용자가 입력한 문자열이 이 값과 정확히 일치할 때만 Correct가 출력된다.&lt;/p&gt;
&lt;hr /&gt;
&lt;h2&gt;FLAG&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;DH{Compar3_the_str1ng}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;h2&gt;마무리&lt;/h2&gt;
&lt;p&gt;Rev-basic-0은 구조가 단순하지만 리버싱에서 자주 등장하는 문자열 비교 패턴을 이해하기에 좋은 문제다.&lt;br /&gt;
입력된 문자열이 어떻게 처리되고 어떤 조건을 만족해야 하는지를 확인하는 과정에서 흐름을 읽는 능력을 기를 수 있다.&lt;br /&gt;
이 문제를 기반으로 다음 단계의 문제를 분석하면 한층 더 수월하게 따라갈 수 있다.&lt;/p&gt;
</content:encoded></item></channel></rss>