Linux Reverse Engineering CTFs for Beginners – InfoSec Write-ups

0
150


After a while, I decided a write a short blog post about Linux binary reversing CTFs in general. How to approach a binary and solving for beginners. I personally am not a fan of Linux reverse engineering challenges in general, since I focus more time on Windows reversing. I like windows reverse engineering challenges more. A reason me liking Windows is as a pentester daily I encounter Windows machines and it’s so rare I come across an entire network running Linux. Even when it comes to exploit development it’s pretty rare you will manually develop an exploit for a Linux software while pentesting. But this knowledge is really useful when it comes to IoT, since almost many devices are based on Linux embedded. If you want to begin reverse engineering and exploit development starting from Linux would be a good idea. I too started from Linux many years ago. Saying that since some people when they see a reverse engineering challenge they try to run away. So if you are a newbie I hope this content might be useful for you to begin with.

Let’s first have a look at the ELF headers. The best way to learn more about this in detail is to check the man pages for ELF.

Here’s in more detail. The “e_shoff” member holds the offset to the section header table. The “sh_offset” member holds the address to the section’s first byte.

            +-------------------+
| ELF header |---+
+---------> +-------------------+ | e_shoff
| | |<--+
| Section | Section header 0 |
| | |---+ sh_offset
| Header +-------------------+ |
| | Section header 1 |---|--+ sh_offset
| Table +-------------------+ | |
| | Section header 2 |---|--|--+
+---------> +-------------------+ | | |
| Section 0 |<--+ | |
+-------------------+ | | sh_offset
| Section 1 |<-----+ |
+-------------------+ |
| Section 2 |<--------+
+-------------------+

Any ELF file starts with an executable header. This contains information about which type of an ELF file, the offsets to different headers. Everything is self-explanatory if you look at the comments. For this example, I am using 32-bit structures. For x86_64 the sizes may change and the naming convention would start with “Elf64_”.

#define EI_NIDENT (16)

typedef struct {
unsigned char e_ident[EI_NIDENT]; /* Magic number and other info */
Elf32_Half e_type; /* Object file type */
Elf32_Half e_machine; /* Architecture */
Elf32_Word e_version; /* Object file version */
Elf32_Addr e_entry; /* Entry point virtual address */
Elf32_Off e_phoff; /* Program header table file offset */
Elf32_Off e_shoff; /* Section header table file offset */
Elf32_Word e_flags; /* Processor-specific flags */
Elf32_Half e_ehsize; /* ELF header size in bytes */
Elf32_Half e_phentsize; /* Program header table entry size */
Elf32_Half e_phnum; /* Program header table entry count */
Elf32_Half e_shentsize; /* Section header table entry size */
Elf32_Half e_shnum; /* Section header table entry count */
Elf32_Half e_shstrndx; /* Section header string table index */
} Elf32_Ehdr;

This is an example using readelf.

# readelf -h /bin/ls
ELF Header:
Magic: 7f 45 4c 46 02 01 01 00 00 00 00 00 00 00 00 00
Class: ELF64
Data: 2's complement, little endian
Version: 1 (current)
OS/ABI: UNIX - System V
ABI Version: 0
Type: DYN (Shared object file)
Machine: Advanced Micro Devices X86-64
Version: 0x1
Entry point address: 0x6130
Start of program headers: 64 (bytes into file)
Start of section headers: 137000 (bytes into file)
Flags: 0x0
Size of this header: 64 (bytes)
Size of program headers: 56 (bytes)
Number of program headers: 11
Size of section headers: 64 (bytes)
Number of section headers: 29
Section header string table index: 28

To calculate the size of the entire binary we can use the following calculation

size = e_shoff + (e_shnum * e_shentsize)
size = Start of section headers + (Number of section headers * Size of section headers)
size = 137000 + (29*64) = 138856

As you can see our calculation is correct.

# ls -l /bin/ls
-rwxr-xr-x 1 root root 138856 Aug 29 21:20 /bin/ls

These headers describe the segments of the binary which important for the loading of the binary. This information is useful for the kernel to map the segments to memory from disk. The members of the structure are self-explanatory. I won’t be explaining in depth about this for this post as I try to keep things basic. However, every section is important to understand in doing cool things in reverse engineering in ELF 🙂

typedef struct {
Elf32_Word sh_name; /* Section name (string tbl index) */
Elf32_Word sh_type; /* Section type */
Elf32_Word sh_flags; /* Section flags */
Elf32_Addr sh_addr; /* Section virtual addr at execution */
Elf32_Off sh_offset; /* Section file offset */
Elf32_Word sh_size; /* Section size in bytes */
Elf32_Word sh_link; /* Link to another section */
Elf32_Word sh_info; /* Additional section information */
Elf32_Word sh_addralign; /* Section alignment */
Elf32_Word sh_entsize; /* Entry size if section holds table */
} Elf32_Shdr;

As any binary, these are the sections. Some sections are familiar with the PE’s headers. However, I won’t be discussing all the sections as I try to keep it basic.

This section contains the program’s uninitialized global data.

This section contains the program’s initialized global variables.

This section contains read-only data such as strings of the program used.

This section contains the program’s actual code, the logic flow.

# readelf -S --wide /bin/ls
There are 29 section headers, starting at offset 0x21728:
Section Headers:
[Nr] Name Type Address Off Size ES Flg Lk Inf Al
[ 0] NULL 0000000000000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00000000000002a8 0002a8 00001c 00 A 0 0 1
[ 2] .note.ABI-tag NOTE 00000000000002c4 0002c4 000020 00 A 0 0 4
[ 3] .note.gnu.build-id NOTE 00000000000002e4 0002e4 000024 00 A 0 0 4
[ 4] .gnu.hash GNU_HASH 0000000000000308 000308 0000c0 00 A 5 0 8
[ 5] .dynsym DYNSYM 00000000000003c8 0003c8 000c90 18 A 6 1 8
[ 6] .dynstr STRTAB 0000000000001058 001058 0005d8 00 A 0 0 1
[ 7] .gnu.version VERSYM 0000000000001630 001630 00010c 02 A 5 0 2
[ 8] .gnu.version_r VERNEED 0000000000001740 001740 000070 00 A 6 1 8
[ 9] .rela.dyn RELA 00000000000017b0 0017b0 001350 18 A 5 0 8
[10] .rela.plt RELA 0000000000002b00 002b00 0009f0 18 AI 5 24 8
[11] .init PROGBITS 0000000000004000 004000 000017 00 AX 0 0 4
[12] .plt PROGBITS 0000000000004020 004020 0006b0 10 AX 0 0 16
[13] .plt.got PROGBITS 00000000000046d0 0046d0 000018 08 AX 0 0 8
[14] .text PROGBITS 00000000000046f0 0046f0 01253e 00 AX 0 0 16
[15] .fini PROGBITS 0000000000016c30 016c30 000009 00 AX 0 0 4
[16] .rodata PROGBITS 0000000000017000 017000 005129 00 A 0 0 32
[17] .eh_frame_hdr PROGBITS 000000000001c12c 01c12c 0008fc 00 A 0 0 4
[18] .eh_frame PROGBITS 000000000001ca28 01ca28 002ed0 00 A 0 0 8
[19] .init_array INIT_ARRAY 0000000000021390 020390 000008 08 WA 0 0 8
[20] .fini_array FINI_ARRAY 0000000000021398 020398 000008 08 WA 0 0 8
[21] .data.rel.ro PROGBITS 00000000000213a0 0203a0 000a38 00 WA 0 0 32
[22] .dynamic DYNAMIC 0000000000021dd8 020dd8 0001f0 10 WA 6 0 8
[23] .got PROGBITS 0000000000021fc8 020fc8 000038 08 WA 0 0 8
[24] .got.plt PROGBITS 0000000000022000 021000 000368 08 WA 0 0 8
[25] .data PROGBITS 0000000000022380 021380 000268 00 WA 0 0 32
[26] .bss NOBITS 0000000000022600 0215e8 0012d8 00 WA 0 0 32
[27] .gnu_debuglink PROGBITS 0000000000000000 0215e8 000034 00 0 0 4
[28] .shstrtab STRTAB 0000000000000000 02161c 00010a 00 0 0 1
Key to Flags:
W (write), A (alloc), X (execute), M (merge), S (strings), I (info),
L (link order), O (extra OS processing required), G (group), T (TLS),
C (compressed), x (unknown), o (OS specific), E (exclude),
l (large), p (processor specific)

Now that you have a basic understanding about the headers, let’s pick a random challenge CTF and explire. Download the binary from here.

When we pass in some random string we get [+] No flag for you. [+] text displayed.

# ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 aaaa[+] No flag for you. [+]

Let’s start by having a look at strings and see any interesting strings.

# strings nix_5744af788e6cbdb29bb41e8b0e5f3cd5 
/lib/ld-linux.so.2
Mw1i#'0
libc.so.6
_IO_stdin_used
exit
sprintf
puts
strlen
__cxa_finalize
__libc_start_main
GLIBC_2.1.3
Y[^]
[^_]
UWVS
[^_]
Usage: script.exe
Length of argv[1] too long.
[+] The flag is: SAYCURE{%s} [+]
[+] No flag for you. [+]
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c
;*2$"
GCC: (Debian 8.2.0-8) 8.2.0
crtstuff.c

We found all the strings printed out from the binary. The “%c” is the format string where our flag gets printed, we can determine the flag must be of 15 characters.

Usage: script.exe
Length of argv[1] too long.
[+] The flag is: SAYCURE{%s} [+]
[+] No flag for you. [+]
%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c

We can get a better view of these strings if we look at the ‘.rodata’ section with the offsets.

# readelf -x .rodata nix_5744af788e6cbdb29bb41e8b0e5f3cd5 Hex dump of section '.rodata':
0x00002000 03000000 01000200 55736167 653a2073 ........Usage: s
0x00002010 63726970 742e6578 65203c6b 65793e00 cript.exe .
0x00002020 4c656e67 7468206f 66206172 67765b31 Length of argv[1
0x00002030 5d20746f 6f206c6f 6e672e00 5b2b5d20 ] too long..[+]
0x00002040 54686520 666c6167 2069733a 20534159 The flag is: SAY
0x00002050 43555245 7b25737d 205b2b5d 0a000a5b CURE{%s} [+]...[
0x00002060 2b5d204e 6f20666c 61672066 6f722079 +] No flag for y
0x00002070 6f752e20 5b2b5d00 25632563 25632563 ou. [+].%c%c%c%c
0x00002080 25632563 25632563 25632563 25632563 %c%c%c%c%c%c%c%c
0x00002090 25632563 256300 %c%c%c.

By checking the symbols of the binary we can realize it uses printf, puts, sprintf, strlen functions.

# nm -D nix_5744af788e6cbdb29bb41e8b0e5f3cd5 
w __cxa_finalize
U exit
w __gmon_start__
00002004 R _IO_stdin_used
w _ITM_deregisterTMCloneTable
w _ITM_registerTMCloneTable
U __libc_start_main
U printf
U puts
U sprintf
U strlen

We can use tools such as strace to trace the system calls used by the program.

# strace ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 aaaa
execve("./nix_5744af788e6cbdb29bb41e8b0e5f3cd5", ["./nix_5744af788e6cbdb29bb41e8b0e"..., "aaaa"], 0x7ffd5ff92d18 /* 46 vars */) = 0
strace: [ Process PID=59965 runs in 32 bit mode. ]
brk(NULL) = 0x56f14000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xf7ef0000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=220471, ...}) = 0
mmap2(NULL, 220471, PROT_READ, MAP_PRIVATE, 3, 0) = 0xf7eba000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
openat(AT_FDCWD, "/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "177ELF1113331 2331004"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1930924, ...}) = 0
mmap2(NULL, 1940000, PROT_READ, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xf7ce0000
mprotect(0xf7cf9000, 1814528, PROT_NONE) = 0
mmap2(0xf7cf9000, 1359872, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x19000) = 0xf7cf9000
mmap2(0xf7e45000, 450560, PROT_READ, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x165000) = 0xf7e45000
mmap2(0xf7eb4000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1d3000) = 0xf7eb4000
mmap2(0xf7eb7000, 10784, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xf7eb7000
close(3) = 0
set_thread_area({entry_number=-1, base_addr=0xf7ef10c0, limit=0x0fffff, seg_32bit=1, contents=0, read_exec_only=0, limit_in_pages=1, seg_not_present=0, useable=1}) = 0 (entry_number=12)
mprotect(0xf7eb4000, 8192, PROT_READ) = 0
mprotect(0x5664d000, 4096, PROT_READ) = 0
mprotect(0xf7f1e000, 4096, PROT_READ) = 0
munmap(0xf7eba000, 220471) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(0x88, 0x2), ...}) = 0
brk(NULL) = 0x56f14000
brk(0x56f35000) = 0x56f35000
brk(0x56f36000) = 0x56f36000
write(1, "n", 1
) = 1
write(1, "[+] No flag for you. [+]n", 25[+] No flag for you. [+]
) = 25
exit_group(26) = ?
+++ exited with 26 +++

To get a better understanding, we can use ltrace to trace the library calls made by demangling C++ function names. We can see there is a string length check being done.

# ltrace -i -C ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 aaaaaaaa
[0x565570e1] __libc_start_main(0x565571e9, 2, 0xffe3a584, 0x56557400
[0x56557249] strlen("aaaaaaaa") = 8
[0x565572ca] puts("n[+] No flag for you. [+]"
[+] No flag for you. [+]
) = 26
[0xffffffffffffffff] +++ exited (status 26) +++

Let’s have a look at the .text section’s disassembly and try to understand. In this binary the symbols are not stripped so we can see the function names which makes it easier to understand. If you can read assembly by now you will have figure out what is happening. If not let’s do some live debugging and try to understand better.

root@Omega:/mnt/hgfs/shared/Linux RE# objdump -D -M intel -j .text nix_5744af788e6cbdb29bb41e8b0e5f3cd5 nix_5744af788e6cbdb29bb41e8b0e5f3cd5:     file format elf32-i386
Disassembly of section .text:000010b0 <_start>:
10b0: 31 ed xor ebp,ebp
10b2: 5e pop esi
10b3: 89 e1 mov ecx,esp
10b5: 83 e4 f0 and esp,0xfffffff0
10b8: 50 push eax
10b9: 54 push esp
10ba: 52 push edx
10bb: e8 22 00 00 00 call 10e2 <_start+0x32>
10c0: 81 c3 40 2f 00 00 add ebx,0x2f40
10c6: 8d 83 60 d4 ff ff lea eax,[ebx-0x2ba0]
10cc: 50 push eax
10cd: 8d 83 00 d4 ff ff lea eax,[ebx-0x2c00]
10d3: 50 push eax
10d4: 51 push ecx
10d5: 56 push esi
10d6: ff b3 f8 ff ff ff push DWORD PTR [ebx-0x8]
10dc: e8 9f ff ff ff call 1080 <__libc_start_main@plt>
10e1: f4 hlt
10e2: 8b 1c 24 mov ebx,DWORD PTR [esp]
10e5: c3 ret
10e6: 66 90 xchg ax,ax
10e8: 66 90 xchg ax,ax
10ea: 66 90 xchg ax,ax
10ec: 66 90 xchg ax,ax
10ee: 66 90 xchg ax,ax
... Output Omitted ...000011e9
:
11e9: 8d 4c 24 04 lea ecx,[esp+0x4]
11ed: 83 e4 f0 and esp,0xfffffff0
11f0: ff 71 fc push DWORD PTR [ecx-0x4]
11f3: 55 push ebp
11f4: 89 e5 mov ebp,esp
11f6: 56 push esi
11f7: 53 push ebx
11f8: 51 push ecx
11f9: 83 ec 1c sub esp,0x1c
11fc: e8 ef fe ff ff call 10f0 <__x86.get_pc_thunk.bx>
1201: 81 c3 ff 2d 00 00 add ebx,0x2dff
1207: 89 ce mov esi,ecx
1209: c7 45 e4 00 00 00 00 mov DWORD PTR [ebp-0x1c],0x0
1210: c7 45 dc 07 00 00 00 mov DWORD PTR [ebp-0x24],0x7
1217: 83 3e 02 cmp DWORD PTR [esi],0x2
121a: 74 1c je 1238
121c: 83 ec 0c sub esp,0xc
121f: 8d 83 08 e0 ff ff lea eax,[ebx-0x1ff8]
1225: 50 push eax
1226: e8 15 fe ff ff call 1040
122b: 83 c4 10 add esp,0x10
122e: 83 ec 0c sub esp,0xc
1231: 6a 01 push 0x1
1233: e8 28 fe ff ff call 1060
1238: 8b 46 04 mov eax,DWORD PTR [esi+0x4]
123b: 83 c0 04 add eax,0x4
123e: 8b 00 mov eax,DWORD PTR [eax]
1240: 83 ec 0c sub esp,0xc
1243: 50 push eax
1244: e8 27 fe ff ff call 1070
1249: 83 c4 10 add esp,0x10
124c: 83 f8 0f cmp eax,0xf
124f: 76 1c jbe 126d
1251: 83 ec 0c sub esp,0xc
1254: 8d 83 20 e0 ff ff lea eax,[ebx-0x1fe0]
125a: 50 push eax
125b: e8 f0 fd ff ff call 1050
1260: 83 c4 10 add esp,0x10
1263: 83 ec 0c sub esp,0xc
1266: 6a 01 push 0x1
1268: e8 f3 fd ff ff call 1060
126d: c7 45 e0 00 00 00 00 mov DWORD PTR [ebp-0x20],0x0
1274: eb 1a jmp 1290
1276: 8b 46 04 mov eax,DWORD PTR [esi+0x4]
1279: 83 c0 04 add eax,0x4
127c: 8b 10 mov edx,DWORD PTR [eax]
127e: 8b 45 e0 mov eax,DWORD PTR [ebp-0x20]
1281: 01 d0 add eax,edx
1283: 0f b6 00 movzx eax,BYTE PTR [eax]
1286: 0f be c0 movsx eax,al
1289: 01 45 e4 add DWORD PTR [ebp-0x1c],eax
128c: 83 45 e0 01 add DWORD PTR [ebp-0x20],0x1
1290: 8b 45 e0 mov eax,DWORD PTR [ebp-0x20]
1293: 3b 45 dc cmp eax,DWORD PTR [ebp-0x24]
1296: 7c de jl 1276
1298: 81 7d e4 21 03 00 00 cmp DWORD PTR [ebp-0x1c],0x321
129f: 75 1a jne 12bb
12a1: e8 33 00 00 00 call 12d9
12a6: 83 ec 08 sub esp,0x8
12a9: 50 push eax
12aa: 8d 83 3c e0 ff ff lea eax,[ebx-0x1fc4]
12b0: 50 push eax
12b1: e8 8a fd ff ff call 1040
12b6: 83 c4 10 add esp,0x10
12b9: eb 12 jmp 12cd
12bb: 83 ec 0c sub esp,0xc
12be: 8d 83 5e e0 ff ff lea eax,[ebx-0x1fa2]
12c4: 50 push eax
12c5: e8 86 fd ff ff call 1050
12ca: 83 c4 10 add esp,0x10
12cd: 90 nop
12ce: 8d 65 f4 lea esp,[ebp-0xc]
12d1: 59 pop ecx
12d2: 5b pop ebx
12d3: 5e pop esi
12d4: 5d pop ebp
12d5: 8d 61 fc lea esp,[ecx-0x4]
12d8: c3 ret 000012d9 :
12d9: 55 push ebp
12da: 89 e5 mov ebp,esp
12dc: 57 push edi
12dd: 56 push esi
12de: 53 push ebx
12df: 83 ec 7c sub esp,0x7c
12e2: e8 09 fe ff ff call 10f0 <__x86.get_pc_thunk.bx>
12e7: 81 c3 19 2d 00 00 add ebx,0x2d19
12ed: c7 45 e4 00 00 00 00 mov DWORD PTR [ebp-0x1c],0x0
12f4: c7 45 a8 4c 00 00 00 mov DWORD PTR [ebp-0x58],0x4c
12fb: c7 45 ac 33 00 00 00 mov DWORD PTR [ebp-0x54],0x33
1302: c7 45 b0 74 00 00 00 mov DWORD PTR [ebp-0x50],0x74
1309: c7 45 b4 73 00 00 00 mov DWORD PTR [ebp-0x4c],0x73
1310: c7 45 b8 5f 00 00 00 mov DWORD PTR [ebp-0x48],0x5f
1317: c7 45 bc 67 00 00 00 mov DWORD PTR [ebp-0x44],0x67
131e: c7 45 c0 33 00 00 00 mov DWORD PTR [ebp-0x40],0x33
1325: c7 45 c4 74 00 00 00 mov DWORD PTR [ebp-0x3c],0x74
132c: c7 45 c8 5f 00 00 00 mov DWORD PTR [ebp-0x38],0x5f
1333: c7 45 cc 69 00 00 00 mov DWORD PTR [ebp-0x34],0x69
133a: c7 45 d0 6e 00 00 00 mov DWORD PTR [ebp-0x30],0x6e
1341: c7 45 d4 32 00 00 00 mov DWORD PTR [ebp-0x2c],0x32
1348: c7 45 d8 5f 00 00 00 mov DWORD PTR [ebp-0x28],0x5f
134f: c7 45 dc 52 00 00 00 mov DWORD PTR [ebp-0x24],0x52
1356: c7 45 e0 33 00 00 00 mov DWORD PTR [ebp-0x20],0x33
135d: 8b 55 e0 mov edx,DWORD PTR [ebp-0x20]
1360: 8b 75 dc mov esi,DWORD PTR [ebp-0x24]
1363: 8b 45 d8 mov eax,DWORD PTR [ebp-0x28]
1366: 89 45 a4 mov DWORD PTR [ebp-0x5c],eax
1369: 8b 4d d4 mov ecx,DWORD PTR [ebp-0x2c]
136c: 89 4d a0 mov DWORD PTR [ebp-0x60],ecx
136f: 8b 7d d0 mov edi,DWORD PTR [ebp-0x30]
1372: 89 7d 9c mov DWORD PTR [ebp-0x64],edi
1375: 8b 45 cc mov eax,DWORD PTR [ebp-0x34]
1378: 89 45 98 mov DWORD PTR [ebp-0x68],eax
137b: 8b 4d c8 mov ecx,DWORD PTR [ebp-0x38]
137e: 89 4d 94 mov DWORD PTR [ebp-0x6c],ecx
1381: 8b 7d c4 mov edi,DWORD PTR [ebp-0x3c]
1384: 89 7d 90 mov DWORD PTR [ebp-0x70],edi
1387: 8b 45 c0 mov eax,DWORD PTR [ebp-0x40]
138a: 89 45 8c mov DWORD PTR [ebp-0x74],eax
138d: 8b 4d bc mov ecx,DWORD PTR [ebp-0x44]
1390: 89 4d 88 mov DWORD PTR [ebp-0x78],ecx
1393: 8b 7d b8 mov edi,DWORD PTR [ebp-0x48]
1396: 89 7d 84 mov DWORD PTR [ebp-0x7c],edi
1399: 8b 45 b4 mov eax,DWORD PTR [ebp-0x4c]
139c: 89 45 80 mov DWORD PTR [ebp-0x80],eax
139f: 8b 7d b0 mov edi,DWORD PTR [ebp-0x50]
13a2: 8b 4d ac mov ecx,DWORD PTR [ebp-0x54]
13a5: 8b 45 a8 mov eax,DWORD PTR [ebp-0x58]
13a8: 83 ec 0c sub esp,0xc
13ab: 52 push edx
13ac: 56 push esi
13ad: ff 75 a4 push DWORD PTR [ebp-0x5c]
13b0: ff 75 a0 push DWORD PTR [ebp-0x60]
13b3: ff 75 9c push DWORD PTR [ebp-0x64]
13b6: ff 75 98 push DWORD PTR [ebp-0x68]
13b9: ff 75 94 push DWORD PTR [ebp-0x6c]
13bc: ff 75 90 push DWORD PTR [ebp-0x70]
13bf: ff 75 8c push DWORD PTR [ebp-0x74]
13c2: ff 75 88 push DWORD PTR [ebp-0x78]
13c5: ff 75 84 push DWORD PTR [ebp-0x7c]
13c8: ff 75 80 push DWORD PTR [ebp-0x80]
13cb: 57 push edi
13cc: 51 push ecx
13cd: 50 push eax
13ce: 8d 83 78 e0 ff ff lea eax,[ebx-0x1f88]
13d4: 50 push eax
13d5: 8d 83 30 00 00 00 lea eax,[ebx+0x30]
13db: 50 push eax
13dc: e8 af fc ff ff call 1090
13e1: 83 c4 50 add esp,0x50
13e4: 8d 83 30 00 00 00 lea eax,[ebx+0x30]
13ea: 8d 65 f4 lea esp,[ebp-0xc]
13ed: 5b pop ebx
13ee: 5e pop esi
13ef: 5f pop edi
13f0: 5d pop ebp
13f1: c3 ret
13f2: 66 90 xchg ax,ax
13f4: 66 90 xchg ax,ax
13f6: 66 90 xchg ax,ax
13f8: 66 90 xchg ax,ax
13fa: 66 90 xchg ax,ax
13fc: 66 90 xchg ax,ax
13fe: 66 90 xchg ax,ax
... Output Omitted ...

I will use GDB-Peda for this which makes it easier to understand. Let’s first check the functions in the binary. We can see functions such as main, comp_key

gdb-peda$ info functions 
All defined functions:
Non-debugging symbols:
0x00001000 _init
0x00001040 printf@plt
0x00001050 puts@plt
0x00001060 exit@plt
0x00001070 strlen@plt
0x00001080 __libc_start_main@plt
0x00001090 sprintf@plt
0x000010a0 __cxa_finalize@plt
0x000010a8 __gmon_start__@plt
0x000010b0 _start
0x000010f0 __x86.get_pc_thunk.bx
0x00001100 deregister_tm_clones
0x00001140 register_tm_clones
0x00001190 __do_global_dtors_aux
0x000011e0 frame_dummy
0x000011e5 __x86.get_pc_thunk.dx
0x000011e9 main
0x000012d9 comp_key
0x00001400 __libc_csu_init
0x00001460 __libc_csu_fini
0x00001464 _fini

This is how you debug a program. We will hit a break point at the main function. Use n to step and ni to step each instruction. If you don’t know assembly, in a basic challenge like this, look for jumps, compare instructions. Try to understand what check the program does and build the logic in your mind. There are many good crash courses on assembly and I would recommend reading few.

gdb-peda$ break main
Breakpoint 1 at 0x11f9
gdb-peda$ run aaaaaaaa
Starting program: /mnt/hgfs/shared/Linux RE/nix_5744af788e6cbdb29bb41e8b0e5f3cd5 aaaaaaaa
[----------------------------------registers-----------------------------------]
EAX: 0xf7f95dd8 --> 0xffffd2f0 --> 0xffffd4d1 ("NVM_DIR=/root/.nvm")
EBX: 0x0
ECX: 0xffffd250 --> 0x2
EDX: 0xffffd274 --> 0x0
ESI: 0xf7f94000 --> 0x1d5d8c
EDI: 0x0
EBP: 0xffffd238 --> 0x0
ESP: 0xffffd22c --> 0xffffd250 --> 0x2
EIP: 0x565561f9 (: sub esp,0x1c)
EFLAGS: 0x282 (carry parity adjust zero SIGN trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x565561f6 : push esi
0x565561f7 : push ebx
0x565561f8 : push ecx
=> 0x565561f9 : sub esp,0x1c
0x565561fc : call 0x565560f0 <__x86.get_pc_thunk.bx>
0x56556201 : add ebx,0x2dff
0x56556207 : mov esi,ecx
0x56556209 : mov DWORD PTR [ebp-0x1c],0x0
[------------------------------------stack-------------------------------------]
0000| 0xffffd22c --> 0xffffd250 --> 0x2
0004| 0xffffd230 --> 0x0
0008| 0xffffd234 --> 0xf7f94000 --> 0x1d5d8c
0012| 0xffffd238 --> 0x0
0016| 0xffffd23c --> 0xf7dd79a1 (<__libc_start_main+241>: add esp,0x10)
0020| 0xffffd240 --> 0xf7f94000 --> 0x1d5d8c
0024| 0xffffd244 --> 0xf7f94000 --> 0x1d5d8c
0028| 0xffffd248 --> 0x0
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x565561f9 in main ()
1: main = {} 0x565561e9

2: puts = {} 0xf7e25e40
gdb-peda$

If you play with gdb for a little you realize how it works. Let’s try to understand the logic part by part.

The program first tries to compare the number of arguments. It’s stored in ecx register and moved to esi and it’s used to compare the value with 0x2. You can use gdb to go through the assembly instructions and understand better.

0x56556207 <+30>:	mov    esi,ecx
0x56556209 <+32>: mov DWORD PTR [ebp-0x1c],0x0
0x56556210 <+39>: mov DWORD PTR [ebp-0x24],0x7
0x56556217 <+46>: cmp DWORD PTR [esi],0x2
0x5655621a <+49>: je 0x56556238
0x5655621c <+51>: sub esp,0xc
0x5655621f <+54>: lea eax,[ebx-0x1ff8]
0x56556225 <+60>: push eax
0x56556226 <+61>: call 0x56556040
0x5655622b <+66>: add esp,0x10
0x5655622e <+69>: sub esp,0xc
0x56556231 <+72>: push 0x1
0x56556233 <+74>: call 0x56556060

We can write pseudo code like this.

if(argc != 2) {
printf("Usage: script.exe ");
exit(1);
}
0x56556238 <+79>: mov eax,DWORD PTR [esi+0x4]
0x5655623b <+82>: add eax,0x4
0x5655623e <+85>: mov eax,DWORD PTR [eax]
0x56556240 <+87>: sub esp,0xc
0x56556243 <+90>: push eax
0x56556244 <+91>: call 0x56556070
0x56556249 <+96>: add esp,0x10
0x5655624c <+99>: cmp eax,0xf
0x5655624f <+102>: jbe 0x5655626d
0x56556251 <+104>: sub esp,0xc
0x56556254 <+107>: lea eax,[ebx-0x1fe0]
0x5655625a <+113>: push eax
0x5655625b <+114>: call 0x56556050
0x56556260 <+119>: add esp,0x10
0x56556263 <+122>: sub esp,0xc
0x56556266 <+125>: push 0x1
0x56556268 <+127>: call 0x56556060

After translating:

if(strlen(argv[1]) > 15) {
puts("Length of argv[1] too long.");
exit(1);
}

If you check this code we can see there is a loop going through iterating each character of our supplied string.

0x5655626d <+132>:	mov    DWORD PTR [ebp-0x20],0x0
0x56556274 <+139>: jmp 0x56556290
0x56556276 <+141>: mov eax,DWORD PTR [esi+0x4]
0x56556279 <+144>: add eax,0x4
0x5655627c <+147>: mov edx,DWORD PTR [eax]
0x5655627e <+149>: mov eax,DWORD PTR [ebp-0x20]
0x56556281 <+152>: add eax,edx
0x56556283 <+154>: movzx eax,BYTE PTR [eax]
0x56556286 <+157>: movsx eax,al
0x56556289 <+160>: add DWORD PTR [ebp-0x1c],eax
0x5655628c <+163>: add DWORD PTR [ebp-0x20],0x1
0x56556290 <+167>: mov eax,DWORD PTR [ebp-0x20]
0x56556293 <+170>: cmp eax,DWORD PTR [ebp-0x24]
0x56556296 <+173>: jl 0x56556276
0x56556298 <+175>: cmp DWORD PTR [ebp-0x1c],0x321
0x5655629f <+182>: jne 0x565562bb
0x565562a1 <+184>: call 0x565562d9
0x565562a6 <+189>: sub esp,0x8
0x565562a9 <+192>: push eax
0x565562aa <+193>: lea eax,[ebx-0x1fc4]
0x565562b0 <+199>: push eax
0x565562b1 <+200>: call 0x56556040
0x565562b6 <+205>: add esp,0x10
0x565562b9 <+208>: jmp 0x565562cd
0x565562bb <+210>: sub esp,0xc
0x565562be <+213>: lea eax,[ebx-0x1fa2]
0x565562c4 <+219>: push eax
0x565562c5 <+220>: call 0x56556050
0x565562ca <+225>: add esp,0x10
0x565562cd <+228>: nop
0x565562ce <+229>: lea esp,[ebp-0xc]
0x565562d1 <+232>: pop ecx
0x565562d2 <+233>: pop ebx
0x565562d3 <+234>: pop esi
0x565562d4 <+235>: pop ebp
0x565562d5 <+236>: lea esp,[ecx-0x4]
0x565562d8 <+239>: ret

Up to how many characters does it loop? Here’s how I found it. Basically, our password must be of 7 characters in length.

[----------------------------------registers-----------------------------------]
EAX: 0x6
EBX: 0x56559000 --> 0x3efc
ECX: 0x6
EDX: 0xffffd4c6 ("1234567890")
ESI: 0xffffd250 --> 0x2
EDI: 0x0
EBP: 0xffffd238 --> 0x0
ESP: 0xffffd210 --> 0xf7f943fc --> 0xf7f95200 --> 0x0
EIP: 0x56556293 (: cmp eax,DWORD PTR [ebp-0x24])
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x56556289 : add DWORD PTR [ebp-0x1c],eax
0x5655628c : add DWORD PTR [ebp-0x20],0x1
0x56556290 : mov eax,DWORD PTR [ebp-0x20]
=> 0x56556293 : cmp eax,DWORD PTR [ebp-0x24]
0x56556296 : jl 0x56556276
0x56556298 : cmp DWORD PTR [ebp-0x1c],0x321
0x5655629f : jne 0x565562bb
0x565562a1 : call 0x565562d9
[------------------------------------stack-------------------------------------]
0000| 0xffffd210 --> 0xf7f943fc --> 0xf7f95200 --> 0x0
0004| 0xffffd214 --> 0x7
0008| 0xffffd218 --> 0x6
0012| 0xffffd21c --> 0x135
0016| 0xffffd220 --> 0x2
0020| 0xffffd224 --> 0xffffd2e4 --> 0xffffd487 ("/mnt/hgfs/shared/Linux RE/nix_5744af788e6cbdb29bb41e8b0e5f3cd5")
0024| 0xffffd228 --> 0xffffd2f0 --> 0xffffd4d1 ("NVM_DIR=/root/.nvm")
0028| 0xffffd22c --> 0xffffd250 --> 0x2
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
0x56556293 in main ()
gdb-peda$ print $ebp-0x24
$24 = (void *) 0xffffd214
gdb-peda$ x/x 0xffffd214
0xffffd214: 0x00000007

After translating to high-level code, it would look something similar to this.

for (i = 0; i < 7; i++) value += argv[1][i];
if (value != 801) return puts("n[+] No flag for you. [+]");
return printf("[+] The flag is: SAYCURE{%s} [+]n", comp_key());

Basically, the sum of each byte of our password must be equal to 801. Givens us 7 characters, we can sum up like this. You can use any calculation which sums up to 801. After this check is done it calls the comp_key function and prints out the flag. We don’t really need to dig the com_key function as it directly gives us the flag.

114 * 6 + 117 = 801

Let’s check those characters in the ASCII table. 114 is ‘r’ and 117 is ‘u’.

Dec Hex    Dec Hex    Dec Hex  Dec Hex  Dec Hex  Dec Hex   Dec Hex   Dec Hex  
0 00 NUL 16 10 DLE 32 20 48 30 0 64 40 @ 80 50 P 96 60 ` 112 70 p
1 01 SOH 17 11 DC1 33 21 ! 49 31 1 65 41 A 81 51 Q 97 61 a 113 71 q
2 02 STX 18 12 DC2 34 22 " 50 32 2 66 42 B 82 52 R 98 62 b 114 72 r
3 03 ETX 19 13 DC3 35 23 # 51 33 3 67 43 C 83 53 S 99 63 c 115 73 s
4 04 EOT 20 14 DC4 36 24 $ 52 34 4 68 44 D 84 54 T 100 64 d 116 74 t
5 05 ENQ 21 15 NAK 37 25 % 53 35 5 69 45 E 85 55 U 101 65 e 117 75 u
6 06 ACK 22 16 SYN 38 26 & 54 36 6 70 46 F 86 56 V 102 66 f 118 76 v
7 07 BEL 23 17 ETB 39 27 ' 55 37 7 71 47 G 87 57 W 103 67 g 119 77 w
8 08 BS 24 18 CAN 40 28 ( 56 38 8 72 48 H 88 58 X 104 68 h 120 78 x
9 09 HT 25 19 EM 41 29 ) 57 39 9 73 49 I 89 59 Y 105 69 i 121 79 y
10 0A LF 26 1A SUB 42 2A * 58 3A : 74 4A J 90 5A Z 106 6A j 122 7A z
11 0B VT 27 1B ESC 43 2B + 59 3B ; 75 4B K 91 5B [ 107 6B k 123 7B {
12 0C FF 28 1C FS 44 2C , 60 3C < 76 4C L 92 5C 108 6C l 124 7C |
13 0D CR 29 1D GS 45 2D - 61 3D = 77 4D M 93 5D ] 109 6D m 125 7D }
14 0E SO 30 1E RS 46 2E . 62 3E > 78 4E N 94 5E ^ 110 6E n 126 7E ~
15 0F SI 31 1F US 47 2F / 63 3F ? 79 4F O 95 5F _ 111 6F o 127 7F DEL

That’s it! We just solved a very simple binary

# ./nix_5744af788e6cbdb29bb41e8b0e5f3cd5 rrrrrru
[+] The flag is: SAYCURE{L3ts_g3t_in2_R3} [+]

http://www.cirosantilli.com/elf-hello-world/



Source link