DASCTF 2023 & 0X401七月暑期挑战赛——— 解析viphouse
保护策略
静态分析
main
主函数在while循环提供了一个菜单。
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
char nptr[10]; // [rsp+Eh] [rbp-12h] BYREF
unsigned __int64 v4; // [rsp+18h] [rbp-8h]
v4 = __readfsqword(0x28u);
set_randnum();
print_string(a1, a2);
while ( 1 )
{
menu();
__isoc99_scanf("%9s", nptr);
switch ( atoi(nptr) )
{
case 1:
if ( login_flag )
{
puts("You are already logged in.");
}
else
{
login();
if ( login_flag )
puts("Logged in successfully.");
else
puts("Login failed. Please try again.");
}
continue;
case 2:
if ( !login_flag )
goto LABEL_16;
puts("Executing fmt function...");
break;
case 3:
if ( !login_flag )
goto LABEL_16;
puts("Executing uaf function...");
uaf_function();
break;
case 4:
if ( login_flag )
{
puts("Executing canary function...");
canary_function();
}
else
{
LABEL_16:
puts("Please log in first.");
}
break;
case 5:
if ( login_flag )
logout();
else
puts("You are not logged in.");
break;
default:
puts("Invalid option. Please try again.");
break;
}
}
}
set_randnum
这个函数的主要作用是打开生成随机数的文件,并向变量src
中读入8字节的随机数。
void set_randnum()
{
unsigned int v0; // eax
int fd; // [rsp+Ch] [rbp-4h]
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
v0 = time(0LL);
srand(v0);
dword_404110 = rand() % 256;
fd = open("/dev/random", 0);
if ( fd >= 0 )
{
read(fd, src, 8uLL);
close(fd);
}
else
{
perror("Failed to open /dev/random");
}
}
print_string
这个函数打印一些欢迎语。
int sub_401602()
{
puts("__ _____ ____ _ _ ___ _ _ ____ _____ ");
puts("\\ \\ / /_ _| _ \\ | | | |/ _ \\| | | / ___|| ____|");
puts(" \\ \\ / / | || |_) |____| |_| | | | | | | \\___ \\| _| ");
puts(" \\ V / | || __/_____| _ | |_| | |_| |___) | |___ ");
puts(" \\_/ |___|_| |_| |_|\\___/ \\___/|____/|_____|");
putchar(10);
puts("Welcome to vip-house!");
return puts("HAVE FUN!!!!");
}
menu
这个函数将程序提供的一些选项打印出来。
int sub_401680()
{
puts("1. login in");
puts("2. fmt");
puts("3. uaf");
puts("4. canary");
puts("5. login out");
return printf("Choose an option: ");
}
login in
这个函数提供了一个登录功能,允许用户输入用户名和密码,在输入密码的地方存在一个栈溢出,可以溢出0x20
字节,登录成功后会设置一个标志位为1
,在此重命名为login_flag
,如果是以admin
登录的,会再设置一个标志位为1
,不妨重命名为admin_flag
。在进入所有菜单里的函数前会通过login_flag
检查登陆状态,未登录状态下必须先登录,不可重复登录,登陆后可使用其他函数。
unsigned __int64 login()
{
char s[100]; // [rsp+0h] [rbp-2A0h] BYREF
int v2; // [rsp+64h] [rbp-23Ch] BYREF
char v3[64]; // [rsp+258h] [rbp-48h] BYREF
unsigned __int64 v4; // [rsp+298h] [rbp-8h]
v4 = __readfsqword(0x28u);
memset(&v2, 0, 0x1F4uLL);
memset(v3, 0, sizeof(v3));
memset(s, 0, sizeof(s));
printf("Please enter your username: ");
sub_4016EA(s, 99LL);
printf("Please enter your password: ");
sub_4016EA(v3, 104LL);
if ( !strcmp(s, "admin") && !strcmp(v3, "root") )
{
puts("Welcome, ADMIN~");
dword_404118 = 1;
}
login_flag = 1;
return v4 - __readfsqword(0x28u);
}
uaf
这个函数提供了一个简单的堆块管理程序,仅有add
和free
功能。add
函数申请一个固定大小的堆块,并允许用户向其中写入8
字节数据。free
只是单纯的释放堆块,没有进行指针置空操作存在uaf
。
unsigned __int64 sub_401882()
{
int v1; // [rsp+0h] [rbp-10h] BYREF
unsigned int v2; // [rsp+4h] [rbp-Ch]
unsigned __int64 v3; // [rsp+8h] [rbp-8h]
v3 = __readfsqword(0x28u);
v2 = 0;
while ( 1 )
{
puts("1. Add Note");
puts("2. Delete Note");
puts("3. Exit");
printf("Choice: ");
__isoc99_scanf("%d", &v1);
getchar();
if ( v1 == 3 )
break;
if ( v1 > 3 )
goto LABEL_10;
if ( v1 == 1 )
{
v2 = add(v2);
}
else if ( v1 == 2 )
{
v2 = free_0(v2);
}
else
{
LABEL_10:
puts("Invalid choice.");
}
}
puts("Goodbye!");
return v3 - __readfsqword(0x28u);
}
canary
这个函数允许用户输入最多十六字节的数据,并拿它和最开始的随机数进行比较,如果相同则会进入存在格式化字符串漏洞的函数泄露出canary
。
unsigned __int64 canary_function()
{
char s[8]; // [rsp+0h] [rbp-30h] BYREF
char v2[16]; // [rsp+8h] [rbp-28h] BYREF
int i; // [rsp+18h] [rbp-18h]
unsigned __int64 v4; // [rsp+28h] [rbp-8h]
v4 = __readfsqword(0x28u);
memset(s, 0, sizeof(s));
memset(v2, 0, sizeof(v2));
strcpy(s, src);
puts("Please input the number you guess: ");
sub_4016EA(v2, 16LL);
for ( i = 0; i <= 7; ++i )
{
if ( v2[i] != s[i] )
{
printf("Wrong input: %s\n", v2);
puts("You can't do anything!");
return v4 - __readfsqword(0x28u);
}
}
if ( dword_404118 )
{
printf("I'll give you a gift!");
sub_4015A7();
}
return v4 - __readfsqword(0x28u);
}
login out
这个函数进行标志位置0
操作,这样就可以再次进入存在栈溢出的login in
函数了。
int logout()
{
login_flag = 0;
return puts("Logged out successfully.");
}
利用思路
这道题出得好有水平(应该很费脑子),以至于我看着write up
复现都很费劲。