虽然题目hint给了对应的论文,但其实看不看问题都不大,本质上就是MD5的爆破。先从程序逻辑开始看:
undefined8 main(void)
{
size_t len;
void *enc;
undefined8 *code;
long in_FS_OFFSET;
int i;
int j;
int arr [4];
undefined8 fin;
undefined8 local_d0;
char salt [33];
char input [65];
char plain [72];
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
setbuf(stdout,(char *)0x0);
salt._0_8_ = 0x57456a4d614c7047;
salt._8_8_ = 0x6b6d6e6e6a4f5670;
salt._16_8_ = 0x367064656c694752;
salt._24_8_ = 0x736c787a6563764d;
salt[32] = '\0';
arr[0] = 8;
arr[1] = 2;
arr[2] = 7;
arr[3] = 1;
memset(input + 1,0,0x40);
memset(plain,0,0x40);
printf("Password: ");
fgets(input + 1,0x40,stdin);
len = strlen(input + 1);
input[len] = '\0';
for (i = 0; i < 4; i = i + 1) {
strncat(plain,input + (long)(i << 2) + 1,4);
strncat(plain,salt + (i << 3),8);
}
enc = malloc(0x40);
len = strlen(plain);
MD5(enc,plain,(int)len);
for (i = 0; i < 4; i = i + 1) {
for (j = 0; j < 4; j = j + 1) {
*(undefined *)((long)&fin + (long)(j * 4 + i)) =
*(undefined *)((long)enc + (long)(arr[j] + j * 0x10 + i));
}
}
code = (undefined8 *)mmap((void *)0x0,0x10,7,0x22,-1,0);
*code = fin;
code[1] = local_d0;
(*(code *)code)(FUN_0010102b);
free(enc);
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}
void MD5(void *enc,void *plain,int len)
{
int blocks;
uint uVar1;
int iVar2;
long in_FS_OFFSET;
void *plain_block;
int i;
int j;
int blocksize;
MD5_CTX context;
uchar enc_block [24];
long canary;
canary = *(long *)(in_FS_OFFSET + 0x28);
if (len % 12 == 0) {
blocks = len / 12;
}
else {
blocks = len / 12 + 1;
}
plain_block = plain;
for (i = 0; i < blocks; i = i + 1) {
blocksize = 0xc;
if ((i == blocks + -1) && (len % 12 != 0)) {
blocksize = blocks % 0xc;
}
MD5_Init(&context);
MD5_Update(&context,plain_block,(long)blocksize);
plain_block = (void *)((long)plain_block + (long)blocksize);
MD5_Final(enc_block,&context);
for (j = 0; j < 16; j = j + 1) {
iVar2 = i * 16 + j;
uVar1 = (uint)(iVar2 >> 31) >> 26;
*(uchar *)((long)(int)((iVar2 + uVar1 & 63) - uVar1) + (long)enc) = enc_block[j];
}
}
if (canary != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
input只有前16字节会用作运算,从input到enc的逻辑梳理后为:
MD5函数实际上就是分块求plain的MD5值再拼接在一起,而fin是enc的一个hash,简单验证后可以发现就是提取出了每个MD5块的特定位置上的字节:
enc = [i for i in range(64)]
fin = [0] * 16
arr = [8, 2, 7, 1]
for i in range(4):
for j in range(4):
fin[i + 4 * j] = enc[i + 16 * j + arr[j]]
# fin = [8, 9, 10, 11, 18, 19, 20, 21, 39, 40, 41, 42, 49, 50, 51, 52]
而fin在最后作汇编码直接执行,且第一个参数为函数指针,函数体为:
void FUN_0010102b(long key)
{
FILE *__stream;
long in_FS_OFFSET;
char local_98 [136];
long local_10;
local_10 = *(long *)(in_FS_OFFSET + 0x28);
if (key == 0x7b3dc26f1) {
__stream = fopen("flag","r");
if (__stream == (FILE *)0x0) {
puts("Flag file not found. Contact an admin.");
/* WARNING: Subroutine does not return */
exit(1);
}
fgets(local_98,0x80,__stream);
puts(local_98);
}
else {
puts("Hmmmmmm... not quite");
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}
这意味我们需要调用这一函数并将第一个参数改为0x7b3dc26f1,再结合这一程序的调用约定,可以写出shellcode:
mov rsi, rdi
mov rdi, 0x7b3dc26f1
call rsi
利用pwntools的asm可以得到最终的shellcode,剩下的事情只需要爆破MD5就可以了,需要注意的是shellcode只有15字节,所以最后一字节可以不做限制,最终的exp为:
from hashlib import md5
from pwn import *
enc = [i for i in range(64)]
fin = [0] * 16
arr = [8, 2, 7, 1]
for i in range(4):
for j in range(4):
fin[i + 4 * j] = enc[i + 16 * j + arr[j]]
for i in range(len(fin)):
fin[i] %= 16
# fin = [8, 9, 10, 11, 2, 3, 4, 5, 7, 8, 9, 10, 1, 2, 3, 4]
context.arch = "amd64"
shellcode = asm(
"""
mov rsi, rdi
mov rdi, 0x7b3dc26f1
call rsi
"""
)
shellcode += b"\x90"
salt = [b"GpLaMjEW", b"pVOjnnmk", b"RGiledp6", b"Mvcezxls"]
for i in range(4):
for a in range(33, 123, 1):
for b in range(33, 123, 1):
for c in range(33, 123, 1):
for d in range(33, 123, 1):
enc_block = (
a.to_bytes(1, "big")
+ b.to_bytes(1, "big")
+ c.to_bytes(1, "big")
+ d.to_bytes(1, "big")
)
md5_sum = md5(enc_block + salt[i]).digest()
match = 0
# print(enc_block)
if i != 3:
for idx, pos in enumerate(fin[i * 4 : i * 4 + 4]):
if md5_sum[pos] == shellcode[idx + i * 4]:
match += 1
if match == 4:
print("The {}: {}".format(i, enc_block))
else:
for idx, pos in enumerate(fin[i * 4 : i * 4 + 3]):
if md5_sum[pos] == shellcode[idx + i * 4]:
match += 1
if match == 3:
print("The {}: {}".format(i, enc_block))
最终运行结果为:
zyd@ubuntu:~$ python test.py
The 0: b'D1v1'
The 1: b'd3An'
The 2: b'dC0n'
The 3: b'\\rpB'
The 3: b'qu3r'
exp只选择了ascii的可打印字符作为字典集,最终D1v1d3AndC0nqu3r
或D1v1d3AndC0n\rpB
就是需要的input。其实更有意思的部分是如何由任意leet style的文本的MD5提取出所需要的序列,即这一题的构造方法。