pwnable kr maze
复制本地路径 | 在线编辑
前言
主要的参考: [https://shayete.tistory.com/entry/Grotesque-maze]
超级感谢这个博主,让我终于有了些思路,否则就对 pwnable kr 做题绝望了。感觉还是应该要分享,排名真的无所谓,可惜现在网上的 writeup 全是加密。
程序分析
main 函数如下,就是走迷宫,每次迷宫通过后都会增加一个敌人,通过二十关后就可以记录姓名。
record_name 函数有一个明显的 Stack Overflow.
int __fastcall main(__int64 a1, char **a2, char **a3)
{
level = 1;
do
{
init_maze();
one_level();
printf("\rlevel %d clear!\n", (unsigned int)level);
sleep(1u);
++level;
}while ( (unsigned int)level <= 0x14 );
puts("Congratz! you win!");
record_name();
return 0LL;
}
int record_name()
{
char v1; // [rsp+0h] [rbp-30h]
stream = fopen("record", "a+");
printf("record your name : ", "a+");
gets(&v1);
fprintf(stream, "PLAYER : %s has PWNED the MAZE\n", &v1);
return fclose(stream);
}
实际上通过第五关就非常难了,所以肯定不是直接 DFS 来通过完。
奇怪的代码
one_level 函数就是使用 while 处理用户输入直到满足条件退出循环,但是这里面有一段特别奇怪的代码。
if ( dword_6020C0 || user_choice != 'O' )
{
if ( !dword_6020C0 ) dword_6020C0 = 0;
if ( dword_6020C0 != 1 || user_choice != 'P' )
{
if ( dword_6020C0 == 1 ) dword_6020C0 = 0;
if ( dword_6020C0 != 2 || user_choice != 'E' )
{
if ( dword_6020C0 == 2 ) dword_6020C0 = 0;
if ( dword_6020C0 != 3 || user_choice != 'N' )
{
...
if ( dword_6020C0 != 10 || row != 14 || col != 8 || (unsigned int)level <= 4 )
{
if ( dword_6020C0 == 10 ) dword_6020C0 = 0;
}else
byte_602218 = 48;
...
}else
dword_6020C0 = 4;
}else
dword_6020C0 = 3;
} else
dword_6020C0 = 2;
} else
dword_6020C0 = 1;
这段代码真的很奇怪,而且
dword_6020C0 这个变量对我们程序没有作用,所以我一开始以为这就是用来迷惑人的,然而大错特错。可以试着这样逆着分析代码
- 我想要执行最里面的
if-else段,这里我换种思路,我想要else里的内容,需要什么条件? - 判断条件可以简化为
dword_6020C0 != 10,所以我需要dword_6020C0 = 10 - 发现这条语句只有在上一级的
if-else中的else里出现过,所以我现在需要执行上一级if-else的else。 - 判断条件是
dword_6020C0 != 9 || user_choice != 'X',所以我需要dword_6020C0 = 9 - 同样,发现只要上上级的
if-else中的else里出现过,所以需要执行上上级if-else的else - 同样,判断条件是
dword_6020C0 != 8 || user_choice != 'W',所以需要dword_6020C0 = 8 - 一直这样,最后发现需要
dword_6020C0 = 1
然后再正着分析
- 第一次我需要
dword_6020C0 = 1,所以不能执行dword_6020C0 || user_choice != 'O',所以我第一次输入是 O - 第二次我需要
dword_6020C0 = 2,所以不能执行dword_6020C0 != 1 || user_choice != 'P',所以第二次输入是 P - 一直这样下去,最后发现只要顺着顺序输入 OPEN... 就可到达最里面的
if-else
程序漏洞
漏洞就是在于上面的这段代码,当时做的时候太粗心没注意最里面的 else 是这样的语句: byte_602218 = 48。
byte_602218 可是我们的迷宫数据啊,所以这样会把迷宫给改掉,ascii 为 48 的字符是 '0'。
对于我们的迷宫程序,它认为 '0' 表示这里可以走,所以如果我们让 byte_602218 = 48,迷宫有个墙变成了 '0'。
这个有什么用处?这里也想了很久,终于想出了,不容易。
此时可以让角色移动到迷宫之外了!因为迷宫的字符是以一个 maze_table[] 这样的形式存储,每次角色移动会赋值 maze_table[role_x * 24 + role_y] = 'X',所以当角色移动到迷宫之外那么就会造成溢出。
数据存储格式如下
.bss:0000000000602120 maze_char db 0F0h dup(?)
.bss:0000000000602210 db ? ;
...
.bss:0000000000602217 db ? ;
.bss:0000000000602218 byte_602218 db ?
.bss:0000000000602219 align 20h
.bss:0000000000602220 rival_count dd ?
.bss:0000000000602220
.bss:0000000000602224 row db ?
.bss:0000000000602224
.bss:0000000000602225 align 10h
.bss:0000000000602230 ; char col_rival[20]
.bss:0000000000602230 col_rival db ?
.bss:0000000000602230
...
.bss:0000000000602244 level dd ?
所以可以修改
level 大于 20,然后让用户从范围外回到迷宫并到达终点,此时就会直接执行 record_name 函数,从而进行简单的栈溢出即可。
总结
- 那个比较复杂的
if-else嵌套指的反复分析 - 让用户移到迷宫外从而造成溢出这一想法,当时想了不少时间