文章目录

  • 题目类型
  • 查壳
  • 拖进ida
  • 整体逻辑
  • 文件读取函数
    • fseek函数
    • ftell函数
    • fread函数(补充)
  • 分析三个func
    • func0
    • func1
    • func2
  • 解决方法分析
    • 分析

题目类型

这道题是一道栈溢出的题目,有点意思。

查壳


无壳

拖进ida

int __cdecl main(int argc, const char **argv, const char **envp)
{int result; // eax@2int v4; // ebx@6size_t v5; // eax@8int v6; // ebx@11int v7; // [sp+1Ch] [bp-48h]@6int v8; // [sp+30h] [bp-34h]@1signed int v9; // [sp+34h] [bp-30h]@1signed int v10; // [sp+38h] [bp-2Ch]@1signed int v11; // [sp+3Ch] [bp-28h]@1int v12; // [sp+40h] [bp-24h]@1int v13; // [sp+44h] [bp-20h]@1int (__cdecl *v14)(int, int, int); // [sp+48h] [bp-1Ch]@1int (__cdecl *v15)(int, int, int); // [sp+4Ch] [bp-18h]@1int (__cdecl *v16)(int, int, int); // [sp+50h] [bp-14h]@1__int32 v17; // [sp+54h] [bp-10h]@3__int32 v18; // [sp+58h] [bp-Ch]@3FILE *v19; // [sp+5Ch] [bp-8h]@1__main();v14 = func0;v15 = func1;v16 = func2;v8 = 0;v9 = 1;v10 = 2;v11 = 3;v12 = 3;v13 = 4;v19 = fopen("data", "rb");if ( v19 ){fseek(v19, 0, 2);v18 = ftell(v19);fseek(v19, 0, 0);v17 = ftell(v19);if ( v17 ){puts("something wrong");result = 0;}else{for ( i = 0; i < v18; ++i ){v4 = i;*((_BYTE *)&v7 + v4) = fgetc(v19);}v5 = strlen((const char *)&v7);if ( v5 <= v18 ){v18 = v11;i = 0;v17 = v13;while ( i <= 2 ){v6 = i + 1;*(&v8 + v6) = (*(&v14 + i))(&v8, v12, v13);v12 = ++i;v13 = i + 1;}if ( v11 ){result = -1;}else{get_key(v18, v17);system("PAUSE");result = 0;}}else{result = -1;}}}else{result = -1;}return result;
}

如何获取到flag?
需要执行get_key函数,如果要执行,那么需要保证v11等于0。那么接下来我们来理理整体逻辑,

整体逻辑

v14 = func0;v15 = func1;v16 = func2;

v14,v15,v16三个函数指针,分别指向func0func1func2,读取data文件到v7,v7数组只有20个字节的大小,如果文件内容超过20字节,那么就会依次向后(也就是说把v8,v9,v10等等)进行覆盖

 while ( i <= 2 ){v6 = i + 1;*(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);v12 = ++i;v13 = i + 1;}

v8的起始值是0,v6是1,所以这里func0(v14指向的函数)的返回值是赋给v9func1(v15指向的函数)的的返回值是赋给v10func2(v16指向的函数)的的返回值是赋给v11(这里是不是找到关键点了???func2的返回值给v11,继续往下观察)

文件读取函数

fseek函数

FILE *fp = fopen(model_path, "rb");
fseek(fp, 0, SEEK_END)

用 法: int fseek(FILE *stream, long offset, int fromwhere);
描 述: 函数设置文件指针stream的位置。如果执行成功,stream将指向以fromwhere为基准,偏移offset个字节的位置。如果执行失败(比如offset超过文件自身大小),则不改变stream指向的位置。
返回值: 成功,返回0,否则返回其他值。
参数:

  1. stream:文件指针;
  2. offset:偏移量,整数表示正向偏移,负数表示负向偏移;
  3. fromwhere:设定从文件的哪里开始偏移,可能取值为:SEEK_CUR,SEEK_END 或 SEEK_SET
    SEEK_SET: 文件开头
    SEEK_CUR: 当前位置
    SEEK_END: 文件结尾

其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2;
例子:

  1. fseek(fp,50L,0);把fp指针移动到离文件开头50字节处;
  2. fseek(fp,50L,1);把fp指针移动到离文件当前位置50字节处;
  3. fseek(fp,50L,2);把fp指针退回到离文件结尾50字节处。

ftell函数

FILE *fp = fopen(model_path, "rb");
fseek(fp, 0, SEEK_END); //fp指针移到文件尾部
int model_len = ftell(fp);

用 法: long ftell(FILE *fp);
描 述: 返回当前文件指针位置。这个位置是当前文件指针相对于文件开头的位移量。
返回值:返回文件指针的位置,若出错则返回-1L。
参数:文件指针。

fread函数(补充)

fread(buffer,100,1,fp)  

用 法: size_t fread( void *buffer, size_t size, size_t count, FILE *stream ) ;
描 述: fread()用来从文件流中读取数据。参数stream为已打开的文件指针,参数buffer指向欲存放读取进来的数据空间,读取的字节数以参数size * count来决定。
返回值: 返回实际读取到的count数目,如果此值比参数count来得小,则代表可能读到了文件尾了或者有错误发生(前者几率大),这时必须用feof()ferror()来决定发生什么情况。
参数:

  1. buffer :读取的数据存放的内存的指针(可以是数组,也可以是新开辟的空间,buffer就是一个索引);
  2. size : 每次读取的字节数 ;
  3. count :读取次数 ;
  4. strean:要读取的文件的指针;

分析三个func

func0

signed int __cdecl func0(int a1, int a2, int a3)
{int v3; // ST0C_4@1v3 = *(_DWORD *)(4 * a2 + a1);*(_DWORD *)(a1 + 4 * a2) = *(_DWORD *)(4 * a3 + a1);*(_DWORD *)(a1 + 4 * a3) = v3;return 1;
}

func0一看就是利用一个temp把两个东西交换一下。

func1

int __cdecl func1(int a1, int a2, int a3)
{int v3; // eax@1v3 = (*(_DWORD *)(4 * a2 + a1) + *(_DWORD *)(4 * a3 + a1)) >> 31;return (v3 ^ (*(_DWORD *)(4 * a2 + a1) + *(_DWORD *)(4 * a3 + a1)))- v3- (((*(_DWORD *)(4 * a3 + a1) >> 31) ^ *(_DWORD *)(4 * a3 + a1))- (*(_DWORD *)(4 * a3 + a1) >> 31))- abs(*(_DWORD *)(4 * a2 + a1))+ 2;
}

func1return值化简后为4a2+a1-abs(4a2+a1)+2(注意,这里我把右移31位直接看成0了,32位的数最高位为1被输入的话很难遇到,直接忽略。)
4a2+a1-abs(4a2+a1)+2也就是y=x-|x|+2的函数图像,如下:

这里返回值的分情况,有小于0,有大于0,有等于0

func2

int __cdecl func2(int a1, int a2, int a3)
{int v3; // ecx@1v3 = (*(_DWORD *)(4 * a3 + a1) + *(_DWORD *)(4 * a2 + a1)) >> 31;return ((*(_DWORD *)(4 * a3 + a1) >> 31) ^ *(_DWORD *)(4 * a3 + a1))- (*(_DWORD *)(4 * a3 + a1) >> 31)- ((v3 ^ (*(_DWORD *)(4 * a3 + a1) + *(_DWORD *)(4 * a2 + a1)))- v3)+ abs(*(_DWORD *)(4 * a2 + a1))+ 2;
}

func2return值化简后为-4a2-a1+abs((4a2 + a1))+2(注意,这里我把右移31位直接看成0了,32位的数最高位为1被输入的话很难遇到,直接忽略。)
-4a2-a1+abs((4a2 + a1))+2也就是y=-x+|x|+2的函数图像,如下:

也就是说返回值恒大于0

解决方法分析

  1. 使func2返回值为0
  2. 使v16函数指针指向的函数改变(指向v15)

第一种方法,我们所画出的函数恒为正,直接pass
第二种方法,如何改变呢?这里func0本来就是用来交换的,直接在调用func0函数时传func1函数的函数指针v15和func2函数的函数指针v16进行交换即可。交换之后,v16函数指针指向的函数就是func1,func1的返回值可正可负可为零,返回值赋给了v11,然后就可以对flag进行打印输出。

分析

signed int __cdecl func0(int a1, int a2, int a3)
{int v3; // ST0C_4v3 = *(_DWORD *)(4 * a2 + a1);*(_DWORD *)(a1 + 4 * a2) = *(_DWORD *)(4 * a3 + a1);*(_DWORD *)(a1 + 4 * a3) = v3;return 1;
}
 *(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);

观察func0,此时func0函数中参与运算的起始地址是v8,偏移分别是v12(3)和v13(4),也就是说,此时func0交换的参数是v11和v12。要使参数变为v15和v16,就需要把偏移改为7和8,也就是把v12的值改为7,v13 的值改为8(其实颠倒过来也行)

v7有20个字节,v8~v11是4个int,也就是4x4=16个字节,于是data文件需要从第36个字节开始,将v12和v13覆盖为7和8。

此时仅仅只是交换了两个函数指针,如何保证func1的返回值为0呢?
v15执行func2函数,v16执行func1函数,而在循环中v12和v13由i赋值,当执行v16(func1)函数时,v12和v13的值分别为2和3

 while ( i <= 2 ){v6 = i + 1;*(&v8 + v6) = (*(&v14 + i))((int)&v8, v12, v13);v12 = ++i;v13 = i + 1;}

4a2+a1-abs(4a2+a1)+2,v10等于2,要使返回的值为0,则需v11为-1,于是同样利用程序data读文件的漏洞将v11覆盖为-1

flag: 8cda1bdb68a72a392a3968a71bdb8cda

BUUCTF Dig the way相关推荐

  1. BUUCTF reverse题解汇总

    本文是BUUCTF平台reverse题解的汇总 题解均来自本人博客 目录 Page1 Page2 Page3 Page4 Page1 easyre reverse1 reverse2 内涵的软件 新年 ...

  2. 在Ubuntu 14.04 64bit上使用dig

    简介 dig即Domain Information Groper,和nslookup作用有些类似,都是DNS查询工具. 一些专业的DNS管理员在追查DNS问题时,都乐于使用dig命令,是看中了dig设 ...

  3. ASCII HEX BIN DIG进制转换工具页

    ASCII  HEX BIN DIG进制转换工具页 http://www.ab126.com/goju/1711.html

  4. go get 失败 no go files in_Go 每日一库之 dig

    简介 今天我们来介绍 Go 语言的一个依赖注入(DI)库--dig.dig 是 uber 开源的库.Java 依赖注入的库有很多,相信即使不是做 Java 开发的童鞋也听过大名鼎鼎的 Spring.相 ...

  5. CentOS系统dig和nslookup的安装

    nslookup是常用来查询本机域名解析情况的命令,但是一些linux系统下默认无此命令.我们可以通过安装一个包来使该命令生效,这个包中也包括dig命令. Ubuntu:# sudo apt-get ...

  6. linux shell dig nslookup 指定dns服务器 查询域名解析

    一般来说linux下查询域名解析有两种选择,nslookup或者dig,而在使用上我觉得dig更加方便顺手. 如果是在linux下的话,只要装上dnsutils这个包就可以使用dig命令, 安装bin ...

  7. linux dig 命令使用方法

    dig 命令主要用来从 DNS 域名服务器查询主机地址信息. 查询单个域名的 DNS 信息 dig 命令最典型的用法就是查询单个主机的信息. $ dig baidu.com dig 命令默认的输出信息 ...

  8. dig挖出DNS的秘密

    本原创文章属于<Linux大棚>博客. 博客地址为http://roclinux.cn. 文章作者为roc. === [初次见面] 我相信使用nslookup的同学一定比使用dig的同学多 ...

  9. python dig trace 功能实现——通过Querying name server IP来判定是否为dns tunnel

    dns tunnel确认方法,查询子域名最终的解析地址: 使用方法:python dig_trace.py  "<7cf1e56b 67fc90f8 caaae86e 0787e907 ...

最新文章

  1. 计算机网络实验报告建立校园网,计算机网络实验报告
  2. C++_reference
  3. AlamofireJsonToObjects+EVReflection:一步解决JSON解析
  4. 1.3 用神经网络进行监督学习-深度学习-Stanford吴恩达教授
  5. 1月第1周要闻回顾:年末威胁上升的态势仍延续
  6. OpenCASCADE:网格
  7. android自定义radiogroup,Android自定义RadioGroup
  8. (组合数学笔记)Pólya计数理论_Part.3_置换群及其性质
  9. 优化的意义,不仅在于业绩的提升
  10. python获取cookie值的方法_Python获取Cookie、设置Cookie的N种方法
  11. sdk manager的列表怎么消失了_腾讯安全SDK的Dll Dump研究
  12. 网友神总结:我们继续用 XP 的十大理由
  13. 【eoeAndroid特刊】第一期到第十八期
  14. 基于华为SMProxy开发cmpp2.0(跳坑版)
  15. 【Python】Scrapy爬虫介绍requests爬虫移植到Scrapy爬虫
  16. [Swust OJ 643]--行列式的计算(上三角行列式变换)
  17. 视频分割算法在移动端如何应用
  18. html div父集子集,怎么让父极元素的宽度自动设为所有子集的宽度之和呢?或者怎么保证子集不换行?不考虑用js!...
  19. 干货 | Islands Architecture(孤岛架构)在携程新版首页的实践
  20. python身体指数BMI

热门文章

  1. python执行shell命令查看输出_python 运行 shell 命令并捕获输出_python_酷徒编程知识库...
  2. ML:MLOps系列讲解之《MLOps Stack Canvas堆栈画布》解读
  3. DL之DilatedConvolutions:Dilated Convolutions(膨胀卷积/扩张卷积)算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
  4. 成功解决TypeError: __init__() got an unexpected keyword argument 'indices'
  5. Python语言学习之字母C开头函数使用集锦:count用法之详细攻略
  6. 亲爱的,热爱的~CTF
  7. 【UVA 437】The Tower of Babylon(记忆化搜索写法)
  8. a, b = b, a+b
  9. Win 8 app 获取窗口的宽度和高度, 本地化, 及文本读取
  10. [Linux].netrc或者_netrc使用可以