[Fuzzing101] Exercise 6

Sisyphus·2024년 12월 14일

Fuzzing101

목록 보기
7/7

Target

  • gimp-console
    • GIMP의 강력한 이미지 처리 기능을 명령줄에서 직접 활용할 수 있도록 해주는 소프트웨어
  • CVE-2016-4994
    • app/xcf/xcf-load.c 파일의 xcf_load_image 함수에서 발생한 UAF 취약점입니다.
    • 서비스 거부 혹은 임의 코드 실행이 가능한 취약점입니다.


Build

Make Directory

cd $HOME
mkdir Fuzzing_gimp && cd Fuzzing_gimp

Install the required dependencies

sudo apt-get install build-essential libatk1.0-dev libfontconfig1-dev libcairo2-dev libgudev-1.0-0 libdbus-1-dev libdbus-glib-1-dev libexif-dev libxfixes-dev libgtk2.0-dev python2.7-dev libpango1.0-dev libglib2.0-dev zlib1g-dev intltool libbabl-dev

Build Gegl

wget https://download.gimp.org/pub/gegl/0.2/gegl-0.2.0.tar.bz2
tar xvf gegl-0.2.0.tar.bz2 && cd gegl-0.2.0
sed -i 's/CODEC_CAP_TRUNCATED/AV_CODEC_CAP_TRUNCATED/g' ./operations/external/ff-load.c
sed -i 's/CODEC_FLAG_TRUNCATED/AV_CODEC_FLAG_TRUNCATED/g' ./operations/external/ff-load.c
./configure --enable-debug --disable-glibtest  --without-vala --without-cairo --without-pango --without-pangocairo --without-gdk-pixbuf --without-lensfun --without-libjpeg --without-libpng --without-librsvg --without-openexr --without-sdl --without-libopenraw --without-jasper --without-graphviz --without-lua --without-libavformat --without-libv4l --without-libspiro --without-exiv2 --without-umfpack
make -j$(nproc)
sudo make install

Build GIMP

cd ..
wget https://mirror.klaus-uwe.me/gimp/pub/gimp/v2.8/gimp-2.8.16.tar.bz2
tar xvf gimp-2.8.16.tar.bz2 && cd gimp-2.8.16/
CC=afl-clang-lto CXX=afl-clang-lto++ PKG_CONFIG_PATH=$PKG_CONFIG_PATH:$HOME/Fuzzing_gimp/gegl-0.2.0/ CFLAGS="-fsanitize=address" CXXFLAGS="-fsanitize=address" LDFLAGS="-fsanitize=address" ./configure --disable-gtktest --disable-glibtest --disable-alsatest --disable-nls --without-libtiff --without-libjpeg --without-bzip2 --without-gs --without-libpng --without-libmng --without-libexif --without-aa --without-libxpm --without-webkit --without-librsvg --without-print --without-poppler --without-cairo-pdf --without-gvfs --without-libcurl --without-wmf --without-libjasper --without-alsa --without-gudev --disable-python --enable-gimp-console --without-mac-twain --without-script-fu --without-gudev --without-dbus --disable-mp --without-linux-input --without-xvfb-run --with-gif-compression=none --without-xmc --with-shm=none --enable-debug  --prefix="$HOME/Fuzzing_gimp/gimp-2.8.16/install"
make -j$(nproc)
make install


Persistent mode

persistent.patch - app/xcf/xcf.c

--- ../xcf.c	2014-08-20 08:27:58.000000000 -0700
+++ ./app/xcf/xcf.c	2021-10-11 13:02:42.800831192 -0700
@@ -277,6 +277,10 @@
 
   filename = g_value_get_string (&args->values[1]);
 
+#ifdef __AFL_COMPILER
+  while(__AFL_LOOP(10000)){
+#endif
+
   info.fp = g_fopen (filename, "rb");
 
   if (info.fp)
@@ -366,6 +370,12 @@
   if (success)
     gimp_value_set_image (&return_vals->values[1], image);
 
+#ifdef __AFL_COMPILER
+  }
+#endif
+
+  exit(0);
+
   gimp_unset_busy (gimp);
 
   return return_vals;
patch ./app/xcf/xcf.c -i persistent.patch
  • 퍼징을 할때 마다 프로세스를 재시작하는 것은 오버헤드가 큽니다.
  • 그래서 Persistent Mode에서는 매번 새로운 프로세스를 fork 하는 대신 하나의 프로세스를 재사용합니다.
  • 그리고 프로그램의 특정 부분을 반복적으로 실행하며 퍼징합니다.
  • 이를 통해 퍼징 속도를 10~20배 향상시킬 수 있습니다.


Seed Corpus Creation

mkdir afl_in && cd afl_in
wget https://github.com/antonio-morales/Fuzzing101/blob/main/Exercise%206/SampleInput.xcf


Fuzzing

rm ./install/lib/gimp/2.0/plug-ins/*
ASAN_OPTIONS=detect_leaks=0,abort_on_error=1,symbolize=0 afl-fuzz -i './afl_in' -o './afl_out' -D -t 100 -- ./install/bin/gimp-console-2.8 --verbose -d -f @@
rm ./install/lib/gimp/2.0/plug-ins/*

  • 퍼저를 계속 돌려놨는데도, 취약점은 찾지 못했습니다.

| Bug 767873 – Multiple Use-After-Free when parsing XCF channel and layer properties

  • 그래서 위 사이트에서 크래시 파일을 얻어 분석을 진행하게 되었습니다.


Root Cause Analysis

Call Stack

**pwndbg> bt
#0  0x00007ffff6c50295 in  () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#1  0x00007ffff6c51579 in g_logv () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#2  0x00007ffff6c51743 in g_log () at /lib/x86_64-linux-gnu/libglib-2.0.so.0
#3  0x00005555558e9687 in gimp_image_set_active_layer (image=0x6250001375e0, layer=0x625000139270) at gimpimage.c:3410
#4  0x00005555556ce986 in xcf_load_image (gimp=0x629000006030, info=0x7fffffffc870, error=0x7fffffffd290) at xcf-load.c:373
#5  0x00005555556ccf21 in xcf_load_invoker (procedure=0x625000006180, gimp=0x629000006030, context=0x625000137280, progress=0x0, args=0x619000487300, error=0x7fffffffd290) at xcf.c:335
#6  0x00005555557a91df in gimp_procedure_real_execute (procedure=0x625000006180, gimp=0x629000006030, context=0x625000137280, progress=0x0, args=0x619000487300, error=0x7fffffffd290) at gimpprocedure.c:169
#7  0x00005555557ca99a in gimp_plug_in_procedure_execute (procedure=0x625000006180, gimp=0x629000006030, context=0x625000137280, progress=0x0, args=0x619000487300, error=0x7fffffffd290) at gimppluginprocedure.c:204
#8  0x00005555557aa5b7 in gimp_procedure_execute (procedure=0x625000006180, gimp=0x629000006030, context=0x625000137280, progress=0x0, args=0x619000487300, error=0x7fffffffd290) at gimpprocedure.c:331
#9  0x000055555579e403 in gimp_pdb_execute_procedure_by_name_args (pdb=0x61d000005420, context=0x62500000d230, progress=0x0, error=0x7fffffffd290, name=0x555555af64a0 "gimp-xcf-load", args=0x619000487300) at gimppdb.c:331
#10 0x000055555579f8ba in gimp_pdb_execute_procedure_by_name (pdb=0x61d000005420, context=0x62500000d230, progress=0x0, error=0x7fffffffd290, name=0x555555af64a0 "gimp-xcf-load") at gimppdb.c:459
#11 0x00005555559d2cc7 in file_open_image (gimp=0x629000006030, context=0x62500000d230, progress=0x0, uri=0x60600008fa20 "file:///home/ion/Fuzzing_gimp/gimp-2.8.16/crash/uaf.xcf", entered_filename=0x60600008fa20 "file:///home/ion/Fuzzing_gimp/gimp-2.8.16/crash/uaf.xcf", as_new=0, file_proc=0x625000006180, run_mode=GIMP_RUN_INTERACTIVE, status=0x7fffffffd280, mime_type=0x7fffffffd120, error=0x7fffffffd290) at file-open.c:158
#12 0x00005555559d453d in file_open_with_proc_and_display (gimp=0x629000006030, context=0x62500000d230, progress=0x0, uri=0x60600008fa20 "file:///home/ion/Fuzzing_gimp/gimp-2.8.16/crash/uaf.xcf", entered_filename=0x60600008fa20 "file:///home/ion/Fuzzing_gimp/gimp-2.8.16/crash/uaf.xcf", as_new=0, file_proc=0x0, status=0x7fffffffd280, error=0x7fffffffd290) at file-open.c:396
#13 0x00005555559d40ce in file_open_with_display (gimp=0x629000006030, context=0x62500000d230, progress=0x0, uri=0x60600008fa20 "file:///home/ion/Fuzzing_gimp/gimp-2.8.16/crash/uaf.xcf", as_new=0, status=0x7fffffffd280, error=0x7fffffffd290) at file-open.c:372
#14 0x00005555559d5545 in file_open_from_command_line (gimp=0x629000006030, filename=0x6060000015e0 "/home/ion/Fuzzing_gimp/gimp-2.8.16/crash/uaf.xcf", as_new=0) at file-open.c:575
#15 0x00005555556c6bf5 in app_run (full_prog_name=0x606000001520 "/home/ion/Fuzzing_gimp/gimp-2.8.16/debug/bin/gimp-console-2.8", filenames=0x602000001470, alternate_system_gimprc=0x0, alternate_gimprc=0x0, session_name=0x0, batch_interpreter=0x0, batch_commands=0x0, as_new=0, no_interface=1, no_data=0, no_fonts=0, no_splash=0, be_verbose=0, use_shm=0, use_cpu_accel=1, console_messages=0, use_debug_handler=0, stack_trace_mode=GIMP_STACK_TRACE_NEVER, pdb_compat_mode=GIMP_PDB_COMPAT_ON) at app.c:253
#16 0x00005555556cb961 in main (argc=2, argv=0x603000000a00) at main.c:488
#17 0x00007ffff68b6083 in __libc_start_main (main=0x5555556cb2c1 <main>, argc=2, argv=0x7fffffffd6d8, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffd6c8) at ../csu/libc-start.c:308
#18 0x00005555556c65fe in _start ()**
  • CVE 정보에 따르면 xcf_load_image 함수에서 취약점이 발생했습니다.

(gimp-console-2.8:3017): Gimp-Core-[1;35mCRITICAL[0m **: [34m02:29:03.282[0m: gimp_image_set_active_layer: assertion 'layer == NULL || GIMP_IS_LAYER (layer)' failed
  • 에러 메시지를 보면 layer 변수에서 문제가 발생한거 같습니다.

GimpLayer *
gimp_image_set_active_layer (GimpImage *image,
                             GimpLayer *layer)
{
  GimpImagePrivate *private;
  GimpLayer        *floating_sel;
  GimpLayer        *active_layer;

  g_return_val_if_fail (GIMP_IS_IMAGE (image), NULL);
  g_return_val_if_fail (layer == NULL || GIMP_IS_LAYER (layer), NULL);
  g_return_val_if_fail (layer == NULL ||
                        (gimp_item_is_attached (GIMP_ITEM (layer)) &&
                         gimp_item_get_image (GIMP_ITEM (layer)) == image),
                        NULL);
  • gimp_image_set_active_layer 함수를 봐보면 에러 메시지와 동일한 코드가 보입니다.

  if (info->active_layer)
    gimp_image_set_active_layer (image, info->active_layer);
  • 함수 호출 부분을 봐보면 layer 변수 자리에 info->active_layer 가 전달되는데, 해당 변수에서 UAF 취약점이 발생한거 같습니다.

➡️ 해당 변수를 참고해서 UAF 취약점 분석을 하려고 했는데, ASAN 로그가 없어서 분석이 쉽지 않았습니다. 그래서 취약점에 대한 상세한 설명을 참고하여 분석을 진행하였습니다.


Vulnerability Description

Created attachment 330078 [details] [review]
Patch

The properties PROP_ACTIVE_LAYER, PROP_FLOATING_SELECTION, PROP_ACTIVE_CHANNEL saves the current object pointer the @info structure. Others like PROP_SELECTION (for channel) and PROP_GROUP_ITEM (for layer) will delete the current object and create a new object, leaving the pointers in @info invalid (dangling).

Therefore, if a property from the first type will come before the second, the result will be an UaF in the last lines of xcf_load_image (when it actually using the pointers from @info).

I wasn't able to exploit this bug because that g_type_instance->c_class gets cleared by the last g_object_unref and GIMP_IS_{LAYER,CHANNEL} detects that and return FALSE. It isn't possible to put a fake a GTypeInstanceClass and put it instead because that the GType is kind of random (based on the address of the GTypeInstanceClass which is the return value of g_malloc).

I have attached a patch that should fix that and a XCF file that will trigger this bug.

Example for running gimp_uaf.xcf on master:
$ gimp Gimp_UaF.xcf
This is a development version of GIMP.  Debug messages may appear here.
...
(gimp:18010): Gimp-Core-CRITICAL **: gimp_image_set_active_layer: assertion 'layer == NULL || GIMP_IS_LAYER (layer)' failed
  • 에러 메시지를 보면 layer 에서 취약점이 발생했습니다.
  • 그래서 layer 부분 설명을 참고하면 될거 같습니다.
  • PROP_ACTIVE_LAYER 에서 info 구조체에 현재 객체 포인터를 저장하고, PROP_GROUP_ITEM 에서 현재 객체를 제거하고 새로운 객체를 생성합니다.
  • 이로 인해 info 구조체에 저장된 layer 객체가 dangling pointer가 됩니다.
  • 이후 gimp_image_set_active_layer 함수 호출시 인자로 info->active_layer 가 전달되게 되는데, 이때 UAF 취약점이 발생합니다.

Flow Graph


xcfInfo

struct _XcfInfo
{
  Gimp               *gimp;
  GimpProgress       *progress;
  FILE               *fp;
  guint               cp;
  const gchar        *filename;
  GimpTattoo          tattoo_state;
  GimpLayer          *active_layer;
  GimpChannel        *active_channel;
  GimpDrawable       *floating_sel_drawable;
  GimpLayer          *floating_sel;
  guint               floating_sel_offset;
  gint                swap_num;
  gint               *ref_count;
  XcfCompressionType  compression;
  gint                file_version;
};

PROP_ACTIVE_LAYER

        case PROP_ACTIVE_LAYER:
          info->active_layer = *layer;
          break;
  • info->active_layer 에 현재 layer 저장

PROP_GROUP_ITEM

case PROP_GROUP_ITEM:
  {
    GimpLayer *group;

    // 새로운 그룹 레이어 생성
    group = gimp_group_layer_new (image);

    // 새 그룹 레이어의 이름을 기존 레이어의 이름으로 설정
    gimp_object_set_name (GIMP_OBJECT (group),
                          gimp_object_get_name (*layer));

    // 새 그룹 레이어의 타입을 기존 레이어의 타입으로 설정
    GIMP_DRAWABLE (group)->private->type =
      gimp_drawable_type (GIMP_DRAWABLE (*layer));

    // 기존 레이어의 참조 카운트 증가
    g_object_ref_sink (*layer);
    // 기존 레이어의 참조 해제
    g_object_unref (*layer);
    // 레이어 포인터를 새 그룹 레이어로 업데이트
    *layer = group;
  }
  break;
  • 새로운 레이어를 생성하고 기존 레이어 제거
  • 새로운 레이어로 layer 를 초기화
  • 이때 info->active_layer 는 dangling pointer가 됩니다.

xcf_load_image

  if (info->active_layer)
    gimp_image_set_active_layer (image, info->active_layer);
  • 함수 호출 인자로 info->active_layer 가 전달되는데, 해당 변수는 dangling pointer 이기 때문에 UAF 취약점 발생


Debugging

위의 과정이 이루어지는 과정을 디버깅을 통해 살펴보겠습니다.

Environment

이를 위해 디버깅용 환경변수를 설정해줘야 합니다.

  • G_DEBUG=fatal-warnings 라는 환경변수를 추가해줍니다.

Break Point

  • 위에서 살펴봤던 코드들에 break point를 걸어줍니다.

xcf_load_image

  • xcf_load_layer 함수로 레이어를 로드하기 전에는 layer 변수 값이 0x0 입니다.

xcf_load_layer

  • gimp_layer_new 함수로 새로운 레이어를 생성하여 layer 변수에 대입해줬기 때문에, layer 변수 값이 세팅되었습니다.
  • 다음으로 xcf_load_layer_props 함수에서 레이어 프로퍼티를 읽어오게 됩니다.

xcf_load_layer_props

PROP_ACTIVE_LAYER

  • 0x0 이었던 info->active_layer 값이

  • layer 값으로 세팅됩니다.

PROP_GROUP_ITEM

이후 layerinfo->active_layer 값을 살펴보면

  • layer 변수에는 새로 생성된 레이어 값이 들어가 있습니다.
  • 반면 info->active_layer 값은 그대로 입니다.
  • 이로 인해 해당 포인터가 dangling pointer가 되었습니다.

xcf_load_image

  • 이후 gimp_image_set_active_layer(image, info->active_layer) 에서 dnagling pointer에 접근하게 되어 UAF 취약점이 발생합니다.

gimp_image_set_active_layer

  • 이후 g_return_val_if_fail (layer == NULL || GIMP_IS_LAYER (layer), NULL); 코드 부분이 실행되면
  • 에러 메시지가 출력되며 프로그램이 중단되게 됩니다.


Patch

새로운 레이어 생성 후 layer 변수 값을 초기화할 때, info->active_layer 도 dangling pointer가 되지 않도록 NULL 로 초기화 해줘야 합니다.

        case PROP_GROUP_ITEM:
          {
            GimpLayer *group;
            gboolean   is_active_layer;

            /* We're going to delete *layer, Don't leave its pointers
             * in @info.  After that, we'll restore them back with the
             * new pointer. See bug #767873.
             */
            is_active_layer = (*layer == info->active_layer);
            if (is_active_layer)
              info->active_layer = NULL;

            if (*layer == info->floating_sel)
              info->floating_sel = NULL;

            group = gimp_group_layer_new (image);

            g_object_ref_sink (*layer);
            g_object_unref (*layer);
            *layer = group;

            if (is_active_layer)
              info->active_layer = *layer;

            /* Don't restore info->floating_sel because group layers
             * can't be floating selections
             */
          }
          break;

          {
            GimpChannel *mask;

            /* We're going to delete *channel, Don't leave its pointer
             * in @info. See bug #767873.
             */
            if (*channel == info->active_channel)
              info->active_channel = NULL;

            mask =
              gimp_selection_new (image,
                                  gimp_item_get_width  (GIMP_ITEM (*channel)),
            *channel = mask;
            (*channel)->boundary_known = FALSE;
            (*channel)->bounds_known   = FALSE;

            /* Don't restore info->active_channel because the
             * selection can't be the active channel
             */
          }
          break;

| Bug 767873 - (CVE-2016-4994) Multiple Use-After-Free when parsing...

0개의 댓글