<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>hsnyus</title>
    <link>https://hsnyus.tistory.com/</link>
    <description>rev, pwn</description>
    <language>ko</language>
    <pubDate>Tue, 2 Jun 2026 23:49:08 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>hsnyus</managingEditor>
    <image>
      <title>hsnyus</title>
      <url>https://tistory1.daumcdn.net/tistory/7913733/attach/ecff34c2f00f4b58890dd84989f55fab</url>
      <link>https://hsnyus.tistory.com</link>
    </image>
    <item>
      <title>Calling Convention</title>
      <link>https://hsnyus.tistory.com/117</link>
      <description>&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;함수 호출 규약(Calling Convention): 함수의 호출 및 반환에 대한 약속&lt;/li&gt;
&lt;li&gt;어느 함수에서 다른 함수를 호출(Calling)할 때 프로그램의 실행 흐름이 다른 함수로 이동한다. 그 후 호출한 함수가 반환되면 다시 원래 함수로 돌아와서 기존 실행 흐름을 이어간다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;그렇기 때문에 함수를 호출할 때는 반환된 이후의 실행 흐름을 이어나가기 위해 Caller 의 Stack frame 와 Return Address 를 저장해야한다. 그리고 Caller 는 Callee 가 요구하는 인자는 전달해줘야 하고 Callee 의 실행이 종료될 때는 반환값을 전달받아야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;함수 호출 규약이 적용되는 것은 보통 개발자가 고수준 언어로 코드를 짜면 컴파일러에 의해서 함수들이 Calling Convention 에 맞춰서 컴파일 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Calling Convention 의 종류&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;x86
	- cdecl
	- stdcall
	- fastcall
	- thiscall
	
x86-64
	- System V AMD64 ABI의 Calling Convention
	- MS ABI의 Calling Convention
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;x86 Calling Convention&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적으로 매개변수는 스택으로 저장되었는데 x86 에서 x86-64 로 확장하면서 스택에 비해 속도가 빠른 레지스터를 주로 활용하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스터의 개수는 한정적이어서 현대 Calling Convention 에선 보통 레지스터와 스택이 함께 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Caller 와 Callee&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출 규약을 이해할 때 가장 중요한 개념은 Caller 와 Callee 이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Caller: 함수를 호출하는 함수&lt;/li&gt;
&lt;li&gt;Callee: 호출당한 함수&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드가 있다고 하면&lt;/p&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;int add(int a, int b) {
    return a + b;
}

int main() {
    int result = add(1, 2);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;main() 함수는 add() 함수를 호출하기 때문에 Caller 이고, add() 함수는 호출당했기 때문에 Callee 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출이 일어날 때 Caller 와 Callee 는 서로 역할이 나뉜다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Caller 는 Callee 에게 인자를 전달해야 한다.&lt;/li&gt;
&lt;li&gt;Callee 는 전달받은 인자를 사용해서 함수를 실행한다.&lt;/li&gt;
&lt;li&gt;Callee 는 함수 실행이 끝나면 반환값을 Caller 에게 전달한다.&lt;/li&gt;
&lt;li&gt;Caller 는 반환값을 받은 뒤 기존 실행 흐름을 이어간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 과정에서 스택, 레지스터, Return Address, Stack Frame 등이 사용된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Frame&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Frame 은 함수가 실행될 때 해당 함수가 사용하는 스택 영역이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 호출되면 보통 해당 함수만의 Stack Frame 이 만들어지고, 함수 실행이 끝나면 해당 Stack Frame 은 정리된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Frame 에는 일반적으로 다음과 같은 정보들이 저장된다.&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;이전 함수의 Stack Frame 정보&lt;/li&gt;
&lt;li&gt;Return Address&lt;/li&gt;
&lt;li&gt;함수 인자&lt;/li&gt;
&lt;li&gt;임시 데이터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86 기준으로는 보통 ebp 와 esp 레지스터를 이용해서 Stack Frame 을 관리한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;esp: 현재 스택의 top 을 가리키는 레지스터&lt;/li&gt;
&lt;li&gt;ebp: 현재 함수의 Stack Frame 기준점을 가리키는 레지스터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 함수 프롤로그는 다음과 같은 형태로 나타난다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push ebp
mov ebp, esp
sub esp, 0x10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 이전 함수의 ebp 값을 스택에 저장하고, 현재 esp 값을 ebp 로 옮긴 뒤, 지역 변수를 위한 공간을 스택에 확보하는 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 에필로그는 보통 다음과 같은 형태로 나타난다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov esp, ebp
pop ebp
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또는 다음과 같이 leave 명령어로 줄여서 나타나기도 한다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;leave
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;leave 는 내부적으로 mov esp, ebp 와 pop ebp 를 수행한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Return Address&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return Address 는 Callee 함수의 실행이 끝난 뒤 다시 돌아가야 할 주소이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Caller 가 Callee 를 호출할 때 call 명령어를 사용하면, CPU 는 다음에 실행할 명령어의 주소를 스택에 저장한다. 이 값이 Return Address 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드가 있다고 하면&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;call add
mov [result], eax
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;call add 명령어가 실행될 때, mov [result], eax 의 주소가 스택에 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 add 함수가 종료되면서 ret 명령어가 실행되면, 스택에 저장되어 있던 Return Address 를 꺼내서 다시 그 주소로 이동한다.&lt;/p&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;Caller 가 call 명령어로 Callee 를 호출한다.&lt;/li&gt;
&lt;li&gt;CPU 가 Return Address 를 스택에 저장한다.&lt;/li&gt;
&lt;li&gt;Callee 함수로 실행 흐름이 이동한다.&lt;/li&gt;
&lt;li&gt;Callee 함수가 실행된다.&lt;/li&gt;
&lt;li&gt;Callee 함수가 ret 명령어를 실행한다.&lt;/li&gt;
&lt;li&gt;스택에 저장된 Return Address 로 다시 이동한다.&lt;/li&gt;
&lt;li&gt;Caller 의 기존 실행 흐름이 이어진다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수 인자 전달 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Calling Convention 에서 중요한 것 중 하나는 함수 인자를 어떻게 전달하는지이다.&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;스택을 통한 인자 전달&lt;/li&gt;
&lt;li&gt;레지스터를 통한 인자 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86 에서는 보통 함수 인자를 스택에 push 해서 전달한다.&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;push 2
push 1
call add
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 add(1, 2) 와 같은 함수 호출을 어셈블리 레벨에서 나타낸 예시이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자를 스택에 넣을 때는 보통 오른쪽 인자부터 먼저 push 한다. 그래서 2 를 먼저 push 하고, 그 다음 1 을 push 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하는 이유는 함수 내부에서 첫 번째 인자를 [ebp+8], 두 번째 인자를 [ebp+12] 와 같은 고정된 위치로 접근하기 쉽게 만들기 위해서이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 x86 cdecl 기준으로 함수 내부에서는 다음과 같이 인자를 참조할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov eax, [ebp+8]
mov edx, [ebp+12]
add eax, edx
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 [ebp+8] 은 첫 번째 인자, [ebp+12] 는 두 번째 인자를 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86-64 에서는 레지스터를 이용한 인자 전달이 주로 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System V AMD64 ABI 기준으로 정수형 인자는 다음 순서의 레지스터를 통해 전달된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;rdi&lt;/li&gt;
&lt;li&gt;rsi&lt;/li&gt;
&lt;li&gt;rdx&lt;/li&gt;
&lt;li&gt;rcx&lt;/li&gt;
&lt;li&gt;r8&lt;/li&gt;
&lt;li&gt;r9&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 함수 호출이 있다고 하면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;add(1, 2);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System V AMD64 ABI 기준으로는 대략 다음과 같이 인자가 전달된다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rdi, 1
mov rsi, 2
call add
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째 인자 1 은 rdi 로 전달되고, 두 번째 인자 2 는 rsi 로 전달된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반환값 전달 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 실행을 끝내면 Caller 에게 반환값을 전달해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환값도 Calling Convention 에 따라 정해진 위치를 통해 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86 에서는 일반적으로 반환값이 eax 레지스터에 저장된다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov eax, 3
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 함수가 3 을 반환하는 것과 비슷한 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86-64 에서는 일반적으로 반환값이 rax 레지스터에 저장된다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, 3
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 함수 호출 후 Caller 는 eax 또는 rax 값을 확인해서 Callee 의 반환값을 얻을 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Cleanup&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86 Calling Convention 에서 중요한 차이점 중 하나는 스택 정리를 누가 담당하는지이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수 호출을 위해 스택에 인자를 push 했다면, 함수 호출이 끝난 뒤 해당 인자들을 스택에서 정리해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 스택 정리를 Caller 가 하느냐, Callee 가 하느냐에 따라 Calling Convention 이 달라진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대표적으로 cdecl 은 Caller 가 스택을 정리한다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push 2
push 1
call add
add esp, 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드에서 add esp, 8 은 Caller 가 스택에 넣었던 인자 2개를 정리하는 코드이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자 하나가 4바이트이고, 인자가 2개이기 때문에 총 8바이트를 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 stdcall 은 Callee 가 스택을 정리한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ret 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ret 8 은 Return Address 로 이동한 뒤, 추가로 스택에서 8바이트를 정리하는 명령어이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, cdecl 과 stdcall 의 큰 차이는 스택 정리를 누가 하느냐이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cdecl: Caller 가 스택 정리&lt;/li&gt;
&lt;li&gt;stdcall: Callee 가 스택 정리&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;cdecl&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cdecl 은 x86 에서 자주 사용되는 Calling Convention 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cdecl 의 특징은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인자는 오른쪽에서 왼쪽 순서로 스택에 push 한다.&lt;/li&gt;
&lt;li&gt;반환값은 eax 에 저장된다.&lt;/li&gt;
&lt;li&gt;스택 정리는 Caller 가 담당한다.&lt;/li&gt;
&lt;li&gt;가변 인자 함수를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 함수 호출이 있다고 하면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;add(1, 2);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;cdecl 에서는 대략 다음과 같은 형태가 된다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push 2
push 1
call add
add esp, 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Caller 가 인자를 스택에 넣고, 함수 호출 후 add esp, 8 로 직접 스택을 정리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;printf 같은 가변 인자 함수는 인자의 개수가 호출 시점마다 달라질 수 있기 때문에 Callee 가 스택을 정리하기 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 cdecl 처럼 Caller 가 스택을 정리하는 방식이 가변 인자 함수에 적합하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;stdcall&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stdcall 은 Windows API 에서 많이 사용되던 x86 Calling Convention 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stdcall 의 특징은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인자는 오른쪽에서 왼쪽 순서로 스택에 push 한다.&lt;/li&gt;
&lt;li&gt;반환값은 eax 에 저장된다.&lt;/li&gt;
&lt;li&gt;스택 정리는 Callee 가 담당한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 함수 호출이 있다고 하면&lt;/p&gt;
&lt;pre class=&quot;avrasm&quot;&gt;&lt;code&gt;push 2
push 1
call add
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Caller 쪽에서는 함수 호출 후 add esp, 8 같은 스택 정리 코드를 넣지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 Callee 함수가 반환할 때 다음과 같이 스택을 정리한다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ret 8
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, stdcall 은 Callee 가 인자 크기만큼 스택을 정리한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;fastcall&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fastcall 은 일부 인자를 스택이 아니라 레지스터로 전달해서 함수 호출 속도를 높이기 위한 Calling Convention 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86 에서는 레지스터 개수가 많지 않기 때문에 모든 인자를 레지스터로 전달하지는 못하고, 일부 인자만 레지스터로 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Microsoft fastcall 기준으로는 보통 첫 번째 인자와 두 번째 인자가 각각 ecx, edx 로 전달된다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;첫 번째 인자: ecx&lt;/li&gt;
&lt;li&gt;두 번째 인자: edx&lt;/li&gt;
&lt;li&gt;나머지 인자: 스택&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 함수 호출이 있다고 하면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;func(1, 2, 3);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대략 다음과 같은 형태로 인자가 전달될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov ecx, 1
mov edx, 2
push 3
call func
&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;thiscall&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;thiscall 은 C++ 의 멤버 함수 호출에서 사용되는 Calling Convention 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C++ 에서 클래스의 멤버 함수는 내부적으로 객체 자신을 가리키는 this 포인터를 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드가 있다고 하면&lt;/p&gt;
&lt;pre class=&quot;autoit&quot;&gt;&lt;code&gt;obj.func(1);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;func 는 멤버 함수이기 때문에 어떤 객체의 함수인지 알아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 객체 자신을 가리키는 포인터가 this 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86 Microsoft thiscall 기준으로는 보통 this 포인터가 ecx 레지스터로 전달된다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov ecx, obj
push 1
call func
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, thiscall 은 C++ 멤버 함수에서 this 포인터를 어떻게 전달할지 정하는 Calling Convention 이라고 볼 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;System V AMD64 ABI&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System V AMD64 ABI 는 Linux, macOS 등 Unix 계열 x86-64 환경에서 주로 사용되는 Calling Convention 이다.&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;rdi&lt;/li&gt;
&lt;li&gt;rsi&lt;/li&gt;
&lt;li&gt;rdx&lt;/li&gt;
&lt;li&gt;rcx&lt;/li&gt;
&lt;li&gt;r8&lt;/li&gt;
&lt;li&gt;r9&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환값은 보통 rax 로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자가 6개보다 많으면 나머지 인자는 스택을 통해 전달된다.&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;func(1, 2, 3, 4, 5, 6, 7);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System V AMD64 ABI 에서는 대략 다음과 같이 인자가 전달된다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rdi, 1
mov rsi, 2
mov rdx, 3
mov rcx, 4
mov r8, 5
mov r9, 6
push 7
call func
&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;MS x64 Calling Convention&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MS x64 Calling Convention 은 Windows x64 환경에서 사용되는 Calling Convention 이다.&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;rcx&lt;/li&gt;
&lt;li&gt;rdx&lt;/li&gt;
&lt;li&gt;r8&lt;/li&gt;
&lt;li&gt;r9&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반환값은 보통 rax 로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자가 4개보다 많으면 나머지 인자는 스택을 통해 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 함수 호출이 있다고 하면&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;func(1, 2, 3, 4, 5);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MS x64 Calling Convention 에서는 대략 다음과 같이 인자가 전달된다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rcx, 1
mov rdx, 2
mov r8, 3
mov r9, 4
push 5
call func
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫 번째부터 네 번째 인자까지는 레지스터로 전달되고, 다섯 번째 인자는 스택으로 전달된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;System V AMD64 ABI 와 MS x64 Calling Convention 은 둘 다 x86-64 환경에서 사용되지만, 인자를 전달하는 레지스터 순서가 다르다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;System V AMD64 ABI: rdi, rsi, rdx, rcx, r8, r9&lt;/li&gt;
&lt;li&gt;MS x64: rcx, rdx, r8, r9&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Caller-saved 와 Callee-saved Register&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Calling Convention 은 레지스터 보존 규칙도 정의한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수를 호출할 때 레지스터 값이 바뀔 수 있기 때문에, 어떤 레지스터를 누가 보존해야 하는지 정해둘 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;레지스터는 크게 Caller-saved Register 와 Callee-saved Register 로 나눌 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Caller-saved Register: Caller 가 필요하면 직접 저장해야 하는 레지스터&lt;/li&gt;
&lt;li&gt;Callee-saved Register: Callee 가 사용하기 전에 저장하고, 반환 전에 복구해야 하는 레지스터&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Caller-saved Register 는 함수 호출 이후 값이 바뀌어도 이상하지 않은 레지스터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Caller 가 해당 레지스터 값을 함수 호출 이후에도 사용해야 한다면, 함수 호출 전에 직접 저장해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Callee-saved Register 는 Callee 가 값을 바꾸면 안 되는 레지스터이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Callee 가 해당 레지스터를 사용해야 한다면, 함수 시작 시점에 기존 값을 저장하고 함수 종료 전에 원래 값으로 복구해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 Callee 가 ebx 를 사용해야 한다면 다음과 같은 형태가 될 수 있다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push ebx
mov ebx, 1
pop ebx
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 규칙이 있기 때문에 Caller 와 Callee 는 서로의 레지스터 사용을 예측할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Calling Convention 이 중요한 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Calling Convention 은 단순히 함수 호출 방식만 정하는 것이 아니라, 바이너리 분석과 익스플로잇에서도 매우 중요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;리버싱을 할 때 Calling Convention 을 알면 다음과 같은 정보를 파악할 수 있다.&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;li&gt;함수 호출 후 스택이 어떻게 정리되는지&lt;/li&gt;
&lt;li&gt;함수가 어떤 레지스터를 보존해야 하는지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 x86 바이너리에서 함수 호출 후 add esp, 0x10 이 보이면, Caller 가 스택을 정리하고 있으므로 cdecl 방식일 가능성이 있다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push 4
push 3
push 2
push 1
call func
add esp, 0x10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반대로 함수가 ret 0x10 으로 끝난다면, Callee 가 스택을 정리하는 방식일 가능성이 있다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;ret 0x10
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 x86-64 바이너리에서 함수 호출 직전에 rdi, rsi, rdx 등에 값이 들어가는 것을 보면 System V AMD64 ABI 환경일 가능성이 있다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rdi, 1
mov rsi, 2
mov rdx, 3
call func
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Windows x64 바이너리에서는 함수 호출 직전에 rcx, rdx, r8, r9 가 사용되는 경우가 많다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rcx, 1
mov rdx, 2
mov r8, 3
mov r9, 4
call func
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Calling Convention 을 알면 어셈블리 코드에서 함수의 구조와 데이터 흐름을 더 정확하게 분석할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Calling Convention 은 함수 호출과 반환에 대한 약속이다.&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;인자를 어디로 전달할지&lt;/li&gt;
&lt;li&gt;반환값을 어디로 전달할지&lt;/li&gt;
&lt;li&gt;스택 정리를 누가 할지&lt;/li&gt;
&lt;li&gt;어떤 레지스터를 보존해야 할지&lt;/li&gt;
&lt;li&gt;Return Address 와 Stack Frame 을 어떻게 관리할지&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x86 에서는 주로 스택을 통해 인자를 전달하고, x86-64 에서는 주로 레지스터를 통해 인자를 전달한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 레지스터의 개수는 제한되어 있기 때문에 인자가 많아지면 스택도 함께 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Calling Convention 을 이해하면 C 코드가 어셈블리에서 어떻게 함수 호출로 변환되는지 이해할 수 있고, 리버싱이나 바이너리 익스플로잇을 할 때 함수의 인자, 반환값, 스택 구조를 분석하는 데 도움이 된다.&lt;/p&gt;</description>
      <category>공부한 것</category>
      <category>Calling Convention</category>
      <category>Compiler</category>
      <category>컴파일러</category>
      <category>함수 호출 규약</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/117</guid>
      <comments>https://hsnyus.tistory.com/117#entry117comment</comments>
      <pubDate>Tue, 19 May 2026 02:54:33 +0900</pubDate>
    </item>
    <item>
      <title>Stack Canary</title>
      <link>https://hsnyus.tistory.com/116</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Canary&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스택 카나리(Stack Canary): 스택 버퍼 오버플로우를 탐지하기 위해 함수의 스택 프레임에 삽입되는 보호 값.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Canary 보호 기법&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary는 함수가 실행될 때 스택에 임의의 값을 넣어두고, 함수가 종료되기 전에 해당 값이 변조되었는지 확인하는 보호 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버퍼 오버플로우가 발생하면 일반적으로 지역 변수 영역을 넘어 SFP, Return Address 까지 덮어쓰게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Return Address 앞에 Canary 값을 배치해두면, Return Address가 덮이기 전에 Canary 값이 먼저 변조된다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;char buf[0x30];

read(0, buf, 0x100);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 C언어 코드는 buf의 크기보다 더 많은 데이터를 입력받기 때문에 Stack Buffer Overflow가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary가 적용되지 않은 경우 스택 구조는 다음과 같다.&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;Return Address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;SFP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;buf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;낮은 주소&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary가 적용된 경우 스택 구조는 다음과 같다.&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;Return Address&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;SFP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;Canary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;td&gt;buf&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;낮은 주소&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, buf를 넘어서 Return Address를 덮으려면 Canary 값을 먼저 덮게 된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Stack Canary가 없는 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void vuln() {
	char buf[0x30];

	read(0, buf, 0x100);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push rbp
mov rbp, rsp
sub rsp, 0x30

lea rax, [rbp-0x30]
mov edx, 0x100
mov rsi, rax
mov edi, 0x0
call read

leave
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드는 buf에 0x100 만큼 입력받지만 buf의 크기는 0x30 이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 buf를 넘어 SFP, Return Address 까지 덮을 수 있다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Stack Canary가 있는 경우&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;void vuln() {
	char buf[0x30];

	read(0, buf, 0x100);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push rbp
mov rbp, rsp
sub rsp, 0x40

mov rax, QWORD PTR fs:0x28
mov QWORD PTR [rbp-0x8], rax
xor eax, eax

lea rax, [rbp-0x40]
mov edx, 0x100
mov rsi, rax
mov edi, 0x0
call read

mov rax, QWORD PTR [rbp-0x8]
sub rax, QWORD PTR fs:0x28
je .L1
call __stack_chk_fail

.L1:
leave
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary가 적용되면 함수 시작 부분에서 Canary 값을 스택에 저장한다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, QWORD PTR fs:0x28
mov QWORD PTR [rbp-0x8], rax
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 함수가 종료되기 전에 스택에 저장된 Canary 값과 원래 Canary 값을 비교한다.&lt;/p&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, QWORD PTR [rbp-0x8]
sub rax, QWORD PTR fs:0x28
je .L1
call __stack_chk_fail
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Canary 값이 변조되었다면 __stack_chk_fail 함수가 호출되고 프로그램이 종료된다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Stack Canary 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;checksec ./vuln
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Canary                        : ✓
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Canary가 활성화되어 있으면 Stack Canary 보호 기법이 적용된 것이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Stack Canary 컴파일 옵션&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary는 gcc의 stack protector 옵션으로 적용할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;gcc -fstack-protector -o vuln vuln.c
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;gcc -fstack-protector-all -o vuln vuln.c
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;gcc -fno-stack-protector -o vuln vuln.c
&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;-fstack-protector&lt;/td&gt;
&lt;td&gt;일부 위험한 함수에 Stack Canary 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-fstack-protector-all&lt;/td&gt;
&lt;td&gt;모든 함수에 Stack Canary 적용&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;-fno-stack-protector&lt;/td&gt;
&lt;td&gt;Stack Canary 비활성화&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Stack Canary 예제 코드&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;#include &amp;lt;unistd.h&amp;gt;

void vuln() {
	char buf[0x30];

	read(0, buf, 0x100);
}

int main() {
	vuln();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 코드를 Stack Canary가 적용된 상태로 컴파일해보자.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;gcc -fstack-protector-all -o canary canary.c
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 입력을 길게 넣으면 Canary 값이 변조되어 프로그램이 종료된다.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;python3 -c 'print(&quot;A&quot; * 100)' | ./canary
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;*** stack smashing detected ***: terminated
Aborted
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이는 buf를 넘어서 입력한 값이 Canary를 덮었고, 함수가 종료될 때 Canary 값 검증에 실패했기 때문이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Canary의 동작 과정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary는 다음과 같은 순서로 동작한다.&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;/ol&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push rbp
mov rbp, rsp
sub rsp, 0x40
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;원래 Canary 값을 가져온다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, QWORD PTR fs:0x28
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가져온 Canary 값을 스택에 저장한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov QWORD PTR [rbp-0x8], rax
&lt;/code&gt;&lt;/pre&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;/ol&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;lea rax, [rbp-0x40]
mov edx, 0x100
mov rsi, rax
mov edi, 0x0
call read
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;함수가 종료되기 전에 Canary 값을 비교한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rax, QWORD PTR [rbp-0x8]
sub rax, QWORD PTR fs:0x28
je .L1
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Canary 값이 다르면 프로그램을 종료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;sqf&quot;&gt;&lt;code&gt;call __stack_chk_fail
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Canary 값이 같으면 정상적으로 함수가 종료된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;leave
ret
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Canary 우회&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary가 적용되어 있으면 단순히 Return Address를 덮는 방식의 BOF 공격은 실패한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Return Address를 덮기 전에 Canary 값이 같이 덮이기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Stack Canary를 우회하려면 Canary 값을 알아내고, 기존 Canary 값을 그대로 유지한 채 Return Address를 덮어야한다.&lt;/p&gt;
&lt;pre class=&quot;ini&quot;&gt;&lt;code&gt;payload = padding + canary + sfp + ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 buf의 크기가 0x30 이고 Canary가 존재하는 경우 payload 구조는 다음과 같다.&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;buf&lt;/td&gt;
&lt;td&gt;padding&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Canary&lt;/td&gt;
&lt;td&gt;원래 Canary 값&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SFP&lt;/td&gt;
&lt;td&gt;dummy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Return Address&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;즉, Canary 값을 모른 상태에서 Return Address를 덮으면 프로그램은 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Canary 값을 알고 있다면 다음과 같이 payload를 구성할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;makefile&quot;&gt;&lt;code&gt;payload = b&quot;A&quot; * 0x30
payload += canary
payload += b&quot;B&quot; * 0x8
payload += ret
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 payload는 Canary 값을 변조하지 않고 그대로 넣어주기 때문에 Canary 검사를 통과할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Canary Leak&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary를 우회하기 위해서는 일반적으로 Canary Leak이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Canary Leak은 프로그램의 출력 기능이나 포맷 스트링 취약점 등을 통해 Canary 값을 알아내는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 다음과 같은 코드가 있다고 하자.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;#include &amp;lt;stdio.h&amp;gt;
#include &amp;lt;unistd.h&amp;gt;

void vuln() {
	char buf[0x30];

	read(0, buf, 0x100);
	printf(&quot;%s&quot;, buf);
}

int main() {
	vuln();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;buf에 널 바이트가 없도록 입력하면, printf가 buf 이후의 스택 값까지 출력할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 Canary 값이 출력되면 해당 값을 이용해 BOF를 수행할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Canary는 일반적으로 마지막 1바이트가 NULL이다.&lt;/p&gt;
&lt;pre class=&quot;basic&quot;&gt;&lt;code&gt;00 ?? ?? ?? ?? ?? ?? ??
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 문자열 출력 함수로 Canary를 Leak 할 때는 NULL 바이트 때문에 바로 출력되지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우 Canary 앞까지 채운 뒤, NULL 바이트를 덮어서 뒤의 Canary 일부를 출력하게 만드는 방식이 사용될 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Canary의 한계&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary는 Return Address 변조를 탐지하는 보호 기법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 모든 공격을 막는 것은 아니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한계 설명&lt;/p&gt;
&lt;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;Canary Leak&lt;/td&gt;
&lt;td&gt;Canary 값을 알아내면 우회 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Non-control-data Attack&lt;/td&gt;
&lt;td&gt;Return Address를 건드리지 않는 공격은 탐지 어려움&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Heap Overflow&lt;/td&gt;
&lt;td&gt;스택이 아닌 힙 영역 오버플로우는 직접적으로 방어하지 못함&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Format String Bug&lt;/td&gt;
&lt;td&gt;Canary 값을 유출할 수 있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Stack Pivot&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;즉, Stack Canary는 BOF 공격을 어렵게 만들지만 완전히 막지는 못한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Stack Canary의 컴파일, 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary를 확인하기 위해 취약한 C언어 코드를 작성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 Stack Canary가 적용된 코드와, Stack Canary가 적용되지 않은 코드이다.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;#include &amp;lt;unistd.h&amp;gt;

void vuln() {
	char buf[0x30];

	read(0, buf, 0x100);
}

int main() {
	vuln();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary를 적용해서 컴파일한다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;gcc -fstack-protector-all -o canary_on canary.c
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary를 비활성화해서 컴파일한다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;gcc -fno-stack-protector -o canary_off canary.c
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각 checksec으로 확인한다.&lt;/p&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;checksec ./canary_on
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Canary                        : ✓
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;checksec ./canary_off
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;Canary                        : ✘
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary가 적용된 바이너리에 긴 입력을 넣어보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;python3 -c 'print(&quot;A&quot; * 100)' | ./canary_on
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;*** stack smashing detected ***: terminated
Aborted
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary가 적용되지 않은 바이너리에 긴 입력을 넣어보자.&lt;/p&gt;
&lt;pre class=&quot;swift&quot;&gt;&lt;code&gt;python3 -c 'print(&quot;A&quot; * 100)' | ./canary_off
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;Segmentation fault
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary가 적용된 경우 Canary 검증에 실패하여 __stack_chk_fail 이 호출된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary가 적용되지 않은 경우 Return Address가 변조되어 Segmentation fault가 발생할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stack Canary는 스택 버퍼 오버플로우 공격을 탐지하기 위해 Return Address 앞에 삽입되는 보호 값이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수가 시작될 때 Canary 값을 스택에 저장하고, 함수가 종료될 때 원래 값과 비교한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Canary 값이 변조되었다면 __stack_chk_fail 이 호출되고 프로그램은 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Canary 값을 Leak 할 수 있다면 원래 Canary 값을 payload에 포함하여 보호 기법을 우회할 수 있다.&lt;/p&gt;</description>
      <category>공부한 것</category>
      <category>Canary</category>
      <category>pwnable</category>
      <category>Stack</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/116</guid>
      <comments>https://hsnyus.tistory.com/116#entry116comment</comments>
      <pubDate>Mon, 18 May 2026 00:27:33 +0900</pubDate>
    </item>
    <item>
      <title>Shellcode - orw Shellcode</title>
      <link>https://hsnyus.tistory.com/115</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Shellcode&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;셸코드(Shellcode): 익스플로잇을 위해 제작된 어셈블리 코드 조각.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;orw Shellcode&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;orw 셸코드: 파일을 열고, 읽고 화면에 출력하는 셸코드.&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;char buf[0x30];

int fd = open(&quot;/tmp/flag&quot;, RD_ONLY, NULL);
read(fd, buf, 0x30);
write(1, buf, 0x30);
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 C언어 코드를 셸코드로 구현해보자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;orw Shellcode를 작성하기 위해 필요한 syscall은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;syscall rax rdi rsi 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;read&lt;/td&gt;
&lt;td&gt;0x00&lt;/td&gt;
&lt;td&gt;unsigned int fd&lt;/td&gt;
&lt;td&gt;chat *buf&lt;/td&gt;
&lt;td&gt;size_t count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;write&lt;/td&gt;
&lt;td&gt;0x01&lt;/td&gt;
&lt;td&gt;unsigned int fd&lt;/td&gt;
&lt;td&gt;const char *buf&lt;/td&gt;
&lt;td&gt;size_t count&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;open&lt;/td&gt;
&lt;td&gt;0x02&lt;/td&gt;
&lt;td&gt;const char *filename&lt;/td&gt;
&lt;td&gt;int flags&lt;/td&gt;
&lt;td&gt;umode_mode&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;int fd = open(&amp;rdquo;/tmp/flag&amp;rdquo;, O_RDONLY, NULL)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;int fd = open(&quot;/tmp/flag&quot;, O_RDONLY, NULL);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push 0x67
mov rax, 0x616c662f706d742f
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;read(fd, buf, 0x30)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;read(fd, buf, 0x30)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rdi, rax
mov rsi, rsp
sub rsi, 0x30
mov rdx, 0x30
mov rax, 0x0
syscall
&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;write(1, buf, 0x30)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;write(1, buf, 0x30)
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;mov rdi, 1
mov rax, 0x1
syscall
&lt;/code&gt;&lt;/pre&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;/ol&gt;
&lt;pre class=&quot;x86asm&quot;&gt;&lt;code&gt;push 0x67
mov rax, 0x616c662f706d742f
push rax
mov rdi, rsp
xor rsi, rsi
xor rdx, rdx
mov rax, 2
syscall

mov rdi, rax
mov rsi, rsp
sub rsi, 0x30
mov rdx, 0x30
mov rax, 0x0
syscall

mov rdi, 1
mov rax, 0x1
syscall
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;orw 셸코드의 컴파일, 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성한 orw Shellcode 를 gcc를 사용해 컴파일하려면 C언어 코드로 만들어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;C언어로 셸코드 실행이 가능한 스켈레톤 코드를 작성하고 거기에 Shellcode 를 넣는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 스켈레톤 코드와, orw Shellcode 를 넣은 스켈레톤 코드이다.&lt;/p&gt;
&lt;pre class=&quot;cpp&quot;&gt;&lt;code&gt;__asm__();

void run_sh();

int main() {
	run_sh();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;lsl&quot;&gt;&lt;code&gt;__asm__(
	&quot;.global run_sh\\n&quot;
	&quot;run_sh:\\n&quot;
	
	&quot;push 0x67\\n&quot;
	&quot;mov rax, 0x616c662f706d742f \\n&quot;
	&quot;push rax\\n&quot;
	&quot;mov rdi, rsp\\n&quot;
	&quot;xor rsi, rsi\\n&quot;
	&quot;xor rdx, rdx\\n&quot;
	&quot;mov rax, 2\\n&quot;
	&quot;syscall\\n&quot;
	&quot;\\n&quot;
	&quot;mov rdi, rax\\n&quot;
	&quot;mov rsi, rsp\\n&quot;
	&quot;sub rsi, 0x30\\n&quot;
	&quot;mov rdx, 0x30\\n&quot;
	&quot;mov rax, 0x0\\n&quot;
	&quot;syscall\\n&quot;
	&quot;\\n&quot;
	&quot;mov rdi, 1\\n&quot;
	&quot;mov rax, 0x1\\n&quot;
	&quot;syscall\\n&quot;
	&quot;\\n&quot;
	&quot;xor rdi, rdi\\n&quot;
	&quot;mov rax, 0x3c\\n&quot;
	&quot;syscall&quot;);

void run_sh();

int main() {
	run_sh();
}
&lt;/code&gt;&lt;/pre&gt;</description>
      <category>공부한 것</category>
      <category>orw</category>
      <category>pwnable</category>
      <category>shellcode</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/115</guid>
      <comments>https://hsnyus.tistory.com/115#entry115comment</comments>
      <pubDate>Sun, 17 May 2026 18:29:12 +0900</pubDate>
    </item>
    <item>
      <title>CVE-2026-28466</title>
      <link>https://hsnyus.tistory.com/114</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CVSS: 9.9&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대상 서비스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenClaw&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;게이트웨이와 노드 간 WebSocket 기반 통신을 통해 명령 실행, 도구 호출, 원격 작업을 처리하는 시스템&lt;/li&gt;
&lt;li&gt;연결된 노드 호스트에 대해 system.run 같은 명령 실행 기능을 제공함&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;대상 버전&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영향 받는 버전: 2026.2.14 이전의 OpenClaw 버전&lt;/li&gt;
&lt;li&gt;패치 버전: 2026.2.14 이후 수정 버전&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 개요&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;RCE&lt;/li&gt;
&lt;li&gt;인증 후 원격 코드 실행&lt;/li&gt;
&lt;li&gt;승인 우회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;node.invoke 호출 시 내부 승인 필드가 정화되지 않아 실행 승인 게이트를 우회할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;파라미터 인젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자가 node.invoke의 params 내부에 승인 제어 필드를 삽입할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;OpenClaw 게이트웨이의 node.invoke 전달 로직에 설계적 결함이 있었음.&lt;/li&gt;
&lt;li&gt;인증된 클라이언트가 WebSocket을 통해 node.invoke 요청을 전송하면, 게이트웨이는 이를 처리기로 매핑한 뒤 AJV 검증을 수행함.&lt;/li&gt;
&lt;li&gt;그런데 해당 검증에서 params가 Type.Unknown()으로 정의되어 있어 내부 구조가 검증되지 않음.&lt;/li&gt;
&lt;li&gt;그 결과 공격자가 params 내부에 approved, approvalDecision 같은 내부 승인용 필드를 포함시켜도 그대로 유효한 요청으로 통과함.&lt;/li&gt;
&lt;li&gt;이후 node.invoke 처리기에서는 명령 권한 여부를 검사하지만, p.params 내부 내용에 대해서는 별도의 필터링이나 제거를 수행하지 않음.&lt;/li&gt;
&lt;li&gt;따라서 공격자가 주입한 승인 제어 필드가 원형 그대로 노드 레지스트리로 전달되고, JSON 직렬화를 거쳐 대상 노드로 전송됨.&lt;/li&gt;
&lt;li&gt;노드 측에서는 전달받은 JSON을 다시 파싱한 뒤 승인 여부를 확인하는데, approved === true이거나 approvalDecision 값이 존재하면 명령 실행을 허용하는 구조였음.&lt;/li&gt;
&lt;li&gt;특히 approvalDecision === &quot;allow-always&quot;인 경우 영구 허용 목록에 추가되는 동작까지 가능했음.&lt;/li&gt;
&lt;li&gt;즉, 원래는 게이트웨이 내부 승인 절차를 통해서만 설정되어야 할 필드를, 클라이언트가 직접 주입할 수 있었고, 게이트웨이가 이를 정화하지 않은 채 신뢰 경계를 넘어 전달했기 때문에 승인 우회형 RCE가 발생함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 원리&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;공격자 &amp;rarr; 게이트웨이에 인증된 클라이언트 자격으로 WebSocket 연결
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;node.invoke 요청 전송&lt;/li&gt;
&lt;li&gt;params 내부에 승인 제어 필드 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버 측 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;WebSocket 연결 수락&lt;/li&gt;
&lt;li&gt;연결 ID 생성&lt;/li&gt;
&lt;li&gt;attachGatewayWsMessageHandler가 후속 메시지 처리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;핸들러 매핑
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;extraHandlers를 통해 node.invoke 문자열이 GatewayRequestHandlers의 처리기로 매핑됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;요청 검증
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AJV 검증기가 요청을 검사하지만 params가 Type.Unknown()이라 내부 구조를 검증하지 않음&lt;/li&gt;
&lt;li&gt;공격자가 삽입한 승인 필드가 그대로 통과함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;node.invoke 처리기 실행
&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;그러나 p.params 내부 승인 필드는 제거하거나 차단하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;노드로 전달
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드 레지스트리가 해당 요청을 JSON 직렬화 후 대상 노드로 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;노드 측 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드가 JSON을 파싱해 params를 복원&lt;/li&gt;
&lt;li&gt;approved === true 또는 approvalDecision 존재 여부를 기준으로 승인된 요청으로 처리&lt;/li&gt;
&lt;li&gt;system.run 명령 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 이미 인증된 게이트웨이 클라이언트 자격을 보유하고 있는 시점에서, node.invoke 요청을 직접 구성해 보낼 수 있음. 이때 요청 본문의 params 내부에 원래 내부 승인 절차에서만 설정되어야 하는 approved: true, approvalDecision: &quot;allow-always&quot; 같은 필드를 함께 넣어 전송하면, 게이트웨이는 이를 별도로 제거하지 않고 그대로 받아들임.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 게이트웨이의 AJV 검증은 params를 구조적으로 검사하지 않기 때문에, 공격자가 삽입한 승인 필드는 그대로 유효한 요청처럼 통과함. node.invoke 처리기에서도 명령 자체가 허용 목록에 있는지만 확인할 뿐, 내부 파라미터에 포함된 승인 제어 필드는 건드리지 않음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 해당 승인 필드는 노드 레지스트리를 통해 JSON 형태로 대상 노드에 전달되고, 노드 측에서는 이를 신뢰한 채 승인된 실행 요청으로 처리하게 됨. 결국 공격자는 정상적인 승인 절차를 거치지 않고도 system.run 명령을 실행시킬 수 있으며, 연결된 개발자 워크스테이션이나 구성 실행기 같은 노드 호스트에서 임의 명령 실행이 가능해짐.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이후 패치 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;OpenClaw 2026.2.14&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;게이트웨이에서 node.invoke 전달 파라미터를 정화하도록 수정되었음.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sanitizeNodeInvokeParamsForForwarding 함수 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;노드로 전달되는 params에서 허용된 화이트리스트 필드만 남기도록 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;내부 승인 필드 직접 주입 차단
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 approved, approvalDecision 같은 승인 제어 필드를 임의로 넣어도 전달되지 않도록 수정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;승인 ID 바인딩 강화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;승인 ID가 요청 디바이스와 결합되도록 하여 다른 장치에서 재사용하거나 재생하는 공격을 방지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;승인 결정 강제 제한
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;승인 결정을 allow-once 수준으로 강등해 임의의 영구 허용 설정 주입을 막음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 패치 이후에는 게이트웨이가 클라이언트 입력을 그대로 신뢰하지 않고, 노드로 전달하기 전에 승인 관련 내부 필드를 정화하는 구조로 변경되어 승인 우회형 RCE가 차단되었음.&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;GHSA: GHSA-gv46-4xfq-jv58&lt;/li&gt;
&lt;li&gt;관련 커밋: 0af76f5f0e93540efbdf054895216c398692afcd&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부한 것</category>
      <category>1-day</category>
      <category>cve</category>
      <category>OpenClaw</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/114</guid>
      <comments>https://hsnyus.tistory.com/114#entry114comment</comments>
      <pubDate>Thu, 14 May 2026 02:34:41 +0900</pubDate>
    </item>
    <item>
      <title>CVE-2026-26954</title>
      <link>https://hsnyus.tistory.com/113</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CVSS: 10.0&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대상 서비스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SandboxJS&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JavaScript 코드를 격리된 환경에서 실행하기 위한 JavaScript 샌드박스 라이브러리&lt;/li&gt;
&lt;li&gt;호스트 전역 객체 및 위험한 내장 객체에 대한 접근을 제한해 비신뢰 코드 실행 환경을 제공하는 목적의 라이브러리&lt;/li&gt;
&lt;li&gt;내부적으로 허용된 프로토타입과 속성 집합, 래퍼 객체, taint 추적, 실행 제한 등을 기반으로 샌드박스 경계를 유지함&lt;/li&gt;
&lt;li&gt;Node.js 환경 등에서 동적 코드 실행, 플러그인 처리, 사용자 스크립트 격리 용도로 사용될 수 있음&lt;/li&gt;
&lt;/ul&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;영향 받는 버전: SandboxJS 0.8.34 이전 모든 버전&lt;/li&gt;
&lt;li&gt;패치 버전: SandboxJS 0.8.34&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 개요&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;Sandbox Escape&lt;/li&gt;
&lt;li&gt;RCE&lt;/li&gt;
&lt;li&gt;비인증 임의 코드 실행 가능 구조&lt;/li&gt;
&lt;li&gt;taint 추적 우회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;위험한 호스트 함수가 배열 및 객체 재구성 과정에서 안전한 값처럼 오인됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Function 생성자 노출
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트 Function 생성자 참조를 획득할 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Promise 체인 기반 실행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Promise.prototype.finally와 재구성된 then 속성을 이용해 호스트 전역 컨텍스트에서 코드 실행 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;SandboxJS의 taint 추적 메커니즘에 구조적 결함이 있었음.&lt;/li&gt;
&lt;li&gt;공격자가 Object.values 같은 내장 메서드를 이용해 위험한 호스트 함수 참조를 새 배열로 옮기는 과정에서, 샌드박스 엔진이 배열 내부 원소까지 taint를 재귀적으로 전파하지 못함.&lt;/li&gt;
&lt;li&gt;그 결과 호스트 Function 생성자가 배열 접근과 Object.fromEntries 재구성 과정을 거친 뒤 위험 객체가 아닌 일반 값처럼 취급됨.&lt;/li&gt;
&lt;li&gt;SandboxExec.ts의 SAFE_PROTOTYPES 설정에서 Object.values, Object.fromEntries 등이 허용되어 있어 공격자가 이 데이터 변환 경로를 정상 기능처럼 사용할 수 있음.&lt;/li&gt;
&lt;li&gt;executor.ts의 LispType.Call 처리에서는 원생성 메서드를 직접 호출한 뒤 getGlobalProp(ret, context) || ret 형태의 얕은 정리만 수행하는데, 이 과정에서 반환값이 배열이나 객체인 경우 내부 원소나 속성은 검사하지 않음.&lt;/li&gt;
&lt;li&gt;이후 LispType.CreateArray 처리에서 valueOrProp(item, context)로 래퍼를 해제하면서도, 해제된 값이 위험한 호스트 참조인지 다시 검증하지 않음.&lt;/li&gt;
&lt;li&gt;이로 인해 누출된 호스트 Function 참조가 샌드박스 내부에서 &amp;ldquo;일반 함수 참조&amp;rdquo;로 가공되어 최종적으로 샌드박스 탈출이 가능해짐.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 원리&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;공격자 &amp;rarr; 샌드박스 내부 코드에서 Object.values(this) 호출
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;호스트 전역 객체에 매핑된 값들 중 Function 같은 위험한 생성자 참조가 포함된 배열을 획득&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;샌드박스 내부 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;executor.ts의 LispType.Call이 원생성 Object.values를 직접 호출&lt;/li&gt;
&lt;li&gt;반환된 배열 전체에 대해서만 얕은 검사 수행&lt;/li&gt;
&lt;li&gt;배열 내부의 위험한 함수 참조는 정리되지 않은 채 그대로 반환됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 형태 변환
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자가 배열 전개 및 Object.fromEntries를 사용해 { then: Function } 형태의 객체를 생성&lt;/li&gt;
&lt;li&gt;이 과정에서도 내부 값에 대한 재귀 정화가 일어나지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Promise 체인 악용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;네이티브 Promise.prototype.finally를 가진 객체와 재구성된 then 속성을 조합&lt;/li&gt;
&lt;li&gt;finally 실행 시 호스트 엔진이 객체의 then 속성을 직접 읽어들임&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;호스트 Function 생성자 호출
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;then이 실제 호스트 Function 생성자를 가리키므로, 전달된 문자열이 전역 호스트 컨텍스트에서 실행됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;샌드박스 탈출 및 임의 코드 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 샌드박스 코드에서 Object.values(...)를 실행하면, SAFE_PROTOTYPES 설정상 이 호출은 허용됨. 이후 executor.ts는 호스트 환경의 원생성 Object.values를 직접 호출하고, 그 반환값을 getGlobalProp(ret, context) || ret로 처리함. 하지만 이 검사는 반환된 배열 자체만 보며, 배열 안에 포함된 Function 같은 위험한 호스트 참조까지 정리하지 않음. 그 결과 호스트 Function이 들어 있는 더러운 배열이 샌드박스 변수로 들어오게 됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 공격자가 배열 전개나 비슷한 동작을 수행하면 LispType.CreateArray가 실행되고, 여기서 각 원소는 valueOrProp(item, context)를 통해 해제됨. 문제는 이 단계에서도 해제된 값이 위험한 호스트 전역 객체인지 다시 검사하지 않는다는 점임. 즉, 한 번 누출된 호스트 Function은 더 이상 위험 값으로 취급되지 않고 샌드박스 내부의 정상 값처럼 유통됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 취약점의 실제 악용에서는 Object.fromEntries([['then', ...Object.values(this).slice(1)]])를 사용해 배열 원소를 객체 속성으로 재구성함. 이렇게 하면 호스트 Function 생성자가 then 속성에 매달린 평범한 객체처럼 보이게 됨. 이어서 네이티브 Promise.prototype.finally를 가진 객체와 이를 합쳐 finally를 호출하면, 호스트 엔진은 샌드박스 바깥에서 해당 객체의 then 속성을 읽고 이를 호출함. 이때 then이 실제 호스트 Function 생성자이므로 결과적으로 Function('악성코드')가 실행되고, 코드가 호스트 전역 스코프에서 수행되어 샌드박스가 완전히 무력화됨.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이후 패치 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;SandboxJS 0.8.34&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;취약점은 0.8.34 버전에서 수정됨&lt;/li&gt;
&lt;li&gt;호스트 Function 참조가 배열/객체 재구성 과정에서 안전한 값처럼 남는 문제를 차단&lt;/li&gt;
&lt;li&gt;실행 제한과 관련된 전역/외부 의존 상태를 줄이는 방향의 수정 포함&lt;/li&gt;
&lt;li&gt;구체적으로 ticks || currentTicks.current 같은 fallback 로직이 제거되고, 고정된 context.ctx.ticks를 사용하도록 변경됨&lt;/li&gt;
&lt;li&gt;기존 구조에서는 외부에서 전달된 ticks 또는 전역 수준의 currentTicks.current에 의존할 수 있어 샌드박스 실행 예산이 외부 상태에 의해 영향을 받을 여지가 있었음&lt;/li&gt;
&lt;li&gt;수정 후에는 실행 제한이 샌드박스 생성 시점의 context에 고정되어 함수 생성 단계에서 외부에 의해 바뀌지 않도록 개선됨&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부한 것</category>
      <category>1-day</category>
      <category>cve</category>
      <category>RCE</category>
      <category>Sandbox Escape</category>
      <category>SandboxJS</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/113</guid>
      <comments>https://hsnyus.tistory.com/113#entry113comment</comments>
      <pubDate>Wed, 13 May 2026 04:02:27 +0900</pubDate>
    </item>
    <item>
      <title>CVE-2026-3672</title>
      <link>https://hsnyus.tistory.com/112</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CVSS: 7.8&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대상 서비스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JeecgBoot&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java 기반의 저코드/엔터프라이즈 개발 프레임워크&lt;/li&gt;
&lt;li&gt;코드 생성기, 권한 관리, 딕셔너리, 온라인 개발 등 백오피스 성격의 기능을 통합 제공함&lt;/li&gt;
&lt;li&gt;Spring Boot, MyBatis-Plus, Vue 기반으로 구성되며 국내외에서 빠른 업무 시스템 구축 용도로 사용됨&lt;/li&gt;
&lt;li&gt;내부적으로 WAF 성격의 SQL 필터링 로직을 포함하고 있어 일부 동적 쿼리 입력에 대한 방어를 수행함&lt;/li&gt;
&lt;/ul&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;영향 받는 버전: JeecgBoot 3.9.0, 3.9.1&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 개요&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;SQL Injection&lt;/li&gt;
&lt;li&gt;인증 후 SQL 인젝션&lt;/li&gt;
&lt;li&gt;WAF 우회
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JeecgBoot 내장 WAF의 정규식 및 블랙리스트 검증 로직을 우회할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;동적 SQL 인젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;외부 입력이 filterSql로 해석되어 SQL의 where 절 뒤에 직접 결합됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;블라인드 SQL 인젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답 차이를 기반으로 테이블명, 컬럼 존재 여부, 데이터 값을 추출할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;GET /sys/dict/getDictItems/{dictCode} 또는 관련 API 경로로 전달된 dictCode 값이 서비스 계층으로 직접 전달되며, 특정 형식의 입력에 대해서는 네 번째 세그먼트가 필터 조건으로 해석됨.&lt;/li&gt;
&lt;li&gt;SysDictServiceImpl.java에서 4단 형식의 dictCode는 queryTableDictItemsByCodeAndFilter(params[0], params[1], params[2], params[3]) 경로로 처리되며, 여기서 네 번째 값이 ${filterSql}로 전달됨.&lt;/li&gt;
&lt;li&gt;이후 SysDictMapper.xml에서 where ${filterSql} 형태로 동적 SQL 문자열 결합이 수행되어, 외부 입력이 SQL 구문 일부로 직접 반영되는 구조임.&lt;/li&gt;
&lt;li&gt;원래는 SQL 인젝션 방지를 위한 필터링 로직이 존재하지만, 일반 검사 로직이 아니라 specialFilterContentForDictSql이라는 딕셔너리 전용 검사 로직을 타게 됨.&lt;/li&gt;
&lt;li&gt;이 딕셔너리 전용 블랙리스트에는 일반 블랙리스트에 포함된 and, or가 빠져 있어 논리 조건 확장이 가능함.&lt;/li&gt;
&lt;li&gt;또한 정규식 기반 탐지 로직이 단순 키워드 포함 여부가 아니라 특정 공백, 비공백 문자열, 특수문자 조합에 의존하는 방식이라 구문 배치만 바꾸면 우회가 가능함.&lt;/li&gt;
&lt;li&gt;즉, 외부 입력을 SQL 조각으로 직접 받아들이는 구조와 불완전한 WAF 로직이 결합되어 SQL 인젝션이 발생함.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 원리&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;공격자 &amp;rarr; 취약한 딕셔너리 조회 엔드포인트로 요청 전송
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dictCode를 4단 형식으로 구성&lt;/li&gt;
&lt;li&gt;네 번째 항목에 SQL 조건식 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버 측 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;컨트롤러가 dictCode를 그대로 서비스 계층으로 전달함&lt;/li&gt;
&lt;li&gt;서비스 계층이 이를 분해한 뒤 네 번째 값을 필터 조건으로 해석함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;WAF 검사 수행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 SQL 인젝션 검사 로직이 아니라 딕셔너리 전용 검사 로직이 적용됨&lt;/li&gt;
&lt;li&gt;and, or 누락 및 정규식 결함으로 인해 필터 우회 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SQL 조립
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Mapper에서 where ${filterSql} 형태로 동적 SQL 결합 수행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;블라인드 SQL 인젝션 수행
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;참/거짓 응답 차이를 기반으로 데이터베이스 정보 추출 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 인증된 일반 사용자 권한만 가지고 있어도 딕셔너리 조회 API에 접근할 수 있는 시점에서, dictCode를 조작해 SQL 필터 구문을 삽입할 수 있음. 이 값은 내부적으로 filterSql로 해석되어 SQL 문장에 직접 결합되므로, 조건절 확장이 가능해짐.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 WAF가 이를 차단해야 하지만, 딕셔너리 전용 검사는 일반 블랙리스트보다 약하게 구성되어 있고 정규식 매칭 또한 실제 SQL 문법 전체를 안정적으로 탐지하지 못함. 예를 들어 select, from 같은 키워드도 특정 형태로 배치하면 필터를 통과할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 결과 공격자는 1=1 and (...) 형태의 조건식을 삽입해 응답 참/거짓을 관찰하는 블라인드 SQL 인젝션을 수행할 수 있고, 이를 통해 현재 데이터베이스의 테이블 개수, 테이블명, 컬럼 존재 여부, 특정 필드의 길이와 내용까지 순차적으로 추출할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 일부 시스템 테이블이 차단되더라도 mysql.innodb_index_stats 같은 대체 가능한 메타데이터 경로를 사용하면 현재 데이터베이스의 테이블 구조를 계속 열거할 수 있음.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이후 패치 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;권장 수정 방향&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4단 형식의 dictCode를 금지하고 외부에서 filterSql을 직접 전달하지 못하도록 해야 함.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dictCode의 네 번째 세그먼트를 필터 조건으로 해석하는 구조 제거&lt;/li&gt;
&lt;li&gt;tableFilterSql에 where 절이나 조건식이 포함되지 않도록 제한&lt;/li&gt;
&lt;li&gt;SysDictMapper.xml 내 where ${filterSql} 방식의 동적 SQL 결합 제거&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블랙리스트는 보조적인 방어 수단으로는 사용할 수 있으나, 문자열 필터링만으로 SQL 인젝션을 막는 것은 근본적인 해결이 될 수 없음. 이 취약점은 결국 외부 입력을 SQL 구문으로 직접 받아들이는 설계 자체가 문제이므로, 근본적으로는 동적 SQL 문자열 결합을 제거하는 방향으로 수정해야 함.&lt;/p&gt;</description>
      <category>공부한 것</category>
      <category>1-day</category>
      <category>cve</category>
      <category>JeecgBoot</category>
      <category>SQLi</category>
      <category>WAF</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/112</guid>
      <comments>https://hsnyus.tistory.com/112#entry112comment</comments>
      <pubDate>Tue, 12 May 2026 01:08:54 +0900</pubDate>
    </item>
    <item>
      <title>CVE-2026-21236</title>
      <link>https://hsnyus.tistory.com/111</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CVSS : 7.8&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대상 서비스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Windows Ancillary Function Driver for WinSock (AFD.sys)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Windows 커널 모드에서 동작하는 드라이버&lt;/li&gt;
&lt;li&gt;유저 모드의 WinSock(Windows Socket) API 호출을 커널 레벨에서 처리하는 네트워킹 컴포넌트&lt;/li&gt;
&lt;li&gt;TCP/IP, UDP 등 네트워크 프로토콜의 소켓 서비스를 구현함&lt;/li&gt;
&lt;li&gt;유저 모드 소켓 API를 마샬링하고 IOCTL 및 제어 요청을 처리한 뒤 하위 레벨 전송 드라이버와 상호작용함&lt;/li&gt;
&lt;li&gt;커널 컨텍스트에서 실행되면서도 일반적인 네트워킹 API를 통해 접근이 가능함&lt;/li&gt;
&lt;/ul&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;Windows 11:&lt;/b&gt; 22H3, 23H2, 24H2, 25H2, 26H1 (build 10.0.28000.0)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Windows 10:&lt;/b&gt; 1607, 1809, 21H2, 22H2&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Windows Server:&lt;/b&gt; 2012, 2012 R2, 2016, 2019, 2022, 2022 23H2, 2025&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 개요&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;로컬 권한 상승(LPE)&lt;/li&gt;
&lt;li&gt;Heap-based Buffer Overflow&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Heap-based BOF를 통해 인가된 로컬 사용자가 SYSTEM 레벨 권한을 획득할 수 있는 권한 상승 취약점임.&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;AFD.sys 드라이버 내부에서 힙 메모리 버퍼에 대한 크기 검증이 부적절하였음이 원인임.&lt;/li&gt;
&lt;li&gt;WinSock 드라이버가 유저 모드에서 전달받은 데이터를 처리할 때 힙에 할당된 버퍼의 경계를 적절히 검증하지 않음&lt;/li&gt;
&lt;li&gt;AFD.sys 는 커널 경계에서 WinSock API를 처리하면서 IOCTL, 소켓 옵션, 관리 작업을 유저 모드에 노출하는데 이 과정에서 특권 코드 경로가 유저 입력에 노출되는 구조라 버퍼 크기, 소유권, 동기화 검증이 중요함&lt;/li&gt;
&lt;li&gt;해당 취약점에서는 이 검증이 불충분하여 힙 오버플로우가 발생할 수 있었음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 원리&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;/li&gt;
&lt;li&gt;공격자가 유저 모드에서 WinSock API를 호출해서 AFD.sys 드라이버에 조작된 IOCTL 요청을 전송시킨다.&lt;/li&gt;
&lt;li&gt;조작된 입력이 커널 모드의 AFD.sys로 전달되면 드라이버가 힙에 할당된 버퍼에 데이터를 기록하는 과정에서 경계 검증 부재로 인해 할당된 버퍼의 범위를 초과하여 인접 메모리 영역을 덮어쓴다.&lt;/li&gt;
&lt;li&gt;오버플로우로 인해 커널 메모리의 핵심 데이터 구조가 손상되며 공격자는 이를 통해 토큰 조작, SYSTEM 프로세스 생성 등으로 SYSTEM 레벨 권한을 얻음.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이후 패치 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2026.02.11 패치 업데이트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총 58개 취약점을 수정하고 해당 취약점도 이 릴리즈에 포함되었음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AFD.sys 드라이버 파일이 패치된 버전으로 교체되고 메모리 연산에 유효성 검증이 추가되어 권한 상승을 차단함.&lt;/p&gt;</description>
      <category>공부한 것</category>
      <category>1-day</category>
      <category>afd</category>
      <category>cve</category>
      <category>Kernel</category>
      <category>Windows</category>
      <category>windows11</category>
      <category>winsock</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/111</guid>
      <comments>https://hsnyus.tistory.com/111#entry111comment</comments>
      <pubDate>Sun, 10 May 2026 10:03:17 +0900</pubDate>
    </item>
    <item>
      <title>CVE-2026-33017</title>
      <link>https://hsnyus.tistory.com/110</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;CVSS: 9.3&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;대상 서비스&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Langflow&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LLM을 기반으로 한 애플리케이션을 노코드/로우코드 방식으로 설계, 실행할 수 있는 비주얼 워크플로우 도구&lt;/li&gt;
&lt;li&gt;깃허브 스타 145,000&lt;/li&gt;
&lt;li&gt;기업에서 AI 워크플로우 자동화 도구로 많이 사용.&lt;/li&gt;
&lt;/ul&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;영향 받는 버전 : Langflow 1.9.0 이전 모든 버전&lt;/li&gt;
&lt;li&gt;패치 버전 : Langflow 1.9.0&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 개요&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;RCE&lt;/li&gt;
&lt;li&gt;비인증 원격 코드 실행&lt;/li&gt;
&lt;li&gt;인증 누락
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공개 플로우 빌드 엔드포인트(/api/v1/build_public_tmp/{flow_id}/flow)가 인증 없이 접근 가능함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;코드 인젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;공격자가 제공한 플로우 정의 데이터 내에 임의의 Python 코드를 삽입할 수 있음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Eval 인젝션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;삽입된 코드라 exec() 함수에 직접 전달되어 샌드박싱 없이 실행되는 구조임.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;/li&gt;
&lt;li&gt;Langflow의 build_public_tmp 엔드포인트는 공개 플로우를 빌드하기 위해 의도적으로 인증을 요구하지 않도록 설계되어 있음. 근데 이 엔드포인트가 선택적 data 파라미터를 받아들이는데 이 파라미터가 제공되면 데이터베이스에 저장된 플로우 데이터 대신 공격자가 제공한 플로우 데이터를 사용함.&lt;/li&gt;
&lt;li&gt;이전에 CVE-2025-3248에서 /api/v1/validate/code 엔드포인트에 인증을 추가하는 패치가 진행되었으나 동일한 exec() 호출 체인이 build_public_tmp 에도 존재해서 발생하였다.&lt;/li&gt;
&lt;li&gt;build_public_tmp 는 공개 플로우를 위한 엔드포인트기에 단순히 인증을 추가하는 것으로 해결이 불가능하다. 인증을 추가하면 공개 플로우 기능이 동작하지 않게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;취약점 원리&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;공격자 &amp;rarr; POST /api/v1/build_public_tmp/{flow_id}/flow
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;요청 본문에 data 파라미터 포함&lt;/li&gt;
&lt;li&gt;노트 정의 내에 임의의 Python 코드 삽입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서버 측 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;data 파라미터가 존재하므로 DB의 신뢰된 플로우 데이터를 무시함&lt;/li&gt;
&lt;li&gt;공격자가 제공한 플로우 데이터를 그대로 사용함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;exec() 호출&lt;/li&gt;
&lt;li&gt;서버 프로세스 권한으로 임의 코드 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공격자가 인증 없이 접근을 시작하는 시점에서 /api/v1/build_public_tmp/{flow_id}/flow 엔드포인트가 공개 플로우를 빌드하기 위한 것이라서 인증이 필요없음. 그래서 공격자는 아무런 자격 증명 없이 이 엔드포인트에 HTTP POST 요청을 보내는게 가능함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;exec() 함수를 호출할 때 공격자가 데이터 내의 노트 정의에 임의의 코드를 삽입하면 검증이나 격리 없이 서버 프로세스 권한으로 실행하게 되어 RCE가 발생함.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이후 패치 사항&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Langflow 1.9.0&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공개 엔드포인트에서 data 파라미터를 완전히 제거함.(공개 플로우는 서버 측 데이터베이스에 저장된 신뢰된 플로우 데이터만 실행 가능하도록)&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Langflow 1.9.0에서 해당 취약점 외에도 관련 취약점(파일 업로드 경로 탐색을 통한 RCE)인 CVE-2026-33309 도 함께 패치했음.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>공부한 것</category>
      <category>1-day</category>
      <category>cve</category>
      <category>langflow</category>
      <category>RCE</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/110</guid>
      <comments>https://hsnyus.tistory.com/110#entry110comment</comments>
      <pubDate>Sat, 9 May 2026 23:53:22 +0900</pubDate>
    </item>
    <item>
      <title>IDA Free 설치</title>
      <link>https://hsnyus.tistory.com/102</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;설치 방법(윈도우 11 기준)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hex-rays.com/ida-free&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://hex-rays.com/ida-free&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1769165265525&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;IDA Free: Disassembler &amp;amp; Decompiler at No Cost&quot; data-og-description=&quot;Free disassembler and decompiler to learn reverse engineering. Core IDA features at no cost for students and non-commercial use. Download and start today.&quot; data-og-host=&quot;hex-rays.com&quot; data-og-source-url=&quot;https://hex-rays.com/ida-free&quot; data-og-url=&quot;https://hex-rays.com/ida-free&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cCXrrc/dJMb9bvWxoo/BHQaEZF0eF9DMRYwb19BM1/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=828_543_1004_719,https://scrap.kakaocdn.net/dn/bUuw8M/dJMb9lL5Prw/mN56B48KqGywg3RcyLYLa1/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=828_543_1004_719,https://scrap.kakaocdn.net/dn/hNiZj/dJMb9g45mNk/AsKtKgQTCn4kKwtnnLcEq0/img.png?width=1024&amp;amp;height=687&amp;amp;face=0_0_1024_687&quot;&gt;&lt;a href=&quot;https://hex-rays.com/ida-free&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://hex-rays.com/ida-free&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cCXrrc/dJMb9bvWxoo/BHQaEZF0eF9DMRYwb19BM1/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=828_543_1004_719,https://scrap.kakaocdn.net/dn/bUuw8M/dJMb9lL5Prw/mN56B48KqGywg3RcyLYLa1/img.jpg?width=2400&amp;amp;height=1260&amp;amp;face=828_543_1004_719,https://scrap.kakaocdn.net/dn/hNiZj/dJMb9g45mNk/AsKtKgQTCn4kKwtnnLcEq0/img.png?width=1024&amp;amp;height=687&amp;amp;face=0_0_1024_687');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;IDA Free: Disassembler &amp;amp; Decompiler at No Cost&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Free disassembler and decompiler to learn reverse engineering. Core IDA features at no cost for students and non-commercial use. Download and start today.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;hex-rays.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사이트에 접속하고 Download IDA Free를 클릭해 라이선스 키를 발급받고 설치한다.&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;IDA를 실행하면 일단 Go를 누르고 아무거나 누르다보면 라이선스 키를 입력하라고 나올텐데 아까 가입하고 발급한 키를 다운로드한 폴더에서 선택하면 인증된다.&lt;/p&gt;</description>
      <category>일반</category>
      <category>IDA</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/102</guid>
      <comments>https://hsnyus.tistory.com/102#entry102comment</comments>
      <pubDate>Fri, 23 Jan 2026 19:50:57 +0900</pubDate>
    </item>
    <item>
      <title>블록 암호 - AES</title>
      <link>https://hsnyus.tistory.com/101</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;AES란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AES는 블록 암호의 표준 알고리즘이다.&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;지금까지도 기밀성을 위협하는 치명적인 취약점이 발견되지 않았고, 현대에 대칭키 암호 알고리즘을 사용할 때 많은 경우 AES가 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;대칭키 암호의 요건&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대칭키 암호가 갖추어야 할 요건엔 혼돈과 확산이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혼돈은 키와 암호문 사이의 관계가 가능한 한 복잡해야 한다는 것. 암호문의 모든 비트는 키의 여러 부분에 의존해야 한다는 것이고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;확산은 평문이 암호문 대부분에 걸쳐 분산되어야 한다는 것. 평문을 1비트만 바꿔도 암호문은 절반 비트 가량이 바뀌어야 한다는 것이다.&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;AES의 암호 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AES는 SPN이라는 암호 구조를 사용한다. SPN은 S-BOX를 사용하는 치환과 P-BOX를 사용하는 순열을 여러 라운드에 걸쳐 반복한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 암호와의 차이&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AES 이전에 사용되었던 블록 암호 표준인 DES는 페이스텔 구조 라고 불리는 구조를 사용한다. 페이스텔 구조의 경우 매 라운드마다 입력의 절반에 대해서만 계산을 적용하지만 SPN 구조에서는 라운드마다 입력 전체에 라운드 함수를 적용하므로 같은 수의 라운드를 사용할 떄 SPN이 페이스텔 구조에 비해 두 배의 암호학적 안전성을 가진다.&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;AES 구조&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AES는 128비트 크기의 블록을 암호화하는 블록 암호이다. 키의 길이는 128, 192, 256비트 중 하나를 선택할 수 있고, 라운드 수는 키의 길이에 따라 10, 12, 14로 결정된다. 키의 길이가 128비트면 AES-128, 192비트면 AES-192, 256비트면 AES-256이라고 한다.&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;AES는 암호화할 때 가장 먼저 각 블록을 4행 4열의 상동 배열로 재구성한다. 상동 배열의 각 칸에는 1바이트가 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AES의 각 라운드 함수들은 역함수가 존재해 AES 복호화 과정에서 이를 사용한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(각 라운드 함수의 자세한 설명은 생략함)&lt;/p&gt;</description>
      <category>공부한 것</category>
      <author>hsnyus</author>
      <guid isPermaLink="true">https://hsnyus.tistory.com/101</guid>
      <comments>https://hsnyus.tistory.com/101#entry101comment</comments>
      <pubDate>Fri, 23 Jan 2026 19:43:36 +0900</pubDate>
    </item>
  </channel>
</rss>