攻防世界逆向高手题之notsequence

99 篇文章 34 订阅

攻防世界逆向高手题之notsequence

继续开启全栈梦想之逆向之旅~
这题是攻防世界逆向高手题的notsequence
在这里插入图片描述

这道题,算是我真正接触逆向算法的题目了,有点兴奋。
.
.
下载附件,照例扔入exeinfope中查看信息:
在这里插入图片描述
.
无壳,32位ELF文件,照例扔入32位IDA中查看伪代码,有Main函数看main函数:
在这里插入图片描述

.
.
题型是与用户输入有关的生成型flag,逻辑是经过两个check,分析第一个check函数v2 = sub_80486CD(&unk_8049BE0):
在这里插入图片描述
这里一开始我没看懂,按照反向逆向逻辑来看双重循环的话我得知道v5的值,但是这里并没有,所以我在主函数处发现了v2=20,但是又不确定v2在第二个check函数里有没有改变过,结果是没有。所以v5=v2=20,有了v5的值就可以进行反向第一个check中双层循环中的 for ( j = 0; j <= v5; ++j )循环了。
在这里插入图片描述
.
.
可是之后我还是逆向不了,我知道v3的个数和v5一样,可是v3 += *(_DWORD *)(4 * (j + i) + input_flag)这句代码标识v3是在input_flag中跳着取的啊,那这个逻辑逆向起来就相当麻烦了,我不会。(哭~)
.
.
查了资料才发现这是杨辉三角,算法逆向题,没办法,只能跟着wp走了,并附上我自己的见解。
.
.
首先这里积累第一个经验,附上杨辉三角解析:

[1] #0 /1 |2^0=1
[1, 1] #1 /2 |2^1=2
[1, 2, 1] #3 /3 |2^2=4
[1, 3, 3, 1] #6 /4 |2^3=8
[1, 4, 6, 4, 1] #10 /5 |2^4=16
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
[1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1]
.
.
这样来看杨辉三角第一批特征:
(1)最左边代表行号,0就是第0行。(行号从0开始)
(2)第1个字符在数组中的位置,#后的数字。
(3)一行的有几个数字 /后的内容。
(4)整行的和。|后的内容,是2的行号次方(行号得从0开始)
.
.
![在这里插入图片描述](https://img-blog.csdnimg.cn/ac472da135d14a209a6851ad422f47ad.png).
.
.
这样来看杨辉三角第二批特征:
第n行数字的和为2^(n) ,行号从0开始
1=2^(0-0), 1+1=2^(1-0), 1+2+1=2^(2-0), 1+3+3+1=2^(3-0) ,1+4+6+4+1=2^(4-0), 1+5+10+10+5+1=2^(5-0)。
.
.
斜线上数字的和等于其向左(从左上方到右下方的斜线),拐角上的数字。(在图中以用红线标好)
1+1=2,1+1+1=3,1+1+1+1=4,1+2=3,1+2+3=6,1+2+3+4=10,1+3=4,1+3+6=10,1+4=5

.
.
.
接下来分析check1函数代码:


int __cdecl sub_80486CD(int input_flag)
{
  int j; // [esp+0h] [ebp-14h]
  int v3; // [esp+4h] [ebp-10h]
  int i; // [esp+8h] [ebp-Ch]
  int v5; // [esp+Ch] [ebp-8h]

  v5 = 0;			//这里积累第二个经验:通过v5的0、1、2、3……然后退出循环中 i 表达式的前几个值0、1、3、6、10……可以发现问题,因为这不是遍历或者有规律的遍历(每次检查第四个),而且v5 * (v5 + 1) / 2 是等差数列的公式,结合前面的逻辑逆向麻烦性,由此要知道考的是算法。
  for ( i = 0; i <= 1024 && *(_DWORD *)(4 * i + input_flag); i = v5 * (v5 + 1) / 2 )
  {
    v3 = 0;
    for ( j = 0; j <= v5; ++j )		//这里积累第三个经验:在杨辉三角那里,每一行的数的总和等于2的以该行号的次方,行号从0开始算起。
      v3 += *(_DWORD *)(4 * (j + i) + input_flag);
    if ( 1 << v5 != v3 )                        // 所以这里v5是行上数的个数,这里1 << v5就表示2的v5次方,就是2的行号次方(从0开始)。 v3 += *(_DWORD *)(4 * (j + i) + input_flag)中i是v5行前的杨辉三角的个数,因为我们是一维排列杨辉三角的,所以只能用(4 * (j + i)这种表达式来遍历第v5上的v5+1个数(杨辉三角行从0开始!)
      return -1;
    ++v5;
  }
  return v5;
}

所以这段代码check1函数的作用是检测每一行求和结果为2`k
可以抽象成一个二维结构,有[k] 行(第一行k=0),每行开头为第k*(k+1)/2个数。
.
.
.
接着分析check2函数的代码:

int __cdecl sub_8048783(int input_flag, int k_20)
{
  int v3; // [esp+10h] [ebp-10h]
  int v4; // [esp+14h] [ebp-Ch]
  int i; // [esp+18h] [ebp-8h]
  int v6; // [esp+1Ch] [ebp-4h]

  v6 = 0;
  for ( i = 1; i < k_20; ++i )				//这里i总0、1、2……这样连续递增
  {
    v4 = 0;
    v3 = i - 1;										//这里v3从0、1、2、这样连续递增,
    if ( !*(_DWORD *)(4 * i + input_flag) )
      return 0;
    while ( k_20 - 1 > v3 )
    {
      v4 += *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + v6) + input_flag);		//这里积累第四个经验:这里等差数列表达式v3 * (v3 + 1) / 2前面说过了,是杨辉三角的行,v6从0开始递增,表示取杨辉三角v3行的v6列的值,而在这个循环中v3是变换的,也就是取得杨辉三角的行是变化的,而v6在此一个该循环中是固定的,所以可以看成是取每一行(v3)的同一个列(v6)
      ++v3;
    }
    if ( *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + i) + input_flag) != v4 )		//这里由于前面循环++v3后表明行号向下了一行,而 i 从1开始,v6从0开始,所以 i 永远比 v6大1。所以这里可以看作[0]-[k-1]行的 [v6] 列求和等于[k]行的 [i] 

      return 0;
    ++v6;
  }
  return 1;
}

这里check2函数验证的就是杨辉三角的这个特征:
在这里插入图片描述
.
.
.
所以答案很明显了,是杨辉三角的前20行就是答案,这里积累第5个经验,写杨辉三角生成脚本:(代码标注很详细了,希望对自己日后有帮助!)

def triangles():
	s=[1]	#这里s[1]作为杨辉三角函数起始值
	while True:		#无限循环生成杨辉三角
		yield s		#每次返回一行的杨辉三角列表
		s.append(0)	#给杨辉三角下一列扩充一个数的空间,因为每一行比上一行多1个
		s=[s[i-1]+s[i] for i in range(len(s))]	#覆盖生成杨辉三角行列表,满足杨辉三角的下一行的第n个数等于上一行的第n和n-1的和
n=0		#设置计数器,因为只打印前20行
flag=''
for i in triangles():	#每次获取从triangels函数的yield返回的一行列表
	#print(i)	#打印每一行杨辉三角
	flag+=''.join(map(str,i))		#返回通过指定字符连接序列中元素后生成的新字符串,以str为间隔,默认为逗号。而列表就是逗号间隔的
	n+=1
	if n==20:
		break
import hashlib
flag=hashlib.md5(flag.encode()).hexdigest()		#这里把flag的列表流变成了字节流,就去掉了列表保留了每个元素了,然后直接加密
print("RCTF{"+flag+"}")

.
.
结果:
在这里插入图片描述
.
.
总结:

1:
首先这里积累第一个经验,附上杨辉三角解析:
[1] #0 /1 |2^0=1
[1, 1] #1 /2 |2^1=2
[1, 2, 1] #3 /3 |2^2=4
[1, 3, 3, 1] #6 /4 |2^3=8
[1, 4, 6, 4, 1] #10 /5 |2^4=16
[1, 5, 10, 10, 5, 1]
[1, 6, 15, 20, 15, 6, 1]
[1, 7, 21, 35, 35, 21, 7, 1]
[1, 8, 28, 56, 70, 56, 28, 8, 1]
[1, 9, 36, 84, 126, 126, 84, 36, 9, 1]
[1, 10, 45, 120, 210, 252, 210, 120, 45, 10, 1]
.
.
这样来看杨辉三角第一批特征:
(1)最左边代表行号,1就是第1行。
(2)第1个字符在数组中的位置,#后的数字。
(3)一行的有几个数字 /后的内容。
(4)整行的和。|后的内容,是2的行号次方(行号得从0开始)
.
.
![在这里插入图片描述](https://img-blog.csdnimg.cn/ac472da135d14a209a6851ad422f47ad.png).
.
.
这样来看杨辉三角第二批特征:
第n行数字的和为2^(n-1) 。1=2^(1-1), 1+1=2^(2-1), 1+2+1=2^(3-1), 1+3+3+1=2^(4-1) ,1+4+6+4+1=2^(5-1), 1+5+10+10+5+1=2^(6-1)。
.
.
斜线上数字的和等于其向左(从左上方到右下方的斜线)或向右拐弯(从右上方到左下方的斜线),拐角上的数字。(在图中以用红线标好)
1+1=2,1+1+1=3,1+1+1+1=4,1+2=3,1+2+3=6,1+2+3+4=10,1+3=4,1+3+6=10,1+4=5

2:
这里积累第二个经验:通过v5的0、1、2、3……然后退出循环中 i 表达式的前几个值0、1、3、6、10……可以发现问题,因为这不是遍历或者有规律的遍历(每次检查第四个),而且v5 * (v5 + 1) / 2 是等差数列的公式,结合前面的逻辑逆向麻烦性,由此要知道考的是算法。
for ( i = 0; i <= 1024 && *(_DWORD *)(4 * i + input_flag); i = v5 * (v5 + 1) / 2 )

3:
这里积累第三个经验:在杨辉三角那里,每一行的数的总和等于2的以该行号的次方,行号从0开始算起。 所以这里v5是行上数的个数,这里1 << v5就表示2的v5次方,就是2的行号次方(从0开始)。 v3 += *(_DWORD *)(4 * (j + i) + input_flag)中i是v5行前的杨辉三角的个数,因为我们是一维排列杨辉三角的,所以只能用(4 * (j + i)这种表达式来遍历第v5上的v5+1个数(杨辉三角行从0开始!)

4:
v4 += *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + v6) + input_flag); //这里积累第四个经验:这里等差数列表达式v3 * (v3 + 1) / 2前面说过了,是杨辉三角的行,v6从0开始递增,表示取杨辉三角v3行的v6列的值,而在这个循环中v3是变换的,也就是取得杨辉三角的行是变化的,而v6在此一个该循环中是固定的,所以可以看成是取每一行(v3)的同一个列(v6)。
.
if ( *(_DWORD *)(4 * (v3 * (v3 + 1) / 2 + i) + input_flag) != v4 ) //这里由于前面循环++v3后表明行号向下了一行,而 i 从1开始,v6从0开始,所以 i 永远比 v6大1。所以这里可以看作[0]-[k-1]行的 [v6] 列求和等于[k]行的 [i]

5: 这里积累第5个经验,写杨辉三角生成脚本:(代码标注很详细了,希望对自己日后有帮助!)
`
def triangles():
s=[1] #这里s[1]作为杨辉三角函数起始值
while True: #无限循环生成杨辉三角
yield s #每次返回一行的杨辉三角列表
s.append(0) #给杨辉三角下一列扩充一个数的空间,因为每一行比上一行多1个
s=[s[i-1]+s[i] for i in range(len(s))] #覆盖生成杨辉三角行列表,满足杨辉三角的下一行的第n个数等于上一行的第n和n-1的和
n=0 #设置计数器,因为只打印前20行
flag=’’
for i in triangles(): #每次获取从triangels函数的yield返回的一行列表
#print(i) #打印每一行杨辉三角
flag+=’’.join(map(str,i)) #返回通过指定字符连接序列中元素后生成的新字符串,以str为间隔,默认为逗号。而列表就是逗号间隔的
n+=1
if n==20:
break
import hashlib
flag=hashlib.md5(flag.encode()).hexdigest() #这里把flag的列表流变成了字节流,就去掉了列表保留了每个元素了,然后直接加密
print(“RCTF{”+flag+"}")

解毕!敬礼!

  • 3
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论
回答: 对于攻防世界pwn新手,其中一个关键是要修改全局变量pwnme的内容。通过格式化字符串的方法,可以实现这个目标。格式化字符串的原理是利用输入的格式化字符串,修改内存中的指定位置的值。具体的方法可以参考CTF-wiki上对格式化字符串的总结。另外,还可以利用栈溢出漏洞来实现攻击。栈溢出漏洞的原理是当输入的数据超过了栈的缓冲区大小时,会覆盖到相邻的内存区域,包括函数返回地址等重要信息。通过溢出覆盖system函数的参数为"/bin/sh",就可以获取到shell权限。在IDA32中,可以通过查看字符串窗口,找到可以直接利用的字符串,比如system和/bin/sh。这样就可以猜测需要溢出覆盖system函数的参数,实现获取shell的目的。123 #### 引用[.reference_title] - *1* *2* [xctf攻防世界pwn基础解(新手食用)](https://blog.csdn.net/lplp9822/article/details/89735167)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] - *3* [攻防世界 pwn 二进制漏洞简单练习区 答(1-10解)](https://blog.csdn.net/qq_33957603/article/details/122450397)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v92^chatsearchT0_1"}} ] [.reference_item] [ .reference_list ]
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

沐一 · 林

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值