zsky 发表于 2021-11-25 17:38

2021GFCTF RE部分WP

本帖最后由 zsky 于 2021-11-25 19:29 编辑

# 2021GFCTFRE_WP



## wordy

去除花指令



```python
addr =0X556AE377FD56
end = 0x0556AE377FE40
flag = ""
for i in range(addr, end, 13):
    c = get_bytes(i+4, 1)
    flag+=chr(c)
   
print(flag)
# GFCTF{u_are2wordy}
```

## BabyReverse

IDA打开,去除所有的花指令



直接看下面对flag如何加密的,进入`sub_412E10`, 发现是SM4加密



而传入的第二个参数是key,即`byte_4409c0`是key,回到main函数再往上看,发现前面有一个对`byte_4409c0`类似RC4加密的操作,

对 `byte_4409C0`进行交叉引用





于是下断点调试来获取key,发现不行,猜测前面是反调试,从main函数头部下断点调试



最终定位到这个函数



采用的是self_mapping技术实现反调试,本质是创建secion的时候设置SEC_NO_CHANGE,映射后不能改变节区属性

(https://github.com/changeofpace/Self-Remapping-Code)

> 关于这个技术,可以参考下这位大佬的笔记 https://jev0n.com/2021/09/23/Self-Remapping.html

我们直接将`call sub_411CE0` 的地方nop掉,手动的把`byte_4409c0`的地方加1

```python
a = [0x07, 0xB8, 0x0D, 0x24, 0xB1, 0x0C, 0x2D, 0xC7, 0x28, 0x2D,
0xC3, 0x61, 0x66, 0x4F, 0x72, 0x13]

addr = 0x04409C0
for i in range(16):
    patch_byte(addr+i, a+1)

print("OK")
```

运行起来



得到key为 `GF?->GirlFriend?`

提取密文

```
0D 40 3B 87 A5 66 DA 74 92 7F BB E1 B8 CD EB BC 59 45 1B C0 38 99 AA 22 AA 3F 9D 21 07 4E 81 1F
```

(https://the-x.cn/cryptography/Sm4.aspx)



`2e69df5961f20aee0897cf1905156344      `, 最终得到flag为 `GFCTF{2e69df5961f20aee0897cf1905156344}`

## re_EasyRE_0x00

IDA打开分析,最关键的是`sub_100016A0`函数





经过分析,发现`sub_10001180`是解密login.key文件,生成的数据放到V13里面

然后下面这个地方是将V13处的数据与生成的一些数据进行对比,猜测是机器码的验证



这是V13处的数据

```
11 55 66 55 0D 50 51 0C FF 01 80 12 CE A9 08 75 73 65 72 32 33 33 33
```

最后8个字符是user2333

将对比的数据也提取出来, 然后结合题目,用户名用admin6677登录,长度是9,整理得

```
11 55 66 55 98 FA 9B 59 6F F6 14 8F E9 DA 09 61 64 6D 69 6E 36 36 37 37
```

我们写脚本,每次运行到对比数据的时候就把v13的数据给他替换掉

```python
data =
addr = 0x004CB348# v13的地址
for i in range(len(data)):
    patch_byte(addr+i, data)
print("OK")
```

然后绕过机器码验证,往下走,来到`sub_10001610`处



可以发现,这个地方肯定是与服务器通信了,我们直接运行,直接Wireshark抓包

提取数据

```cpp
---> 11 55 66 55 1a 27 00 00 00 00
   
<--- 11 55 66 55 66 27 00 00 0f 00 f3 46 8a be 81 62 ed 36 d5 df 28 dc 04 8a fd
   
---> 11 55 66 55 1a 27 01 00 40 00 0e a2 60 19 1f df 39 0d bc 62 48 57 5a 11 87 78 69 11 03 76 4b f9 2c 1f 35 fd ff 4a b8 d8 63 8f b6 b1 f0 cd d3 90 2d 27 05 b7 1e 01 22 74 91 1a a4 53 df 1d f4 69 7d 3e 29 bd d3 30 da 94 a3 03
   
<--- 11 55 66 55 66 27 01 00 48 00 84 cb 11 ef 71 51 30 0b b3 d8 c1 22 ac c4 ca f1 29 12 cf 79 f5 36 5f 5a 5e a8 f5 fa 62 3c e8 32 69 d6 a1 54 eb 1b 06 06 b0 68 20 5a 62 ea 48 ec 8a 3d 5c 40 d0 a8 03 94 6a 2e b7 f0 e4 33 aa a0 e3 f2 da f8 a9 cf 5d 92
```

重新调试,接着刚才的位置往下分析,看到了RC4的初始化及加密





猜测是刚开始,服务器端返回RC4的key,然后后面全部使用RC4加密方式进行加密



根据`sub_10001350`这个函数可以猜测出数据包的格式, 拿上面服务器返回的key举例子

```cpp
11 55 66 55 //标志
66 27       //版本
00 00       //命令
0f 00       //后面数据的长度
f3 46 8a be 81 62 ed 36 d5 df 28 dc 04 8a fd//数据,当命令为0的时候,是RC4的key,命令为1和2的时候,是RC4加密的数据
```

写脚本验证RC4加密

```python
from Crypto.Cipher import ARC4 as rc4cipher
import binascii


def rc4_algorithm(encrypt_or_decrypt, data, key1):
    if encrypt_or_decrypt == "enc":
      key = key1
      enc = rc4cipher.new(key)
      res = enc.encrypt(data)
      return res
    elif encrypt_or_decrypt == "dec":
      key = key1
      enc = rc4cipher.new(key)
      res = enc.decrypt(data)
      return res


key = binascii.unhexlify("f3468abe8162ed36d5df28dc048afd")
data1 = binascii.unhexlify(
    "0ea260191fdf390dbc6248575a118778691103764bf92c1f35fdff4ab8d8638fb6b1f0cdd3902d2705b71e012274911aa453df1df4697d3e29bdd330da94a303")
m1 = rc4_algorithm("dec", data1, key)


data2 = binascii.unhexlify(
    "84cb11ef7151300bb3d8c122acc4caf12912cf79f5365f5a5ea8f5fa623ce83269d6a154eb1b0606b068205a62ea48ec8a3d5c40d0a803946a2eb7f0e433aaa0e3f2daf8a9cf5d92")
m2 = rc4_algorithm("dec", data2, key)


print(m1)
print(m2)
# b'\x8ayqv,\x8eYjj\xdb\xfa\x10\xd6\xa0=\xed!w\xa9/\xdd\xa3\x1a \x05!+\xbd\xd0\xa7\xe7\xd4\xba\t%\xb9N\xeeYR\xdc\xb0Pfq\xae\xe9\xc7\x1eB\xa3\x0eA\xb3\x08\xcf1\xb3\x12\xa5L\xd4`\xcc'
# b'\x00\x10\x00\x80B\x00Please update client!\r\nClient version=10010, Server version=10086\x00'
```



结合login.key,发现当命令为1的时候,向服务器发送的是login.key的数据,然后服务器返回信息

所以现在需要构造 真正的login.key(11 55 66 55 98 FA 9B 59 6F F6 14 8F E9 DA 09 61 64 6D 69 6E 36 36 37 37)

加密后的数据

`sub_10001180`是解密函数,进去分析,发现是RSA的PKCS#1加密



根据这个结构找到e和n





提取出来

```cpp
e: 65537
n: 0xd928b8efe000f72db5bda67a9aa0740defb555b2603736eecd6d01f38ef2fc79
分解得到p, q
p = 322922590106035145437937724697895880569
q = 304171468404401467258708275665013611777
```

利用rsatool.py生成private.pem

```
python rsatool.py -e 65537 -p 322922590106035145437937724697895880569 -q 304171468404401467258708275665013611777 -o private.pem
```

利用在线解密网站测试 https://the-x.cn/cryptography/Rsa.aspx



发现解密成功,将构造好的数据进行加密,



> 对于PKCS#1的填充方式可以参考下面2篇文章
>
> https://www.cloudcared.cn/3155.html
>
> https://www.cnblogs.com/feng9exe/p/8075447.html



然后写程序与服务器交互,发现服务器返回命令为2的验证码问题

`Question(Send result in uint32_t format, 1 second!): 9540808 * 32 + 509 * 859 = ?`

然后利用eval计算数值,构造,返回给服务器,即可得到flag,完整的exp如下

```python
import socket
from Crypto.Cipher import ARC4 as rc4cipher
import re
import struct

login_key = [0x5D, 0x98, 0xEE, 0x8B, 0x68, 0x86, 0x2F, 0x56, 0xBA, 0xA1, 0x27, 0x2A, 0x68, 0x8B, 0x19, 0x31, 0x37, 0xC1, 0x2B, 0x1A, 0x80, 0x5F, 0xAB, 0x8C, 0xE0, 0xE6, 0x81, 0xDF, 0x05, 0xC6, 0xB1,
             0x2F, 0x0E, 0x59, 0xC8, 0x45, 0x8A, 0x7D, 0x83, 0x35, 0x5F, 0x02, 0x05, 0x10, 0x8A, 0x35, 0x6D, 0x0C, 0xE8, 0x3C, 0x9C, 0x15, 0xD7, 0xDA, 0xF0, 0x96, 0x6D, 0x2E, 0x77, 0xEC, 0x78, 0x3B, 0x83, 0xB2]


def rc4_algorithm(encrypt_or_decrypt, data, key1):
    if encrypt_or_decrypt == "enc":
      key = key1
      enc = rc4cipher.new(key)
      res = enc.encrypt(data)
      return res
    elif encrypt_or_decrypt == "dec":
      key = key1
      enc = rc4cipher.new(key)
      res = enc.decrypt(data)
      return res


def get_data(_cmd, _len, _data, _key):
    sig =       # 签名
    banben =                # 版本
    cmd_list =              # 命令
    data_len_list =       # 数据长度
    if _len != 0:
      return bytes(sig + banben + cmd_list + data_len_list) + rc4_algorithm('enc', bytes(_data), _key)
    return bytes(sig + banben + cmd_list + data_len_list)


def get_captcha(_captcha_str):
    m = re.search(
      r"Question\(Send result in uint32_t format, 1 second!\): (.*?) = ", _captcha_str)
    c = eval(m.group(1))
    return struct.pack("I", c)


if __name__ == '__main__':
    address = ('119.27.179.145', 10086)

    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    s.connect(address)

    s.send(get_data(0, 0, [], None))
    data = s.recv(1024)
    rc4_key = data               # 获取RC4密钥

    s.send(get_data(1, 0x40, login_key, rc4_key))
    data = s.recv(1024)
    captcha_str = rc4_algorithm("dec", data, rc4_key).decode()
    captcha = get_captcha(captcha_str)      # 计算得到验证码
    print(f"Captcha: {captcha}")

    # 向服务器返回验证码
    send_data = get_data(2, len(captcha), list(captcha), rc4_key)
    s.send(send_data)
    data = s.recv(1024)
    m = rc4_algorithm("dec", data, rc4_key)
    print(m)
    s.close()

    # Captcha: b'\xc84\x06\x03'
    # b'\x00\x10\x00\x805\x00flag_0x00 = \x00GFCTF{e8e9071b7a70770bec1f6415c4ed4c1d}\x00'
```

得到flag为 `GFCTF{e8e9071b7a70770bec1f6415c4ed4c1d}`

这个Easy_RE在番外篇结束第1天捣鼓出来了,有点可惜,一直卡在找N和那个RSA解密了,一直在用常规的pow(c,d,n)解,后来才发现,RSA加密也得符合一定的标准,填充数据什么的,这里就是用了最普通的PKCS#1,还是学到了很多东西的,继续加油。

ZJevon 发表于 2021-11-26 20:09

chenjingyes 发表于 2021-11-26 16:52
楼主不把题目附件传上?

https://jev0n.lanzoui.com/ibhzswxjbza可以看这里,同时也欢迎师傅们参加明年的GFCTF:lol

chenjingyes 发表于 2021-11-29 16:36

ZJevon 发表于 2021-11-26 20:09
https://jev0n.lanzoui.com/ibhzswxjbza可以看这里,同时也欢迎师傅们参加明年的GFCTF

好的 哈哈

Hmily 发表于 2021-11-25 18:16

最后多了两张图没有插入到正文?

zsky 发表于 2021-11-25 18:46

Hmily 发表于 2021-11-25 18:16
最后多了两张图没有插入到正文?

好的,我修正下

52wxl 发表于 2021-11-25 19:04

大佬,你真厉害

rzxcs 发表于 2021-11-25 23:57

高手在民间。

JPK 发表于 2021-11-26 09:08

不错哦,get到了

lsjhwei 发表于 2021-11-26 10:07

谢谢分享

liujiata 发表于 2021-11-26 14:33

分析的很不错。值得学习。

chenjingyes 发表于 2021-11-26 16:52

楼主不把题目附件传上?:lol

zsky 发表于 2021-11-26 17:35

chenjingyes 发表于 2021-11-26 16:52
楼主不把题目附件传上?

这个的题目附件我应该是没备份,全是我patch后的,之后的比赛我记得备份
页: [1] 2 3
查看完整版本: 2021GFCTF RE部分WP