CISCN2024 RE 后两道 wp 复现

5. gdb_debug

其实逻辑还是挺简单的,当时没认真做

伪代码还算清晰

几个循环的加密之后判断密文

难点是前面有随机数参与加密,不过可以猜测随机数是不变的。

第一段加密

flag异或一组随机数,这里可以在异或的位置下条件断点,用idapython把随机数直接打印出来(会发现是不变的)

al = idaapi.get_reg_val("al")
print(hex(al), end=',')

得到第一段异或的随机数

第二段ptr数组直接点开看不见,鼠标悬停在上面可以看见一个地址,跳转过去0x55610C1B5AC0

跳过去取38个

第三段加密换表+异或

直接从crypto倒推,异或的tmp随机数用相同的方法打印

#gdb wp
crypto = [0x63,0x6f,0x6e,0x67,0x72,0x61,0x74,0x75,0x6c,0x61,0x74,0x69,0x6f,0x6e,0x73,0x74,0x6f,0x79,0x6f,0x75,0x63,0x6f,0x6e,0x67,0x72,0x61,0x74,0x75,0x6c,0x61,0x74,0x69,0x6f,0x6e,0x73,0x74,0x6f,0x79]

key = [ 0xBF, 0xD7, 0x2E, 0xDA, 0xEE, 0xA8, 0x1A, 0x10, 0x83, 0x73, 
  0xAC, 0xF1, 0x06, 0xBE, 0xAD, 0x88, 0x04, 0xD7, 0x12, 0xFE, 
  0xB5, 0xE2, 0x61, 0xB7, 0x3D, 0x07, 0x4A, 0xE8, 0x96, 0xA2, 
  0x9D, 0x4D, 0xBC, 0x81, 0x8C, 0xE9, 0x88, 0x78]

enc2 = [0]*38

for i in range(38):
    enc2[i] = crypto[i] ^ key[i]

xor2 = [0xde,0xaa,0x42,0xfc,0x9,0xe8,0xb2,0x6,0xd,0x93,0x61,0xf4,0x24,0x49,0x15,0x1,0xd7,0xab,0x4,0x18,0xcf,0xe9,0xd5,0x96,0x33,0xca,0xf9,0x2a,0x5e,0xea,0x2d,0x3c,0x94,0x6f,0x38,0x9d,0x58,0xea]

for i in range(38):
    enc2[i] ^= xor2[i]

ptr = [0x12, 0x0E, 0x1B, 0x1E, 0x11, 0x05, 0x07, 0x01, 0x10, 0x22, 0x06, 0x17, 0x16, 0x08, 0x19, 0x13, 
    0x04, 0x0F, 0x02, 0x0D, 0x25, 0x0C, 0x03, 0x15, 0x1C, 0x14, 0x0B, 0x1A, 0x18, 0x09, 0x1D, 0x23, 
    0x1F, 0x20, 0x24, 0x0A, 0x00, 0x21]

enc1 = [0]*38

for i in range(38):
    enc1[ptr[i]] = enc2[i]

xor1 = [0xd9,0xf,0x18,0xbd,0xc7,0x16,0x81,0xbe,0xf8,0x4a,0x65,0xf2,0x5d,0xab,0x2b,0x33,0xd4,0xa5,0x67,0x98,0x9f,0x7e,0x2b,0x5d,0xc2,0xaf,0x8e,0x3a,0x4c,0xa5,0x75,0x25,0xb4,0x8d,0xe3,0x7b,0xa3,0x64]

flag = ''

for i in range(38):
    flag += chr(enc1[i] ^ xor1[i])

print(flag)
#flag{78bace5989660ee38f1fd980a4b4fbcd}

 6. GoReverse

恶心的Golang

主函数没啥东西,调试一下

第9行 while部分会循环一会,是在分配需要的栈帧

第11行 有个反调试,patch一下

调试时遇到异常就discard掉

主逻辑应该在main_FlatControlFlow内

在里面调试,发现走到call rdx这里打印了一句话

再循环走到这里又打印出后面的

然后结束

再动调一遍发现实际上跳到了main__ptr_co6Pxq_Execute

就光打了一句话

第二次

main__ptr_B2bUPq_Execute应该是加密部分

前面是分配占空间和错误提示

注意这里调试需要flag文件作为输入

然后初始化,读取文件,一个错误处理

下面第一个加密函数main_ylFyZv

实际上就一个循环异或

第二个加密函数main_zQyveE,是一个魔改XXTEA加密

一个是改了delta

一个是改了MX

main_Q05qm6主要是做一些字符串切片

然后是main_AkuFrt

好像是个sm4

github可以搜到上面那个函数的源码gmsm/sm4/sm4.go at master · tjfoc/gmsm · GitHub

看一下汇编会发现是CTR加密

也能搜到示例代码cipher package - crypto/cipher - Go Packages,不过是个AES的示例

package main

import (
    "crypto/aes"
    "crypto/cipher"
    "crypto/rand"
    "encoding/hex"
    "fmt"
    "io"
)

func main() {
    // Load your secret key from a safe place and reuse it across multiple
    // NewCipher calls. (Obviously don't use this example key for anything
    // real.) If you want to convert a passphrase to a key, use a suitable
    // package like bcrypt or scrypt.
    key, _ := hex.DecodeString("6368616e676520746869732070617373")
    plaintext := []byte("some plaintext")

    block, err := aes.NewCipher(key)
    if err != nil {
        panic(err)
    }

    // The IV needs to be unique, but not secure. Therefore it's common to
    // include it at the beginning of the ciphertext.
    ciphertext := make([]byte, aes.BlockSize+len(plaintext))
    iv := ciphertext[:aes.BlockSize]
    if _, err := io.ReadFull(rand.Reader, iv); err != nil {
        panic(err)
    }

    stream := cipher.NewCTR(block, iv)
    stream.XORKeyStream(ciphertext[aes.BlockSize:], plaintext)

    // It's important to remember that ciphertexts must be authenticated
    // (i.e. by using crypto/hmac) as well as being encrypted in order to
    // be secure.

    // CTR mode is the same for both encryption and decryption, so we can
    // also decrypt that ciphertext with NewCTR.

    plaintext2 := make([]byte, len(plaintext))
    stream = cipher.NewCTR(block, iv)
    stream.XORKeyStream(plaintext2, ciphertext[aes.BlockSize:])

    fmt.Printf("%s\n", plaintext2)
}

总之大致能看出来NewCipher处参数是key,NewCTR处参数是iv

而且CTR mode is the same for both encryption and decryption, so we can also decrypt that ciphertext with NewCTR.对称的加解密

然后是main_JrkmHd

CBC的AES cipher package - crypto/cipher - Go Packages

package main

import (
	"crypto/aes"
	"crypto/cipher"
	"crypto/rand"
	"encoding/hex"
	"fmt"
	"io"
)

func main() {
	// Load your secret key from a safe place and reuse it across multiple
	// NewCipher calls. (Obviously don't use this example key for anything
	// real.) If you want to convert a passphrase to a key, use a suitable
	// package like bcrypt or scrypt.
	key, _ := hex.DecodeString("6368616e676520746869732070617373")
	plaintext := []byte("exampleplaintext")

	// CBC mode works on blocks so plaintexts may need to be padded to the
	// next whole block. For an example of such padding, see
	// https://tools.ietf.org/html/rfc5246#section-6.2.3.2. Here we'll
	// assume that the plaintext is already of the correct length.
	if len(plaintext)%aes.BlockSize != 0 {
		panic("plaintext is not a multiple of the block size")
	}

	block, err := aes.NewCipher(key)
	if err != nil {
		panic(err)
	}

	// The IV needs to be unique, but not secure. Therefore it's common to
	// include it at the beginning of the ciphertext.
	ciphertext := make([]byte, aes.BlockSize+len(plaintext))
	iv := ciphertext[:aes.BlockSize]
	if _, err := io.ReadFull(rand.Reader, iv); err != nil {
		panic(err)
	}

	mode := cipher.NewCBCEncrypter(block, iv)
	mode.CryptBlocks(ciphertext[aes.BlockSize:], plaintext)

	// It's important to remember that ciphertexts must be authenticated
	// (i.e. by using crypto/hmac) as well as being encrypted in order to
	// be secure.

	fmt.Printf("%x\n", ciphertext)
}

NewCipher处调用key, NewCBCENcrypter处调用iv

main_NJVCTq就是base32

总体逻辑就出来了

然后就是收集各个参数


假设结果 VPAFNU3PTHPAUQTCYUBTVJY6TGBWY3NGGKZ6OFKZ74JJBPF7LSQ3ZYO4IZLOXPVEIE7KZS46VJKNDJGRBJPUTVKTNFLZRQBLLZUD7VI=

1 xor
v24 = a5;
v19 = a1;
...
a5 = a4;  
a4 = v24;
...
v13 = *(i + v19);
...
*(result + i) = a5[i % v24] ^ v13;
a5 = D7BJLsOk9@f&1dWIn53IDlJqUS6$^WhkAk2kk*2GaqmLwiLX^bGGE$&dmqR^g5bL3lCA5^HGK$9qo5T@Bwom9vEXya0HAV3LrWW
v24 = 0x63
v13 = input

2 xxtea_kai
key 0x385E7342, 0x345A772A, 0x6F38756C, 0x6B402652
delta = 0x7FAB4CAD
MX_kai   (((z >> 5 ^ y << 2) + (y >> 3 ^ z << 4) ^ (sum ^ y)) + ((key[(p & 3) ^ e] ^ z)))

3 SM4_CTR
key pg5g#k6Qo3L&1EzT
iv 随机生成

4 AES_CBC
key dPGWgcLpqmxw3uOXhKpKV009Cql@@XE6
iv dPGWgcLpqmxw3uOX(key前16位)

5 base32

xor的key很容易得到

xxtea的key

SM4的key

AES的key

SM4的iv是随机的,动调看看怎么个事儿

随机数在这里看到

16个字节,我们把它全改成1跟一下(C0000BC2B0)

跟到AES加密的位置,发现随机数被加到了加密数据的开头?

再从头调

再改一下随机数1111111111111111

看一下SM4输出

随机数被加到了输出的开头,也就是说密文的开头添加了随机数iv

那解密的时候把AES解密的开头16位取出就是iv了

最后解一个魔改XXTEA,然后异或一串字符即可 

#go_wp
def byte2uint32(d):
    l=[]
    for i in range(len(d)//4):
        tmp=d[i*4:i*4+4]
        l.append(tmp[3]*256**3+tmp[2]*256**2+tmp[1]*256+tmp[0])
    return l

def hexprint(arr):
    for byte in arr:
        print(hex(byte), end=', ') 
    print()

# 给定的十六进制字符串
hex_string = "69124cadc128ffe15752488b318e78c68f359d69fb2b8e4bc35ae98ee05493ca31ceb32174201bb091c9406e56430d53c046d1d128a0de5862ee5e6d"
# 将十六进制字符串转换为字节数组
byte_array = bytes.fromhex(hex_string)
# 取出前16个字节
first_16_bytes = byte_array[:16]
# 将前16个字节转换为十六进制字符串
first_16_hex_string = first_16_bytes.hex()
# 打印前16个字节的十六进制字符串
print(first_16_hex_string)

enc = [0xe9,0xd3,0xa0,0x68,0x40,0xc8,0x27,0xb6,0x29,0xaf,0x66,0x77,0x61,0xad,0xd2,0x18,0xae,0x3d,0x67,0x5c,0x85,0x9a,0x92,0x1b,0xf6,0xb8,0x5b,0x5d,0x38,0x5a,0x79,0x5e,0x00,0x53,0xde,0x67,0x20,0xe4,0x14,0x34,0x0b,0x91,0xc5,0x54]
enc32 = byte2uint32(enc)
hexprint(enc32)

from ctypes import * 

def MX(z, y, sum1, k, p, e):
    #return c_uint32(((z.value>>5^y.value<<2)+(y.value>>3^z.value<<4))^((sum1.value^y.value)+(k[(p&3)^e.value]^z.value)))
    return c_uint32(( (z.value>>5^y.value<<2)+(y.value>>3^z.value<<4)^(sum1.value^y.value)) + (k[(p&3)^e.value]^z.value))
def btea(v,k,n,delta):

    if n>1:
        sum1=c_uint32(0)
        z=c_uint32(v[n-1])
        rounds=6+52//n
        e=c_uint32(0)

        while rounds>0:
            sum1.value+=delta
            e.value=((sum1.value>>2)&3)	#e都要32位哦
            for p in range(n-1):
                y=c_uint32(v[p+1])
                #v[p]=c_uint32(v[p]+c_uint32((((z.value>>5^y.value<<2)+(y.value>>3^z.value<<4))^((sum1.value^y.value)+(k[(p&3)^e.value]^z.value)))).value).value
                v[p] = c_uint32(v[p] + MX(z,y,sum1,k,p,e).value).value
                z.value=v[p]

            y=c_uint32(v[0])
            #v[n-1]=c_uint32(v[n-1]+c_uint32((((z.value>>5^y.value<<2)+(y.value>>3^z.value<<4))^((sum1.value^y.value)+(k[((n-1)&3)^e.value]^z.value)))).value).value		#这里tmd传入的是k[((n-1)&3)啊我草,找了半天!!!
            v[n-1] = c_uint32(v[n-1] + MX(z,y,sum1,k,n-1,e).value).value
            z.value=v[n-1]
            rounds-=1

    else:
        sum1=c_uint32(0)
        n=-n
        rounds=6+52//n
        sum1.value=rounds*delta
        y=c_uint32(v[0])
        e=c_uint32(0)

        while rounds>0:
            e.value=((sum1.value>>2)&3)	#e都要32位哦
            for p in range(n-1, 0, -1):
                z=c_uint32(v[p-1])
                #y[p]=c_uint32(v[p]-c_uint32((((z.value>>5^y.value<<2)+(y.value>>3^z.value<<4))^((sum1.value^y.value)+(k[(p&3)^e.value]^z.value)))).value).value
                v[p] = c_uint32(v[p] - MX(z,y,sum1,k,p,e).value).value
                y.value=v[p]

            z=c_uint32(v[n-1])
            #v[n-1]=c_uint32(v[n-1]-c_uint32((((z.value>>5^y.value<<2)+(y.value>>3^z.value<<4))^((sum1.value^y.value)+(k[((n-1)&3)^e.value]^z.value)))).value).value		#这里tmd传入的是k[((n-1)&3)啊我草,找了半天!!!
            v[0] = c_uint32(v[0] - MX(z,y,sum1,k,0,e).value).value
            y.value=v[0]
            sum1.value-=delta
            rounds-=1

    return v

a=[0x68a0d3e9, 0xb627c840, 0x7766af29, 0x18d2ad61, 0x5c673dae, 0x1b929a85, 0x5d5bb8f6, 0x5e795a38, 0x67de5300, 0x3414e420, 0x54c5910b]
k=[0x385E7342, 0x345A772A, 0x6F38756C, 0x6B402652]
delta=0x7FAB4CAD
n=11
res=btea(a,k,-n,delta)
hexprint(res)

def uint322byte(uint32_list):
    byte_array = []
    for number in uint32_list:
        byte_array.append(number & 0xFF)
        byte_array.append((number >> 8) & 0xFF)
        byte_array.append((number >> 16) & 0xFF)
        byte_array.append((number >> 24) & 0xFF)
    return byte_array

flag_byte = uint322byte(res)
flag = ''
xor_key = 'D7BJLsOk9@f&1dWIn53IDlJqUS6$^WhkAk2kk*2GaqmLwiLX^bGGE$&dmqR^g5bL3lCA5^HGK$9qo5T@Bwom9vEXya0HAV3LrWW'
for i in range(len(flag_byte)):
    flag += chr(flag_byte[i]^ord(xor_key[i%0x63]))
print(flag)
#flag{3a4575cf-c85c-4350-90ca-baef8252425e}
  • 20
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值