BUUCTF刷题记录 [Reverse] [2023.01.02--01.08]

[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>

遍历每一位字符,依次进行如下操作:

  1. c与字符Z比较大小,大于则赋值122(z),反之赋值90(Z);
  2. c = c + 13;
  3. 前两步得到的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解密得到的结果没有什么作用。

此时,有两种思路:

  1. _init_array_fini_array部分,查找main函数没有调用但仍然会执行的函数;
  2. 查找其他可疑字符串。

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函数关键逻辑如下:

  1. 程序使用某种方法,获得长整型变量v4,可以看做4个char;
  2. byte_6CC0A0每4个字符为一组,与v4的4个char进行分组异或;
  3. 已知分组异或的结果,前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_6CC0A0v4进行分组异或预算,即可得到最终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 参考链接

第三届红帽杯网络安全攻防大赛官方WP

BUUCTF-RE-[2019红帽杯]easyRE

学习LOWORD、 HIWORD、LOBYTE、HIBYTE

2019 红帽杯 easyRE

[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中的一位字符被分为两部分:

  1. a1[v3] >> 4a1[v3]除以16后的整数部分;
  2. a1[v3++] & 0xFa1[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~9a~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 参考链接

攻防世界逆向高手题之SignIn

在线大数分解

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}

在线Rsa公私钥分解

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值