此篇文章记录了我对Reversing.kr
网站的解题过程,有的直接看的wp,有的自己复现了一遍。
先开个头,后续会慢慢补充。
Ea5yR3versing
K3yg3nm3
00401150
LIstenCare
这几题比较简单直接贴上答案
ida
打开,定位到关键字符串,向上回溯,下断,跟了一遍,程序在0040466F mov byte ptr [eax], 90h
处报错,结合逻辑分析,eax
和我们的输入相关,0x90
代表着nop
,继续往下看调用堆栈。
最后要跳转到loc_401071
,为了到达correct
处,我们需要将loc_401071
处的代码nop
,通过程序自身代码就可以做到,我们只需使eax==0x401071
即可。
答案:2687109798
。。写着写着忘记保存了,东西都丢了。还是定位到关键函数,然后找到对比的资源文件,这里建议使用resourcehacker
工具将资源文件dump
下来,然后写个简单的脚本即可。
from PIL import Image
width = 200
height = 150
image_file = open('Data_1.bin', 'rb')
data = image_file.read()
image = Image.frombuffer('RGB', (width, height), data, 'raw', 'RGB')
image = image.transpose(Image.FLIP_TOP_BOTTOM)
image.show()
image_file.close()
答案:GOT
很简单的不想写了
答案:L1NUX
首先upx
然后又是一堆花指令,写个简单的ipython
脚本patch
一下,这里还是需要手动的修改函数头55 8B EC 83 EC 24 53 56 57
,然后使用ida
重新f5
一下便可以看到反编译后的结果。
但是这个sub_401000
函数还是被加了花,但是对逆向程序逻辑没有影响。
主要的加密过程,这里需要得到key
值,观察pe
文件可以看到题中给的hint
,找到file
文件对应的位置,解密得到key
enc='C7F2E2FFAFE3ECE9FBE5FBE1ACF0FBE5E2E0E7BEE4F9B7E8F9E2B3F3E5ACCBDCCDA6F1F8FEE9'.decode('hex')
dec='This program cannot be run in DOS mode'
key=''
enc = list(enc)
dec = list(dec)
for i in range(len(enc)):
enc[i] = (~ord(enc[i]))&255
key += chr(ord(dec[i])^enc[i])
print key
letsplaychess
最后解密file
文件,得到exe
文件,运行即可以得到flag
data_c = open('file','rb').read()
data_d=''
key='letsplaychess'
key_len = len(key)
for i in range(len(data_c)):
data_d += chr(ord(key[i%key_len])^(~ord(data_c[i]))&255)
open('file.exe','wb').write(data_d)
答案:Colle System
(我感觉如果没有提示有点脑洞了,看着wp写的)
这题有点神 输入一个空格 就可以看到 flag
》??
不过还是有必要了解这个文件格式的CSHOP.exe: PE32 executable (GUI) Intel 80386 Mono/.Net assembly, for MS Windows
./net
框架,采用c#
编写,使用dnSpy
进行反编译,然后可以修改IL
指令。感觉这个dnSpy
工具还是用的少。
答案:P4W6RP6SES
我的电脑上无法运行这个程序,只能静态分析了,ida
分析字符串,交叉引用到byte_407028
其中byte_407028
的数据十分的可疑,但是无法确定result
为何值,向上回溯,可以发现sub_403400
在一个循环体中,可以猜测result
是o-len(byte_407028)
,但是另一个角度来说,result
和v2*4
是线性关系,所以影响不大。
但是问题来了,通过交叉引用byte_409184
发现此数据只有读的引用,没有写,因此在程序初始化时,此数据将被初始化,那么之前说了,我无法运行此程序,因此我在程序init
下断,查看此时的byte_409184
数据,我这里显示的都为0,但是我看wp却说是存在byte_409184[528*i] = 4*i
这样的关系。
23333.
flag_c='436B666B62756C694C455C455F5A461C07252529701734390116494C20150B0FF7EBFAE8B0FDEBBCF4CCDA9FF5F0E8CEF0A9'.decode('hex')
flag_d=''
for i in range(len(flag_c)):
flag_d += chr(ord(flag_c[i])^4*i)
print flag_d
#Congratulation~ Game Clear! Password is Thr3EDPr0m
mac
下的程序,但是我没有mac
的电脑,pizza来说静态看就是了,不过程序也简单,通过字符串,定位到关键函数(我经常这么写,因为确实是这么做的!)
算法不可逆,但是可以逐字节爆破,脚本也简单,注意下uint_8
变量范围0-256
即可。
dic = [0x44, 0xF6, 0xF5, 0x57, 0xF5, 0xC6, 0x96, 0xB6, 0x56, 0xF5, 0x14, 0x25, 0xD4, 0xF5, 0x96, 0xE6, 0x37, 0x47, 0x27, 0x57, 0x36, 0x47, 0x96, 0x03, 0xE6, 0xF3, 0xA3, 0x92, 0x00]
flg = ''
for j in range(len(dic)):
for fl in range(256):
k = fl
for i in range(4):
tmp = (k*2)
if tmp&0x100:
tmp |= 1
k = tmp%256
if k == dic[j]:
flg +=chr(fl)
print flg
答案:Do_u_like_ARM_instructi0n?:)
下载ffdec
工具同样的分析字符串,依次输入1456,25,44,8,88,20546
这题我没有复现,flash打不开。
答案:16876
这题之前做z3
总结的时候做过,写出约束条件,然后z3
求解即可
from z3 import *
username = [BitVec('u%d'%i,8) for i in range(0,4)]
solver = Solver() #76876-77776
solver.add(((username[0]&1)+5+(((username[1]>>2) & 1 )+1))==ord('7')-0x30)
solver.add(((((username[0]>>3) & 1)+5)+(((username[1]>>3)&1)+1))==ord('6')-0x30)
solver.add((((username[0]>>1) & 1)+5+(((username[1]>>4) & 1 )+1))==ord('8')-0x30)
solver.add((((username[0]>>2) & 1)+5+(((username[1]) & 1 )+1))==ord('7')-0x30)
solver.add((((username[0]>>4) & 1)+5+(((username[1]>>1) & 1 )+1))==ord('6')-0x30)
solver.add((((username[2]) & 1)+5+(((username[3]>>2) & 1 )+1))==ord('7')-0x30)
solver.add((((username[2]>>3) & 1)+5+(((username[3]>>3) & 1 )+1))==ord('7')-0x30)
solver.add((((username[2]>>1) & 1)+5+(((username[3]>>4) & 1 )+1))==ord('7')-0x30)
solver.add((((username[2]>>2) & 1)+5+(((username[3]) & 1 )+1))==ord('7')-0x30)
solver.add((((username[2]>>4) & 1)+5+(((username[3]>>1) & 1 )+1))==ord('6')-0x30)
solver.add(username[3] == ord('p'))
for i in range(0,4):
solver.add(username[i] >= ord('a'))
solver.add(username[i] <= ord('z'))
solver.check()
result = solver.model()
flag = ''
for i in range(0,4):
flag += chr(result[username[i]].as_long().real)
print flag
以上全部参考 https://veritas501.space/2017/03/04/Reversing.kr%20writeup/
加壳并且需要逆vm
ida
动态调试,然后dump
出内存
static main(void)
{
auto fp,dex_addr,end_addr;
fp=fopen("/home/jeb/Downloads/Reverse.Kr/SimpleVM/dump.dex","wb");
end_addr=0x804C000;
for(dex_addr=0x8048000;dex_addr
脱完壳之后,分析起来还是十分的困难,因为没有函数的导入表,我们需要手动的去识别!
我们可以静态和动态结合起来去看。
对于脱完壳之后的这几个函数。
我们可以通过动态的去寻找相应的函数,例如我们在源程序中搜索804B020
导入表所对应的函数
进而定位到libc
中
f5
可以看到最后有个vfork
,所以猜测该函数为libc
中fork
函数。
类似的我们可以慢慢的定位其他函数。
大概如下
父进程:
子进程:
由于程序中对wrong correct
等字符串进行了加密,所以无法直接搜索得到,但同样可以利用动静结合的方式,得出。
为了使其输出正确,我们需要使func==1 && dword_804B190!=0
,但是func
函数确实太繁琐了。
这个函数存在一个xor
操作
这里有一个判断操作,所以我们可以尝试侧信道攻击。
是时候拿出我们的利器pintools
但是我在使用pinCTF
工具进行长度爆破时,程序直接卡死了,而且直接确定好长度后也同样会报错,本来我以为这个github
项目的容错性比较好,最后试了一下还是自己写脚本比较靠谱。
先是手动尝试flag
的长度
比较清楚的看到flag
长度为7
,这里需要注意一点的是由于改程序存在子进程,所以会有两个Count
,这也是为什么pinCTF
工具出错的原因!
手动的测试了第一个字符为i
从结果来看,主要抓住第二个Count
即可
最终脚本如下:
shell = shell()
dic = string.letters+'_+*/'+string.digits
cout_old = 234174
cur=''
for i in range(7):
for s in dic:
shell.initPin(cmd)
pwd = cur+s+'?'*(6-len(cur))
print pwd
shell.pinWrite(pwd+'\n')
sout,serr = shell.pinRun()
cout = sout.split("Count ")[2]
cout_sub= int(cout) - cout_old
cout_old = int(cout)
if cout_sub > 10 and cout_sub < 1000:
cur=cur+s
break
print ("current cur ", cur,"current count:",cout,"sub_count ",cout_sub)
有关pintools
的使用可以参考我之前写的几篇文章。
答案;id3*ndh
参考文章:
https://blog.csdn.net/whklhhhh/article/details/78221365
http://www.freebuf.com/news/164664.html
第二篇文章的思路也很好,做vm
的题就是需要耐心,而且很多时候我们是没有办法去动态调试的,需要硬着头皮静态分析。
这题很蒙,看了wp还是没有复现出来,upx
脱壳之后就不知道干什么了,目标不是很明确!
最后是解两个md5
答案:isolated pawn
第二天又看了一遍,还是没做出来,感觉OD用的不熟
感觉这题比1要友好,至少不用对着一堆壳代码进行分析。根据字符串定位到关键函数。这是一个程序自校验的函数。
sub_4508C7
返回0
程序逻辑我是动态调试分析的。
v11
是相对固定的,所以我们需要手动的修改最后四个字节,以使程序不会崩溃
结果是A15A7F44
,继续往下。
这里需要注意理解fseek(*v3, (int)v3[1], 0);
此时的文件指针指向的是0x90839adc
也就是程序后8个字节所指向的地址。
然后取出改地址中的16个字节同,固定字节进行比较。因此我们需要在程序中找到对应数据所在的位置。
所以我们修改倒数8个字节为006e0600
,通过手动修改标志位寄存器可以得到
英文翻译一下,得到答案:jonsnow
其实做到最后发现这题不修改程序也是可以的,只要在动态调试遇见跳转时手动修改ZF
值即可,最终也能走到最后的分支,也能弹出那段提示!
算法好难!如果是crc32那就很简单的,但是64感觉增加了一个维度。
程序的逻辑很简单,就是输入一个8位的flag,然后替换一个内置的table
,之后使用crc64
进行加密,最后的结果与某个值进行比较。
实在是太笨了,看了wp
还是没有复现出来。
答案:CrCA1g@!
这题更加看不懂了。
贴下答案:goodRevKrF0rU
参考链接
这位国外的老哥太强了!
这题我现在只能给个参考链接
需要在本地搭建AVR
调试环境。
这题还是比较有意思的,了解了./net
程序的破解以及IL
指令
首先dySpn
反编译,从程序逻辑可以看出有一个方法被加密了,在运行时会进行解密,但是我们无法动态的跟进去,有两种方法。
得到解密后的IL code
,使用ILByteDecoder
工具解密得到IL
指令,整体来说IL
指令并不复杂
IL_0000: ldarg.1
IL_0001: ldlen
IL_0002: conv.i4
IL_0003: ldc.i4.s 0xC
IL_0005: bne.un
IL_000A: ldarg.0
IL_000B: ldc.i4.0
IL_000C: ldc.i4.2
IL_000D: stelem.i1
IL_000E: ldarg.1
IL_000F: ldc.i4.0
IL_0010: ldelem.u1
IL_0011: ldc.i4 0x10
IL_0016: xor
IL_0017: ldc.i4.s 0x4A
IL_0019: beq.s
IL_001B: ldarg.0
IL_001C: ldc.i4.0
IL_001D: ldc.i4.1
IL_001E: stelem.i1
IL_001F: ldarg.1
IL_0020: ldc.i4.3
IL_0021: ldelem.u1
IL_0022: ldc.i4 0x33
IL_0027: xor
IL_0028: ldc.i4.s 0x46
IL_002A: beq.s
IL_002C: ldarg.0
IL_002D: ldc.i4.0
IL_002E: ldc.i4.1
IL_002F: stelem.i1
IL_0030: ldarg.1
IL_0031: ldc.i4.1
IL_0032: ldelem.u1
IL_0033: ldc.i4 0x11
IL_0038: xor
IL_0039: ldc.i4.s 0x57
IL_003B: beq.s
IL_003D: ldarg.0
IL_003E: ldc.i4.0
IL_003F: ldc.i4.1
IL_0040: stelem.i1
IL_0041: ldarg.1
IL_0042: ldc.i4.2
IL_0043: ldelem.u1
IL_0044: ldc.i4 0x21
IL_0049: xor
IL_004A: ldc.i4.s 0x4D
IL_004C: beq.s
IL_004E: ldarg.0
IL_004F: ldc.i4.0
IL_0050: ldc.i4.1
IL_0051: stelem.i1
IL_0052: ldarg.1
IL_0053: ldc.i4.s 0xB
IL_0055: ldelem.u1
IL_0056: ldc.i4 0x11
IL_005B: xor
IL_005C: ldc.i4.s 0x2C
IL_005E: beq.s
IL_0060: ldarg.0
IL_0061: ldc.i4.0
IL_0062: ldc.i4.1
IL_0063: stelem.i1
IL_0064: ldarg.1
IL_0065: ldc.i4.8
IL_0066: ldelem.u1
IL_0067: ldc.i4 0x90
IL_006C: xor
IL_006D: ldc.i4 0xF1
IL_0072: beq.s
IL_0074: ldarg.0
IL_0075: ldc.i4.0
IL_0076: ldc.i4.1
IL_0077: stelem.i1
IL_0078: ldarg.1
IL_0079: ldc.i4.4
IL_007A: ldelem.u1
IL_007B: ldc.i4 0x44
IL_0080: xor
IL_0081: ldc.i4.s 0x1D
IL_0083: beq.s
IL_0085: ldarg.0
IL_0086: ldc.i4.0
IL_0087: ldc.i4.1
IL_0088: stelem.i1
IL_0089: ldarg.1
IL_008A: ldc.i4.5
IL_008B: ldelem.u1
IL_008C: ldc.i4 0x66
IL_0091: xor
IL_0092: ldc.i4.s 0x31
IL_0094: beq.s
IL_0096: ldarg.0
IL_0097: ldc.i4.0
IL_0098: ldc.i4.1
IL_0099: stelem.i1
IL_009A: ldarg.1
IL_009B: ldc.i4.s 0x9
IL_009D: ldelem.u1
IL_009E: ldc.i4 0xB5
IL_00A3: xor
IL_00A4: ldc.i4 0xE2
IL_00A9: beq.s
IL_00AB: ldarg.0
IL_00AC: ldc.i4.0
IL_00AD: ldc.i4.1
IL_00AE: stelem.i1
IL_00AF: ldarg.1
IL_00B0: ldc.i4.7
IL_00B1: ldelem.u1
IL_00B2: ldc.i4 0xA0
IL_00B7: xor
IL_00B8: ldc.i4 0xEE
IL_00BD: beq.s
IL_00BF: ldarg.0
IL_00C0: ldc.i4.0
IL_00C1: ldc.i4.1
IL_00C2: stelem.i1
IL_00C3: ldarg.1
IL_00C4: ldc.i4.s 0xA
IL_00C6: ldelem.u1
IL_00C7: ldc.i4 0xEE
IL_00CC: xor
IL_00CD: ldc.i4 0xA3
IL_00D2: beq.s
IL_00D4: ldarg.0
IL_00D5: ldc.i4.0
IL_00D6: ldc.i4.1
IL_00D7: stelem.i1
IL_00D8: ldarg.1
IL_00D9: ldc.i4.6
IL_00DA: ldelem.u1
IL_00DB: ldc.i4 0x33
IL_00E0: xor
IL_00E1: ldc.i4.s 0x75
IL_00E3: beq.s
IL_00E5: ldarg.0
IL_00E6: ldc.i4.0
IL_00E7: ldc.i4.1
IL_00E8: stelem.i1
IL_00E9: ret
IL
指令集的参考文章
整体来说,先判断长度ldc.i4.s 0xC bne.un
,然后异或之后判断,不过这样子还是比较的麻烦的。
得到解密后的hex
我们可以选择覆盖源程序,然后重新使用dySpn
反编译,这样就不需要自己去分析IL
指令了。
经过一番折腾之后终于得到了解密后的程序。
所以加密的过程很简单,难点在于如何解密该函数。
我大致说一下过程。
首先如下两个断点
调试,查看此时的Form1.bb
值,据此在文件中找到相应的位置。
运行到解密处,然后将此时的Form.bb
dump出来。
覆盖文件中的对应位置的值,保存.exe
然后重新使用dnSpy
加载,此时就可以看到反编译后的结果了。
解密脚本如下:(直接用的夜影师傅的代码)
import base64
bt = [0 for i in range(12)]
bt[0] = 16 ^ 74
bt[3] = 51 ^ 70
bt[1] = 17 ^ 87
bt[2] = 33 ^ 77
bt[11] = 17 ^ 44
bt[8] = 144 ^ 241
bt[4] = 68 ^ 29
bt[5] = 102 ^ 49
bt[9] = 181 ^ 226
bt[7] = 160 ^ 238
bt[10] = 238 ^ 163
bt[6] = 51 ^ 117
flag = ''
for i in bt:
flag+=(chr(i))
print(base64.b64decode(flag))
参考文章
答案:`dYnaaMic
一道简单的java
题,我的ubuntu不知道咋滴jd-gui
死活无法运行,最后下载了jadx
,然后反编译。
就这么几行代码。
知识点就是一个long
类型溢出,有符号数的运算。
所以-1536092243306511225L
很明显是溢出了,
该值在内存应为2**64-(-1536092243306511225L)=0xeaaeb43e477b8487L
然后简单运算一下,无法整除。
那么便可以说明,高位全部溢出了,所以我们需要爆破出高位。
在编写代码时暂且不考虑有无符号
i = 1
while(1):
k = i*2**64 + 0xEAAEB43E477B8487
if(k % 26729 == 0):
print k
print(k/26729)
print(2**64-k/26729)
break
else:
i += 1
k=0x3597eaaeb43e477b8487L
爆破出高位为3597
所以答案为8978084842198767761
这题表示不太明白出题人的意图??
输入仅仅是用来同伪随机函数进行判断,并没有用于flag的加密,所以直接设置ip
到flag
输出的位置即可。
参考了夜影师傅的文章,发现还有很多解题的思路。
一种方法,由于是伪随机,那么我们便可以写一个程序,然后传入参数给lotto
程序。
如下代码:
#include
#include
#include
void main()
{
char s[100];
int n[6], i;
time_t t;
t = _time64(NULL);#得到当前时间秒数
srand(t);#随机数生成种子
for(i=0;i<6;i++)
n[i] = rand()%100;#取出数列中的6个数
sprintf(s, "echo %d %d %d %d %d %d | F:\\ctf\\reversing.kr\\Lotto\\Lotto.exe", n[0], n[1], n[2], n[3], n[4], n[5]);#生成通过管道送入值的字符串
system(s);#控制台调用
system("pause");
}
第二种,直接用python复现一遍算法。不贴代码了。
很抱歉这题,由于
提示说Windows8 MetroStyle App
,环境我懒得搭建了,所以就直接静态的看了。
IDA
搜索字符串没发现什么结果,但是我手动看了下rdata
段,还是发现了一些可疑字符,从而可以定位到关键函数。
好多windows api
不认识,上msdn
搜一下即可
WindowsCompareStringOrdinal
比较字符串是否相等
WindowsCreateStringReference
创建新的字符串引用
sub_40F4E0
那么很明显便是用来获取用户输入的。
v1 = ((int (__stdcall *)(int, int ))((_DWORD *)a1 + 24))(a1, &v7);// 类似于 jni

除了的作用不了解外,其它都可以知道,难道答案就是
MERONG???这也太简单了吧!很明显不是的。
correct
在一头雾水之时,我仔细的分析了分支,发现运行到这里时并没有
return`而是跳转到了另一个大循环中。
上面一大段代码没怎么看懂,直到。。
v50 = WindowsGetStringRawBuffer(v41, 0);
v51 = WindowsGetStringRawBuffer(v45, 0);
v52 = WindowsGetStringRawBuffer(v49, 0);
通过交叉引用我发现这三个变量均来自固定的结构。
因此我猜测v3 = (*(int (__stdcall **)(int, int *))(*(_DWORD *)v38 + 24))(v38, &v98);
便是用于获取用户输入的具体函数,当然这仅仅是猜测
那么这一段便是加密验证的部分,继续往下查看,发下v80
类似i
用于自增
最后我们需要直到比较的对象是谁,对v50
查交叉引用,最后我找到了v100
,但是好像没什么用处。
LOBYTE(v1) = *(_WORD *)(v50 + 2 * v80 + 2) != (unsigned __int8)(byte_4307A8[v80 & 7] ^ __ROL1__(
*(_BYTE *)(v52 + 2 * v80),
*(_WORD *)(v51 + 2 * v80) & 7));
我继续关注了这个验证的代码。
__ROL1__
函数第一个参数是变量,第二个参数是右移的长度,所以正常来说,v51
应该是长度信息才对,v52
是字符信息,我又仔细看了三者的来源,发现v51和v52
的来源相同,v50
则不同,所以又可以猜测,这应该是某种结构体,存放了长度和字符信息,类似与a[n+1]==a[n]>>len(a[n]&7)^(4307a8[n&7])
好,现在理清楚了,程序会从第一个字符开始,生成一串字符串,根据某种线性关系进行验证。这时可以想起来提示的意图了。Password must be composed of uppercase letter and digit
这里可能稍微需要补充下ROL
指令,循环左移。
所以我们编写脚本,遍历大写和数字字符,根据条件爆破出符合条件的字符串。
def ROR(x, n,bits = 8):
mask = (2L**n) - 1
mask_bits = x & mask
return (x >> n) | (mask_bits << (bits - n))
def ROL(x, n ,bits = 8):
return ROR(x, bits - n,bits)
byte_4307a8 = [0x77,0xAD,0x07,0x02,0xA5,0x00,0x29,0x99]
flag = []
s = [0]
for i in range(48,90):
s[0]= i
for j in range(20):
k = byte_4307a8[j&7]^ROL(s[j],s[j]&7,bits = 8)
if (not k):
flag.append(s)
break
s.append(k)
s=[0]
r = ''
for i in flag:
for j in i:
r+=chr(j)
print r
r=''
最后得到两组解,所以最后的答案:D34DF4C3
这个蛮难的,感觉全是靠感觉猜的。233333
现在还没到总结的时候!