CTF | Reverse - Part 9
CrackRTF
拿到一个EXE,无壳,运行看看
拖进IDA,进入主函数
一共需要输入两次密码,长度都为6
- 第一个密码经历过
atoi(参数 str 所指向的字符串转换为一个整数,类型为 int 型, 如果字符串是数字,则转换为相应int型数据,如果为字母则为0)
函数,并且小于100000,再拼接@DBApp
,传进sub_40100A
函数,在于某一个字符串比较 - 第二个密码并不是纯数字,将输入的字符串与第一个密码(拼接后)再次拼接,再传进
sub_401019
函数,再与一串字符比较,相同再调用sub_40100F
函数
先看第一个密码加密的函数sub_40100A
百度得知是一个WinAPI
,CryptCreateHash
是一个可以进行很多Hash算法的函数,0x8004
是进行SHA1
算法,得知字符串是6位数字可以爆破
BOOL CryptCreateHash(
HCRYPTPROV hProv,
ALG_ID Algid, // 加密类型
HCRYPTKEY hKey,
DWORD dwFlags,
HCRYPTHASH *phHash
);
from hashlib import sha1
sha_1 = "6E32D0943418C2C33385BC35A1470250DD8923A9"
for i in range(100000, 10000000):
pass1 = str(i) + "@DBApp"
s1 = sha1()
s1.update(pass1.encode())
res = s1.hexdigest().upper()
if res == sha_1:
print(pass1)
break
# 解出第一个密码为 `123321`
第二个密码,不肯定是全数字,加密算法是0x8003
,查询后得知是MD5
,那爆破成功的机率就很微小了,继续往下看,进入sub_40100F
函数
通过findResourceA
找到名为AAA
的资源,通过和第二次输入的密码拼接之后的字符串做异或,写入dbapp.rtf
unsigned int __cdecl sub_401005(LPCSTR lpString, int a2, int a3)
{
unsigned int result; // eax
unsigned int i; // [esp+4Ch] [ebp-Ch]
unsigned int v5; // [esp+54h] [ebp-4h]
v5 = lstrlenA(lpString);
for ( i = 0; ; ++i ){
result = i;
if ( i >= a3 )
break;
*(_BYTE *)(i + a2) ^= lpString[i % v5];
}
return result;
}
也就是pass2 ^ [AAA] = rtf
,参考writeup,使用ResourceHacker
找到AAA
资源,在和rtf
的前六个字节异或即可得到我们需要输入的密码
AAA = [0x05, 0x7D, 0x41, 0x15, 0x26, 0x01]
rtf_header = r'{\rtf1'
p2 = ""
for i, c in enumerate(rtf_header):
tmp = ord(c)
p2 += chr(tmp ^ AAA[i])
print(p2)
# ~!3a@0
得到第二次密码:~!3a@0
运行程序,依次输入两次密码就会生成一个rtf
文件,内容为flag
Flag{N0_M0re_Free_Bugs}
[2019红帽杯]easyRE
关键函数在主函数下一个函数
[ACTF新生赛2020]rome
无壳,拖进IDA
s = "Qsw3sj_lz4_Ujw@l"
flag = ""
for i in range(len(s)):
c = ord(s[i])
if 64 < c <= 90:
c -= 65
c -= 14
c %= 26
flag += chr(65+c)
elif 96 < c <= 122:
c -= 97
c -= 18
c %= 26
flag += chr(97+c)
else:
flag += s[i]
print("ACTF{"+flag+"}")
s = "Qsw3sj_lz4_Ujw@l"
flag = ""
for i in range(len(s)):
c = ord(s[i])
if 64 < c <= 90:
c -= 65
c += 51
c += 26
if c > 90:
c -= 26
flag += chr(c)
elif 96 < c <= 122:
c -= 97
c += 79
c += 26
if c > 122:
c -= 26
flag += chr(c)
else:
flag += s[i]
print("ACTF{"+flag+"}")
s = "Qsw3sj_lz4_Ujw@l"
flag = ""
for i in range(len(s)):
c = ord(s[i])
if 64 < c <= 90:
c -= 65
c += 12
c %= 26
flag += chr(65+c)
elif 96 < c <= 122:
c -= 97
c += 8
c %= 26
flag += chr(97+c)
else:
flag += s[i]
print("ACTF{"+flag+"}")
# ACTF{Cae3ar_th4_Gre@t}
应该还有一种解法,通过动态调试,因为知道是凯撒加密,对称加密,将密文输入,在比较处下断点,应该就可知道明文是什么
但是得到却是Eko3kb_dr4_Ibo@d
,方向错误?,得到的是以下运行的结果
s = "Qsw3sj_lz4_Ujw@l"
flag = ""
for i in range(len(s)):
c = ord(s[i])
if 64 < c <= 90:
c -= 65
c -= 12
c %= 26
flag += chr(65+c)
elif 96 < c <= 122:
c -= 97
c -= 8
c %= 26
flag += chr(97+c)
else:
flag += s[i]
print("ACTF{"+flag+"}")
[FlareOn4]login
得到一个HTML
document.getElementById("prompt").onclick = function () {
var flag = document.getElementById("flag").value;
var rotFlag = flag.replace(/[a-zA-Z]/g, function(c){
return String.fromCharCode((c <= "Z" ? 90 : 122) >= (c = c.charCodeAt(0) + 13) ? c : c - 26);});
if ("PyvragFvqrYbtvafNerRnfl@syner-ba.pbz" == rotFlag) {
alert("Correct flag!");
} else {
alert("Incorrect flag, rot again");
}
alert("flag{"+rotFlag+"}"); // 新增
}
是一个rot
位移密码,rot密码其实可以看作是凯撒密码的一种变式,本质都是移位运算
只需新增一条alert
,输入PyvragFvqrYbtvafNerRnfl@syner-ba.pbz
即可
flag{ClientSideLoginsAreEasy@flare-on.com}
[GUET-CTF2019]re
UPX加固
拖进IDA,找到主函数
复制代码,正则提取数字,这里需要注意顺序,特别是a1[17]
和a1[16]
,并且少了第一个字符(在下标为6)第七个字符
b = [166163712, 731332800, 357245568, 1074393000, 489211344, 518971936, 406741500, 294236496, 177305856, 650683500,
298351053, 386348487, 438258597, 249527520, 445362764, 174988800, 981182160, 493042704, 257493600, 767478780,
312840624, 1404511500, 316139670, 619005024, 372641472, 373693320, 498266640, 452465676, 208422720, 515592000,
719890500]
a = [1629056, 6771600, 3682944, 10431000, 3977328, 5138336, 7532250, 5551632, 3409728, 13013670, 6088797, 7884663,
8944053, 5198490, 4544518, 3645600, 10115280, 9667504, 5364450, 13464540, 5488432, 14479500, 6451830, 6252576,
7763364, 7327320, 8741520, 8871876, 4086720, 9374400, 5759124]
flag = ""
for i in range(len(a)):
flag += chr(b[i] // a[i])
print(flag)
# flag{e65421110b0a3099a1c039337}
# flag{e165421110b0a3099a1c039337} 添加一个字符
# flag{e165421110ba03099a1c039337} 调换位置
[SUCTF2019]SignIn
无壳,拖进IDA
// mpz_init_set_str的原型是:
int mpz_init_set_str (mpz_t rop, char *str, int base)
// 这三个参数分别是多精度整数变量,字符串,进制。
一个大数分解,已知n
,e
,以及密文v7
c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35 # v7
n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
e = 65537
先使用在线网站分解n
得到q
, p
求到解d即可解密文
def rsa_moder(n):
base = 2
while base < n:
if n % base == 0:
return base, n // base
base += 1
# 求欧拉函数f(n)
def rsa_get_euler(prime1, prime2):
return (prime1 - 1) * (prime2 - 1)
# 求私钥
def rsa_get_key(e, euler):
k = 1
while True:
if (((euler * k) + 1) % e) == 0:
return (euler * k + 1) // e
k += 1
# 根据n,e计算d(或根据n,d计算e)
def get_rsa_e_d(n, e=None, d=None):
if e is None and d is None:
return
arg = e
if arg is None:
arg = d
# primes = rsa_moder(n)
# p = primes[0]
# q = primes[1]
# 在线分解n: http://www.factordb.com/
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
d = rsa_get_key(arg, rsa_get_euler(p, q))
return d
def orginal_algorithm(a, b, c): # a^b%c
ans = 1
a = a % c # 预处理,防止出现a比c大的情况
for i in range(b):
ans = (ans * a) % c
return ans
def quick_algorithm(a, b, c):
a = a % c
ans = 1
# 这里我们不需要考虑b<0,因为分数没有取模运算
while b != 0:
if b & 1:
ans = (ans * a) % c
b >>= 1
a = (a * a) % c
return ans
if __name__ == '__main__':
"""
e: 公钥
d: 私钥
n = p*q
n 大数分解使用在线网站: http://www.factordb.com/
c = m^e mod n
m = c^d mod n
"""
c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35 # 密文
n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
e = 65537 # 公钥
d = 91646299298871237857836940212608056141193465208586711901499120163393577626813 # 私钥
d = get_rsa_e_d(n, e=e)
print(d)
# a = eval(input("底数:"))
# b = eval(input("指数:"))
# c = eval(input("模:"))
# print("朴素算法结果%d" % (orginal_algorithm(a, b, c)))
res = (quick_algorithm(c, d, n)) # 快速幂算法
import binascii
flag = binascii.unhexlify(hex(res)[2:]).decode()
print(flag)
# suctf{Pwn_@_hundred_years}
Youngter-drive
检测到使用UPX加固,upx -d
脱壳之后,拖进IDA
找到主函数
输入字符串,备份一份,创建两个线程,分别执行StartAddress
和sub_41119F
函数
// StartAddress 线程一
void __stdcall StartAddress_0(int a1){
while ( 1 ){
WaitForSingleObject(hObject, 0xFFFFFFFF);
if ( int_29 > -1 ){
sub_41112C((int)input, int_29);
--int_29;
Sleep(100u);
}
ReleaseMutex(hObject);
}
}
// sub_41112C 线程一
char *__cdecl sub_411940(int input, int int_29){
char *result; // eax
char v3; // [esp+D3h] [ebp-5h]
v3 = *(_BYTE *)(int_29 + input);
if ( (v3 < 97 || v3 > 122) && (v3 < 65 || v3 > 90) )
exit(0);
if ( v3 < 97 || v3 > 122 ){
result = off_418000[0]; // off_418000 == QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm
*(_BYTE *)(int_29 + input) = off_418000[0][*(char *)(int_29 + input) - 38];
}
else{
result = off_418000[0];
*(_BYTE *)(int_29 + input) = off_418000[0][*(char *)(int_29 + input) - 96];
}
return result;
}
// sub_41119F 线程二
void __stdcall sub_411B10(int a1){
while ( 1 ) {
WaitForSingleObject(hObject, 0xFFFFFFFF);
if ( int_29 > -1 )
{
Sleep(100u);
--int_29;
}
ReleaseMutex(hObject);
}
}
第一个线程就是将输入的字符判断到小写,然后根据给出了表进行交换,参考writeup才知道,还有一个线程负责将计数器-1
,然后两个线程都有sleep(100ms)
睡眠0.1秒
,所以是每隔一个字符变化,至于是偶数还是奇数,都试试即可,因为后面判断加密后的字符串值判断了29位,但实际加密(变换了)0-29
也就是30位
s = "TOiZiZtOrYaToUwPnToBsOaOapsyS"
map = "QWERTYUIOPASDFGHJKLZXCVBNMqwertyuiopasdfghjklzxcvbnm"
flag = ""
for i in range(len(s)):
c_value = ord(s[i])
if i % 2 == 0:
flag += s[i]
else:
if 97 <= c_value <= 122:
flag += chr(map.index(s[i]) + 38)
else:
flag += chr(map.index(s[i]) + 96)
print(f'flag{{{flag}}}')
# flag{ThisisthreadofwindowshahaIsES}
# 最后一个位实际可以任写,但是buuctf上是`E`