Zer0 day

[Plaid 2014] pwnable : tenement & sass 본문

CTFs/Plaid 2014

[Plaid 2014] pwnable : tenement & sass

Zero Day 2016.08.27 16:38

These write ups don't include any exploit codes but just EIP control.


tenement

Given binary 'tenement' is x86 stripped elf file, too.

1
2
zero@ubuntu:~/Desktop/ctf/plaid2014$ file tenement
tenement: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2for GNU/Linux 2.6.32, stripped
cs

And applied memory protections are...

1
2
3
4
5
6
7
8
zero@ubuntu:~/Desktop/ctf/plaid2014$ gdb -q ./tenement
Reading symbols from ./tenement...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : disabled
NX        : disabled
PIE       : disabled
RELRO     : disabled
cs

None!

But when i just execute binary, following error appears..

1
2
zero@ubuntu:~/Desktop/ctf/plaid2014$ ./tenement
./tenement: error while loading shared libraries: libseccomp.so.2: cannot open shared object file: No such file or directory
cs

Above lib error could be resolved with installing...


- libseccomp : libseccomp2_2.1.0+dfsg-1_i386.deb

- libjansson : libjansson4_2.2.1-1_386.deb


After installing all of them, we can see the following message when trying to run it.

1
2
zero@ubuntu:~/Desktop/ctf/plaid2014$ ./tenement
USAGE: ./tenement CONFIG
cs

Does It mean 'CONFIG' file is needed?

After some inspections, i can find about CONFIG and kind of AUTH.


First, Load a CONFIG file and it needs to be 'json' type.

Second,  parsing elements string 'key's value and integer 'addrs's value.

Especially, 'addrs's value is used when allocating memory with mmap(). ( size is 0x4000 )

1
2
3
4
5
6
7
8
9
10
11
12
  v1 = json_load_file(CONFIG, 0&v23);
  v20 = v1;
  if ( v1 )
  {
    v4 = json_object_get(v1, &key, v2, v2);
...
          if ( snprintf((char *)v9, v8, "%s%s""pppp: ", v5) >= 0 )
          {
            sub_8048A4D();
            for ( i = 0; i <= 3++i )
              *((_BYTE *)ptr + i) -= 32;
            v10 = json_object_get(v20, "addrs", i, i);
cs

And we could see the flag format from above ( pppp:'flag' )

Then all we have to do is making a shellcode which works as finding 'pppp' in memory,


There is stack overflow bug at 0x8048dc1. just simply found..

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
.text:08048DC1 vuln            proc near               ; CODE XREF: main+66p
.text:08048DC1
.text:08048DC1 buf             = byte ptr -88h
.text:08048DC1 var_4           = dword ptr -4
.text:08048DC1
.text:08048DC1                 push    ebp
.text:08048DC2                 xor     eax, eax
.text:08048DC4                 mov     ebp, esp
.text:08048DC6                 or      ecx, 0FFFFFFFFh
.text:08048DC9                 push    edi
.text:08048DCA                 sub     esp, 88h
.text:08048DD0                 mov     edx, buf
.text:08048DD6                 mov     edi, edx
.text:08048DD8                 repne scasb
.text:08048DDA                 lea     edi, [ebp+buf]
.text:08048DE0                 not     ecx
.text:08048DE2                 dec     ecx
.text:08048DE3                 push    ecx             ; n
.text:08048DE4                 push    edx             ; buf
.text:08048DE5                 push    1               ; fd
.text:08048DE7                 call    _write
.text:08048DEC                 add     esp, 0Ch
.text:08048DEF                 push    128             ; nbytes
.text:08048DF4                 push    edi             ; buf
.text:08048DF5                 push    0               ; fd
.text:08048DF7                 call    _read
.text:08048DFC                 call    edi
.text:08048DFE                 add     esp, 10h
.text:08048E01                 mov     edi, [ebp+var_4]
.text:08048E04                 leave
.text:08048E05                 retn
cs

We have 128 bytes to write on stdin.


Final exploit is..

1. making a shellcode finding 'pppp' on memory. Of course its size has to smaler than 128 bytes.

-> The reason finding 'pppp' on memory is next to string 'pppp', there is 'FLAG'

2. just send it...


------------  Skip below progress because i don't know exact server environment,,,


sass

Given file is x86 elf binary.

1
2
zero@ubuntu:~/Desktop/ctf/plaid2014$ file ./sass
./sass: ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2for GNU/Linux 2.6.24, not stripped
cs

Applied memory protections are..

1
2
3
4
5
6
7
8
zero@ubuntu:~/Desktop/ctf/plaid2014$ gdb -q ./sass
Reading symbols from ./sass...(no debugging symbols found)...done.
gdb-peda$ checksec
CANARY    : disabled
FORTIFY   : ENABLED
NX        : ENABLED
PIE       : ENABLED
RELRO     : Partial
cs

There are PIE, NX, FORTIFY but canary. Also ASLR is full on my env.


This binary only has 2 functions, main() and read_numbers().

In main(),

read numbers from stdin with read_numbers(). then, qsort() ( qucik sort ) and print all of them.


In read_numbers func(),

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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
int __cdecl read_numbers(int a1)
{
  int v1; // ebp@1
  char v2; // di@1
  int res; // esi@1
  ssize_t input_size; // eax@2
  char *start; // edx@3
  int buf[i]; // eax@3
  char *end// [sp+14h] [bp-1028h]@3
  char v9; // [sp+1Ah] [bp-1022h]@1
  char v10; // [sp+1Bh] [bp-1021h]@1
  char v11[4124]; // [sp+20h] [bp-101Ch]@2
 
  v1 = 0;
  v2 = 0;
  res = 0;
  v10 = 0;
  v9 = 0;
LABEL_2:
  input_size = read(0, v11, 4096u);
  if ( input_size <= 0 )
    __assert_fail("bytesread > 0""sass.c", 0x1Bu, "read_numbers");
  start = v11;
  end = &v11[input_size];
  buf[i] = v11[0];
  if ( v11[0> '9' )
    goto LABEL_13;
  while ( 1 )
  {
    if ( buf[i] >= 48 )
    {
      res = res * (v9 < 1u ? 10 : 16+ buf[i] - 48;
      goto LABEL_10;
    }
    if ( buf[i] != 10 )
    {
      if ( buf[i] <= 10 )
      {
        if ( buf[i] != 9 )
          goto LABEL_28;
      }
      else if ( buf[i] != 32 )
      {
        if ( buf[i] == 45 )
        {
          v10 = 1;
          goto LABEL_10;
        }
LABEL_28:
        __assert_fail("!\"Bad input.\"""sass.c", 0x3Cu, "read_numbers");
      }
      goto LABEL_19;
    }
    if ( v2 )
      return v1;
    v2 = 1;
LABEL_19:
    v9 = 0;
    if ( v10 )
      res = -res;
    *(a1 + 4 * v1+++= res;
    res = 0;
    if ( buf[i] != 10 )
      goto LABEL_10;
    while ( 2 )
    {
      if ( ++start == end )
        goto LABEL_2;
      buf[i] = *start;
      if ( buf[i] > '9' )
      {
LABEL_13:
        if ( buf[i] > 'f' )
        {
          if ( buf[i] != 'x' )
            goto LABEL_28;
          v9 = 1;
        }
        else if ( buf[i] >= 'a' )
        {
          res = 16 * res + buf[i] - 'W';
        }
        else
        {
          if ( (buf[i] - 'A'> 5u )
            goto LABEL_28;
          res = 16 * res + buf[i] - '7';
        }
LABEL_10:
        v2 = 0;
        continue;
      }
      break;
    }
  }
}
 
// Hey-rayed codes are so dirty. Let's beautify that codes.
 
int read_numbers(int *num) {
    int cnt = 0;
    int number = 0;
    bool IsHex = false// Check is there 'x' in '0x????'
    bool IsNewline = false// Check '\n'
    bool IsNegative = false// Check is number negative
    char buff[4096= {0, };
 
    for(;;) {
        int size = read(0, buf, 4096);
        assert(size > 0);
        for(int i = 0; i < size++i) {
            switch(buf[i]) {
            case '0'~'9': number *= IsHex ? 16 : 10 + buf[i] - '0'break;
            case 'A'~'F': number *= 16; number += buf[i] - 'A' + 10break;
            case 'a'~'f': number *= 16; number += buf[i] - 'a' + 10break;
            case 'x': IsHex = truebreak;
            case '\n':
                if(IsNewline) return cnt;
                else IsNewline = truebreak;
            case '-': IsNegative = truebreak;
            case ' '~'\t'
                IsHex= false;
                num[cnt] += IsNegative ? -number : number;
                number = 0;
                ++cnt; break;
            default: assert("!Bad input."); break;
            }
            if(buf[i] != '\n') IsNewline = false;
        }
    }
}
// Actually there is no '~'  grammar in switch~case but i use for convince :). 
cs

Just works as really reading number. if number is hex format, chagne to dec type.

The problem is... i think there is no leaking verctor. Even as i know, specific libc version isn't given too.

which means, this chal may be solved on server's local env and ALSR would be partialy(?) disabled.


Anyway, let's find exploit vector to overwrite ret with system() for ROP or RTL, etc..

Then i found there is buffer overflow at 40s input and control EIP successfully.

1
2
3
4
5
6
zero@ubuntu:~/Desktop/ctf/plaid2014$ gdb -q ./sass core
Reading symbols from ./sass...(no debugging symbols found)...done.
[New LWP 7599]
Core was generated by `./sass'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x41414141 in ?? ()
cs

But my env ( ubuntu 16.04 ) is ASLR-FULL and i can't find any leaking vector on binary.

Which means exploit would be proceeded unless ALSR is disabled. ( i can disable ASLR but i didn't do it :) for my laziness ...)

Despite disabling ASLR, kind of brute-force is needed because libc base address is changed by PIE.

Anyway, after disabling ASLR, RTL would be worked. Try it yourself!

'CTFs > Plaid 2014' 카테고리의 다른 글

[Plaid 2014] pwnable : tenement & sass  (0) 2016.08.27
[Plaid 2014] pwnable : kappa  (0) 2016.08.27
[Plaid 2014] pwnable : ezhp  (0) 2016.08.27
[Plaid 2014] reversing : hudak  (0) 2016.08.27
[Plaid 2014] forensic : zfs  (0) 2016.08.27
[Plaid 2014] forensic : rsa  (0) 2016.08.27
0 Comments
댓글쓰기 폼