【翻译】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 签名文件...
加载签名文件
现在我们已经恢复了 libc 的符号!
另一个库的签名是由我的队友 howdays
使用 lib{ssl,crypto}.so 获取的。看起来效果更好了!
那么如何获取 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
函数中。
-
程序执行流程依赖于获取 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, 8, 0);
if ( result == 8 )
{
SSL_write(a1, (__int64)&key, 8);
SSL_read(a1, (__int64)&key + 8, 8);
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, 16, 0);
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, 32, 0);
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, 16, 0);
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 算法。
由于这是一个静态链接的二进制文件,只要正确映射地址,就可以直接使用其中的函数。
以下是一个示例。
void *(*SHA512)(void *, int, int) = (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, -1, 0);
if (prob_data_base == MAP_FAILED)
fatal("mmap");
memcpy((char *)prob_data_base + 0x1000, (char *)prob_base + 0x468000, 0x6558);
close(fd);
return prob_base;
}
voidtest(void)
{
char *sha512_out;
char hash_input[0x20];
memset(hash_input, 'A', 0x20);
sha512_out = SHA512(hash_input, 0x20, 0);
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 地址处填充正确的值。
而这个正确的值应该是环境变量指针数组(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;
// ...
}
现在一切运行正常!
./test
0x000000: 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
-
使用从问题服务器获取的密钥对
x02x00x00x00
进行 sm4_decrypt,从而将执行流程切换到 2-2。 -
通过暴力破解找到使 sm3_digest(N) 结果为
x00x00x00x??...
的 N 值,然后对该值进行 sm4_decrypt 来解决 2-2。
解决 2-1
-
使用从问题服务器获取的密钥对
x01x00x00x00
进行 sm4_decrypt,从而将执行流程切换到 2-1。 -
通过暴力破解找到使 SHA512(N) 结果为
x8dx36x00x??...
的 N 值,然后对该值进行 sm4_decrypt 来解决 2-1。(因为 byte_6E1000 是x8dx36x00...
)
这是我使用的完整代码
// gcc -o test test.c
void *(*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 *, int, int) = (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, -1, 0);
if (prob_data_base == MAP_FAILED)
fatal("mmap");
memcpy((char *)prob_data_base + 0x1000, (char *)prob_base + 0x468000, 0x6558);
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, 0, sizeof(iv));
EVP_DecryptInit(cipher_ctx, EVP_sm4_ctr(), key, iv);
memset(ciphertext, 0, sizeof(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, 0, sizeof(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, 0, sizeof(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, 0, sizeof(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, 0, sizeof(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, 0x20, 0);
if (!memcmp(sha512_out, "x8dx36x00", 3))
break;
i++;
}
printf("hash_input: %ldn", i);
memset(ciphertext, 0, sizeof(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, 0, sizeof(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, 0x20, 0);
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 socket
import ssl
from pwn import *
import os
context.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_3
context.maximum_version = ssl.TLSVersion.TLSv1_3
context.check_hostname = False
context.verify_mode = ssl.CERT_NONE
with 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)
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论