본문 바로가기

시스템

5주차_시스템

SYSV

리눅스는 SYSTEM V(SYSV) Application Binary Interface(ABI)를 기반으로 만들어졌다.

SYSV에서 정의한 함수 호출 규약은 3가지 특징을 가진다. 

  1. 6개의 인자를 RDI, RSI, RDX, RCX, R8, R9에 순서대로 저장하여 전달합니다. 더 많은 인자를 사용해야 할 때는 스택을 추가로 이용한다.
  2. Caller에서 인자 전달에 사용된 스택을 정리한다.
  3. 함수의 반환 값은 RAX로 전달한다.
// Name: sysv.c
// Compile: gcc -fno-asynchronous-unwind-tables  -masm=intel \
//         -fno-omit-frame-pointer -S sysv.c -fno-pic -O0

#define ull unsigned long long

ull callee(ull a1, int a2, int a3, int a4, int a5, int a6, int a7) {
  ull ret = a1 + a2 + a3 + a4 + a5 + a6 + a7;
  return ret;
}

void caller() { callee(123456789123456789, 2, 3, 4, 5, 6, 7); }

int main() { caller(); }

해당 코드는 sysv함수 호출 규약이다. sysv를 분석해보겠다. 

1.인자전달

gdb로 sysv를 로드한 후 중단점을 설정한다. callee 함수를 호출하기 전까지 실행하고, 레지스터와 스택을 확인해본다. disass 명령어로 caller()의 디스어셈블된 코드를 보고 callee()를 호출하는 부분을 파악한 후 해당 부분에 중단점을 설정한다. c 명령어를 사용해서 프로그램 실행을 진행하면 callee() 를 호출하기 직전에 멈춥니다. 코드로 보면 아래의 순서다.

$ gdb -q sysv
pwndbg: loaded 139 pwndbg commands and 49 shell commands. Type pwndbg [--shell | --all] [filter] for a list.
pwndbg: created $rebase, $ida GDB functions (can be used with print/break)
Reading symbols from sysv...
...
pwndbg> b *caller
Breakpoint 1 at 0x1185
pwndbg> r
Starting program: /home/dreamhack/sysv

Breakpoint 1, 0x0000555555555185 in caller ()
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
 ► 0x555555555185 <caller>       endbr64
   0x555555555189 <caller+4>     push   rbp
   0x55555555518a <caller+5>     mov    rbp, rsp
   0x55555555518d <caller+8>     push   7
   0x55555555518f <caller+10>    mov    r9d, 6
   0x555555555195 <caller+16>    mov    r8d, 5
   0x55555555519b <caller+22>    mov    ecx, 4
   0x5555555551a0 <caller+27>    mov    edx, 3
   0x5555555551a5 <caller+32>    mov    esi, 2
   0x5555555551aa <caller+37>    movabs rax, 0x1b69b4bacd05f15
   0x5555555551b4 <caller+47>    mov    rdi, rax
   0x5555555551b7 <caller+50>    call   0x555555555129 <callee>
   0x5555555551bc <caller+55>    add    rsp,0x8
...
pwndbg> disass caller
...
   0x00005555555551b7 <+50>:  call   0x555555555129 <callee>
...
pwndbg> b *caller+50
Breakpoint 2 at 0x5555555551b7
pwndbg> c
Continuing.

Breakpoint 2, 0x00005555555551b7 in caller ()
...
─────────────[ REGISTERS / show-flags off / show-compact-regs off ]─────────────
*RAX  0x1b69b4bacd05f15
 RBX  0x0
*RCX  0x4
*RDX  0x3
*RDI  0x1b69b4bacd05f15
*RSI  0x2
*R8   0x5
*R9   0x6
 R10  0x7ffff7fc3908 ◂— 0xd00120000000e
 R11  0x7ffff7fde680 (_dl_audit_preinit) ◂— endbr64
...

pwndbg> x/4gx $rsp
0x7fffffffe2f8: 0x0000000000000007  0x00007fffffffe310
0x7fffffffe308: 0x00005555555551d5  0x0000000000000001

2. 반환 주소 저장

si 명령어로 한 단계 더 실행시킵니다. 

pwndbg> si
0x00005555555545fa in callee ()
...
pwndbg> x/4gx $rsp
0x7fffffffdf70:	0x0000555555554682	0x0000000000000007
0x7fffffffdf80:	0x00007fffffffdf90	0x0000555555554697
pwndbg> x/10i 0x0000555555554682 - 5
   0x55555555467d <caller+43>:	call   0x5555555545fa <callee>
   0x555555554682 <caller+48>:	add    rsp,0x8

3. 스택 프레임 저장

x/5i $rip 명령어로 callee함수의 도입부(Prologue)를 살펴보면, 가장 먼저 push rbp를 통해 호출자(caller())의 rbp를 저장하고 있다. rbp가 스택프레임의 가장 낮은 주소를 가리키는 포인터이므로, 이를 Stack Frame Pointer (SFP)라고도 부릅른다. callee에서 반환될 때, SFP를 꺼내어 caller의 스택 프레임으로 돌아갈 수 있다.

pwndbg> x/9i $rip
=> 0x555555555129 <callee>:	endbr64
   0x55555555512d <callee+4>:	push   rbp
   0x55555555512e <callee+5>:	mov    rbp,rsp
   0x555555555131 <callee+8>:	mov    QWORD PTR [rbp-0x18],rdi
   0x555555555135 <callee+12>:	mov    DWORD PTR [rbp-0x1c],esi
   0x555555555138 <callee+15>:	mov    DWORD PTR [rbp-0x20],edx
   0x55555555513b <callee+18>:	mov    DWORD PTR [rbp-0x24],ecx
   0x55555555513e <callee+21>:	mov    DWORD PTR [rbp-0x28],r8d
   0x555555555142 <callee+25>:	mov    DWORD PTR [rbp-0x2c],r9d
pwndbg> si
pwndbg> si
0x000055555555512e in callee ()
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
   0x555555555129 <callee>       endbr64
   0x55555555512d <callee+4>     push   rbp
 ► 0x55555555512e <callee+5>     mov    rbp, rsp
   0x555555555131 <callee+8>     mov    qword ptr [rbp - 0x18], rdi
...
pwndbg> x/4gx $rsp
0x7fffffffe2e8: 0x00007fffffffe300  0x00005555555551bc
0x7fffffffe2f8: 0x0000000000000007  0x00007fffffffe310
pwndbg> print $rbp
$1 = (void *) 0x7fffffffe300

4. 스택 프레임 할당

mov rbp, rsp로 rbp와 rsp가 같은 주소를 가리키게 한다. callee 함수는 지역 변수를 사용하지 않으므로, 새로운 스택 프레임을 만들지 않는다. si로 실행하고, 레지스터를 보면 이 둘이 같은 주소를 가리키는 것을 확인할 수 있습니다.

pwndbg> x/5i $rip
=> 0x55555555512e <callee+5>: mov    rbp,rsp
   0x555555555131 <callee+8>: mov    QWORD PTR [rbp-0x18],rdi
   0x555555555135 <callee+12>:  mov    DWORD PTR [rbp-0x1c],esi
   0x555555555138 <callee+15>:  mov    DWORD PTR [rbp-0x20],edx
   0x55555555513b <callee+18>:  mov    DWORD PTR [rbp-0x24],ecx

pwndbg> print $rbp
$2 = (void *) 0x7fffffffe300
pwndbg> print $rsp
$3 = (void *) 0x7fffffffe2e8

pwndbg> si

pwndbg> print $rbp
$4 = (void *) 0x7fffffffe2e8
pwndbg> print $rsp
$5 = (void *) 0x7fffffffe2e8

5. 반환값 전달

덧셈 연산을 모두 마치고, 함수의 종결부(Epilogue)에 도달하면, 반환값을 rax에 옮긴다.

pwndbg> b *callee+79
Breakpoint 3 at 0x555555555178
pwndbg> c
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
 ► 0x555555555178 <callee+79>    add    rax, rdx
   0x55555555517b <callee+82>    mov    qword ptr [rbp - 8], rax
   0x55555555517f <callee+86>    mov    rax, qword ptr [rbp - 8]
   0x555555555183 <callee+90>    pop    rbp
   0x555555555184 <callee+91>    ret

pwndbg> b *callee+91
Breakpoint 4 at 0x555555555184
pwndbg> c
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
   0x555555555178 <callee+79>    add    rax, rdx
   0x55555555517b <callee+82>    mov    qword ptr [rbp - 8], rax
   0x55555555517f <callee+86>    mov    rax, qword ptr [rbp - 8]
   0x555555555183 <callee+90>    pop    rbp
 ► 0x555555555184 <callee+91>    ret                                  <0x5555555551bc; caller+55>
    ↓
...

pwndbg> print $rax
$1 = 123456789123456816

6 반환

반환은 저장해뒀던 스택 프레임과 반환 주소를 꺼내면서 이루어진다.  스택 프레임을 꺼낸 뒤에는, ret로 호출자로 복귀다.

 

pwndbg> d
pwndbg> b *callee+90
Breakpoint 1 at 0x1183
pwndbg> r
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
 ► 0x555555555183 <callee+90>                     pop    rbp
   0x555555555184 <callee+91>                     ret
    ↓
...

pwndbg> si
pwndbg> si
...
──────────────────────[ DISASM / x86-64 / set emulate on ]──────────────────────
   0x555555555183 <callee+90>                     pop    rbp
   0x555555555184 <callee+91>                     ret
    ↓
 ► 0x5555555551bc <caller+55>                     add    rsp, 8
   0x5555555551c0 <caller+59>                     nop
   0x5555555551c1 <caller+60>                     leave
   0x5555555551c2 <caller+61>                     ret
    ↓
...
pwndbg> print $rbp
$1 = (void *) 0x7fffffffe300
pwndbg> print $rip
$2 = (void (*)()) 0x5555555551bc <caller+55>

 

cdecl

스택을 통해 인자를 전달합니다. 또한, 인자를 전달하기 위해 사용한 스택을 호출자가 정리하는 특징이 있다. 스택을 통해 인자를 전달할 때는, 마지막 인자부터 첫 번째 인자까지 거꾸로 스택에 push한다.

 

퀴즈

 

A.push 0x3

이유:스택에 3개의 인수를 차례대로 push했기 때문이다. 

 

Q2. 아래 코드를 컴파일 했을 때, 컴파일된 어셈블리 코드 중 (b)에 들어갈 것으로 가장 적절할 것을 고르시오.

A.push 0x2

이유:스택에 3개의 인수를 차례대로 push했기 때문이다. 

 

Q3. 아래 코드를 컴파일 했을 때, 컴파일된 어셈블리 코드 중 (c)에 들어갈 것으로 가장 적절할 것을 고르시오.

A.push 0x1

이유:스택에 3개의 인수를 차례대로 push했기 때문이다. 

 

A. add esp, 0xc

sum 함수를 호출한 뒤에 push된 인수들을 스택에서 제거한다. esp를 12바이트 증가시켜 푸시된 3개의 인수를 스택에서 제거한다.

 

Q5. SYSV를 적용하여 아래 코드를 컴파일 했을 때, 컴파일된 어셈블리 코드 중 (a)에 들어갈 것으로 가장 적절한 것을 고르시오.

A. mov edi, 0x3

인수 1을 rdi 레지스터에 저장한

 

Q6. SYSV를 적용하여 아래 코드를 컴파일 했을 때, 컴파일된 어셈블리 코드 중 (b)에 들어갈 것으로 가장 적절한 것을 고르시오.

A. mov esi, 0x2

인수 2를 rsi 레지스터에 저장한다.

 

Q7. SYSV를 적용하여 아래 코드를 컴파일 했을 때, 컴파일된 어셈블리 코드 중 (c)에 들어갈 것으로 가장 적절한 것을 고르시오. 

A, mov edi, 0x1

세 번째 인수 3을 rdx 레지스터에 저장

Bomb

 

여기서 정수를 2개 입력받고, 첫번째 숫자가 7보다 클 시 폭발함을 알 수 있다. 값을 옮기는 부분이 사진에서는 잘렸지만 총 7개가 있다. 첫번째로 입력된 숫자에 따라 0에서 7사이인지를 고려하여 터지는지 안 터지는지가 결정된다. 첫 숫자를 2로 입력하면 이동하여 2c3이 된다. 16진수이므로 10진수로 바꾸면 707이 된다.

'시스템' 카테고리의 다른 글

7주차 시스템  (0) 2024.08.19
4주차_시스템  (0) 2024.07.29
3주차_시스템  (1) 2024.07.22
시스템 2주  (1) 2024.07.15
시스템 1주  (0) 2024.07.07