DEFCON 33 CTF 解题系列 #3:dialects (pwn)

admin 2025年5月15日10:28:54评论3 views字数 12690阅读42分18秒阅读模式

【翻译】DEFCON 33 CTF Write-Up Series 3 dialects (pwn)

dialects

逆向分析第一步

静态链接、符号表被剥离的二进制文件 🤮

file ctf                  ctf: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped

让我们从修复被剥离的符号开始。

FLIRT 技术

作者在挑战中提供了 libc.a 和 libstdc++.a 文件。我们是否可以直接从这些文件中提取符号并应用它们?是的,你可以使用 FLIRT 来实现。

那么,什么是 FLIRT 以及它是如何工作的?FLIRT 是由 Hex-Rays 创建的用于从程序中分离库函数的技术。为了解决这个问题,Hex-Rays 首先从库中创建函数的签名。然后,它将程序中使用的函数签名与预先创建的库函数签名进行比较。如果两个签名相同,我们就可以假设这两个函数也是相同的。更详细的解释,请参阅官方文档

顺便说一下,如何创建签名文件并将其应用到程序中?

可以尝试以下方法:

# create signature files/path/to/IDA/tools/flair/pelf libc.a libc.pat/path/to/IDA/tools/flair/makesig libc.pat libc.sig# fix libc.exc /path/to/IDA/tools/flair/makesig libc.pat libc.sig

从 文件 > 加载文件 > FLIRT 签名文件... 加载签名文件DEFCON 33 CTF 解题系列 #3:dialects (pwn)

现在我们已经恢复了 libc 的符号!DEFCON 33 CTF 解题系列 #3:dialects (pwn)

另一个库的签名是由我的队友 howdays 使用 lib{ssl,crypto}.so 获取的。看起来效果更好了!DEFCON 33 CTF 解题系列 #3:dialects (pwn)

那么如何获取 flag 呢 😅

很容易理解程序在做什么。该程序使用 stdio 通过 SSL 协议进行通信。

int __fastcall main(int argc, constchar **argv, constchar **envp){  __int64 v3; // rbp  _DWORD *v4; // rbp  signal(0xDu, 1);  v3 = sub_400676(13);  sub_4006A4(v3);  alarm(0xAu);  v4 = (_DWORD *)SSL_new(v3);  SSL_set_rfd(v4, 0);  SSL_set_wfd(v4, 1);  if ( (int)SSL_accept((__int64)v4) > 0 )    return sub_40027D((__int64)v4);  else    return sub_4A00C6(0xA6E340);}

程序的核心逻辑实现在 sub_40027D 函数中。

  1. 程序执行流程依赖于获取 4 字节输入并使用 SM4 加密算法进行加密。

2-1. 如果 SM4 加密结果为 1,则检查我们是否提供了正确的输入,并验证 strcmp(SHA512(sm4_encrypt(ctx, 随机 16 字节 | 用户输入 16 字节), byte_6E1000)) 的结果是否为真。如果为真,我们就可以读取随机文件。

2-2. 如果 SM4 加密结果为 2,则检查 memcmp(sm3_digest(ctx, sm4_encrypt(ctx, 随机 16 字节 | 用户输入 16 字节), 32), “x00x00x00”, 3) 的结果是否为真。

因此,我们可以先解决 2-2,然后再解决 2-1 来读取 /flag,对吗?

__int64 __fastcall sub_40027D(__int64 a1){ // ...   cipher_ctx = (__int64 *)EVP_CIPHER_CTX_new();  md_ctx = EVP_MD_CTX_new();  v4 = 8;  v5 = iv;  mdctx = (__int64)md_ctx;  while ( v4 )  {    *v5++ = 0;    --v4;  }  v19 = 0;  key = 0;  result = getrandom(&key, 80);  if ( result == 8 )  {    SSL_write(a1, (__int64)&key, 8);    SSL_read(a1, (__int64)&key + 88);    cipher = EVP_sm4_ctr();    EVP_CipherInit((__int64)cipher_ctx, (__int64)cipher, (__int64)&key, (__int64)iv);    v8 = EVP_sm3();    EVP_DigestInit(mdctx, (__int64)v8, 0);    LODWORD(v15) = 0;    while ( (unsigned int)SSL_read(a1, (__int64)v28, 256) == 4 )    {      EVP_EncryptUpdate(cipher_ctx, (__int64)&v19, &outlen, (__int64)v28, 4);      inbuf = &v25;      outbuf = v27;      if ( v19 == 1 )       {        if ( !(_DWORD)v15 )          break;        getrandom(&v23, 160);        SSL_write(a1, (__int64)&v23, 16);        SSL_read(a1, (__int64)inbuf, 16);        EVP_EncryptUpdate(cipher_ctx, (__int64)outbuf, &outlen, (__int64)inbuf, 16);        *(_OWORD *)&v27[4] = v23;        v9 = (char *)SHA512((int)outbuf, 320);        if ( (unsigned int)strcmp(v9, byte_6E1000) )          break;        outlen = 256;        SSL_read(a1, (__int64)v28, 256);        EVP_EncryptUpdate(cipher_ctx, (__int64)v14, &outlen, (__int64)v28, 256);        v10 = open((__int64)v14, 0);        outlen = read(v10, (int)v28, 256);        SSL_write(a1, (__int64)v28, outlen);      }      else      {        if ( v19 != 2 )          break;        v11 = outbuf;        for ( i = 16; i; --i )          *v11++ = 0;        v15 = &v22;        getrandom(&v22, 160);        SSL_write(a1, (__int64)v15, 16);        v23 = 0;        SSL_read(a1, (__int64)&v23, 16);        v26 = v22;        EVP_EncryptUpdate(cipher_ctx, (__int64)inbuf, &outlen, (__int64)&v23, 16);        EVP_DigestUpdate(mdctx, inbuf, 32);        EVP_DigestFinal_ex(mdctx, outbuf, 0);        if ( BYTE2(v27[0]) | (unsigned __int8)(BYTE1(v27[0]) | LOBYTE(v27[0])) )        {          v13 = 2;          goto LABEL_19;        }      }      LODWORD(v15) = 1;    }    v13 = 0;LABEL_19:    exit(v13);  }  return result;}

自定义加密算法?

我原本以为直接使用 openssl 库编写代码会很简单,但 sm3 和 sm4 的实现方式有些奇怪。由于我不清楚作者做了多少修改,与其尝试找出所有差异,我决定直接使用作者实现的 SM3 和 SM4 算法。

由于这是一个静态链接的二进制文件,只要正确映射地址,就可以直接使用其中的函数。

以下是一个示例。

#define PROB_PATH "./ctf"#define PROB_BASE (void *)(0x400000)#define PROB_DATA_BASE (void *)(0xa67000)void *(*SHA512)(void *, intint) = (typeof(SHA512))(0x4DC76A);voidhexdump(void *mem, unsignedint len);voidfatal(constchar *msg);void *map_prob(void){    int fd;    size_t prob_size;    void *prob_base;    void *prob_data_base;    fd = open(PROB_PATH, O_RDONLY);    if (fd < 0)        fatal("open");    prob_size = lseek(fd, 0, SEEK_END);    lseek(fd, 0, SEEK_SET);    prob_base = mmap(PROB_BASE, prob_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_FIXED_NOREPLACE, fd, 0);    if (prob_base == MAP_FAILED)        fatal("mmap");    prob_data_base = mmap(PROB_DATA_BASE, 0xc000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED_NOREPLACE | MAP_ANON, -10);    if (prob_data_base == MAP_FAILED)        fatal("mmap");    memcpy((char *)prob_data_base + 0x1000, (char *)prob_base + 0x4680000x6558);    close(fd);    return prob_base;}voidtest(void){    char *sha512_out;    char hash_input[0x20];    memset(hash_input, 'A'0x20);    sha512_out = SHA512(hash_input, 0x200);    hexdump(sha512_out, 0x20);}intmain(int argc, char *argv[], char *envp[]){    void *prob_base;    setbuf(stdin, NULL);    setbuf(stdout, NULL);    setbuf(stderr, NULL);    prob_base = map_prob();    test();    return 0;}

段错误(segmentation fault)?问题出在哪里?

> ./test[2]    1547625 segmentation fault (core dumped)  ./test

看起来我们需要在 0xa72e08 地址处填充正确的值。DEFCON 33 CTF 解题系列 #3:dialects (pwn)

而这个正确的值应该是环境变量指针数组(envp)的结束地址。

__int64 __fastcall _init_libc(__int64 envp, __int64 a2){  v2 = 0;  qword_A72AF8 = envp;  memset(v14, 0, 0x130u);  while ( *(_QWORD *)(envp + 8 * v2++) )    ;  v4 = (unsigned __int64 *)(envp + 8 * v2);  qword_A72E08 = (__int64)v4;  // ...}

现在一切运行正常!

./test0x000000: ac 80 04 71 de 52 f7 4e 8d 32 32 37 b1 b9 13 ca ...q.R.N.227....0x000010: 28 bc 0f 46 f2 6b 88 f8 b2 5e 25 e7 33 c1 a6 08 (..F.k...^%.3...

现在我们需要实现它来获取 flag

解决 2-2

  1. 使用从问题服务器获取的密钥对x02x00x00x00进行 sm4_decrypt,从而将执行流程切换到 2-2。

  2. 通过暴力破解找到使 sm3_digest(N) 结果为x00x00x00x??...的 N 值,然后对该值进行 sm4_decrypt 来解决 2-2。

解决 2-1

  1. 使用从问题服务器获取的密钥对x01x00x00x00进行 sm4_decrypt,从而将执行流程切换到 2-1。

  2. 通过暴力破解找到使 SHA512(N) 结果为x8dx36x00x??...的 N 值,然后对该值进行 sm4_decrypt 来解决 2-1。(因为 byte_6E1000 是x8dx36x00...

这是我使用的完整代码

// gcc -o test test.c#include<stdio.h>#include<unistd.h>#include<fcntl.h>#include<stdlib.h>#include<ctype.h>#include<string.h>#include<sys/mman.h>#define PROB_PATH "./ctf"#define PROB_BASE (void *)(0x400000)#define PROB_DATA_BASE (void *)(0xa67000)#ifndef HEXDUMP_COLS#define HEXDUMP_COLS 16#endifvoid *(*EVP_MD_CTX_new)(void) = (typeof(EVP_MD_CTX_new))(0x4A1B50);void *(*EVP_CIPHER_CTX_new)(void) = (typeof(EVP_CIPHER_CTX_new))(0x4A3932);void (*EVP_EncryptInit)(void *, void *, void *, void *) = (typeof(EVP_EncryptInit))(0x4A619D);void (*EVP_EncryptUpdate)(void *, void *, void *, void *, int) = (typeof(EVP_EncryptUpdate))(0x4A3DDA);void (*EVP_DecryptInit)(void *, void *, void *, void *) = (typeof(EVP_DecryptInit))(0x4A61A5);void (*EVP_DecryptUpdate)(void *, void *, void *, void *, int) = (typeof(EVP_DecryptUpdate))(0x4A413A);void (*EVP_DigestInit_ex)(void *, void *, int) = (typeof(EVP_DigestInit_ex))(0x4A2AE1);void (*EVP_DigestUpdate)(void *, void *, int) = (typeof(EVP_DigestUpdate))(0x4A1BD1);void (*EVP_DigestFinal_ex)(void *, void *, int) = (typeof(EVP_DigestFinal_ex))(0x4A1CAE);void *(*EVP_sm4_ctr)(void) = (typeof(EVP_sm4_ctr))(0x4A37E4);void *(*EVP_sm3)(void) = (typeof(EVP_sm3))(0x4DDB2D);void *(*SHA512)(void *, intint) = (typeof(SHA512))(0x4DC76A);voidfatal(constchar *msg){    perror(msg);    exit(EXIT_FAILURE);}voidhexdump(void *mem, unsignedint len){    unsigned int i, j;    for (i = 0; i < len + ((len % HEXDUMP_COLS) ? (HEXDUMP_COLS - len % HEXDUMP_COLS) : 0); i++) {        /* print offset */        if (i % HEXDUMP_COLS == 0)            printf("0x%06x: ", i);        /* print hex data */        if (i < len)            printf("%02x "0xFF & ((char*)mem)[i]);        else /* end of block, just aligning for ASCII dump */            printf("   ");        /* print ASCII dump */        if (i % HEXDUMP_COLS == (HEXDUMP_COLS - 1)) {            for(j = i - (HEXDUMP_COLS - 1); j <= i; j++) {                if(j >= len) /* end of block, not really printing */                    putchar(' ');                else if(isprint(((char*)mem)[j])) /* printable char */                    putchar(0xFF & ((char*)mem)[j]);                else /* other char */                    putchar('.');            }            putchar('n');        }    }}void *map_prob(void){    int fd;    size_t prob_size;    void *prob_base;    void *prob_data_base;    fd = open(PROB_PATH, O_RDONLY);    if (fd < 0)        fatal("open");    prob_size = lseek(fd, 0, SEEK_END);    lseek(fd, 0, SEEK_SET);    prob_base = mmap(PROB_BASE, prob_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_FIXED_NOREPLACE, fd, 0);    if (prob_base == MAP_FAILED)        fatal("mmap");    prob_data_base = mmap(PROB_DATA_BASE, 0xc000, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_FIXED_NOREPLACE | MAP_ANON, -10);    if (prob_data_base == MAP_FAILED)        fatal("mmap");    memcpy((char *)prob_data_base + 0x1000, (char *)prob_base + 0x4680000x6558);    close(fd);    return prob_base;}voidsolve_main(void){    void *md_ctx;    void *cipher_ctx;    char key[16];    char iv[16];    char ciphertext[0x100];    char plaintext[0x100];    int out_num;    cipher_ctx = EVP_CIPHER_CTX_new();    puts("[+] round 1");    printf("key: ");    read(STDIN_FILENO, key, sizeof(key));    memset(iv, 0sizeof(iv));    EVP_DecryptInit(cipher_ctx, EVP_sm4_ctr(), key, iv);    memset(ciphertext, 0sizeof(ciphertext));    *(int *)ciphertext = 2;    EVP_DecryptUpdate(cipher_ctx, plaintext, &out_num, ciphertext, 4);    printf("answer: ");    write(STDOUT_FILENO, plaintext, 4);    puts("");    puts("[+] round 2");    printf("input_16: ");    char input_16[0x10];    read(STDIN_FILENO, input_16, sizeof(input_16));    size_t i;    char hash_input[0x20];    char hash_output[0x20];    memset(hash_input, 0sizeof(hash_input));    memcpy(hash_input + 0x10, input_16, sizeof(input_16));    while (1) {        md_ctx = EVP_MD_CTX_new();        EVP_DigestInit_ex(md_ctx, EVP_sm3(), 0);        *(size_t *)hash_input = i;        EVP_DigestUpdate(md_ctx, hash_input, 0x20);        EVP_DigestFinal_ex(md_ctx, hash_output, 0);        if (!(hash_output[0] | hash_output[1] | hash_output[2]))            break;        i++;    }    printf("hash_input: %ldn", i);    memset(ciphertext, 0sizeof(ciphertext));    *(size_t *)ciphertext = i;    EVP_DecryptUpdate(cipher_ctx, plaintext, &out_num, ciphertext, 0x10);    printf("answer: ");    write(STDOUT_FILENO, plaintext, 0x10);    puts("");    puts("[+] round 3");    memset(ciphertext, 0sizeof(ciphertext));    *(int *)ciphertext = 1;    EVP_DecryptUpdate(cipher_ctx, plaintext, &out_num, ciphertext, 4);    printf("answer: ");    write(STDOUT_FILENO, plaintext, 4);    puts("");    puts("[+] round 4");    printf("input_16: ");    read(STDIN_FILENO, input_16, sizeof(input_16));    i = 0;    memset(hash_input, 0sizeof(hash_input));    memcpy(hash_input + 0x10, input_16, sizeof(input_16));    char *sha512_out;    while (1) {        *(size_t *)hash_input = i;        sha512_out = SHA512(hash_input, 0x200);        if (!memcmp(sha512_out, "x8dx36x00"3))            break;        i++;    }    printf("hash_input: %ldn", i);    memset(ciphertext, 0sizeof(ciphertext));    *(size_t *)ciphertext = i;    EVP_DecryptUpdate(cipher_ctx, plaintext, &out_num, ciphertext, 0x10);    printf("answer: ");    write(STDOUT_FILENO, plaintext, 0x10);    puts("");    puts("[+] round 5");    memset(ciphertext, 0sizeof(ciphertext));    strcpy(ciphertext, "/flag");    EVP_DecryptUpdate(cipher_ctx, plaintext, &out_num, ciphertext, 256);    printf("answer: ");    write(STDOUT_FILENO, plaintext, 256);    puts("");}voidtest(void){    char *sha512_out;    char hash_input[0x20];    memset(hash_input, 'A'0x20);    sha512_out = SHA512(hash_input, 0x200);    hexdump(sha512_out, 0x20);}intmain(int argc, char *argv[], char *envp[]){    void *prob_base;    setbuf(stdin, NULL);    setbuf(stdout, NULL);    setbuf(stderr, NULL);    prob_base = map_prob();    int count = 0;    while ( envp[count++])        ;    *(unsigned long *)(0xA72E08) = (unsigned long)&envp[count];    solve_main();    _exit(0);    return 0;}
import socketimport sslfrom pwn import *import oscontext.arch = "amd64"context.terminal = ["sudo""konsole""-e""zsh""-c"]if args.REMOTE:    HOST = "dialects-7qpig3ofzdmyi.shellweplayaga.me"    PORT = 4433     TICKET = "ticket{~~~}"else:    HOST = "localhost"    PORT = 8080     os.system("sudo pkill ctf")    os.system("pkill socat")    os.system("socat TCP-LISTEN:8080,reuseaddr,fork EXEC:./ctf,stderr &")p = process("./test")context = ssl.SSLContext(ssl.PROTOCOL_TLS_CLIENT)context.minimum_version = ssl.TLSVersion.TLSv1_3context.maximum_version = ssl.TLSVersion.TLSv1_3context.check_hostname = Falsecontext.verify_mode = ssl.CERT_NONEwith socket.create_connection((HOST, PORT)) as sock:    if args.REMOTE:        sock.recv(15)        sock.send(TICKET.encode() + b"n")    with context.wrap_socket(sock, server_hostname=HOST) as ssock:        key = ssock.recv(8)        ssock.send(b'a'*8        final_key = key + b'a'*8        log.info("final_key: %s", final_key.hex())        p.sendafter(b"key: ", final_key)        p.recvuntil(b"answer: ")        answer = p.recvn(4)        ssock.send(answer)        input_16 = ssock.recv(16)        log.info("input_16: %s", input_16.hex())        p.sendafter(b"input_16: ", input_16)        p.recvuntil(b"answer: ")        answer = p.recvn(16)        ssock.send(answer)        p.recvuntil(b"answer: ")        answer = p.recvn(4)        ssock.send(answer)        input_16 = ssock.recv(16)        log.info("input_16: %s", input_16.hex())        p.sendafter(b"input_16: ", input_16)        p.recvuntil(b"answer: ")        answer = p.recvn(16)        ssock.send(answer)        p.recvuntil(b"answer: ")        answer = p.recvn(256)        ssock.send(answer)        flag = ssock.recv(256)        log.info("flag: %s", flag.decode())        p.interactive()

原文始发于微信公众号(securitainment):DEFCON 33 CTF 解题系列 #3:dialects (pwn)

免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉。
  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2025年5月15日10:28:54
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   DEFCON 33 CTF 解题系列 #3:dialects (pwn)https://cn-sec.com/archives/4060067.html
                  免责声明:文章中涉及的程序(方法)可能带有攻击性,仅供安全研究与教学之用,读者将其信息做其他用途,由读者承担全部法律及连带责任,本站不承担任何法律及连带责任;如有问题可邮件联系(建议使用企业邮箱或有效邮箱,避免邮件被拦截,联系方式见首页),望知悉.

发表评论

匿名网友 填写信息