题目分析

题目设置的还是比较巧妙的。

本身是一个二进制的文件,linux 64环境,保护情况如下:

    Arch:     amd64-64-littleRELRO:    Partial RELROStack:    No canary foundNX:       NX enabledPIE:      PIE enabled

功能一共三个:
1. go: 选择一个level,然后会再问你一次level,输入之后,回答两次level想加这么多次的问题,就是a * b类型,回答完成之后输出你在多少秒内完成了多少level
2. hint: 打印NO PWN NO FUN
3. give up: 退出

实现如下:

go

int go(void)
{int v1; // ST0C_4@10__int64 v2; // [sp+0h] [bp-120h]@1int v3; // [sp+8h] [bp-118h]@9__int64 v4; // [sp+10h] [bp-110h]@0__int64 v5; // [sp+10h] [bp-110h]@4signed __int64 v6; // [sp+18h] [bp-108h]@7__int64 v7; // [sp+20h] [bp-100h]@10puts("How many levels?");v2 = read_num();if ( v2 > 0 )v4 = v2;elseputs("Coward");puts("Any more?");v5 = v4 + read_num();if ( v5 > 0 ){if ( v5 <= 999 ){v6 = v5;}else{puts("More levels than before!");v6 = 1000LL;}puts("Let's go!'");v3 = time(0LL);if ( (unsigned int)level(v6) != 0 ){v1 = time(0LL);sprintf((char *)&v7, "Great job! You finished %d levels in %d seconds\n", v6, (unsigned int)(v1 - v3));puts((const char *)&v7);}else{puts("You failed.");}exit(0);}return puts("Coward");
}

其中level函数如下

__int64 __fastcall level(signed int a1)
{__int64 result; // rax@2__int64 inputs; // rax@8char buf[32]; // [sp+10h] [bp-30h]@1int answer; // [sp+30h] [bp-10h]@5int num2; // [sp+34h] [bp-Ch]@5int num1; // [sp+38h] [bp-8h]@5int i; // [sp+3Ch] [bp-4h]@5*(_QWORD *)buf = 0LL;*(_QWORD *)&buf[8] = 0LL;*(_QWORD *)&buf[16] = 0LL;*(_QWORD *)&buf[24] = 0LL;if ( a1 ){if ( (unsigned int)level(a1 - 1) == 0 ){result = 0LL;}else{num1 = rand() % a1;num2 = rand() % a1;answer = num2 * num1;puts("====================================================");printf("Level %d\n", (unsigned int)a1);printf("Question: %d * %d = ? Answer:", (unsigned int)num1, (unsigned int)num2);for ( i = read(0, buf, 0x400uLL); i & 7; ++i )buf[i] = 0;inputs = strtol(buf, 0LL, 10);result = inputs == answer;}}else{result = 1LL;}return result;
}

总结来说,首先是问level,如果小于等于0了,输出coward,然后再问一次level,这次无论大小,直接加在第一次问的level上。 这里就有一个洞了,如果第一次给出的值小于等于0的话,这里的v4是没有初始化的。另外还有一个问题,就是第二次问level,并没有判断是不是小于0。

之后进入level,来生成问题判断答案是否正确。

level的实现使用了递归,在read answer的时候读取了0x400个字符,明显的栈溢出,不过这里需要注意,这里的栈溢出是没有办法使用partial write的,那个循环处理了partial write的情况。

hint

函数如下:

int hint(void)
{signed __int64 v1; // [sp+8h] [bp-108h]@2signed int v2; // [sp+10h] [bp-100h]@3signed __int16 v3; // [sp+14h] [bp-FCh]@3if ( show_hint ){sprintf((char *)&v1, "Hint: %p\n", &system, &system);}else{v1 = 0x4E204E5750204F4ELL;v2 = 0x5546204F;v3 = 0x4E;}return puts((const char *)&v1);
}

show_hint变量位于BSS,由于开启了PIE,是没法拿到地址的。 这里有个问题,只看C函数是看不出来的,我们来看汇编:

var_110         = qword ptr -110h
.text:0000000000000CF0
.text:0000000000000CF0                 push    rbp
.text:0000000000000CF1                 mov     rbp, rsp
.text:0000000000000CF4                 sub     rsp, 110h
.text:0000000000000CFB ; 8:     sprintf((char *)&v1, "Hint: %p\n", &system, &system);
.text:0000000000000CFB                 mov     rax, cs:system_ptr
.text:0000000000000D02                 mov     [rbp+var_110], rax
.text:0000000000000D09 ; 6:   if ( show_hint )
.text:0000000000000D09                 lea     rax, show_hint
.text:0000000000000D10                 mov     eax, [rax]
.text:0000000000000D12                 test    eax, eax
.text:0000000000000D14                 jz      short loc_D41
.text:0000000000000D16                 mov     rax, [rbp+var_110]
.text:0000000000000D1D                 lea     rdx, [rbp+var_110]
.text:0000000000000D24                 lea     rcx, [rdx+8]
.text:0000000000000D28                 mov     rdx, rax
.text:0000000000000D2B                 lea     rsi, aHintP     ; "Hint: %p\n"
.text:0000000000000D32                 mov     rdi, rcx        ; s
.text:0000000000000D35                 mov     eax, 0
.text:0000000000000D3A                 call    _sprintf
.text:0000000000000D3F                 jmp     short loc_D66

这一段汇编是在进入分支之前的部分,所以system无论哪个分支,都会被放在栈上。

漏洞分析

根据刚刚对题目的分析,其实漏洞的点已经找到了:
1. go函数中的两次level询问,第一次如果小于等于0会导致本应该记录第一次询问的level结果的变量未初始化,第二次询问没有判断是否小于0
2. level函数存在栈溢出
3. hint函数始终会将system的值放在栈上

这么看两个漏洞有关联,但是还没办法结合,但是巧合的是,system在栈上的位置刚好和第一次询问记录level的v4变量位置重合。那么事情就好办了。

利用思路

  1. 使用hint,将system放在栈上
  2. 进入go,第一次给出小于等于0的值,使得v4=system的地址。
  3. 第二次询问,填0,可以导致最后可以进入system,但是参数不太好处理,所以可以使用one_gadget,那么第二次询问又不会判断大小,直接给出one_gadget和system地址的偏移,这样level值通过想加就变成了one_gadget的地址
  4. 完成999次回答
  5. 最后一次回答利用栈溢出,返回地址处填入vsyscall的gettimeofday(其实就是vsyscall的最开始位置)地址,填入3次(这里的三次是调试时候计算得出的),使得从返回地址位置一直到保存在栈上的one_gadget之间的位置全部填为gettimeofday
  6. 触发,搞定

总结

关于未初始化

以后做pwn的时候应该注意一下这种未初始化的情况,之前没怎么见过由栈上位置未初始化造成的问题,这次明显就忽略了这一点

关于vsyscall

vsyscall是以前linux内核使用的用来处理syscall的一个解决方案,后来被废弃,由vdso方案代替,但是这个方案由于历史原因保留了下来。

vsyscall的特点是在于其地址是固定的,所以可以用来在PIE+ASLR的情况中进行一定的利用。不过他的利用也有一些限制,vsyscall有一些固定的entry入口,内核在处理的时候会判断一下,如果执行的部分在vsyscall内,但是不是从entry入口开始的,会直接seg fault掉。

从这道题中可以看出来,vsyscall有这么一个用法,由于vsyscall直接进行syscall,并没有利用栈空间,所以在处理这种有栈溢出,但是由于PIE没有别的地址可以用,而栈上又有某个有用的地址的时候,可以通过vsyscall构造一个rop链,这个rop链没有别的作用,就是用来ret,每次ret都会消耗掉一个地址,这样就可以逐渐去贴近想要的那个地址,最后成功ret到相应的位置。

hitb-2017 1000levels writeup相关推荐

  1. Nuit du hack 2017 webcrypto Writeup

    Nuit du hack 2017 web&crypto Writeup 新博客地址:http://bendawang.site/article/Nuit-du-hack-2017-web-a ...

  2. (json web token)JWT攻击

    前记 最近国赛+校赛遇到两次json web token的题,发现自己做的并不算顺畅,于是有了这篇学习文章. 为什么要使用Json Web Token Json Web Token简称jwt 顾名思义 ...

  3. Day 10 - Anticipation | RIPS 2017 PHP代码安全审计挑战(RIPSTECH PRESENTS PHP SECURITY CALENDAR)/ Writeup

    RIPSTECH PRESENTS PHP SECURITY CALENDAR 是由 RIPS 团队出品的PHP代码安全审计挑战系列题目,RIPSTECH PRESENTS PHP SECURITY ...

  4. Day 8 - Candle | RIPS 2017 PHP代码安全审计挑战(RIPSTECH PRESENTS PHP SECURITY CALENDAR)/ Writeup

    RIPSTECH PRESENTS PHP SECURITY CALENDAR 是由 RIPS 团队出品的PHP代码安全审计挑战系列题目,RIPSTECH PRESENTS PHP SECURITY ...

  5. HCTF 2017 bin Level1 Evr_Q Writeup

    HCTF 2017完结撒花,萌新领教到了赛棍们的强大,但也有了一些自己的收获.我想将其中一些题的思路记录下来,一是记录,二是与有需要的人分享,当然本篇WP和大佬们的比还是很菜.话不多说,进入正题. 首 ...

  6. 2017年美亚杯资格赛 个人赛 writeup

    2017年美亚杯资格赛

  7. 2017 Redhat广东省信息安全竞赛 Writeup

    WEB 0x1 刮刮乐 通过文件扫描找到有文件泄露 利用Githack工具就可以把文件下下来 flag{027ea8c2-7be2-4cec-aca3-b6ba400759e8} 0x2 PHPMyW ...

  8. 2017广东省红帽杯网络安全攻防大赛writeup

    签到 扫码按操作即得 brian(Y) 打开题目,发现是一段字符: +++++ +++++ [->++ +++++ +++<] >++.+ +++++ .<+++ [-> ...

  9. 【Writeup】2017陕西网络空间安全技术大赛CSTC misc部分

    Misc 一维码 扫描一维码得到keyword:hydan 对一维码使用Stegsolve LSB隐写提取得到一个ELF文件 网上找hydan得到这个信息隐藏工具 安装好,然后执行 ./hydan-d ...

  10. 2017湖湘杯Writeup

    作者:LB919 出处:http://www.cnblogs.com/L1B0/ RE部分 0x01 Re4newer 解题思路: Step1:die打开,发现有upx壳. Step2:脱壳,执行up ...

最新文章

  1. 国外优秀开源PHP建站程序一览
  2. 将要看的两本书 - Hide From All - ITeye技术网站
  3. android视频播放指定位置,android – 如何在某个特定位置的视图中查看视频?
  4. 如何修炼成某一领域的高手
  5. Activity-Service-Receiver-Provider
  6. c++服务器开源项目,开源一个c++ lua服务器框架
  7. java微博开发_【新手入门篇】新浪微博应用开发之Java入门篇
  8. 20道做完信心嫉妒膨胀的前端测试题
  9. mysql数据类型范围导致失败
  10. spring学习笔记二(基于注解)
  11. 实战 Nginx 与 PHP(FastCGI)的安装、配置与优化
  12. Adobe Flash CS4 从入门到精通
  13. window.dialogArguments
  14. C# .net 源码合集
  15. 移动端WEB前端开发最佳实践
  16. 怎么做应力应变曲线_如何用Origin画应力应变曲线 - 图文 -
  17. iOS开发一路走来看到,好奇,好玩,学习的知识点记录
  18. 血泪总结:如何从微信小程序的坑跳进支付宝小程序的大坑
  19. android端采用FFmpeg进行视频剪切、转码与添加水印
  20. C语言试题115之两个乒乓球队进行比赛,各出三人。甲队为 a,b,c 三人,乙队为 x,y,z 三人。已抽签决定 比赛名单。有人向队员打听比赛的名单。a 说他不和 x 比,c 说他不和 x,z 比,请

热门文章

  1. 使用USB Key Utility工具制作bootable USB Key
  2. 为什么俺推荐Python「1」:作为脚本语言的Python
  3. 《前沿科技·吴军讲5G》思维导图详细解析
  4. 《计算机系统:核心概念及软硬件实现(原书第4版)》——3.1 无符号二进制表示...
  5. Yolov3中先验框生成
  6. UM2 3D 打印机 DIY 实践 (1)结构篇
  7. opengl初学 error C2664: 无法将参数 1 从“const char [7]”转换为“LPCWSTR”
  8. 利用jsonp跨域访问
  9. STM32入门学习 第二天
  10. 光纤传输设备如何选择?光纤网络的优缺点分析