X-CTF(REVERSE高级) asong
asong加密程序、out加密结果、that_girl加密引用的数据
out:一堆乱码二进制
that_girl:66行英语文字
asong:
一、函数功能解析
main函数
获取用户输入的值,引用that_girl文件做词频统计,对用户的输入值和统计结果进行一通操作后将结果保存输出到out文件
sub_400AAA函数
打开that_girl文件逐字读取,读取的内容给sub_400936函数,计算得到v2,地址(a2+v2)的值加1
sub_400936函数,用case语法判断读取的数字
sub_400E54函数
根据flag获取数组v5,改变v5的数组顺序,对v5的数据进行位移和或操作,将v5写入到out文件
sub_400936函数,用case语法判断读取的数字
sub_400D33函数,改变v5数组的顺序,改变规则是通过index的变换,index的变换由s数组决定
s数组的值
sub_400DB4函数,对v5数组的值进行移位和或运算
sub_400CC0函数,v5写入out文件
二、调试获取统计数据
开始调试,获取that_girl文件词频统计的结果。ida报错“Bochs executable "bochsdbg.exe" is not found:
Please install Bochs and/or specify the location of "bochsdbg.exe" in the dbg_bochs.cfg file.”
进入官网:http://sourceforge.net/projects/bochs/files/bochs/,下载bochs,安装。
进入本地\IDA_Pro_v7.5_Portable\cfg目录找到“dbg_bochs.cfg”文件,修改bochs安装路径,取消注释
报错,“Please ensure that Bochs is installed and configured properly.
Bochs output can be checked in the message window”
是因为boot配置问题,“ROM: couldn't open ROM image file '(null)/BIOS-bochs-latest'.”
对文件“asong.bochsrc”进行配置
还是有问题,算了,自己统计
1.不区分大小写
2.数字和字母的数组index,0开始为数字,10开始为字母
26个英文字母加10个数字,从36开始的index也安排好了
统计词频
s = """there's_a_girl_but_i_let_her_get_away it's_all_my_fault_cause_pride_got_in_the_way and_i'd_be_lying_if_i_said_i_was_ok about_that_girl_the_one_i_let_get_away i_keep_saying_no this_can't_be_the_way_we're_supposed_to_be i_keep_saying_no there's_gotta_be_a_way_to_get_you_close_to_me now_i_know_you_gotta speak_up_if_you_want_somebody can't_let_him_get_away_oh_no you_don't_wanna_end_up_sorry the_way_that_i'm_feeling_everyday no_no_no_no there's_no_hope_for_the_broken_heart no_no_no_no there's_no_hope_for_the_broken there's_a_girl_but_i_let_her_get_away it's_my_fault_cause_i_said_i_needed_space i've_been_torturing_myself_night_and_day about_that_girl_the_one_i_let_get_away i_keep_saying_no this can't be the way we're supposed to be i keep saying no there's gotta be a way to get you there's gotta be a way to_get_you_close_to_me you_gotta speak_up_if_you_want_somebody can't_let_him_get_away_oh_no you_don't_wanna_end_up_sorry the_way_that_i'm_feeling_everyday no_no_no_no there's_no_hope_for_the_broken_heart no no no no there's no hope for the broken no home for me no home cause i'm broken no room to breathe and i got no one to blame no home for me no_home_cause_i'm_broken about_that_girl the_one_i_let_get_away so_you_better speak_up_if_you_want_somebody you_can't_let_him_bet_away_no_no you_don't_wanna_end_up_sorry the_way_that_i'm_feeling_everyday don't_you_know no_no_no_no there's_no_hope_for_the_broken_hearty don't you know no no no no there's no hope for the broken oh you don't wanna lose at love it's only gonna hurt too much i'm telling you you_don't_wanna_lose_at_love it's_only_gonna_hurt_too_much i'm_telling_you you_don't_wanna_lose_at_love cause_there's_no_hope_for_the_broken_heart that_girl the_one_i_let_get_away """ out = {} for i in s: out.update({i:s.count(i)}) out = sorted(out.items()) print(out) ''' [('\n', 66), (' ', 71), ("'", 40), ('_', 245), ('a', 104), ('b', 30), ('c', 15), ('d', 29), ('e', 169), ('f', 19), ('g', 38), ('h', 67), ('i', 60), ('k', 20), ('l', 39), ('m', 28), ('n', 118), ('o', 165), ('p', 26), ('r', 61), ('s', 51), ('t', 133), ('u', 45), ('v', 7), ('w', 34), ('y', 62)] 没有数字,没有case以外的统计,根据规则重新整理一下数组排序: [('a', 104), ('b', 30), ('c', 15), ('d', 29), ('e', 169), ('f', 19), ('g', 38), ('h', 67), ('i', 60), ('k', 20), ('l', 39), ('m', 28), ('n', 118), ('o', 165), ('p', 26), ('r', 61), ('s', 51), ('t', 133), ('u', 45), ('v', 7), ('w', 34), ('y', 62),(' ', 71),('_', 245)] '''
三、解密
对“out”文件的数据进行反向操作,先做移位和或运算,再改变顺序,最后和词频统计换算
这道题算法的小细节很多,仿佛回到了高中做数学奥数题,最简洁精妙的解法还是官方的wp,所以放上添加了我注释的官方wp吧,之后讲讲为什么精妙。
s = [22, 0, 6, 2, 30, 24, 9, 1, 21, 7, 18, 10, 8, 12, 17, 23, 13, 4, 3, 14, 19, 11, 20, 16, 15, 5, 25, 36, 27, 28, 29, 37, 31, 32, 33, 26, 34, 35] #从程序data区复制出来的s数组值 mapp={' ': 71, "'": 40, '_': 245, 'a': 104, 'c': 15, 'b': 30, 'e': 169, 'd': 29, 'g': 38, 'f': 19, 'i': 60, 'h': 67, 'k': 20, 'm': 28, 'l': 39, 'o': 165, 'n': 118, 'p': 26, 's': 51, 'r': 61, 'u': 45, 't': 133, 'w': 34, 'v': 7, 'y': 62} #that_girl词频统计 def decypt(): enc = open("out","rb").read() d0 = [] temp = enc[len(enc)-1] & 0x7 for i in range(len(enc)): d0.append((temp << 5) | enc[i] >> 3) temp = enc[i] & 0x7 #移位、或运算的解 i = 37 temp = d0[37] while s.index(i) != 37: d0[i] = d0[s.index(i)] i = s.index(i) #改变顺序的解 d0[i] = temp flag = [] for i in d0: flag.append(list(mapp.keys())[list(mapp.values()).index(i)]) #和词频统计换算的解 return "QCTF{%s}" % ''.join(flag) print(decypt())
官方wp代码解析:
一、为什么移位和或运算可以用&0x7去逆推
仿照程序 sub_400DB4函数,对v5数组的值进行移位和或运算,写一个s数组模仿对s的加密解密,发现能通过&0x7去逆推位移和或运算,太神奇了吧。但是逆推回去的最后一位不准,s数组的值超过255会造成多位值不准。正好对应了程序加密保留原v5数组的最后一位值不变,“out”文件的byte读取值范围是0到0xff。
那为什么&0x7可以逆推移位和或运算呢,<<是向左移动,>>是向右移动,|或运算(有1则1),&与运算(都1则1)
加密:s[i]<<3,左移三位,为保留本i的二进制后七位到当前值。s[i+1]>>5,右移5位,为保留下一i的二进制前三位到当前值。
解密:&0x7,与运算0000 0111,为取二进制后三位的值。temp<<5,为恢复当前i的二进制前三位的值。enc[i] >> 3,为恢复当前i的二进制后七位的值。
所以,整体逻辑是,加密运算本i保留后七位和下一i的前三位,逆运算把本i七位退回去,用temp去前i找保留的本i的前三位。
想了一晚上,谁逆向放二进制加密谁是🐕,平常要多多积累啊
二、为什么s数组是根据index改变数组顺序
*(_DWORD *)
是强制类型转化,然后再提领指针。*(int *) 也是强行转换后,再提领指针。
因为v2等于a1的地址,v2[1]为数组的值,那么&v2[1]为获取元素v2[1]在数组v2中的索引,然后*(int *)强制类型转换为int
v2[1]就是while循环的变量i,因为out的长度是37,所以其逆推其初始长度为37
最后词频统计的逻辑很简单,逆推就是根据上面算出result数组的值,去查找对应字典的key