BUUCTF Dig the way
文章目录
- 题目类型
- 查壳
- 拖进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
三个函数指针,分别指向func0
,func1
,func2
,读取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指向的函数)的返回值是赋给v9
;func1
(v15指向的函数)的的返回值是赋给v10
;func2
(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,否则返回其他值。
参数:
- stream:文件指针;
- offset:偏移量,整数表示正向偏移,负数表示负向偏移;
- fromwhere:设定从文件的哪里开始偏移,可能取值为:SEEK_CUR,SEEK_END 或 SEEK_SET
SEEK_SET: 文件开头
SEEK_CUR: 当前位置
SEEK_END: 文件结尾
其中SEEK_SET,SEEK_CUR和SEEK_END和依次为0,1和2;
例子:
- fseek(fp,50L,0);把fp指针移动到离文件开头50字节处;
- fseek(fp,50L,1);把fp指针移动到离文件当前位置50字节处;
- 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()
来决定发生什么情况。
参数:
- buffer :读取的数据存放的内存的指针(可以是数组,也可以是新开辟的空间,buffer就是一个索引);
- size : 每次读取的字节数 ;
- count :读取次数 ;
- 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;
}
func1
的return
值化简后为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;
}
func2
的return
值化简后为-4a2-a1+abs((4a2 + a1))+2
(注意,这里我把右移31
位直接看成0
了,32
位的数最高位为1被输入的话很难遇到,直接忽略。)
-4a2-a1+abs((4a2 + a1))+2
也就是y=-x+|x|+2的函数图像,如下:
也就是说返回值恒大于0
解决方法分析
- 使func2返回值为0
- 使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相关推荐
- BUUCTF reverse题解汇总
本文是BUUCTF平台reverse题解的汇总 题解均来自本人博客 目录 Page1 Page2 Page3 Page4 Page1 easyre reverse1 reverse2 内涵的软件 新年 ...
- 在Ubuntu 14.04 64bit上使用dig
简介 dig即Domain Information Groper,和nslookup作用有些类似,都是DNS查询工具. 一些专业的DNS管理员在追查DNS问题时,都乐于使用dig命令,是看中了dig设 ...
- ASCII HEX BIN DIG进制转换工具页
ASCII HEX BIN DIG进制转换工具页 http://www.ab126.com/goju/1711.html
- go get 失败 no go files in_Go 每日一库之 dig
简介 今天我们来介绍 Go 语言的一个依赖注入(DI)库--dig.dig 是 uber 开源的库.Java 依赖注入的库有很多,相信即使不是做 Java 开发的童鞋也听过大名鼎鼎的 Spring.相 ...
- CentOS系统dig和nslookup的安装
nslookup是常用来查询本机域名解析情况的命令,但是一些linux系统下默认无此命令.我们可以通过安装一个包来使该命令生效,这个包中也包括dig命令. Ubuntu:# sudo apt-get ...
- linux shell dig nslookup 指定dns服务器 查询域名解析
一般来说linux下查询域名解析有两种选择,nslookup或者dig,而在使用上我觉得dig更加方便顺手. 如果是在linux下的话,只要装上dnsutils这个包就可以使用dig命令, 安装bin ...
- linux dig 命令使用方法
dig 命令主要用来从 DNS 域名服务器查询主机地址信息. 查询单个域名的 DNS 信息 dig 命令最典型的用法就是查询单个主机的信息. $ dig baidu.com dig 命令默认的输出信息 ...
- dig挖出DNS的秘密
本原创文章属于<Linux大棚>博客. 博客地址为http://roclinux.cn. 文章作者为roc. === [初次见面] 我相信使用nslookup的同学一定比使用dig的同学多 ...
- python dig trace 功能实现——通过Querying name server IP来判定是否为dns tunnel
dns tunnel确认方法,查询子域名最终的解析地址: 使用方法:python dig_trace.py "<7cf1e56b 67fc90f8 caaae86e 0787e907 ...
最新文章
- 计算机网络实验报告建立校园网,计算机网络实验报告
- C++_reference
- AlamofireJsonToObjects+EVReflection:一步解决JSON解析
- 1.3 用神经网络进行监督学习-深度学习-Stanford吴恩达教授
- 1月第1周要闻回顾:年末威胁上升的态势仍延续
- OpenCASCADE:网格
- android自定义radiogroup,Android自定义RadioGroup
- (组合数学笔记)Pólya计数理论_Part.3_置换群及其性质
- 优化的意义,不仅在于业绩的提升
- python获取cookie值的方法_Python获取Cookie、设置Cookie的N种方法
- sdk manager的列表怎么消失了_腾讯安全SDK的Dll Dump研究
- 网友神总结:我们继续用 XP 的十大理由
- 【eoeAndroid特刊】第一期到第十八期
- 基于华为SMProxy开发cmpp2.0(跳坑版)
- 【Python】Scrapy爬虫介绍requests爬虫移植到Scrapy爬虫
- [Swust OJ 643]--行列式的计算(上三角行列式变换)
- 视频分割算法在移动端如何应用
- html div父集子集,怎么让父极元素的宽度自动设为所有子集的宽度之和呢?或者怎么保证子集不换行?不考虑用js!...
- 干货 | Islands Architecture(孤岛架构)在携程新版首页的实践
- python身体指数BMI
热门文章
- python执行shell命令查看输出_python 运行 shell 命令并捕获输出_python_酷徒编程知识库...
- ML:MLOps系列讲解之《MLOps Stack Canvas堆栈画布》解读
- DL之DilatedConvolutions:Dilated Convolutions(膨胀卷积/扩张卷积)算法的简介(论文介绍)、架构详解、案例应用等配图集合之详细攻略
- 成功解决TypeError: __init__() got an unexpected keyword argument 'indices'
- Python语言学习之字母C开头函数使用集锦:count用法之详细攻略
- 亲爱的,热爱的~CTF
- 【UVA 437】The Tower of Babylon(记忆化搜索写法)
- a, b = b, a+b
- Win 8 app 获取窗口的宽度和高度, 本地化, 及文本读取
- [Linux].netrc或者_netrc使用可以