[FlareOn4]login
0x01 算法解析
<!DOCTYPE Html />
<html>
<head>
<title>FLARE On 2017</title>
</head>
<body>
<input type="text" name="flag" id="flag" value="Enter the flag" />
<input type="button" id="prompt" value="Click to check the flag" />
<script type="text/javascript">
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");
}
}
</script>
</body>
</html>
遍历每一位字符,依次进行如下操作:
- c与字符
Z
比较大小,大于则赋值122(z
),反之赋值90(Z
); - c = c + 13;
- 前两步得到的c比较,若
1.
>=2.
,返回c + 13
;反之,返回c - 13 (c + 13 - 26)
。
上述算法即为rot13算法。
rot13加密:一句话来说就是A到M之间的字母加密就是直接+13,N到Z之间的字母加密是直接-13. 例如A替换为N,N替换为A(超过Z循环回A)只有这些出现在英文字母里的字符受影响;数字、符号、空白字符以及所有其他字符都不变。替换后的字母大小写保持不变。
https://baike.baidu.com/item/ROT13/7086083#%E4%B8%BB%E8%A6%81%E7%94%A8%E9%80%94
0x02 EXP
rot13trans = str.maketrans('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz',
'NOPQRSTUVWXYZABCDEFGHIJKLMnopqrstuvwxyzabcdefghijklm')
# Function to translate plain text
def rot13(text):
return text.translate(rot13trans)
def main():
txt = "PyvragFvqrYbtvafNerRnfl@syner-ba.pbz"
print("flag{%s}" %rot13(txt))
if __name__ == "__main__":
main()
flag{ClientSideLoginsAreEasy@flare-on.com}
[GUET-CTF2019]re
0x01 脱壳
64位,UPX壳
$ checksec --file=re
[*] '/home/kali/Desktop/Reverse/re'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Packer: Packed with UPX
使用upx -d [filename]
命令脱壳
$ upx -d re
Ultimate Packer for eXecutables
Copyright (C) 1996 - 2020
UPX 3.96 Markus Oberhumer, Laszlo Molnar & John Reiser Jan 23rd 2020
File size Ratio Format Name
-------------------- ------ ----------- -----------
840640 <- 304524 36.23% linux/amd64 re
Unpacked 1 file.
0x02 逆向分析
没有符号表,全都是一些sub开头的函数。
使用取巧的办法,查看字符串,搜索flag
,跟踪进sub_400E28
函数,之后发现输入的字符串会传进sub_4009AE
函数。
_BOOL8 __fastcall sub_4009AE(char *a1)
{
if ( 1629056 * *a1 != 166163712 )
return 0LL;
if ( 6771600 * a1[1] != 731332800 )
return 0LL;
if ( 3682944 * a1[2] != 357245568 )
return 0LL;
if ( 10431000 * a1[3] != 1074393000 )
return 0LL;
if ( 3977328 * a1[4] != 489211344 )
return 0LL;
if ( 5138336 * a1[5] != 518971936 )
return 0LL;
if ( 7532250 * a1[7] != 406741500 )
return 0LL;
if ( 5551632 * a1[8] != 294236496 )
return 0LL;
if ( 3409728 * a1[9] != 177305856 )
return 0LL;
if ( 13013670 * a1[10] != 650683500 )
return 0LL;
if ( 6088797 * a1[11] != 298351053 )
return 0LL;
if ( 7884663 * a1[12] != 386348487 )
return 0LL;
if ( 8944053 * a1[13] != 438258597 )
return 0LL;
if ( 5198490 * a1[14] != 249527520 )
return 0LL;
if ( 4544518 * a1[15] != 445362764 )
return 0LL;
if ( 3645600 * a1[17] != 174988800 )
return 0LL;
if ( 10115280 * a1[16] != 981182160 )
return 0LL;
if ( 9667504 * a1[18] != 493042704 )
return 0LL;
if ( 5364450 * a1[19] != 257493600 )
return 0LL;
if ( 13464540 * a1[20] != 767478780 )
return 0LL;
if ( 5488432 * a1[21] != 312840624 )
return 0LL;
if ( 14479500 * a1[22] != 1404511500 )
return 0LL;
if ( 6451830 * a1[23] != 316139670 )
return 0LL;
if ( 6252576 * a1[24] != 619005024 )
return 0LL;
if ( 7763364 * a1[25] != 372641472 )
return 0LL;
if ( 7327320 * a1[26] != 373693320 )
return 0LL;
if ( 8741520 * a1[27] != 498266640 )
return 0LL;
if ( 8871876 * a1[28] != 452465676 )
return 0LL;
if ( 4086720 * a1[29] != 208422720 )
return 0LL;
if ( 9374400 * a1[30] == 515592000 )
return 5759124 * a1[31] == 719890500;
return 0LL;
}
可以知道flag一共32位,将数据相除即可得到flag。但是,上段代码中不存在a1[6]
,所以需要爆破数字、字母和特殊字符。
0x03 EXP
flag = ''
flag += chr(166163712 // 1629056)
flag += chr(731332800 // 6771600)
flag += chr(357245568 // 3682944)
flag += chr(1074393000 // 10431000)
flag += chr(489211344 // 3977328)
flag += chr(518971936 // 5138336)
flag += '?'
flag += chr(406741500 // 7532250)
flag += chr(294236496 // 5551632)
flag += chr(177305856 // 3409728)
flag += chr(650683500 // 13013670)
flag += chr(298351053 // 6088797)
flag += chr(386348487 // 7884663)
flag += chr(438258597 // 8944053)
flag += chr(249527520 // 5198490)
flag += chr(445362764 // 4544518)
flag += chr(981182160 // 10115280)
flag += chr(174988800 // 3645600)
flag += chr(493042704 // 9667504)
flag += chr(257493600 // 5364450)
flag += chr(767478780 // 13464540)
flag += chr(312840624 // 5488432)
flag += chr(1404511500 // 14479500)
flag += chr(316139670 // 6451830)
flag += chr(619005024 // 6252576)
flag += chr(372641472 // 7763364)
flag += chr(373693320 // 7327320)
flag += chr(498266640 // 8741520)
flag += chr(452465676 // 8871876)
flag += chr(208422720 // 4086720)
flag += chr(515592000 // 9374400)
flag += chr(719890500 // 5759124)
for i in range(0, 10):
print(flag[:6] + str(i) + flag[7:])
flag{e165421110ba03099a1c039337}
[WUSTCTF2020]level1
f = open('output.txt', 'r')
data = []
for line in f:
data.append(int(line.strip()))
flag = ""
for i in range(19):
if ((i + 1) & 1):
flag += chr(data[i] >> (i + 1))
else:
flag += chr(data[i] // (i + 1))
print(flag)
flag{d9-dE6-20c}
[2019红帽杯]easyRE
0x01 Continue前
64位无壳elf文件,无符号表,全都是sub开头的函数。
shift+F12
查看字符串,发现诸如You found me!!!
、bye bye~
、continue
之类的字符串,查看交叉引用后发现都在sub_4009C6
函数中。
打印continue
之前,程序定义了36个字符,且连续存储,可以视为一个数组。
v19 = __readfsqword(0x28u);
v12[0] = 73;
v12[1] = 111;
v12[2] = 100;
v12[3] = 108;
v12[4] = 62;
v12[5] = 81;
v12[6] = 110;
v12[7] = 98;
v12[8] = 40;
v12[9] = 111;
v12[10] = 99;
v12[11] = 121;
v12[12] = 127;
v12[13] = 121;
v12[14] = 46;
v12[15] = 105;
v12[16] = 127;
v12[17] = 100;
v12[18] = 96;
v12[19] = 51;
v12[20] = 119;
v12[21] = 125;
v12[22] = 119;
v12[23] = 101;
v12[24] = 107;
v12[25] = 57;
v12[26] = 123;
v12[27] = 105;
v12[28] = 121;
v12[29] = 61;
v12[30] = 126;
v12[31] = 121;
v12[32] = 76;
v12[33] = 64;
v12[34] = 69;
v12[35] = 67;
memset(v13, 0, sizeof(v13));
v14 = 0;
v15 = 0;
sub_4406E0(0, v13, 0x25uLL);
v15 = 0;
if ( sub_424BA0(v13) == 36 )
{
for ( i = 0; i < (unsigned __int64)sub_424BA0(v13); ++i )
{
if ( (unsigned __int8)(v13[i] ^ i) != v12[i] )
{
result = 4294967294LL;
goto LABEL_13;
}
}
sub_410CC0((__int64)"continue!");
输入的v13
,异或循环下标后,与v12
比较,异或求逆即可得到v13
。
data = [0] * 36
data[0] = 73
data[1] = 111
data[2] = 100
data[3] = 108
data[4] = 62
data[5] = 81
data[6] = 110
data[7] = 98
data[8] = 40
data[9] = 111
data[10] = 99
data[11] = 121
data[12] = 127
data[13] = 121
data[14] = 46
data[15] = 105
data[16] = 127
data[17] = 100
data[18] = 96
data[19] = 51
data[20] = 119
data[21] = 125
data[22] = 119
data[23] = 101
data[24] = 107
data[25] = 57
data[26] = 123
data[27] = 105
data[28] = 121
data[29] = 61
data[30] = 126
data[31] = 121
data[32] = 76
data[33] = 64
data[34] = 69
data[35] = 67
v13 = ""
for i in range(36):
v13 += chr(data[i] ^ i)
print(v13)
输出结果如下:
Info:The first four chars are `flag`
0x02 Base64
打印continue
之后,读取长度为39的输入,将其作为参数调用10次sub_400E44
函数。
该函数中使用了ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/
字符串,输出字符串是输入字符串的4/3倍。
因此,基本可以断定该函数是base64加密。
sub_410CC0((__int64)"continue!");
memset(&v16, 0, 0x40uLL);
v18 = 0;
sub_4406E0(0, &v16, 0x40uLL);
v17 = 0;
if ( sub_424BA0(&v16) == 39 )
{
v2 = sub_400E44((__int64)&v16);
v3 = sub_400E44(v2);
v4 = sub_400E44(v3);
v5 = sub_400E44(v4);
v6 = sub_400E44(v5);
v7 = sub_400E44(v6);
v8 = sub_400E44(v7);
v9 = sub_400E44(v8);
v10 = sub_400E44(v9);
v11 = sub_400E44(v10);
if ( !(unsigned int)sub_400360(v11, (__int64)off_6CC090) )
{
sub_410CC0((__int64)"You found me!!!");
sub_410CC0((__int64)"bye bye~");
}
result = 0LL;
}
else
{
result = 4294967293LL;
}
(__int64)off_6CC090
存储的是十次base64加密后的密文,编写脚本进行解密
import base64
cipherText = "Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJsbDNXa1pPVlUxV2NIcFhhMk0xVmpKS1NHVkdXbFpOYmtKVVZtcEtTMUl5VGtsaVJtUk9ZV3hhZVZadGVHdFRNVTVYVW01T2FGSnRVbGhhVjNoaFZWWmtWMXBFVWxSTmJFcElWbTAxVDJGV1NuTlhia0pXWWxob1dGUnJXbXRXTVZaeVdrWm9hVlpyV1hwV1IzaGhXVmRHVjFOdVVsWmlhMHBZV1ZSR1lWZEdVbFZTYlhSWFRWWndNRlZ0TVc5VWJGcFZWbXR3VjJKSFVYZFdha1pXWlZaT2NtRkhhRk5pVjJoWVYxZDBhMVV3TlhOalJscFlZbGhTY1ZsclduZGxiR1J5VmxSR1ZXSlZjRWhaTUZKaFZqSktWVkZZYUZkV1JWcFlWV3BHYTFkWFRrZFRiV3hvVFVoQ1dsWXhaRFJpTWtsM1RVaG9hbEpYYUhOVmJUVkRZekZhY1ZKcmRGTk5Wa3A2VjJ0U1ExWlhTbFpqUldoYVRVWndkbFpxUmtwbGJVWklZVVprYUdFeGNHOVhXSEJIWkRGS2RGSnJhR2hTYXpWdlZGVm9RMlJzV25STldHUlZUVlpXTlZadE5VOVdiVXBJVld4c1dtSllUWGhXTUZwell6RmFkRkpzVWxOaVNFSktWa1phVTFFeFduUlRhMlJxVWxad1YxWnRlRXRXTVZaSFVsUnNVVlZVTURrPQ=="
for i in range(10):
cipherText = base64.b64decode(cipherText)
plainText = cipherText
print(plainText)
输出结果是一个网址,然而打开后发现没什么卵用,呵呵~
b’https://bbs.pediy.com/thread-254172.htm’
0x03 主动防御
破解出来的网址写的内容是主动防御
,网页源代码中也没有任何有效信息。
在设计KCTF的防守作品时 防守方都会在攻击方寻找正确答案的路上 设置各种障碍 以防止发生防守方不愿意看到的结果:被破解
这是被动防御
其实也可以使用主动防御 其思路是:防止攻击方走向正确答案的方向
什么是主动防御呢?
举个栗子
笔者多次参加KCTF 从来没有胜出过
Q老师说 要想作品存活 ccfer是个巨大的威胁 最好的办法是:趁比赛那2天拖他出去吃喝玩乐 不让他碰电脑 这样存活的可能性会大很多
这虽然只是句玩笑话 但仔细想想 却很有道理 直接有效
这 就是一种主动防御
主动防御的目的 就是让对手不要走正确的破解之路
而不是像被动防御那样 在正确的破解之路上设置重重困难
所谓“让对手不要走正确的破解之路”
讲人话 就是“把对手往沟里带”
可想而知,这是出题人主动防御的一部分,base64解密得到的结果没有什么作用。
此时,有两种思路:
- 去
_init_array
和_fini_array
部分,查找main函数没有调用但仍然会执行的函数; - 查找其他可疑字符串。
0x04 Flag
0x02
步骤中,追踪off_6CC090
时,细心的童鞋会注意到到下方存在一串非空字符
(笔者最开始也没看到,参考了大神们的wp才知道)
.data:00000000006CC090 off_6CC090 dq offset aVm0wd2vhuxhtwg
.data:00000000006CC090 ; DATA XREF: sub_4009C6+31B↑r
.data:00000000006CC090 ; "Vm0wd2VHUXhTWGhpUm1SWVYwZDRWVll3Wkc5WFJ"...
.data:00000000006CC098 align 20h
.data:00000000006CC0A0 ; char byte_6CC0A0[3]
.data:00000000006CC0A0 byte_6CC0A0 db 40h ; DATA XREF: sub_400D35+95↑r
.data:00000000006CC0A0 ; sub_400D35+C1↑r
.data:00000000006CC0A1 db 35h ; 5
.data:00000000006CC0A2 db 20h
.data:00000000006CC0A3 byte_6CC0A3 db 56h ; DATA XREF: sub_400D35+A6↑r
.data:00000000006CC0A4 db 5Dh ; ]
.data:00000000006CC0A5 db 18h
.data:00000000006CC0A6 db 22h ; "
.data:00000000006CC0A7 db 45h ; E
.data:00000000006CC0A8 db 17h
.data:00000000006CC0A9 db 2Fh ; /
.data:00000000006CC0AA db 24h ; $
.data:00000000006CC0AB db 6Eh ; n
.data:00000000006CC0AC db 62h ; b
.data:00000000006CC0AD db 3Ch ; <
.data:00000000006CC0AE db 27h ; '
.data:00000000006CC0AF db 54h ; T
.data:00000000006CC0B0 db 48h ; H
.data:00000000006CC0B1 db 6Ch ; l
.data:00000000006CC0B2 db 24h ; $
.data:00000000006CC0B3 db 6Eh ; n
.data:00000000006CC0B4 db 72h ; r
.data:00000000006CC0B5 db 3Ch ; <
.data:00000000006CC0B6 db 32h ; 2
.data:00000000006CC0B7 db 45h ; E
.data:00000000006CC0B8 db 5Bh ; [
.data:00000000006CC0B9 db 0
.data:00000000006CC0BA db 0
.data:00000000006CC0BB db 0
.data:00000000006CC0BC db 0
.data:00000000006CC0BD db 0
.data:00000000006CC0BE db 0
.data:00000000006CC0BF db 0
这串非空字符的引用位置是sub_400D35
函数,更加有趣的是,该函数的引用位置是_fini_array
段。
.fini_array:00000000006CBEE0 ; Segment type: Pure data
.fini_array:00000000006CBEE0 ; Segment permissions: Read/Write
.fini_array:00000000006CBEE0 _fini_array segment qword public 'DATA' use64
.fini_array:00000000006CBEE0 assume cs:_fini_array
.fini_array:00000000006CBEE0 ;org 6CBEE0h
.fini_array:00000000006CBEE0 off_6CBEE0 dq offset sub_400940 ; DATA XREF: sub_402080:loc_4020C8↑o
.fini_array:00000000006CBEE0 ; sub_402110+6↑o
.fini_array:00000000006CBEE8 dq offset sub_400D35
.fini_array:00000000006CBEF0 dq offset sub_4005C0
.fini_array:00000000006CBEF0 _fini_array ends
sub_400D35
函数关键逻辑如下:
- 程序使用某种方法,获得长整型变量
v4
,可以看做4个char; byte_6CC0A0
每4个字符为一组,与v4的4个char进行分组异或;- 已知分组异或的结果,前4个字符是
flag
。
HIBYTE()
函数的作用是获取高字节也就是数组的最后一位,同时还有BYTE()、BYTE1()、BYTE2()
第一个是获取数组的第一位,第二个就是获取第二位,依次类推。
unsigned __int64 sub_400D35()
{
unsigned __int64 result; // rax
unsigned int v1; // [rsp+Ch] [rbp-24h]
int i; // [rsp+10h] [rbp-20h]
int j; // [rsp+14h] [rbp-1Ch]
unsigned int v4; // [rsp+24h] [rbp-Ch]
unsigned __int64 v5; // [rsp+28h] [rbp-8h]
v5 = __readfsqword(0x28u);
v1 = sub_43FD20(0LL) - qword_6CEE38;
for ( i = 0; i <= 1233; ++i )
{
sub_40F790(v1);
sub_40FE60();
sub_40FE60();
v1 = sub_40FE60() ^ 0x98765432;
}
v4 = v1;
if ( ((unsigned __int8)v1 ^ byte_6CC0A0[0]) == 'f' && (HIBYTE(v4) ^ (unsigned __int8)byte_6CC0A3) == 'g' )
{
for ( j = 0; j <= 24; ++j )
sub_410E90(byte_6CC0A0[j] ^ *((_BYTE *)&v4 + j % 4));
}
result = __readfsqword(0x28u) ^ v5;
if ( result )
sub_444020();
return result;
}
因此,解题关键是求得v4
的值。
已知byte_6CC0A0
内容,运算结果前四个字符为flag
,将byte_6CC0A0
的前四个字符与flag
异或即可得到v4
。
然后,将byte_6CC0A0
与v4
进行分组异或预算,即可得到最终flag。
EXP如下:
v4 = ''
result = 'flag'
byte_6CC0A0 = [0x40, 0x35, 0x20, 0x56, 0x5D, 0x18, 0x22, 0x45, 0x17, 0x2F, 0x24, 0x6E, 0x62, 0x3C, 0x27, 0x54, 0x48, 0x6C, 0x24, 0x6E, 0x72, 0x3C, 0x32, 0x45, 0x5B]
for i in range(4):
v4 += chr(byte_6CC0A0[i] ^ ord(result[i]))
flag=''
for i in range(25):
flag += chr(byte_6CC0A0[i] ^ ord(v4[i % 4]))
print(flag)
flag{Act1ve_Defen5e_Test}
0x05 参考链接
学习LOWORD、 HIWORD、LOBYTE、HIBYTE
[MRCTF2020]Transform
byte_40F0E0 = [0x67, 0x79, 0x7B, 0x7F, 0x75, 0x2B, 0x3C, 0x52, 0x53, 0x79,
0x57, 0x5E, 0x5D, 0x42, 0x7B, 0x2D, 0x2A, 0x66, 0x42, 0x7E,
0x4C, 0x57, 0x79, 0x41, 0x6B, 0x7E, 0x65, 0x3C, 0x5C, 0x45,
0x6F, 0x62, 0x4D]
dword_40F040 = [0x09, 0x0A, 0x0F, 0x17, 0x07, 0x18, 0x0C, 0x06, 0x01, 0x10,
0x03, 0x11, 0x20, 0x1D, 0x0B, 0x1E, 0x1B, 0x16, 0x04, 0x0D,
0x13, 0x14, 0x15, 0x02, 0x19, 0x05, 0x1F, 0x08, 0x12, 0x1A,
0x1C, 0x0E, 0x0]
Str = [0] * 33
byte_414040 = [0] * 33
for i in range(33):
byte_414040[i] = byte_40F0E0[i] ^ dword_40F040[i]
for j in range(33):
Str[dword_40F040[j]] = byte_414040[j]
flag = ""
for k in range(33):
flag += chr(Str[k])
print(flag)
flag{Tr4nsp0sltiON_Clph3r_1s_3z}
[SUCTF2019]SignIn
0x01 主函数分析
主函数逻辑较为简单,输入的字符串经sub_96A
函数处理后,在经过一系列库函数处理,与v7
进行比较。
__int64 __fastcall main(int a1, char **a2, char **a3)
{
char v4; // [rsp+0h] [rbp-4A0h]
char v5; // [rsp+10h] [rbp-490h]
char v6; // [rsp+20h] [rbp-480h]
char v7; // [rsp+30h] [rbp-470h]
char v8; // [rsp+40h] [rbp-460h]
char v9; // [rsp+B0h] [rbp-3F0h]
unsigned __int64 v10; // [rsp+498h] [rbp-8h]
v10 = __readfsqword(0x28u);
puts("[sign in]");
printf("[input your flag]: ", a2);
__isoc99_scanf("%99s", &v8);
sub_96A(&v8, (__int64)&v9);
__gmpz_init_set_str((__int64)&v7, (__int64)"ad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35", 16LL);
__gmpz_init_set_str((__int64)&v6, (__int64)&v9, 16LL);
__gmpz_init_set_str(
(__int64)&v4,
(__int64)"103461035900816914121390101299049044413950405173712170434161686539878160984549",
10LL);
__gmpz_init_set_str((__int64)&v5, (__int64)"65537", 10LL);
__gmpz_powm((__int64)&v6, (__int64)&v6, (__int64)&v5, (__int64)&v4);
if ( (unsigned int)__gmpz_cmp((__int64)&v6, (__int64)&v7) )
puts("GG!");
else
puts("TTTTTTTTTTql!");
return 0LL;
}
__gmpz_init_set_str 其实就是 mpz_init_set_str int mpz_init_set_str (mpz_t rop, const char *str, int base) 函数:
这三个参数分别是多精度整数变量,字符串,进制。 这个函数的作用就是将 str 字符数组以 base 指定的进制解读成数值并写入 rop 所指向的内存。
void mpz_powm (mpz_t rop, const mpz_t base, const mpz_t exp, const mpz_t mod) 函数:
其实就是计算 base 的 exp 次方,并对 mod 取模,最后将结果写入 rop 中, 这个运算的过程和RSA的加密过程一样。接下来就是gmpz_cmp函数,看这个函数名就知道这是比较函数。
mpz_cmp(b, c); //b 大于 c,返回 1;b
等于 c,返回 0;b 小于 c,返回-1重述一下就是:
mpz_powm(op1,op2,op3,op4); //求幂模函数 即 op1=op2^op3 mod op4;
mpz_init_set_str(b, “200000”, 10); //即 b=200000,十进制
mpz_cmp(b, c); //b 大于 c,返回 1;b 等于 c,返回 0;b 小于 c,返回-1
因此,主函数本质上就是对v6
进行了RSA加密。
n = v4 = 103461035900816914121390101299049044413950405173712170434161686539878160984549
e = v5 = 65537
c = v7 = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35
问题归纳为:RSA算法,已知n、e、c,求m。
0x02 sub_96A函数分析
size_t __fastcall sub_96A(const char *a1, __int64 a2)
{
size_t result; // rax
int v3; // [rsp+18h] [rbp-18h]
int i; // [rsp+1Ch] [rbp-14h]
v3 = 0;
for ( i = 0; ; i += 2 )
{
result = strlen(a1);
if ( v3 >= result )
break;
*(_BYTE *)(a2 + i) = byte_202010[a1[v3] >> 4];
*(_BYTE *)(a2 + i + 1LL) = byte_202010[a1[v3++] & 0xF];
}
return result;
}
sub_96A
函数将a1
的每一位经过处理后存储到a2
中,a1
中的一位在a2
中占两位。
a1
中的一位字符被分为两部分:
a1[v3] >> 4
指a1[v3]
除以16后的整数部分;a1[v3++] & 0xF
指a1[v3]
除以16后的余数部分,然后v3
加一。
byte_202010
在程序中定义如下:
.data:0000000000202010 byte_202010 db 30h, 31h, 32h, 33h, 34h, 35h, 36h, 37h, 38h, 39h, 61h
.data:0000000000202010 ; DATA XREF: sub_96A+47↑o
.data:0000000000202010 ; sub_96A+7F↑o
.data:0000000000202010 db 62h, 63h, 64h, 65h, 66h
.data:0000000000202010 _data ends
这些其实就是0~9
、a~f
的ASCII码,在IDA中按r
即可转换成字符格式查看。
.data:0000000000202010 byte_202010 db '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a'
.data:0000000000202010 ; DATA XREF: sub_96A+47↑o
.data:0000000000202010 ; sub_96A+7F↑o
.data:0000000000202010 db 'b', 'c', 'd', 'e', 'f'
.data:0000000000202010 _data ends
因此,sub_96A
函数的作用就是将输入的字符转换成16进制ASCII码。
冷知识:C语言中,一个字符在内存中占一字节,一个字节由8位二进制数表示,一个16进制数占半个字节。
0x03 EXP
RSA,已知n、e、c,求m。
先进行大数分解,求得p和q,然后用脚本求解即可。
import gmpy2
from Crypto.Util.number import long_to_bytes
def RSADecrypt(c, e, p, q, n):
L = (p -1) * (q -1)
d = gmpy2.invert(e, L)
m = gmpy2.powmod(c, d, n)
flag = long_to_bytes(m)
print(flag)
if __name__ == '__main__':
c = 0xad939ff59f6e70bcbfad406f2494993757eee98b91bc244184a377520d06fc35
n = 103461035900816914121390101299049044413950405173712170434161686539878160984549
e = 65537
p = 282164587459512124844245113950593348271
q = 366669102002966856876605669837014229419
RSADecrypt(c, e, p, q, n)
flag{Pwn_@_hundred_years}
0x04 参考链接
gmp_lib.mpz_init_set_str Method
RSA
import gmpy2
import hashlib
import rsa
n = 86934482296048119190666062003494800588905656017203025617216654058378322103517
e = 65537
p = 285960468890451637935629440372639283459
q = 304008741604601924494328155975272418463
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
key = rsa.PrivateKey(n, e, d, p, q)
with open("flag.enc", "rb") as f:
print(rsa.decrypt(f.read(), key).decode())
flag{decrypt_256}