本文为之前参加过的ctf比赛的writeup,迁移到这个博客来吧

剑指2021 西湖论剑 writeup

RE

官方wp在安恒的公众号里面,回复”逆向wp”

Cellular(solved 1血)

  • 读题意大概是输入由L或者R组成的字符串,然后对某个数据结构进行操作,使用动态调试发现程序只要判断某个位置的L或者R字符输入错误就会结束;
  • 这样的话可以逐位进行L或者R的输入,可以在0x4021D5位置下个断点,每次构造输入看能否到达这个断点,不能的话就修改当前字符,这样做25次之后可以得到path,再进行一下md5即为flag;
  • flag:md5(RLRLLRLLLRRLLRLLRLRLLRLLR)=b144da06ead3c5585cf18ce942055857

babyre(unsolved)

  • 0x5440EC处修改为1可以打印调试信息;
  • 输入32位
  • 12345678912345678912345678912121

loader(solved 3血)

  • loader.exe运行需要带三个参数;
  • bat替换掉一些字符可以得到以下的代码,这样可以得到运行loader.exe的参数;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
@set @=
@set ;=
@set a=
@set b=
@set }=
@set c=
@set d=
@set ,=
@set e=
@set f=
@set g=
@set !=
@set h=
@set i=
@set j=
@set '=
@set k=
@set l=
@set m=
@set #=
@set n=
@set o=
@set )=
@set p=
@set q=
@set r=
@set _=
@set s=
@set t=
@set u=
@set (=
@set v=
@set $=
@set w=
@set x=
@set y=
@set {=
@set z=
@echo off
echo Your Password:
loader.exe code -d 114514
pause
  • 动态调试跟踪到输入和验证的代码,发现是简单的异或,dump数据重用代码可以解出输入字符串,flag:15cf4ce97a270960b10bed48b0cfe4b8
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include<iostream>
using namespace std;
int main(){
char a[32]={0x02, 0x0D, 0x6A, 0x1A, 0x27, 0x7F, 0x32, 0x65, 0x86, 0xA5,
0xC5, 0xD7, 0xCF, 0xDD, 0xE3, 0x98, 0x3D, 0x79, 0x53, 0x6A,
0x18, 0x54, 0x37, 0x14, 0xA9, 0xE0, 0x82, 0xB2, 0xCE, 0x80,
0xCD, 0x8C};
char cfb[6]={0x50, 0x45, 0x46, 0x69, 0x6C, 0x65};
for (int i = 0; i < 32; ++i )
a[i] ^= 16 * i ^ ((i | 0xE3) + cfb[i % 6]);
for(int i =0;i<32;i++){
cout<<a[i];
}
cout<<endl;
return 0;
}

flow(unsolved)

  • 好像是用了类似rop的方法去控制流,代码在数据段里面;
  • 输入会变成二进制的数据(然后被0xcc覆盖?)
  • 123333343
  • 看起来像是运算流;
  • 3 有点像取值
  • 4 +
  • 5 >>
  • 6 ^
  • 7 <<
  • 8 判断
  • 9 返回
  • 最后判断输入经过运算之后是否和一个固定的16位的bytes数组相等;
  • 仔细看了下sys_rt_sigreturn这个系统调用,发现是SROP的原理,看了下资料,简单来说就是通过栈溢出构造一个signal frame,然后调用这个系统调用,这个系统调用会识别栈顶的值并用一个结构套入,这个结构的可以构造所有寄存器的值,由signal handler实现,相比于ROP的优势应该在于不需要去找很多的写入寄存器的gadget,只需要一个栈溢出带一个系统调用就可以对寄存器进行赋值,并且还可以实现栈迁移,形成rop链也比较容易;

    参考资料:

    https://www.jianshu.com/p/ca4a5dacd1a2
    https://www.freebuf.com/articles/network/87447.html

  • 首先在0x4017ED处有一个memcpy的栈溢出写入了第一个sig_frame,跳转到0x401634的地方,先不管这个函数是做什么的,先想想怎么把流给dump出来;
  • 这个函数在之前动态调试的时候也看了一下,本来以为会有第二次的栈溢出,结果并没有发现;同时想着会不会下一个sig_frame也放在原来栈溢出的地方,但是看了下数据只有第一个sig_frame的数据,那么后面的sig_frame在哪里呢;
  • 看了sig_frame的结构,发现rip和rsp寄存器的位置都有值,那么就很明显了,rip寄存器是指向下一个函数的地址,rsp寄存器放的就是下一个sig_frame结构;
  • 感觉需要整一个IDApython脚本把流和寄存器信息给导出来,再去学习一下;
  • 以上的两个链接的sigreturnframe的寄存器位置好像不太对,和pwntools中的SigreturnFrame的结构不太一样,原因应该是博客里面的结构是完整的攻击结构,考虑了ret的时候栈顶的结构应该,但是这道题不用,因为毕竟是逆向题,不怎么用考虑gadget的完整性,把返回的流给dump出来就行;
  • dump出寄存器的值了,现在是分析一下各个指向的函数的功能,这一步要结合寄存器的值来看;
  • 可以根据逆向的内容写一个解释器看看
  • 逆出来算是个表达式了,用z3解解看;
  • z3解不了,因为有左移和右移,看了wp,原来还是TEA,总结一下这道题把;
    • 从后面说起,有一个函数反编译之后看不出来循环里判断了条件,虽然看到了32但是没往循环去想,还以为简单加密了一轮就完了;此外搜索了TEA里面的delta好像也没搜出来是TEA,有点尴尬(看了下是搜错常数了,这是赛后自己慢慢做可能就没那么仔细,但是打比赛肯定要把所有常数都给搜一遍),因为之前想到有可能是已知加密算法了,而且这种不可解的感觉就会偏向这种虽然中间有很复杂的加密操作但是可以通过其他方式给逆出来;
    • 奥现在回过头去看解出来的那个式子,其实已经可以看出来用一个已知的可以解出来另一个,再由另一个解出已知的,昨晚最后把式子给总结出来的时候没有发现;
    • 还有一个是未知的知识点要赶紧找东西看原理,比如SROP,有个奇怪的syscall肯定要多留意去看;
    • IDA脚本也要写快点,pwntools有个现成的结构可以用;
    • 对TEA还不是很熟,现在有几个特征点记一下:0x9e3779b9常数,左移四位右移五位;
  • 最后贴一下结果还有脚本吧;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import idautils
import idc
import idaapi
import struct
import json

'''
by flash 20201010
'''

def transbytetoint(byteinput):
return struct.unpack('<Q',byteinput)

class sig_frame:
def __init__(self,hexinput):
self.r8 =transbytetoint(hexinput[0x28:0x30])
self.r9 =transbytetoint(hexinput[0x30:0x38])
self.r10=transbytetoint(hexinput[0x38:0x40])
self.r11=transbytetoint(hexinput[0x40:0x48])
self.r12=transbytetoint(hexinput[0x48:0x50])
self.r13=transbytetoint(hexinput[0x50:0x58])
self.r14=transbytetoint(hexinput[0x58:0x60])
self.r15=transbytetoint(hexinput[0x60:0x68])
self.rdi=transbytetoint(hexinput[0x68:0x70])
self.rsi=transbytetoint(hexinput[0x70:0x78])
self.rbp=transbytetoint(hexinput[0x78:0x80])
self.rbx=transbytetoint(hexinput[0x80:0x88])
self.rdx=transbytetoint(hexinput[0x88:0x90])
self.rax=transbytetoint(hexinput[0x90:0x98])
self.rcx=transbytetoint(hexinput[0x98:0xa0])
self.rsp=transbytetoint(hexinput[0xa0:0xa8])
self.rip=transbytetoint(hexinput[0xa8:0xb0])

def allreg(self):
varlist={}
for name,value in vars(self).items():
varlist[name]=value
return varlist

print '------------------'

c=idc.get_bytes(0x40B288,0x100)
d=sig_frame(c)
flowlist=[]
count=0
while d.rsp:
flowlist.append(d.allreg())
print(d.rsp[0])
c=idc.get_bytes(d.rsp[0],0x100)
d=sig_frame(c)
if count>70:
break
count+=1

print flowlist

with open('result.json','w') as f:
json.dump(flowlist,f)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
dst1=[0x71, 0xDE, 0xA5, 0xF6, 0x43, 0x22, 0x89, 0xF7, 0xD1, 0xA4, 
0x0C, 0x19, 0x9B, 0xF3, 0x4F, 0x97]


import json

class sig_frame:
def __init__(self,reglist):
self.r8 =reglist["r8"][0]
self.r9 =reglist["r9"][0]
self.r10=reglist["r10"][0]
self.r11=reglist["r11"][0]
self.r12=reglist["r12"][0]
self.r13=reglist["r13"][0]
self.r14=reglist["r14"][0]
self.r15=reglist["r15"][0]
self.rdi=reglist["rdi"][0]
self.rsi=reglist["rsi"][0]
self.rbp=reglist["rbp"][0]
self.rbx=reglist["rbx"][0]
self.rdx=reglist["rdx"][0]
self.rax=reglist["rax"][0]
self.rcx=reglist["rcx"][0]
self.rsp=reglist["rsp"][0]
self.rip=reglist["rip"][0]

def func_4015f1(reglist):
curreg=sig_frame(reglist)
if curreg.rbx:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') = ('+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rcx))+')')

else:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') = '+str(hex(curreg.rcx)))


def func_401594(reglist):
curreg=sig_frame(reglist)
if curreg.rbx:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') += ('+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rcx))+')')

else:

print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') += '+str(hex(curreg.rcx)))


def func_401535(reglist):
curreg=sig_frame(reglist)
if curreg.rbx:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') >>= ('+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rcx))+')')

else:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') >>= '+str(hex(curreg.rcx)))


def func_401477(reglist):
curreg=sig_frame(reglist)
if curreg.rbx:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') ^= ('+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rcx))+')')

else:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') ^= '+str(hex(curreg.rcx)))


def func_4014d6(reglist):
curreg=sig_frame(reglist)
if curreg.rbx:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') <<= ('+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rcx))+')')

else:
print('( '+str(hex(curreg.rdx))+' + 4*'+str(hex(curreg.rax))+') <<= '+str(hex(curreg.rcx)))



def checkfunc(currip,reglist):
if currip==0x4015f1:
func_4015f1(reglist)
return 0

if currip == 0x401594:
func_401594(reglist)
return 0

if currip == 0x401477:
func_401477(reglist)
return 0

if currip == 0x401535:
func_401535(reglist)
return 0

if currip == 0x4014d6:
func_4014d6(reglist)
return 0

return 1


with open('result.json','r') as f:
flowlist=json.load(f)

count=0
for flow in flowlist:
count+=1

if checkfunc(flow["rip"][0],flow):
print("curfunction : "+str(hex(flow["rip"][0])))
for i,j in flow.items():
if i!="rsp" and i!='rip' and j[0]:
print(i+':'+str(hex(j[0])),end=' , ')
print('\n------------------')


with open('flowana','r') as f:
b=f.read()
b=b.split('\n')

strlist=['']*0xf
strlist[0]="input_0"
strlist[1]="input_1"
strlist[2]="input_2"
strlist[3]="input_3"

import re


for row in b:
curindexlist=re.findall(r'(?<=4\*0x)[0-9a-z]*',row)
curindex=int(curindexlist[0],16)
curtag=re.findall(r'(?<=\) )[=|>|+|<|^]*(?= )',row)[0]
if curtag=='=':
if len(curindexlist) > 1:
strlist[curindex] = strlist[int(curindexlist[1],16)]
else:
strlist[curindex]=re.findall(r'(?<= = ).*',row)[0]
print(strlist[curindex])
elif curtag=='+=':
if len(curindexlist) > 1:
strlist[curindex] = strlist[curindex] + ' + ' + strlist[int(curindexlist[1],16)]
else:
strlist[curindex]=strlist[curindex] + ' + ' + re.findall(r'(?<= \+= ).*',row)[0]
print(strlist[curindex])
elif curtag=='>>=':
if len(curindexlist) > 1:
strlist[curindex] =' ( ( ' + strlist[curindex] + ' ) >> ' + strlist[int(curindexlist[1],16)] + ' ) '
else:
strlist[curindex]=' ( ( ' + strlist[curindex] + ' ) >> ' + re.findall(r'(?<= >>= ).*',row)[0] + ' ) '
print(strlist[curindex])
elif curtag=='<<=':
if len(curindexlist) > 1:
strlist[curindex] =' ( ( ' + strlist[curindex] + ' ) << ' + strlist[int(curindexlist[1],16)] + ' ) '
else:
strlist[curindex]=' ( ( ' + strlist[curindex] + ' ) << ' + re.findall(r'(?<= <<= ).*',row)[0] + ' ) '
print(strlist[curindex])
elif curtag=='^=':
if len(curindexlist) > 1:
strlist[curindex] =' ( ( ' + strlist[curindex] + ' ) ^ ' + strlist[int(curindexlist[1],16)] + ' ) '
else:
strlist[curindex]=' ( ( ' + strlist[curindex] + ' ) ^ ' + re.findall(r'(?<= ^= ).*',row)[0] + ' ) '
print(strlist[curindex])
else:
print("error")
print(curtag)
exit()

print('------------------')
for i in range(4):
print(strlist[i])