REVERSE-COMPETITION-HGAME2022-Week4

  • ( WOW )
  • server
  • ezvm
  • hardasm

( WOW )

32位exe,ida打开
观察伪代码逻辑,上面的红框中,输入经过加密,密文放入Buf2中,然后Buf2和已知的密文res比较
比较完,打印"win"或者"error"
发现下面还有一部分对Buf2处理的代码逻辑,而且几乎和上面对输入加密的结构是一样的
于是猜测下面的红框中就是解密逻辑

起调试,随便构造一个长度为32的输入
在调用memcmp前下断,将内存中Buf2的数据patch成已知的密文res
进行下面对Buf2的解密,解密后的input即为flag

server

64位exe,用ida7.7打开
(52pojie上有最新的全插件绿色版ida7.7可供下载)
从main函数中知道监听本地的9090端口
回调函数为main_HttpHandleFunc,调试发现,输入经过main_encrypt处理后,与已知的res比较

进入main_encrypt函数,伪代码看不出什么,玄机在汇编指令
可以发现main_encrypt的前半部分实际上是个RSA加密,并且已知p,q,e

输入经rsa加密后,将十进制密文的每一个数字转成字符存入rsa_cipher中
然后进行后半部分的两轮异或运算,异或后的结果与已知的res比较

这里举一个例子来理解后半部分的两轮异或运算,以便能够更好地逆向

# -*- coding:utf-8 -*-
import gmpy2
num1=92582184765240663364795767694262273105045150785272129481762171937885924776597#p
num2=107310528658039985708896636559112400334262005367649176746429531274300859498993#q
num1_mul_num2=num1*num2#n
num3=950501#e
my_in_s="hgame{abcdefghijklmnopqrstuvwxy}"#构造的输入
my_in=0x6867616d657b6162636465666768696a6b6c6d6e6f707172737475767778797d
my_in_cipher=gmpy2.powmod(my_in,num3,num1_mul_num2)#rsa加密
my_in_cipher_s=str(my_in_cipher)
print("RSA加密后的十进制密文:")
print(my_in_cipher_s)
print("RSA加密后的十进制密文长度:")
print(len(my_in_cipher_s))
#模拟main_encrypt中后半部分的两轮异或运算
res=[]
for i in range(len(my_in_cipher_s)-1):res.append(ord(my_in_cipher_s[i]))
print("十进制密文的每一个数字转成字符:")
print(res)
v22=102
index=0
while index<153:tmp=res[index]res[index]^=v22v22=tmpindex+=1
print("第一轮异或运算结果:")
print(res)
index=0
while index<153:tmp=res[index]res[index]^=v22v22=tmpindex+=1
print("第二轮异或运算结果:")
print(res)
# RSA加密后的十进制密文:
# 2315576653393559402200063691755666172531457200527539170715551199511112051176056764826673635703056993058028383465485142593456001504496707046294726739445446
# RSA加密后的十进制密文长度:
# 154
# 十进制密文的每一个数字转成字符:(后面用cipher表示)
# [50, 51, 49, 53, 53, 55, 54, 54, 53, 51, 51, 57, 51, 53, 53, 57, 52, 48, 50, 50, 48, 48, 48, 54, 51, 54, 57, 49, 55, 53, 53, 54, 54, 54, 49, 55, 50, 53, 51, 49, 52, 53, 55, 50, 48, 48, 53, 50, 55, 53, 51, 57, 49, 55, 48, 55, 49, 53, 53, 53, 49, 49, 57, 57, 53, 49, 49, 49, 49, 50, 48, 53, 49, 49, 55, 54, 48, 53, 54, 55, 54, 52, 56, 50, 54, 54, 55, 51, 54, 51, 53, 55, 48, 51, 48, 53, 54, 57, 57, 51, 48, 53, 56, 48, 50, 56, 51, 56, 51, 52, 54, 53, 52, 56, 53, 49, 52, 50, 53, 57, 51, 52, 53, 54, 48, 48, 49, 53, 48, 52, 52, 57, 54, 55, 48, 55, 48, 52, 54, 50, 57, 52, 55, 50, 54, 55, 51, 57, 52, 52, 53, 52, 52]
# 第一轮异或运算结果:(后面用round_1表示)
# [84, 1, 2, 4, 0, 2, 1, 0, 3, 6, 0, 10, 10, 6, 0, 12, 13, 4, 2, 0, 2, 0, 0, 6, 5, 5, 15, 8, 6, 2, 0, 3, 0, 0, 7, 6, 5, 7, 6, 2, 5, 1, 2, 5, 2, 0, 5, 7, 5, 2, 6, 10, 8, 6, 7, 7, 6, 4, 0, 0, 4, 0, 8, 0, 12, 4, 0, 0, 0, 3, 2, 5, 4, 0, 6, 1, 6, 5, 3, 1, 1, 2, 12, 10, 4, 0, 1, 4, 5, 5, 6, 2, 7, 3, 3, 5, 3, 15, 0, 10, 3, 5, 13, 8, 2, 10, 11, 11, 11, 7, 2, 3, 1, 12, 13, 4, 5, 6, 7, 12, 10, 7, 1, 3, 6, 0, 1, 4, 5, 4, 0, 13, 15, 1, 7, 7, 7, 4, 2, 4, 11, 13, 3, 5, 4, 1, 4, 10, 13, 0, 1, 1, 0]
# 第二轮异或运算结果:(后面用round_2表示)
# [96, 85, 3, 6, 4, 2, 3, 1, 3, 5, 6, 10, 0, 12, 6, 12, 1, 9, 6, 2, 2, 2, 0, 6, 3, 0, 10, 7, 14, 4, 2, 3, 3, 0, 7, 1, 3, 2, 1, 4, 7, 4, 3, 7, 7, 2, 5, 2, 2, 7, 4, 12, 2, 14, 1, 0, 1, 2, 4, 0, 4, 4, 8, 8, 12, 8, 4, 0, 0, 3, 1, 7, 1, 4, 6, 7, 7, 3, 6, 2, 0, 3, 14, 6, 14, 4, 1, 5, 1, 0, 3, 4, 5, 4, 0, 6, 6, 12, 15, 10, 9, 6, 8, 5, 10, 8, 1, 0, 0, 12, 5, 1, 2, 13, 1, 9, 1, 3, 1, 11, 6, 13, 6, 2, 5, 6, 1, 5, 1, 1, 4, 13, 2, 14, 6, 0, 0, 3, 6, 6, 15, 6, 14, 6, 1, 5, 5, 14, 7, 13, 1, 0, 1]

我们可以发现
round_1[0]=102 xor cipher[0],round_1[1]=cipher[0] xor cipher[1],依次类推(这里的102是已知的
round_2[0]=52 xor round_1[0],round_2[1]=round_1[0] xor round_1[1],依次类推
这里的52实际上等于cipher[len(cipher)-1],也就是cipher的最后一个值
但是由于cipher是RSA加密后的密文,所以其实cipher[len(cipher)-1]是未知的
想要逆向两轮异或运算从而得到正确的RSA密文
需要爆破cipher[len(cipher)-1],范围是十进制数字字符

# -*- coding:utf-8 -*-
import gmpy2
from Crypto.Util.number import long_to_bytes
# res 相当于 round_2
res=[0x63,0x55,0x4,0x3,0x5,0x5,0x5,0x3,0x7,0x7,0x2,0x8,0x8,0xb,0x1,0x2,0xa,0x4,0x2,0xd,0x8,0x9,0xc,0x9,0x4,0xd,0x8,0x0,0xe,0x0,0xf,0xd,0xe,0xa,0x2,0x2,0x1,0x7,0x3,0x5,0x6,0x4,0x6,0x7,0x6,0x2,0x2,0x5,0x3,0x3,0x9,0x6,0x0,0xb,0xd,0xb,0x0,0x2,0x3,0x8,0x3,0xb,0x7,0x1,0xb,0x5,0xe,0x5,0x0,0xa,0xe,0xf,0xd,0x7,0xd,0x7,0xe,0x1,0xf,0x1,0xb,0x5,0x6,0x2,0xc,0x6,0xa,0x4,0x1,0x7,0x4,0x2,0x6,0x3,0x6,0xc,0x5,0xc,0x3,0xc,0x6,0x0,0x4,0xf,0x2,0xe,0x7,0x0,0xe,0xe,0xc,0x4,0x3,0x4,0x2,0x0,0x0,0x2,0x6,0x2,0x3,0x6,0x4,0x4,0x4,0x7,0x1,0x2,0x3,0x9,0x2,0xc,0x8,0x1,0xc,0x3,0xc,0x2,0x0,0x3,0xe,0x3,0xe,0xc,0x9,0x1,0x7,0xf,0x5,0x7,0x2,0x2,0x4]
p=92582184765240663364795767694262273105045150785272129481762171937885924776597
q=107310528658039985708896636559112400334262005367649176746429531274300859498993
n=p*q
e=950501
phin=(p-1)*(q-1)
d=gmpy2.invert(e,phin)
# i 相当于 cipher[len(cipher)-1],爆破的目标
for i in range(0x30,0x3a):tmp1=[res[0]^i] # tmp1 相当于 round_1for j in range(1,len(res)):tmp1.append(res[j]^tmp1[j-1])tmp2=[tmp1[0]^102]# tmp2 相当于 cipherfor j in range(1,len(tmp1)):tmp2.append(tmp1[j]^tmp2[j-1])wrong=0for m in tmp2: # cipher 中每个元素都必须是十进制数字字符if m<0x30 or m>0x39:wrong=1breakif wrong:continueif i==tmp2[len(tmp2)-1]:# 验证爆破的目标和还原出来的cipher[len(cipher)-1]是否相同cipher=""for k in tmp2:cipher+=chr(k)cipher_num=int(cipher)m=gmpy2.powmod(cipher_num,d,n)# RSA解密print(long_to_bytes(m))
# hgame{g0_and_g0_http_5erv3r_nb}

ezvm

vm,调试确定输入的长度应为32
对输入的处理逻辑为:输入的每个字符,左移1位,和一个值异或,然后比较
构造一个长度为32的输入,得到经程序处理后的结果
逆向对输入的处理逻辑,得到输入的每个字符左移1位后,要去异或的值的集合
最后由真正的密文爆破出flag

# -*- coding:utf-8 -*-
s="hgame{abcdefghijklmnopqrstuvwxy}"# 对输入的处理逻辑:输入的每个字符,左移1位,和一个值异或,然后比较print(hex((ord("h")<<1)^0x5e))# 0x8e
print(hex((ord("g")<<1)^0x46))# 0x88# my_res: 构造的输入s,经过程序处理后的结果
my_res=[0x8e,0x88,0xa3,0x99,0xc4,0xa5,0x8b,0xdb,0x97,0x96,0xfc,0xfb,0xe7,0x91,0xb1,0xef,0xb2,0xe3,0xcf,0xc4,0x85,0xde,0xc0,0xb4,0xa0,0xb6,0xdf,0xa2,0xad,0xd3,0x92,0xc1]
# 逆向对输入的处理逻辑,得到输入的每个字符左移1位后,要去异或的值的集合
xor_box=[]
for i in range(len(s)):tmp=(ord(s[i])<<1)&0xffxor_box.append(tmp^my_res[i])# res: 真正的密文
res=[0x8e,0x88,0xa3,0x99,0xc4,0xa5,0xc3,0xdd,0x19,0xec,0x6c,0x9b,0xf3,0x1b,0x8b,0x5b,0x3e,0x9b,0xf1,0x86,0xf3,0xf4,0xa4,0xf8,0xf8,0x98,0xab,0x86,0x89,0x61,0x22,0xc1]
flag=""
for i in range(len(res)):for j in range(32,128):if (j<<1)^xor_box[i]==res[i]:flag+=chr(j)
print(flag)
# hgame{Ea$Y-Vm-t0-PrOTeCT_cOde!!}

hardasm

近7000行的汇编指令,应该是有什么地方设计得很巧妙
起调试,构造一个长度为32的输入"hgame{abcdefghijklmnopqrstuvwxy}"
在如下位置下断点

程序断下后,观察此时[rsp+70h+var_50]上的数据
可以发现,前6个数据均为非0的0xFF,而后面的数据均为0
这是因为我们构造的输入的前6个字符"hgame{“是正确的,而后面凑长度的字符是错误的

于是我们可以知道
可以通过[rsp+70h+var_50]上连续的0xFF的个数n,确定我们的输入前n个字符是正确的
那么怎么知道[rsp+70h+var_50]上连续的0xFF的个数n呢?我们需要能够打印的函数
程序最后会打印"success"或者"error”,我们可以利用这个打印函数
从框住的汇编指令处可以观察到,我们的目标数据地址是借rcx寄存器传入打印函数的

于是我们patch程序,将[rsp+70h+var_50]借rcx传入打印函数,如下
(这里不知道怎么能直接实现lea rcx,[rsp+70h+var_50],如果有师傅知道,请在评论区留言,谢谢)

这样程序在最后就不会打印"success"或者"error",而会打印[rsp+70h+var_50]上的数据
将patch应用到程序后,再起调试,同样的输入,在调用打印函数printf后下断,观察打印出的内容
可以看到虽然没有打印出具体的字符(因为0xFF不可见),但是长度是正确的,为6

或者可以用python来确认

import subprocess
flag="hgame{abcdefghijklmnopqrstuvwxy}"
p = subprocess.Popen(["D:\\ctfdownloadfiles\\hardasm.exe"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
p.stdin.write(flag.encode())
p.stdin.close()
out = p.stdout.read()
print(len(out))
for c in out:print(hex(c))
# 6
# 0xff
# 0xff
# 0xff
# 0xff
# 0xff
# 0xff

最后就是依次爆破每个位置上的字符
根据程序打印出的连续的0xFF的个数来确定某位置上的字符是否正确

# -*- coding:utf-8 -*-
import subprocess
real_flag="hgame{"#绝对正确的前6个字符
cur_index=6#当前爆破的位置
while cur_index<32:for i in range(32,128):#当前爆破的位置上的字符real_flag_arr = [0] * 32for j in range(len(real_flag)):#正确的先复制一下real_flag_arr[j]=ord(real_flag[j])real_flag_arr[len(real_flag_arr)-1]=ord("}")#最后一个字符"}"固定for j in range(len(real_flag_arr)-2,cur_index,-1):#除了当前爆破的位置,其他位置上都设置为32real_flag_arr[j]=32real_flag_arr[cur_index]=i#设置当前爆破的位置上的字符real_flag_arr_s="".join(chr(k) for k in real_flag_arr)#输入到程序中的字符串p = subprocess.Popen(["D:\\ctfdownloadfiles\\hardasm.exe"], stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE)p.stdin.write(real_flag_arr_s.encode())p.stdin.close()out = p.stdout.read()if len(out)>cur_index:#判断程序打印出的0xFF的个数是否增加,增加则说明当前爆破的位置上的字符设置的是正确的real_flag+=chr(i)cur_index+=1print(real_flag)break
# hgame{r
# hgame{ri
# hgame{rig
# hgame{righ
# hgame{right
# hgame{right_
# hgame{right_y
# hgame{right_yo
# hgame{right_you
# hgame{right_your
# hgame{right_your_
# hgame{right_your_a
# hgame{right_your_as
# hgame{right_your_asm
# hgame{right_your_asm_
# hgame{right_your_asm_i
# hgame{right_your_asm_is
# hgame{right_your_asm_is_
# hgame{right_your_asm_is_g
# hgame{right_your_asm_is_go
# hgame{right_your_asm_is_goo
# hgame{right_your_asm_is_good
# hgame{right_your_asm_is_good!
# hgame{right_your_asm_is_good!!
# hgame{right_your_asm_is_good!!}
# hgame{right_your_asm_is_good!!}

REVERSE-COMPETITION-HGAME2022-Week4相关推荐

  1. VK Cup 2016 - Round 1 (Div. 2 Edition) A. Bear and Reverse Radewoosh 水题

    A. Bear and Reverse Radewoosh 题目连接: http://www.codeforces.com/contest/658/problem/A Description Lima ...

  2. HGame 2023 Week4 部分Writeup

    文章同时发布于我的博客:https://blog.vvbbnn00.cn/archives/hgame2023week4-bu-fen-writeup 第四周的比赛难度较高,同时也出现了不少颇为有趣的 ...

  3. BUUCTF reverse题解汇总

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

  4. Coursera, How to win a competition 课程笔记

    How to win a data science competition 课程简介 课程收获 how to preprocess the data extract features how to s ...

  5. 206. Reverse Linked List

    Reverse a singly linked list. 反转单链表 C++(9ms):  迭代 1 /** 2 * Definition for singly-linked list. 3 * s ...

  6. LeetCode 7. Reverse Integer

    问题链接 LeetCode 7 题目解析 给定一个32位有符号整数,求其反转数字. 解题思路 如果是简单反转的话,那这道题就太简单了.题目要求判断溢出问题,32位int类型的范围是-214748364 ...

  7. CUDA Samples: approximate image reverse

    以下CUDA sample是分别用C++和CUDA实现的对图像进行某种类似reverse的操作,并对其中使用到的CUDA函数进行了解说,各个文件内容如下: common.hpp: #ifndef FB ...

  8. C++中std::reverse和std::reverse_copy的使用

    std::reverse:反转排序容器内指定范围中的元素. std::reverse_copy与std::reverse唯一的区别是:reverse_copy会将结果拷贝到另外一个容器中,而不影响原容 ...

  9. leetcode Reverse Linked List

    Reverse a singly linked list 对于这种可以修改值的,把值逆序就可以了....用vector存,然后逆序读. 都忘了指针怎么赋值初始化了.*p=&head; 1 /* ...

  10. 2009 Competition Highlights by ICPC Live

    2009 Competition Highlights by ICPC Live Links:http://www.youtube.com/watch?v=n0oZRcAz6w0 转载于:https: ...

最新文章

  1. python 把元组转为列表
  2. 好书推介---Windows Server 2003企业部署原理与实践
  3. 不同坐标系下角速度_坐标系统及常见坐标系
  4. oracle归档目录莫名删除,Oracle归档目录被自动删除的bug
  5. python tkinter的基础用法
  6. php db类 应用实例,PHP封装类似thinkphp连贯操作数据库Db类与简单应用示例
  7. 另类方法激活你的Winodws 2008
  8. 沙巴克服务器占用,传奇私服服务端里最完整的攻沙传送教程,直接飞皇宫和影之道方法...
  9. Ajax怎么解决乱码PHP,php Ajax乱码
  10. 计算机显示磁盘但是打不开怎么办,移动硬盘显示盘符但打不开解决教程
  11. DBF文件初步了解(二)——DBF数据导出代码实现
  12. 小程序创建搜索记录,获取搜索记录,删除搜索历史
  13. malloc失败的一个原因
  14. 格式化U盘提示Windows 无法完成格式化
  15. U8根据发货单生成销售订单(反向生单)
  16. 不可战胜的苹果:全球最酷企业十大经验
  17. Logo Grabber 一键快速下载网站Logo 的免费插件
  18. oracle执行存储过程参数,Oracle定时任务执行存储过程带参数
  19. iOS安全-测试内容
  20. Unity连接Photon

热门文章

  1. POJ1033 Defragment
  2. java算法:冒泡排序
  3. HDU 3306 Another kind of Fibonacci
  4. [Leetcode][第75题][JAVA][颜色分类][双(三)指针][计数排序]
  5. 【数据结构与算法】复杂度分析
  6. 940B. Our Tanya is Crying Out Loud
  7. html HTML1300 进行了导航,jquery根据文章H标签自动生成导航目录
  8. python中面向对象_简述Python中的面向对象编程的概念
  9. 计算机过程控制系统教材,过程控制系统-样章试读.PDF
  10. java单例设计模式_Java设计模式之单例模式详解