sekaiCTF-2024-pwn-nolibc如何解析?
摘要:sekaiCTF 2024 nolibc 程序逆向 IDA反编译之后: 全是没有符号表的函数。start函数就是主函数。然后发现一些类似于printf的函数也没有符号。 我们linux上运行程序可以确定,至少sub_1322(&am
sekaiCTF 2024
nolibc
程序逆向
IDA反编译之后:
全是没有符号表的函数。start函数就是主函数。然后发现一些类似于printf的函数也没有符号。
我们linux上运行程序可以确定,至少sub_1322("Welcome to String Storage!");这样的函数实现的功能就是类似于printf。
逆向函数:
__int64 __fastcall sub_1322(__int64 a1)
{
__int64 result; // rax
sub_12C8(a1);
result = dword_15004;
__asm { syscall; LINUX - }
return result;
}
因为这些函数都是出题人自己实现的。直接调用syscall实现了输出的功能
这里看汇编会更直观一点,从rax的变化和syscall指令来分析题目到底用到了什么调用。
主要关注rax是怎么被赋值的。 mov edx, cs:dword_15004中cs:dword_15004存放的是1,所以可以确定这里的调用了write系统调用。而rdi和rdx固定,所以类似于puts,逐个打印每个字符
程序的另外一大部分是实现了一个类似于malloc的内存管理程序
在start函数的开头调用了一个init函数,初始化了bss上的一段内存指针,之后在一些文件读写、输出处理的时候,会申请一段空间来进行数据保存。
之后逆向可以发现,程序开辟了bss段上0x5000--0x15000之间的内容作为heap,然后紧接着的内容中存放了:
作为syscall的系统调用号:0,1,2,3
用户是否登录的标记
用户登录的个数
也就是说,我们如果可以造成溢出,覆盖bss上的系统调用号,就可以调用任意syscall
之后我们正常动调看一下:
可以看到在程序偏移0x15000的位置保存了四个系统调用号
漏洞利用
当时做的时候已经从EX师傅那里确定是可以覆盖系统调用号了。
所以剩下的就很简单了。只需要思考如何能构造溢出覆盖系统调用号即可。
这个题目中,自定义的堆块在topchunk的起始位置保存了剩余堆块的大小,初始堆块的大小是0x10000
主要的漏洞点在这些位置:
add程序:
__int64 add()
{
int *v1; // [rsp+0h] [rbp-10h]
int v2; // [rsp+Ch] [rbp-4h]
if ( *(qword_15020[login_flag] + 16LL) > 2046 )
return puts("You have reached the maximum number of strings");
putstring("Enter string length: ");
v2 = atoi();
if ( v2 > 0 && v2 <= 256 )
{
putstring("Enter a string: ");
v1 = malloc(v2 + 1);
if ( !v1 )
{
puts("Failed to allocate memory");
puts(&unk_3124);
exit(&unk_3124);
}
read(v1, v2 + 1);
*(qword_15020[login_flag] + 8 * ((*(qword_15020[login_flag] + 16LL))++ + 2LL) + 8) = v1;
return puts("String added successfully!");
}
else
{
puts("Invalid length");
return puts(&unk_3124);
}
}
add程序这里可以申请0x101大小的heap
v1 = malloc(v2 + 1);
而在malloc程序中:
起始位置有这样两行程序:
if ( !size )
return 0LL;
chunk_size = (size + 15) & 0xFFFFFFF0;
将申请的chunk+15再和0xFFFFFFF0与,但是如果我们最初申请的是0x100,+1再+15,最后与一下,就导致我们可以申请0x110大小的堆块。
然后再加上程序自定义chunk头的0x10。
