Points: 281 | Solves: 36/1035 | Category: Reverse
And you lost a key again. This time it's the key to your missiles command station. However, the command station got a silent password override. Even though your memory isn't that good, you don't remember the password either, your technical skills are!. You've already dumped the binary, which checks the password. Now you just have to reverse it!
The binary is elf32-i386
. We have difficulty doing static analysis because the .text
segment is not disassembled properly and it seems that the machine instructions are pretty random.
We decided to try to do dynamic analysis. The execution of the binary doesn't show clear behavior because there is no interaction whatsoever. Execution using strace shows that the program exited with code 1.
$ strace ./forgetful_commander
...
open("/proc/self/maps", O_RDONLY) = 3
read(3, "5660d000-5660f000 r--p 00000000 "..., 16384) = 1528
close(3) = 0
exit_group(1) = ?
+++ exited with 1 ++
The binary is compiled as PIE (Independent Position-Executables). We try to use gdb
and set breakpoint
to the .text
address in the hope that the correct instructions will be executed.
$ gdb ./forgetful_commander
...
gdb-peda$ b *0x0
Breakpoint 1 at 0x0
gdb-peda$ r
Starting program: /vagrant/forgetful_commander
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x0
gdb-peda$ del 1
gdb-peda$ info files
Symbols from "/vagrant/forgetful_commander".
Native process:
Using the running image of child process 28513.
While running this, GDB does not access memory from...
Local exec file:
`/vagrant/forgetful_commander', file type elf32-i386.
warning: Cannot find section for the entry point of /vagrant/forgetful_commander.
Entry point: 0xd000
0x56556194 - 0x565561a7 is .interp
0x565561a8 - 0x565561c8 is .note.ABI-tag
0x565561c8 - 0x565561fc is .hash
0x565561fc - 0x5655621c is .gnu.hash
0x5655621c - 0x5655629c is .dynsym
0x5655629c - 0x56556339 is .dynstr
0x5655633a - 0x5655634a is .gnu.version
0x5655634c - 0x5655637c is .gnu.version_r
0x5655637c - 0x565563bc is .rel.dyn
0x565563bc - 0x565563cc is .rel.plt
0x56557000 - 0x56557020 is .init
0x56557020 - 0x56557050 is .plt
0x56557050 - 0x56557321 is .text
0x56557324 - 0x56557338 is .fini
0x56558000 - 0x5655842c is .rodata
0x5655842c - 0x56558450 is .eh_frame_hdr
0x56558450 - 0x565584f0 is .eh_frame
0x56559eec - 0x56559ef0 is .init_array
0x56559ef0 - 0x56559ef4 is .fini_array
0x56559ef4 - 0x56559fec is .dynamic
0x56559fec - 0x5655a000 is .got
0x5655a000 - 0x5655a014 is .got.plt
0x5655a014 - 0x5655a01c is .data
0x5655a01c - 0x5655a020 is .bss
gdb-peda$ x/10i 0x56557050
0x56557050: cdq
0x56557051: addr16 int 0x30
0x56557054: movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
0x56557055: add bh,ah
0x56557057: add ebp,ebx
0x56557059: xchg ecx,eax
0x5655705a: into
0x5655705b: ins DWORD PTR es:[edi],dx
0x5655705c: xor al,0x2c
0x5655705e: daa
gdb-peda$ b *0x56557050
Breakpoint 2 at 0x56557050
gdb-peda$ c
Continuing.
...
Breakpoint 2, 0x56557050 in ?? ()
gdb-peda$ s
...
gdb-peda$ s
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xf7ffd918 --> 0x56555000 --> 0x464c457f
EBX: 0xf7ffd000 --> 0x23f3c
ECX: 0xffffd694 --> 0xffffd7c3 ("/vagrant/forgetful_commander")
EDX: 0xffffffff
ESI: 0xffffd69c --> 0xffffd7de ("XDG_SESSION_ID=221")
EDI: 0x56562000 --> 0x819c6050
EBP: 0x0
ESP: 0xffffd690 --> 0x1
EIP: 0x56557051 --> 0xb4895eed
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x56557046: push 0x8
0x5655704b: jmp 0x56557020
0x56557050: cdq
=> 0x56557051: in eax,dx
0x56557052: pop esi
0x56557053: mov DWORD PTR [ebx+eax*4+0x5405f0e4],esi
0x5655705a: push edx
0x5655705b: call 0x565570d7
[------------------------------------stack-------------------------------------]
0000| 0xffffd690 --> 0x1
0004| 0xffffd694 --> 0xffffd7c3 ("/vagrant/forgetful_commander")
0008| 0xffffd698 --> 0x0
0012| 0xffffd69c --> 0xffffd7de ("XDG_SESSION_ID=221")
0016| 0xffffd6a0 --> 0xffffd7f1 ("SHELL=/bin/bash")
0020| 0xffffd6a4 --> 0xffffd801 ("TERM=xterm-256color")
0024| 0xffffd6a8 --> 0xffffd815 ("SSH_CLIENT=10.0.2.2 56511 22")
0028| 0xffffd6ac --> 0xffffd832 ("SSH_TTY=/dev/pts/0")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x56557051 in ?? ()
As we can see, this looks strange because the execution of the random instructions still occurs and produces SIGSEGV. It seems like the program somehow use self-modifying code at one point.
Assuming that __libc_start_main
will be called, we try to interrupt when call to __libc_start_main
is occurs by hijacking the function using LD_PRELOAD
. By raising SIGINT
, gdb
will capture the signal
and we can check the state
of the program at that time.
Here is the hijack.c
code.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <signal.h>
int __libc_start_main(int (*main) (int,char **,char **),
int argc, char **ubp_av,
void (*init) (void),
void (*fini)(void),
void (*rtld_fini)(void),
void (*stack_end)) {
int (*original__libc_start_main)(int (*main) (int,char **,char **),
int argc, char **ubp_av,
void (*init) (void),
void (*fini)(void),
void (*rtld_fini)(void),
void (*stack_end));
raise(SIGINT);
original__libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
return original__libc_start_main(main, argc,ubp_av, init, fini,
rtld_fini, stack_end);
}
Compile the code to hijack.so
(32 bit).
gcc --shared -fPIC hijack.c -o hijack.so -ldl -m32
Use that library in the gdb
.
$ gdb --args env LD_PRELOAD=/vagrant/hijack.so ./forgetful_commander
...
gdb-peda$ r
Starting program: /usr/bin/env LD_PRELOAD=/vagrant/hijack.so ./forgetful_commander
process 28754 is executing new program: /vagrant/forgetful_commander
Program received signal SIGINT, Interrupt.
...
Stopped reason: SIGINT
0xf7fd8be9 in ?? ()
gdb-peda$ info files
...
0x56557050 - 0x56557321 is .text
...
gdb-peda$ x/10i 0x56557050
0x56557050: xor ebp,ebp
0x56557052: pop esi
0x56557053: mov ecx,esp
0x56557055: and esp,0xfffffff0
0x56557058: push eax
0x56557059: push esp
0x5655705a: push edx
0x5655705b: call 0x56557082
0x56557060: add ebx,0x2fa0
0x56557066: lea eax,[ebx-0x2ce0]
It appears that the contents of the .text
has changed and it seems like the instructions are correct. You can analyze the whole assembly. For easiness, you can also take the bytes from correct .text
and override the .text
in the binary file so that they can be decompiled by IDA to pseudo-code. Here, we try to figure out what the program is doing by just looking at the assembly.
By looking at the contents of the stack when the original __libc_start_main
is called, we can know that main
is located in .text+320
or 0x56557190
in the case above.
Here are the first few instructions in the main
function.
0x56557190: push ebp
0x56557191: mov ebp,esp
0x56557193: push ebx
0x56557194: push edi
0x56557195: push esi
0x56557196: sub esp,0x2c
0x56557199: call 0x5655719e
0x5655719e: pop eax
0x5655719f: add eax,0x2e62
0x565571a5: mov ecx,DWORD PTR [ebp+0xc]
0x565571a8: mov edx,DWORD PTR [ebp+0x8]
0x565571ab: mov DWORD PTR [ebp-0x10],0x0
0x565571b2: cmp DWORD PTR [ebp+0x8],0x2
0x565571b6: mov DWORD PTR [ebp-0x28],eax
0x565571b9: mov DWORD PTR [ebp-0x2c],ecx
0x565571bc: mov DWORD PTR [ebp-0x30],edx
0x565571bf: je 0x565571d1
0x565571c5: mov DWORD PTR [ebp-0x10],0x1
0x565571cc: jmp 0x565572a8
In the cmp DWORD PTR [ebp + 0x8], 0x2
instruction, it will compare the contents of [ebp+0x8]
where the number of arguments (argc) is located. So, the number of arguments must be two in order for the execution to go to 0x565571d1
. If not, the execution will continue to 0x565572a8
where the execution will go directly to ret
.
The instructions after 0x565571d1
seem to be related to retrieving values in the argument, calling to strlen
, and also checking the the argument. The following section looks interesting.
0x5655723f: movsx eax,BYTE PTR [ebp-0x21]
0x56557243: mov ecx,DWORD PTR [ebp-0x20]
0x56557246: mov esi,DWORD PTR [ebp-0x28]
0x56557249: movsx ecx,BYTE PTR [esi+ecx*1-0x1c0f]
0x56557251: mov edi,DWORD PTR [ebp-0x18]
0x56557254: mov ebx,DWORD PTR [ebp-0x20]
0x56557257: imul ebx,DWORD PTR [ebp-0x1c]
0x5655725b: add edi,ebx
0x5655725d: movsx edi,BYTE PTR [esi+edi*1-0x1ff8]
0x56557265: xor ecx,edi
0x56557267: cmp eax,ecx
0x56557269: jne 0x56557278
When the program reaches 0x5655723f
, the value of [ebp-0x21]
is the first letter of the argument and stored to eax
. Then at the 0x56557267
, the value of eax
is compared to ecx
which is the value from a xor operation.
By setting the breakpoint
in 0x56557267
(cmp eax, ecx
), we can know the value of ecx
when compared to the argument's characters. For reasons that we have not explored further, this breakpoint
must be set while still in __libc_start_main
. If it is set in the middle of execution on .text
, there will be SIGTRAP
continuously.
From observations on the value of ecx
in 0x56557267
one by one, we get flag {Just_type__Please__and_the_missles_will_be_launched.}.
Execution with this flag as the argument will make the program exit with code 0.
$ strace ./forgetful_commander flag{Just_type__Please__and_the_missles_will_be_launched.}
...
open("/proc/self/maps", O_RDONLY) = 3
read(3, "56596000-56598000 r--p 00000000 "..., 16384) = 1594
close(3) = 0
exit_group(0) = ?
+++ exited with 0 +++
Binary yang diberikan adalah elf32-i386
. Kami kesulitan untuk melakukan static analysis karena segmen .text
tidak ter-disassemble dengan baik dan sepertinya bukan instruksi mesin yang akan dijalankan.
Kami memutuskan untuk mencoba melakukan dynamic analysis. Eksekusi pada binary tidak memperlihatkan perilaku yang jelas karena tidak ada interaksi apapun. Eksekusi menggunakan strace
memperlihatkan bahwa program exit dengan kode 1.
$ strace ./forgetful_commander
...
open("/proc/self/maps", O_RDONLY) = 3
read(3, "5660d000-5660f000 r--p 00000000 "..., 16384) = 1528
close(3) = 0
exit_group(1) = ?
+++ exited with 1 ++
Binary dikompilasi sebagai PIE (Position-Independent Executables). Kami mencoba untuk menggunakan gdb
dan mengatur breakpoint
pada alamat .text
. Instruksi pada .text
terlihat random tetapi mari coba untuk mengeksekusinya.
$ gdb ./forgetful_commander
...
gdb-peda$ b *0x0
Breakpoint 1 at 0x0
gdb-peda$ r
Starting program: /vagrant/forgetful_commander
Warning:
Cannot insert breakpoint 1.
Cannot access memory at address 0x0
gdb-peda$ del 1
gdb-peda$ info files
Symbols from "/vagrant/forgetful_commander".
Native process:
Using the running image of child process 28513.
While running this, GDB does not access memory from...
Local exec file:
`/vagrant/forgetful_commander', file type elf32-i386.
warning: Cannot find section for the entry point of /vagrant/forgetful_commander.
Entry point: 0xd000
0x56556194 - 0x565561a7 is .interp
0x565561a8 - 0x565561c8 is .note.ABI-tag
0x565561c8 - 0x565561fc is .hash
0x565561fc - 0x5655621c is .gnu.hash
0x5655621c - 0x5655629c is .dynsym
0x5655629c - 0x56556339 is .dynstr
0x5655633a - 0x5655634a is .gnu.version
0x5655634c - 0x5655637c is .gnu.version_r
0x5655637c - 0x565563bc is .rel.dyn
0x565563bc - 0x565563cc is .rel.plt
0x56557000 - 0x56557020 is .init
0x56557020 - 0x56557050 is .plt
0x56557050 - 0x56557321 is .text
0x56557324 - 0x56557338 is .fini
0x56558000 - 0x5655842c is .rodata
0x5655842c - 0x56558450 is .eh_frame_hdr
0x56558450 - 0x565584f0 is .eh_frame
0x56559eec - 0x56559ef0 is .init_array
0x56559ef0 - 0x56559ef4 is .fini_array
0x56559ef4 - 0x56559fec is .dynamic
0x56559fec - 0x5655a000 is .got
0x5655a000 - 0x5655a014 is .got.plt
0x5655a014 - 0x5655a01c is .data
0x5655a01c - 0x5655a020 is .bss
gdb-peda$ x/10i 0x56557050
0x56557050: cdq
0x56557051: addr16 int 0x30
0x56557054: movs DWORD PTR es:[edi],DWORD PTR ds:[esi]
0x56557055: add bh,ah
0x56557057: add ebp,ebx
0x56557059: xchg ecx,eax
0x5655705a: into
0x5655705b: ins DWORD PTR es:[edi],dx
0x5655705c: xor al,0x2c
0x5655705e: daa
gdb-peda$ b *0x56557050
Breakpoint 2 at 0x56557050
gdb-peda$ c
Continuing.
...
Breakpoint 2, 0x56557050 in ?? ()
gdb-peda$ s
...
gdb-peda$ s
Program received signal SIGSEGV, Segmentation fault.
[----------------------------------registers-----------------------------------]
EAX: 0xf7ffd918 --> 0x56555000 --> 0x464c457f
EBX: 0xf7ffd000 --> 0x23f3c
ECX: 0xffffd694 --> 0xffffd7c3 ("/vagrant/forgetful_commander")
EDX: 0xffffffff
ESI: 0xffffd69c --> 0xffffd7de ("XDG_SESSION_ID=221")
EDI: 0x56562000 --> 0x819c6050
EBP: 0x0
ESP: 0xffffd690 --> 0x1
EIP: 0x56557051 --> 0xb4895eed
EFLAGS: 0x10286 (carry PARITY adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x56557046: push 0x8
0x5655704b: jmp 0x56557020
0x56557050: cdq
=> 0x56557051: in eax,dx
0x56557052: pop esi
0x56557053: mov DWORD PTR [ebx+eax*4+0x5405f0e4],esi
0x5655705a: push edx
0x5655705b: call 0x565570d7
[------------------------------------stack-------------------------------------]
0000| 0xffffd690 --> 0x1
0004| 0xffffd694 --> 0xffffd7c3 ("/vagrant/forgetful_commander")
0008| 0xffffd698 --> 0x0
0012| 0xffffd69c --> 0xffffd7de ("XDG_SESSION_ID=221")
0016| 0xffffd6a0 --> 0xffffd7f1 ("SHELL=/bin/bash")
0020| 0xffffd6a4 --> 0xffffd801 ("TERM=xterm-256color")
0024| 0xffffd6a8 --> 0xffffd815 ("SSH_CLIENT=10.0.2.2 56511 22")
0028| 0xffffd6ac --> 0xffffd832 ("SSH_TTY=/dev/pts/0")
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x56557051 in ?? ()
Seperti yang dapat kita lihat, ini terlihat aneh karena eksekusi pada instruksi random tersebut tetap terjadi dan menghasilkan SIGSEGV. Sepertinya program melakukan self-modifying pada satu titik dengan suatu cara.
Dengan asumsi bahwa __libc_start_main
suatu saat terpanggil, kami mencoba untuk melakukan interrupt pada saat __libc_start_main
dipanggil dengan melakukan hijack pada fungsi tersebut menggunakan LD_PRELOAD
. Dengan memberikan SIGINT
, maka gdb
akan menangkap signal
tersebut dan kita dapat memeriksa state
program pada saat itu.
Berikut adalah kode hijack.c
.
#define _GNU_SOURCE
#include <dlfcn.h>
#include <signal.h>
int __libc_start_main(int (*main) (int,char **,char **),
int argc, char **ubp_av,
void (*init) (void),
void (*fini)(void),
void (*rtld_fini)(void),
void (*stack_end)) {
int (*original__libc_start_main)(int (*main) (int,char **,char **),
int argc, char **ubp_av,
void (*init) (void),
void (*fini)(void),
void (*rtld_fini)(void),
void (*stack_end));
raise(SIGINT);
original__libc_start_main = dlsym(RTLD_NEXT, "__libc_start_main");
return original__libc_start_main(main, argc,ubp_av, init, fini,
rtld_fini, stack_end);
}
Kompilasi kode menjadi hijack.so
(32 bit).
gcc --shared -fPIC hijack.c -o hijack.so -ldl -m32
Gunakan library
tersebut pada gdb
dan jalankan.
$ gdb --args env LD_PRELOAD=/vagrant/hijack.so ./forgetful_commander
...
gdb-peda$ r
Starting program: /usr/bin/env LD_PRELOAD=/vagrant/hijack.so ./forgetful_commander
process 28754 is executing new program: /vagrant/forgetful_commander
Program received signal SIGINT, Interrupt.
...
Stopped reason: SIGINT
0xf7fd8be9 in ?? ()
gdb-peda$ info files
...
0x56557050 - 0x56557321 is .text
...
gdb-peda$ x/10i 0x56557050
0x56557050: xor ebp,ebp
0x56557052: pop esi
0x56557053: mov ecx,esp
0x56557055: and esp,0xfffffff0
0x56557058: push eax
0x56557059: push esp
0x5655705a: push edx
0x5655705b: call 0x56557082
0x56557060: add ebx,0x2fa0
0x56557066: lea eax,[ebx-0x2ce0]
Terlihat bahwa isi .text
sudah berubah dan sepertinya instruksi yang benar. Anda dapat menganalis assembly-nya secara keseluruhan. Untuk mempermudah, Anda juga dapat mengambil nilai bytes dari .text
yang sudah berubah dan menimpanya pada .text
pada berkas binary sehingga dapat didekompilasi oleh IDA menjadi pseudo-code. Di sini, kami mencoba untuk mengira-ngira apa yang dilakukan oleh program dengan melihat assembly-nya saja.
Dengan melihat isi stack
(parameter) pada saat __libc_start_main
yang asli akan dipanggil, kita dapat mengetahui bahwa main
terdapat pada alamat .text+320
atau 0x56557190
pada kasus di atas.
Berikut beberapa instruksi pertama pada fungsi main
.
0x56557190: push ebp
0x56557191: mov ebp,esp
0x56557193: push ebx
0x56557194: push edi
0x56557195: push esi
0x56557196: sub esp,0x2c
0x56557199: call 0x5655719e
0x5655719e: pop eax
0x5655719f: add eax,0x2e62
0x565571a5: mov ecx,DWORD PTR [ebp+0xc]
0x565571a8: mov edx,DWORD PTR [ebp+0x8]
0x565571ab: mov DWORD PTR [ebp-0x10],0x0
0x565571b2: cmp DWORD PTR [ebp+0x8],0x2
0x565571b6: mov DWORD PTR [ebp-0x28],eax
0x565571b9: mov DWORD PTR [ebp-0x2c],ecx
0x565571bc: mov DWORD PTR [ebp-0x30],edx
0x565571bf: je 0x565571d1
0x565571c5: mov DWORD PTR [ebp-0x10],0x1
0x565571cc: jmp 0x565572a8
Instruksi cmp DWORD PTR [ebp+0x8],0x2
akan membandingkan isi dari [ebp+0x8]
di mana pada alamat tersebut tersimpan jumlah dari argumen (argc). Sehingga, jumlah argumen harus berjumlah dua agar program lanjut ke 0x565571d1
. Jika tidak, program akan lanjut ke 0x565572a8
yang mana program akan langsung menuju ret
.
Instruksi-instruksi setelah 0x565571d1
sepertinya terkait dengan pengambilan nilai pada argumen, pemanggilan ke strlen
, dan juga memeriksa isi argumen. Bagian pemeriksaan berikut terlihat menarik.
0x5655723f: movsx eax,BYTE PTR [ebp-0x21]
0x56557243: mov ecx,DWORD PTR [ebp-0x20]
0x56557246: mov esi,DWORD PTR [ebp-0x28]
0x56557249: movsx ecx,BYTE PTR [esi+ecx*1-0x1c0f]
0x56557251: mov edi,DWORD PTR [ebp-0x18]
0x56557254: mov ebx,DWORD PTR [ebp-0x20]
0x56557257: imul ebx,DWORD PTR [ebp-0x1c]
0x5655725b: add edi,ebx
0x5655725d: movsx edi,BYTE PTR [esi+edi*1-0x1ff8]
0x56557265: xor ecx,edi
0x56557267: cmp eax,ecx
0x56557269: jne 0x56557278
Pada saat program mencapai 0x5655723f
, isi dari [ebp-0x21]
adalah huruf pertama dari argumen dan disimpan ke eax
. Kemudian pada alamat 0x56557267
, isi dari eax
dibandingkan dengan ecx
yang merupakan suatu nilai dari operasi xor.
Dengan mengatur breakpoint
pada 0x56557267
pada instruksi cmp eax,ecx
, kita dapat mengetahui nilai ecx
yang dibandingkan dengan argumen karakter demi karakter. Karena alasan yang belum kami eksplorasi lebih lanjut, pemasangan breakpoint
ini harus dilakukan ketika masih di __libc_start_main
. Jika dilakukan di tengah-tengah eksekusi pada .text
, terjadi SIGTRAP
terus menerus.
Dari observasi pada nilai ecx
pada 0x56557267
satu persatu, didapatkan flag{Just_type__Please__and_the_missles_will_be_launched.}.
Eksekusi dengan flag tersebut sebagai argumen akan membuat program exit dengan kode 0.
$ strace ./forgetful_commander flag{Just_type__Please__and_the_missles_will_be_launched.}
...
open("/proc/self/maps", O_RDONLY) = 3
read(3, "56596000-56598000 r--p 00000000 "..., 16384) = 1594
close(3) = 0
exit_group(0) = ?
+++ exited with 0 +++